Problem
update_work_item (and likely create_work_item) accept a state parameter and the official MCP/plane_sdk serialize it as "state" in the PATCH/POST body. On some Plane deployments that works; on others — including self-hosted Plane Community Edition — the REST API expects "state_id" instead.
When the wrong field name is sent, Plane returns HTTP 200 with an updated updated_at, but the work item's state is unchanged. Agents treat this as success and loop or invent wrong explanations (workflow constraints, parent blocking, etc.).
This is the inverse of the field-name mismatch fixed in community forks such as ZethicTech/plane-mcp-server#13 / #14, which renamed state_id → state for API compatibility. That direction fixes Cloud-style APIs but reintroduces a silent no-op on CE instances that only honor state_id.
Reproduction (self-hosted Plane CE)
Against a self-hosted instance (workspace homelab-ops, MCP plane_mcp_server 0.2.10 / plane_sdk 0.2.19):
- Call MCP
update_work_item with a valid full state UUID:
{
"project_id": "<project-uuid>",
"work_item_id": "<work-item-uuid>",
"state": "ba5e07a3-b2a0-4d45-93bf-8b03b0593e13"
}
- MCP reports success; response still shows the previous state (e.g. Todo).
- Same PATCH via the REST API:
| Body |
Result |
{"state": "<In Progress UUID>"} |
200, state unchanged |
{"state_id": "<In Progress UUID>"} |
200, state updated |
Endpoints tested (both behave the same for this field):
PATCH /api/v1/workspaces/<slug>/projects/<id>/issues/<id>/
PATCH /api/v1/workspaces/<slug>/projects/<id>/work-items/<id>/
Comments and other updates via MCP work fine on the same token — this is isolated to the state field name.
Root Cause
UpdateWorkItem in plane_sdk exposes state: str | None and serializes that key as "state". The MCP tool mirrors that parameter name. Plane CE's issue/work-item update handler appears to read "state_id" and ignore unknown/extra "state" without erroring.
Cloud (and forks that followed #13-style renames) expect "state". So a single-field rename in either direction breaks the other target.
Suggested Fix
Send both fields when a state UUID is provided, e.g.:
{
"state": "<uuid>",
"state_id": "<uuid>"
}
That should satisfy both Cloud (state) and CE (state_id) without version sniffing. Same pattern is worth considering for any other id/rename pairs that have flipped between API generations (parent / parent_id, etc.) if those show the same silent-ignore behavior.
Alternatively: document which field each Plane edition accepts and branch in the SDK — dual-send is simpler and avoids another silent-break cycle.
Impact
Agent clients (Hermes, Cursor, Claude, etc.) cannot reliably move work items between states via official MCP on self-hosted CE. Reads/list/comments succeed; only state transitions fail silently.
Problem
update_work_item(and likelycreate_work_item) accept astateparameter and the official MCP/plane_sdkserialize it as"state"in the PATCH/POST body. On some Plane deployments that works; on others — including self-hosted Plane Community Edition — the REST API expects"state_id"instead.When the wrong field name is sent, Plane returns HTTP 200 with an updated
updated_at, but the work item'sstateis unchanged. Agents treat this as success and loop or invent wrong explanations (workflow constraints, parent blocking, etc.).This is the inverse of the field-name mismatch fixed in community forks such as ZethicTech/plane-mcp-server#13 / #14, which renamed
state_id→statefor API compatibility. That direction fixes Cloud-style APIs but reintroduces a silent no-op on CE instances that only honorstate_id.Reproduction (self-hosted Plane CE)
Against a self-hosted instance (workspace
homelab-ops, MCPplane_mcp_server0.2.10 /plane_sdk0.2.19):update_work_itemwith a valid full state UUID:{ "project_id": "<project-uuid>", "work_item_id": "<work-item-uuid>", "state": "ba5e07a3-b2a0-4d45-93bf-8b03b0593e13" }{"state": "<In Progress UUID>"}{"state_id": "<In Progress UUID>"}Endpoints tested (both behave the same for this field):
PATCH /api/v1/workspaces/<slug>/projects/<id>/issues/<id>/PATCH /api/v1/workspaces/<slug>/projects/<id>/work-items/<id>/Comments and other updates via MCP work fine on the same token — this is isolated to the state field name.
Root Cause
UpdateWorkIteminplane_sdkexposesstate: str | Noneand serializes that key as"state". The MCP tool mirrors that parameter name. Plane CE's issue/work-item update handler appears to read"state_id"and ignore unknown/extra"state"without erroring.Cloud (and forks that followed #13-style renames) expect
"state". So a single-field rename in either direction breaks the other target.Suggested Fix
Send both fields when a state UUID is provided, e.g.:
{ "state": "<uuid>", "state_id": "<uuid>" }That should satisfy both Cloud (
state) and CE (state_id) without version sniffing. Same pattern is worth considering for any other id/rename pairs that have flipped between API generations (parent/parent_id, etc.) if those show the same silent-ignore behavior.Alternatively: document which field each Plane edition accepts and branch in the SDK — dual-send is simpler and avoids another silent-break cycle.
Impact
Agent clients (Hermes, Cursor, Claude, etc.) cannot reliably move work items between states via official MCP on self-hosted CE. Reads/list/comments succeed; only state transitions fail silently.