Decide agent access per endpoint, not per HTTP method - #96
Merged
Conversation
The switch beside "share with agents" assumes GET means read and POST means write. For an API where every operation is a POST it is useless: off, and the collection can't be used; on, and there is no guard left. A collection can now carry an access policy instead — a jq filter answering "allow", "ask" or "deny" per endpoint, run against what the manifest already publishes. Every scalar `x-` extension on an OpenAPI operation comes through the loader into `meta`, so a rule reads the API's own vocabulary; Fiber knows nothing about `x-kind` or any other key, and the filter is the one place meaning is attached. "ask" puts the call in front of a person through MCP elicitation, in the client they are already sitting in, and sends it only on accept. Ten minutes, then it is refused. A client with no way to prompt often declares the capability and cancels instantly anyway, so a cancel that arrives too fast to have been read says nobody was asked rather than "denied" — the difference decides whether an agent retries elsewhere or gives up. Everything fails closed. A filter that doesn't compile, throws, fans out or answers something other than the three words denies the whole collection, and a path the catalogue doesn't list is decided with no metadata rather than inheriting a listed endpoint's permission. The editor's preview and the server's answer are one shared function over one shared catalogue, so the two cannot disagree about an endpoint. Collections with no policy keep the old switch, untouched.
…hod-gating # Conflicts: # src-tauri/src/mcp.rs
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The switch beside Share with agents assumes GET means read and POST means write. For an API where every operation is a POST that switch is useless: off, and the collection can't be used; on, and there is no guard left at all.
What
A collection can carry an access policy — a jq filter answering
"allow","ask"or"deny"per endpoint:Every scalar
x-extension on an OpenAPI operation now comes through the loader intometa, so a rule reads the API's own vocabulary. Fiber knows nothing aboutx-kind, or any other key — the filter is the single place meaning is attached, and it is one a person can read and change.jq for the reasons loaders already use it: it cannot do anything but transform, it re-runs instantly against the real endpoint list, and a second matching language invented here would buy nothing.
"ask"goes through MCP elicitation, to the client the user is already sitting in when an agent is working. Onlyacceptsends. Ten minutes, then refused. At most 8 prompts wait at once.Everything fails closed. A filter that doesn't compile, throws, fans out, or answers something other than the three words denies the whole collection and says why. A path the catalogue doesn't list is decided with no metadata, so it cannot inherit a listed endpoint's permission —
POST /orders/42/refundis notPOST /orders/{id}.Nothing migrates. An empty policy keeps the old switch exactly. A policy replaces it outright while set, GET included, so a read that returns the whole customer table can say so.
The one that took a spike to find
Headless Claude Code declares
"elicitation": {}at initialize and then answerscancelin ~5ms without showing anyone anything. Safe — it cancels rather than accepts — but it is not an approval, and reporting it as "denied" would send an agent looking for another way round. A cancel too fast to have been read now says nobody was asked, and names the client that did it.Verification
Beyond 160 Rust tests and 338 e2e, the gate was driven end to end against the built
fiber mcpbinary with a small MCP client that answers elicitations on command:Notes
else. The shippedBy x-kindtemplate gives them their own branch; worth a look when reviewing that default.