Skip to content

PQL filters are silently ignored on self-hosted CE — filtered queries return the FULL result set with HTTP 200 #192

Description

@addhass

Affected: plane-mcp-server 0.2.9 and 0.2.10 (SDK plane.api.work_items). Tested against self-hosted Plane Community Edition v1.3.1, API-key auth (X-Api-Key).

Dedup note: distinct from #136 (search param), #170/#172/#188 (*-lite 404s) and #183 (state silently ignored on update). Those either 404 or ignore one field. This one returns HTTP 200 with a complete, plausible, wrong result set for any filtered read. I couldn't find an existing PQL issue.

Summary

Self-hosted CE does not implement the pql query parameter, and — like most DRF endpoints — it ignores unknown query params rather than rejecting them. So every PQL-filtered call returns the entire unfiltered set with HTTP 200.

The MCP actively advertises PQL: list_work_items/count_work_items document it, and there's a dedicated get_pql_reference tool. An agent therefore composes a filter, gets a confident 200, and reasons over data that was never filtered.

Reproduction

Workspace with 113 work items in one project, most of them Done:

BASE=https://<self-hosted>/api/v1/workspaces/<slug>
PROJ=<project-uuid>

# 1. no filter
curl -sLG -H "x-api-key: $KEY" --data-urlencode 'fields=id' \
  "$BASE/projects/$PROJ/work-items/" | jq .total_count
# => 113

# 2. filter to open states only
curl -sLG -H "x-api-key: $KEY" \
  --data-urlencode 'pql=stateGroup IN openStates()' --data-urlencode 'fields=id' \
  "$BASE/projects/$PROJ/work-items/" | jq .total_count
# => 113   (expected: far fewer; most items are Done)

# 3. deliberately INVALID pql
curl -sLG -H "x-api-key: $KEY" \
  --data-urlencode 'pql=((((not valid' --data-urlencode 'fields=id' \
  "$BASE/projects/$PROJ/work-items/" | jq .total_count
# => 113   HTTP 200

Step 3 is the important one: syntactically broken PQL still succeeds. There is no server-side validation to fail against, so nothing distinguishes "filter applied" from "filter discarded".

Same via the MCP tools:

Why this one bites harder than a 404

A 404 is self-announcing. This isn't. In our case an agent searched for an existing ticket, got a filtered-looking result set, concluded the ticket didn't exist, and filed a duplicate. Nothing in the response indicated the filter had been dropped. (Our search was also returning empty because of #136 on 0.2.9, so the two compounded — but the PQL behaviour stands on its own.)

Suggested fix — any one of these

  1. Preferred: probe capability once per session and fail loudly if pql is passed to a backend that doesn't implement it (raise with a message pointing at client-side filtering).
  2. Have the server reject unknown/unsupported query params with 400 instead of ignoring them.
  3. If CE genuinely won't support PQL (cf. Is the Community Edition a supported target? #171), drop pql from the tool schemas when the target isn't Cloud, so agents never compose one.

Option 1 is what we've done locally as a stopgap: a guard in prepare_work_item_params — the single choke point every PQL-carrying call passes through (list, list_workspace, list_archived, count_workspace, plus the cycle/module list variants) — raising a ValueError explaining that the caller should omit pql and filter client-side using expand=state.

if payload.get("pql"):
    raise ValueError(_PQL_UNSUPPORTED)

Happy to open a PR for option 1 if that's a direction you'd take.

Environment

plane-mcp-server 0.2.9 (also present in 0.2.10)
Plane Community Edition v1.3.1, self-hosted
Auth API key (X-Api-Key)
Transport stdio

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions