Repository navigation
MCP tool calls fail: invalid JSON when passing session_name / session_id (Cursor agent) #17
Description
Summary
When driving libtmux-mcp from Cursor’s agent, some tool invocations fail before the Python server runs: the MCP layer rejects the request because the JSON payload for tool arguments is malformed.
This is not the same as the existing workaround for dict-shaped parameters sent as JSON strings (_coerce_dict_arg / Composer dict-stringification). Here, string values that must be JSON strings are emitted without quotes, producing invalid JSON.
Symptoms
Errors observed on the client/MCP bridge when calling tools that accept optional session targeting, for example:
Unexpected token '$', "{"session_id": $10}" is not valid JSONUnexpected token 'c', "{"session_name": cv}" is not valid JSON
Valid JSON would require:
"session_id": "$10""session_name": "cv"
Impact
- Tools such as
list_windows(withsession_name/session_id) andselect_windowfail entirely; the agent falls back to shelltmuxcommands. - Any tool parameter that is a string could theoretically hit the same class of bug if the serializer mishandles it.
Steps to reproduce (agent)
- Use Cursor with the libtmux MCP server configured (stdio).
- From the agent, call
list_windowswithsession_nameset to a bare session name (e.g.cv), orlist_windows/ related tools withsession_id(e.g.$10). - Observe MCP parse/validation errors instead of a normal tool result.
(Exact UI steps depend on Cursor version; the failure is visible in the agent tool-call error output.)
Expected behavior
Tool arguments are serialized as valid JSON. String parameters are JSON-encoded (quoted, with proper escaping).
Actual behavior
The serialized arguments are not valid JSON (unquoted $10, unquoted cv), so the request fails at parse time.
Nature of the bug
- Likely location: MCP client / Cursor’s tool-argument serialization for this model or code path—not libtmux-mcp’s Python handlers (they never run).
- Relation to existing work: The project already documents dict parameters arriving as JSON strings (Composer 1/1.5) in
_coerce_dict_argandcreate_session.environment. This issue is orthogonal: it is invalid JSON at the wire level, not a semantic coercion problem.
Suggestions
- Upstream: If reproducible only in Cursor, report or cross-link to Cursor with a minimal failing tool call payload.
- Docs: Optionally note in MCP/client docs that agents should prefer globally unique
pane_idtargeting where possible to reduce reliance on session filters. - Investigation: Confirm whether FastMCP / MCP server can surface a clearer error when the client sends malformed JSON (may be outside server control).
Environment (observed)
- OS: Linux (WSL2)
- Cursor agent + libtmux-mcp MCP server (stdio)
- tmux sessions present (e.g. session name
cv, id$10)
If maintainers prefer this tracked as documentation-only or upstream-meta, feel free to relabel; the goal is to capture the failure mode and distinguish it from _coerce_dict_arg-style workarounds.
Activity
fix(server): guide clients to emit JSON-quoted string arguments
fix(server): repair unquoted JSON values at stdio transport
tony commentedon Apr 19, 2026
Revisited this and empirically confirmed there is no server-side fix. Closing the attempted PR (#25) without merge.
Summary of attempts
instructionspayload telling models to emit JSON-quoted stringsJSONRPCMessage.model_validate_jsonJSON.parses Composer's output locally and rejects it client-side, so no MCP frame is ever sentScope of the bug
Reproduced on Composer 1, Composer 1.5, and Composer 2. Not reproduced on Claude / GPT backends inside Cursor (those quote correctly).
Recommendation
Keeping the issue open as a record of the failure mode; closing would misleadingly imply a libtmux-mcp-side fix was applied.
tony commentedon May 24, 2026
Triage update (2026-05-24): this still appears to be a live Cursor/Composer-side MCP serialization issue, not a libtmux-mcp server bug.
Environment checked locally:
3.5.21(9d6e5ed, x64)cursor-agent:2026.05.20-2b5dd59composer-2.5cursor-agent mcp list-tools tmuxstill advertiseslist_windows (session_name, session_id, ...), sosession_nameremains a string-shaped tool argument from the MCP side.Repro prompt asked Cursor Agent to use only the tmux MCP server: first call
list_sessionswith no arguments, then calllist_windowswith the first returnedsession_name, notsession_id.Observed behavior:
list_sessionssucceeded and returned a first session withsession_name: "3".list_windowsattempt sentargs: {"session_name": 3}instead of{"session_name": "3"}.The same structured run also emitted a malformed-tool-call error before the successful
list_sessionsresult:A later retry where Composer explicitly sent
session_name: "3"succeeded, so libtmux-mcp can handle the parameter correctly. The remaining failure mode is that Composer can still produce malformed or wrongly typed MCP arguments for numeric-looking tmux identifiers, then sometimes self-recover on retry.Keeping this open as a current Cursor/Composer compatibility record. I still do not see a libtmux-mcp-side fix beyond documenting the client/model trap.
tony commentedon May 24, 2026
Triage pass (2026-05-24): keep open as a current Cursor/Composer compatibility record, not a libtmux-mcp server bug.
Current-code check: this remains distinct from the existing
_coerce_dict_argworkaround._coerce_dict_argcan help when a dict-shaped parameter arrives as a JSON string after the MCP frame is valid; it cannot repair malformed JSON rejected by the client before the server sees it, and it should not broaden string parameters to accept incorrectly typed numeric values such assession_name: 3.Overlap/value: no overlap with the wait-family or batching issues. The value is diagnostic and documentation-oriented: it prevents future server-side repair attempts for a client/model serializer failure and gives users a known explanation/workaround. Recommended action is still upstream/client reporting plus optional docs wording; no libtmux-mcp handler change is indicated from the current evidence.