Skip to content

SEP-XXXX: Global Capability Scope for Per-Request Client Capabilities#31

Open
gjz22 wants to merge 2 commits intomodelcontextprotocol:mainfrom
gjz22:capability-scope
Open

SEP-XXXX: Global Capability Scope for Per-Request Client Capabilities#31
gjz22 wants to merge 2 commits intomodelcontextprotocol:mainfrom
gjz22:capability-scope

Conversation

@gjz22
Copy link
Copy Markdown

@gjz22 gjz22 commented Apr 13, 2026

This is a draft SEP specifying the scope of per-request client capabilities introduced by SEP-1442.

Motivation and Context

SEP-1442 moves client capabilities from a single initialization handshake to per-request declarations in _meta, but does not specify whether a capability declared on one request should be assumed to hold for other requests. This creates an ambiguity for requests like tools/list, which need to decide what to return based on what capabilities the client will provide on a future tools/call.

This SEP proposes that per-request capabilities should be treated as globally scoped: when a client declares capabilities on any request, the server should assume those same capabilities apply to all requests from that client. Without this, the server does not have enough information to know whether to return a tool with a required capability, and if it does return the tool, the client has no way to know whether it can actually execute it.

Alternatives are discussed in detail:

  1. Tool-level capability annotations (client-side filtering, with an optional whenCapabilitiesUnavailable substitution attribute)
  2. Related request capabilities (relatedRequestCapabilities keyed by method name in _meta)
  3. Targeted capability scope (capabilities apply only to the derived operations of the declaring request)
  4. Fail at call time (no filtering, just let tools/call fail)

See SEP for details.

How Has This Been Tested?

Not tested yet, that will happen when we implement.

Breaking Changes

This proposal is additive to SEP-1442 and does not introduce breaking changes on its own. See SEP for details.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

N/A

Draft SEP specifying that per-request client capabilities (introduced
in SEP-1442) should be treated as globally scoped, so servers can filter
and adapt tool lists based on the capabilities the client will provide
on subsequent tools/call requests.
Copy link
Copy Markdown
Collaborator

@pja-ant pja-ant left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great, thanks for putting it together!

Added some comments, mostly semantical things that would make it a bit more precise (in my opinion), but high-level I think this is the right framing of options. Would love to see a comparison matrix to help us debate trade-offs.

FWIW, I'm really not sure what the right thing is here. I was originally leaning towards your proposed option but can see pros/cons versus others as well.

Comment on lines +16 to +18
proposal does not specify the *scope* of those capabilities: whether a
capability declared on one request should be assumed to hold for other
requests, or whether each request's capabilities are independent.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit of a semantical nit, but I'm not sure we should use "scope" here. It's really not about scope and I find the "globally scoped" phrasing confusing (what does "global" mean?). With 1442 we have a stateless protocol, so the only scope that exists from MCP's perspective is the request.

What is being discussed is if/how servers should factor in capabilities sent on list/tools, which I think this SEP does a good job of exploring. I'd recommend dropping the "scope" verbiage.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More broadly, I feel like we need to define what criteria should can be used to affect the outcome of things like tools/list, resources/list. The wording in SEP-2567 says:

tools/list, resources/list, and prompts/list: there is no longer a per-session or per-connection scope for their results to depend on. Lists can still change for other reasons — a server deploys a new version, a user's plan or granted scopes change — and this SEP does not enumerate or restrict those.

It seems like we need to clearly enumerate in which case the tools/list should be cached or shared. In what instances should clients refresh the list?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Global here means what it means today in the protocol. That you have one set of capabilities and those capabilities apply globally to all requests as opposed to being able to specify per request what capabilities the client has for that request only.

When a client sends per-request capabilities (as defined in SEP-1442),
the following rules apply:

1. A client **SHOULD** send the same `clientCapabilities` object on every
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where the scoping ("every") makes things confusing. What does "every" request mean? Are clients not allowed to change capabilities?

A more direct phrasing would be something like "A client should not assume that a tools list returned with one set of supplied capabilities is the same that another set of capabilities would return" -- with the implication being that if a client changes capabilities then it should probably re-fetch tools. It should also not cache tools across capability sets.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense.

Comment on lines +146 to +151
2. A server that receives capabilities on any request **SHOULD** assume
that those capabilities apply to all requests from that client.
Specifically, if a server receives capabilities on a `tools/list`
request, it **SHOULD** use those capabilities to determine which tools
to return, assuming the same capabilities will be present on
subsequent `tools/call` requests.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should not assume that capabilities apply to subsequent requests: every request is independent. The capabilities may change on subsequent tools/call requests and the server will have to handle that (e.g. by rejecting the request).

Comment on lines +153 to +161
3. If a client's capabilities change (e.g., a user enables or disables
a feature), the client **SHOULD** re-issue applicable list requests
(e.g., `tools/list`, `resources/list`) with the updated capabilities
rather than relying on cached results. This ensures the server can
return results that reflect the new capability set.

