CallMcpWithQuery weakens target read-only, permission, and dispatch semantics
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 152
- Forks
- 16
- Avg merge
- 14h 48m
- Merged PRs (30d)
- 536
Description
Summary
CallMcpWithQuery is intended as a fallback for narrowing an oversized structured MCP result before it enters model context. Its current model-facing and execution semantics instead make it an attractive general-purpose MCP caller, and—more importantly—treat every addressed MCP target as read-only and floor-allowed regardless of the target tool's own annotations or permission behavior.
The wrapper can address any tool exposed by the MCP manager. It unconditionally returns ReadOnly() == true, is registered whenever any MCP tool exists, and has a built-in default Allow. The authority evaluator correctly checks the addressed mcp__<server>__<tool> capability, but permission evaluation and dispatch classification still use the wrapper itself. The remote call occurs before jq filtering, so a remotely mutating target can perform its mutation even though the wrapper was dispatched and permitted as read-only.
Why this matters
A direct mutating MCP tool and the same tool reached through CallMcpWithQuery do not inherit the same safety behavior:
- A direct MCP tool without an allow rule defaults to an interactive approval.
CallMcpWithQueryis floor-allowed.- A direct tool without
readOnlyHint: trueis serialized conservatively. CallMcpWithQueryis always classified as read-only and can be read-parallel dispatched.- The wrapper does not limit target selection to MCP tools advertising
readOnlyHint: true.
This creates a permission and concurrency bypass for remote side effects. Capability authorization alone does not close the gap when the session legitimately carries the target capability.
Session evidence
Observed in local session 50028801afa1b8a77186921f8e171d66 using mecated v0.0.38-57-g7cf4e5dfd and gpt-5.6-terra:
- The model used ten
CallMcpWithQuerycalls for GitHubpull_request_readrather than the directly exposed GitHub MCP tool. - Every call used
jq_filter: ".", so none narrowed its result. The wrapper added no value over the direct call. - The calls ran without permission prompts because
CallMcpWithQueryis floor-allowed. - Later direct
mcp__github__add_issue_commentcalls each producedpermission.askwithno permission rule matched mcp__github__add_issue_comment; client approval required.
We did not invoke add_issue_comment through the wrapper, because that would exercise a real remote mutation. The implementation path is sufficient to show that the wrapper accepts the target and calls it before attempting to filter its result.
Contributing usability behavior
The description already says to use the wrapper when a tool returns a large JSON structure and to prefer the remote tool's own pagination/filter parameters. However:
- The generic name
CallMcpWithQuerypresents it as a normal MCP calling path. - It is disclosed eagerly alongside direct MCP tools rather than after an oversized-result failure.
- It accepts identity/no-op jq filters such as
.. - Its floor-allow and read-parallel behavior make it operationally easier than direct calls.
The observed model behavior therefore appears to be encouraged by more than wording alone. Description clarification would help, but would not fix the target-semantics defect.
Expected behavior
Reaching an MCP tool through CallMcpWithQuery must not weaken that target's permission or dispatch semantics. The wrapper should remain a bounded result-projection mechanism, not an alternate authority or mutation path.
Possible design directions:
- Restrict
CallMcpWithQueryto targets that explicitly advertisereadOnlyHint: true; fail closed for missing or false hints. - If mutating targets must be supported, resolve permission and dispatch behavior against the addressed target so the same call asks/denies and serializes exactly as its direct form.
- Reject identity/no-op filters and make the error direct the model to the native MCP tool.
- Strengthen the description to say: use only after a direct MCP call returns an oversized structured-result error, never for a first attempt.
- Consider disclosing the fallback only after the oversized-result path recommends it.
Suggested acceptance coverage
- A wrapper call to an explicit read-only target remains usable and bounded.
- A target with
readOnlyHintfalse or absent cannot execute under unconditional read-only/floor-allow semantics. - A mutating target, if supported, receives the same permission decision and serial dispatch as its direct namespaced tool.
- Target-specific configured Ask/Deny rules cannot be bypassed through the wrapper.
- The remote
CallTooloperation is never reached when target semantics reject the call. - A no-op jq filter such as
.is rejected or otherwise cannot serve as a general direct-call substitute.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at the CallMcpWithQuery registration and trace its ReadOnly, authority, permission, dispatch, jq filtering, and CallTool paths. Compare those behaviors with direct namespaced MCP calls, then use the suggested acceptance coverage to verify read-only restrictions, matching permission and dispatch decisions, rejection before CallTool, and no-op filter handling.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- authorization, backend, security
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100