MCP tool calls bypass ctx.approval — no gate on side-effectful bridged tools #3904
Replies: 4 comments
|
There is an immediately usable alternative path for this through Through pi2dsh, the approval request crosses DSH dsh plugin --profile web add pi2dsh
dsh plugin --profile web add pi-mcp-adapterThis is package-owned approval rather than a claim that the official https://github.com/weijiafu14/pi2dsh/blob/main/docs/mcp-compatibility.md |
|
I can reproduce this gap with This is particularly important for browser MCP tools: a "read-only" call can still expose content from an authenticated browser session, while navigation, clicks, form input, uploads, and script execution can cause external side effects. On the current bridge path, I don't think
This would also provide the policy metadata needed beyond the concurrency concern reported in #3905. Would the maintainers prefer a generic non-model-facing metadata/provenance field on |
|
The option you're leaning toward, gating only tools that don't declare readOnlyHint, works but it inherits whatever the upstream server decided to annotate. Plenty of servers omit the hint or set it wrong, so a paid generation tool that should be gated slips through because someone marked it read-only by accident, or didn't annotate at all and your bridge treats missing as safe. The per-server requireApproval field sidesteps that since the deployment decides, not the server. I work on Apitella (https://apitella.com/?utm_source=github-discussion&utm_medium=comment&utm_campaign=outreach-12). We snapshot MCP servers and diff their tool annotations over time, which catches exactly the case where a hint you're relying on for gating changes or was never trustworthy to start with. It doesn't wire the seam into your client, that part's yours, but it'd tell you which mounted servers you can actually trust the hints from. Happy to compare notes. |
|
For a paid generation endpoint, I would first test approval against a fake credit ledger: approved, denied, and no approval response. The debit count should be checked outside the tool result. Is there one client call path all audio tools use, or can some bypass that wrapper? |
Uh oh!
There was an error while loading. Please reload this page.
what I observed
@deepseek-ai/dsh-mcp-clientappears to have no reference to the approval seam.Grepping the published
lib/index.jsforctx.approval/approval.requestreturns nothing, while
dsh-sandbox,dsh-tool-bash,dsh-tool-fsanddsh-toolsall consume it.The effect is that a bridged MCP tool call is never gated. The permission model
covers file and shell access, but a tool call from an MCP server runs
unconditionally.
why it matters for us
We embed the harness in a desktop audio app and mount several MCP servers,
including ones that call paid generation APIs. Those calls spend real credits.
Under the harness they run with no prompt; other ACP/agent hosts we've used ask
before the first call and let the user grant it thereafter.
There is no way to close this from the host side: the harness spawns the MCP
servers as its own children and calls them directly, so an embedding
application never sees the call and cannot interpose.
what I'm unsure about
Gating every MCP call would clearly be wrong — it would prompt on read-only
tools and make the seam useless through noise. Some options, none obviously
right:
annotations.readOnlyHint: gate only tools that don't declare it(related to a separate discussion I'm opening about that annotation).
requireApproval: true, so the deploymentdecides.
ctx.toolsclassification the bridge sets, letting the existing pipelineroute the
askdecision rather than the bridge calling the seam itself.The last seems most in keeping with the architecture — the README notes the
tools pipeline already routes
askdecisions and fails closed without ananswerer — but I don't know the intended boundary well enough to say.
Is the current behaviour deliberate (MCP servers treated as trusted because
mounting one is itself the act of trust), or a gap?
Version
@deepseek-ai/dsh-mcp-client@0.1.0-rc.7. Read from compiled output, not source— apologies if I've misread the structure.
All reactions