4. A server **MUST NOT** assume capabilities are available if the client
has not declared them on any request. The absence of capabilities
is always a safe default.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are good. I do think we're on the same page, but the language needs to be phrased in a more stateless way where every request is treated in isolation (since you have no other choice).

request carries the full capability set, so any server instance can
make the correct decision without shared state.

### Alternatives Considered
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for including these in so much detail. I think it could be very useful to provide a comparison matrix across all options to give a high-level overview.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added (quickly but will revise). I included only the main option, and alternative 1 and alternative 2 because I consider these the main viable options.


## Abstract

[SEP-1442](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1442)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[SEP-1442](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1442)
[SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/2575)

Copy link
Copy Markdown
Collaborator

@kurtisvg kurtisvg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At it's core, I think this issue should be reframed as "what information can be used to filter */list calls", and probably should explicitly call out auth/user identity in addition to capabilities.

It also needs to be refocused on defining client behavior more sot than server behavior, because in a stateless world the server doesn't differentiate between different clients, so it's up to the client to refetched cached info (and not the server to try and enforce that).

Comment on lines +16 to +18
proposal does not specify the *scope* of those capabilities: whether a
capability declared on one request should be assumed to hold for other
requests, or whether each request's capabilities are independent.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More broadly, I feel like we need to define what criteria should can be used to affect the outcome of things like tools/list, resources/list. The wording in SEP-2567 says:

tools/list, resources/list, and prompts/list: there is no longer a per-session or per-connection scope for their results to depend on. Lists can still change for other reasons — a server deploys a new version, a user's plan or granted scopes change — and this SEP does not enumerate or restrict those.

It seems like we need to clearly enumerate in which case the tools/list should be cached or shared. In what instances should clients refresh the list?

capability declared on one request should be assumed to hold for other
requests, or whether each request's capabilities are independent.

This proposal specifies that per-request client capabilities SHOULD be
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a big fan of this wording -- it feels like we should be giving the client guidance on when it should refresh tools/list, not what the server should assume. The client could change capabilities at any time, and it should be the clients responsibility to refresh the given tools/list accordingly.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wording aside, to be clear, the assumption here is that if a client declares capabilities on tools/list, unless the client changes capabilities (in which the client should refetch tools), the server should assume the client will have the same capabilities on tools/call

Compares the primary Global Scope proposal against Alternative 1
(Tool-Level Capability Annotations) and Alternative 2 (Related Request
Capabilities) across the main goals motivating this SEP.
@ZachGerman
Copy link
Copy Markdown

Firstly, I want to call out that I believe there's a significant rift in desire here from closed-system and public server perspectives.

Since I've put a lot of thought into Alt 1 (e.g. [1],[2]), I'd like to weigh in on the differences between this and Alt 1, which I believe will have significant impact on this protocol going forward:

Server-side filtering (vs Client-side): (assuming no room for filtering parameters on tools/list requests) Either the servers must disclose their entire tool list, or the client must disclose their entire capability set. Servers today exist to expose tools for calling. Clients today don't exist to expose their capability set, and shouldn't be required to outside of closed-systems to achieve full protocol functionality (capability-specific CVEs are scary "New MCP sampling exploit takes users by surprise!"). From a closed-system owner perspective, there may be desire to control client behavior via the server's tool filtering. From a public server perspective, they rely on independent clients to be competent in selecting the right tools given the definitions, and may even appreciate the extra exposure provided by the opportunity to convey their entire tool list to a client. Maybe there's room for both. I'm not sure.

Substitution: This is filtering.

Parameterized adaptation: I think this is a better approach to support this than forcing clients to disclose their full capability details. Again, a closed-system owner may not have the same privacy or security concerns here that client users calling public servers would. Say a user is operating a client with some kind of private capability. They may not want to share that with all the public servers they interact with.

Simplicity for client implementers: I think the differences here are trivial compared to some of the problems being solved outside this discussion. I developed matching logic for my implementation example for #1385 very quickly. It's definitely worth it in order to maintain client discretion in exposing capabilities.

Simplicity for server implementers: I disagree with the label of MEDIUM here for Alt 1. They wrote the tool. They know what's required for it to execute successfully.

Transparency to the client: While I agree with the assessment, I think if you're going to have a row for this, you shouldn't leave out the row for the inverse: "Exposure (forcing transparency) of client to the server". It's important to remember that public servers advertise themselves and strive to be transparent in order to earn trust from clients, who have to be careful of what servers they reach out to (just like we're all careful of the websites we visit).

True per-request capability scoping: Agree.


Overall, I think there's much to be learned from the evolution of user-agent strings in HTTP here. The timeline was something akin to:

  1. Full client disclosure of incredibly detailed capabilities
  2. Attackers began fingerprinting and exploiting clients
  3. user-agent verbosity was reduced heavily
  4. In came HTTP Client Hints

So, unless we start limiting capability declaration (I worry about custom capabilities before they have their time here) in similar ways, we'd be on course to start from point 1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants