Skip to content

Client sampling and roots capabilities set to None if not implemented #802

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

lorenzocesconetto
Copy link
Contributor

@lorenzocesconetto lorenzocesconetto commented May 25, 2025

Right now clients always claims to support sampling and roots capabilities, which is inaccurate.
Having this properly set allows servers to fallback to alternative implementations.

Motivation and Context

Servers might rely on the correctness of client capabilities, so clients should inform them accurately.

How Has This Been Tested?

Unit tests

Breaking Changes

No

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

@lorenzocesconetto
Copy link
Contributor Author

@ihrpr I think this is an important PR to make sure the SDK adheres to the protocol.
This enables servers to make decisions based on the client capabilities.
For instance, server won't waste time trying to send a sampling request to a client that has no sampling capabilities.

roots = None
else:
roots = types.RootsCapability(
# TODO: Should this be based on whether we
Copy link
Contributor

@hesreallyhim hesreallyhim May 26, 2025

Choose a reason for hiding this comment

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

The spec says:

When roots change, clients that support listChanged MUST send a notification:

So I guess it would be the former.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Totally agree!
This TODO comment was already in the code base, I just kept it...
I only added the if-else structure around it.

@ihrpr ihrpr added this to the HPR milestone May 28, 2025
ihrpr
ihrpr previously approved these changes May 28, 2025
Copy link
Contributor

@ihrpr ihrpr left a comment

Choose a reason for hiding this comment

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

Thank you!

Please can can those nits be addressed and we can merge.

@lorenzocesconetto
Copy link
Contributor Author

@ihrpr I've addressed your comments, but since there's a new commit, we need your approval again.

Btw, I want to implement a larger feature, but before actually implementing it and opening a PR, I'd love to chat with you just to make sure it makes sense. Is there a way I can reach out to you @ihrpr ?

else None
)
roots = (
types.RootsCapability(listChanged=True)
Copy link
Contributor

Choose a reason for hiding this comment

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

i'm curious - does this entail that any client that supports roots, or list_roots, supports roots.listChanged?

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe worth putting the comment back, sorry.

The client can send list changes, as the code is there to trigger that notification, so it's supported.
But the spec says "listChanged indicates whether the client will emit notifications when the list of roots changes."
We do need to address this, but a separate PR would be probably be better

Copy link
Contributor

Choose a reason for hiding this comment

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

add the comment back

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know if I follow...
So I read the comment as asking - should the listChanged=True be included if and only if the client WILL send a notification when the roots list is changed, or that it MAY (i.e., it "supports it"). And I think I even said roughly "this is answered in the spec":

listChanged indicates whether the client will emit notifications when the list of roots changes.

So if that's the meaning of the question/comment, I think it's answered by the spec. My question was - if a client SUPPORTS ROOTS (e.g. they support "roots/list"), does it follow that they support "roots/list_changed"?

That's MY question, and it seems to me that the spec implies NO, otherwise (a) why would this be need to be announced in the capabilities; (b) why would the spec talk about "whether the client will emit notifications" if its non-optional.

Cuz the client could (a) not support roots; (b) support roots, and therefore support "roots/list", but not support "notifications/roots/list_changed" (c) support roots, "roots/list" and listChanged.

In the spec it sort of visually implies that SUPPORTS ROOTS -> SUPPORTS ROOTS/LIST -> SUPPORTS NOTIFICATIONS/ROOTS/LIST_CHANGED. But I don't believe this is actually stated anywhere.

So my case is - a client may or may not support roots full stop. If they support roots, then they must support "roots/list" - but that's not something that's declared in the capabilities - but I don't see any language that says that any client that SUPPORTS ROOTS also supports roots/list_changed.

So my reading of: "Clients that support roots MUST declare the roots capability during initialization" is that the existence of the "roots" key indicates support for roots (and therefore list-roots), and the value for roots.listChanged indicates support for list_CHANGED. So the capabilities object could have:
(1) no roots support:

"capabilities": {
      "sampling": {}
    },

(2) supports roots (and "roots/list") but not listChanged:

"capabilities": {
      "roots": {
        "listChanged": false
      },
      "sampling": {}
    },

(3) supports roots, "roots/list" and listChanged:

"capabilities": {
      "roots": {
        "listChanged": true
      },
      "sampling": {}
    },

I think this is consistent with the schema.ts:

export interface ClientCapabilities {
  /**
   * Experimental, non-standard capabilities that the client supports.
   */
  experimental?: { [key: string]: object };
  /**
   * Present if the client supports listing roots.
   */
  roots?: {
    /**
     * Whether the client supports notifications for changes to the roots list.
     */
    listChanged?: boolean;
  };
  /**
   * Present if the client supports sampling from an LLM.
   */
  sampling?: object;
}

capabilities.roots is optional and is present if the client supports listing roots. capabilities.roots.listChanged is optional and is true if the client support "roots/list_changed" and false if the client supports "roots/list" but not "roots/list_changed".

FWIW, I think this is slightly non-optimal and that listChanged?: boolean should be listChanged: boolean, the "meaning" overal being: if you support roots at all, then you must support "roots/list" (hence, this does not need to be announced), but it's still yes/no if you support "roots/list_changed" (hence that must be announced).

The current structure seems to allow:

...
roots: {}
...

which I guess is equivalent to:

...
roots: {
  listChanged: false
}
...

SO - on my reading, there are three states - (a) no roots (omit the key), (b) "roots/list" support but not "roots/list_changed" (include key and object value and either omit roots.listChanged or set it to false, and (c) supports "roots/list_changed" (include roots.listChanged: true) - in case (c), the client MUST send "notifications/roots/list_changed" when list changes. This also comports with this:

Category | Capability | Description
-------- -------- -------
Client | roots | Ability to provide filesystem roots

Meaning the presence of the roots key is optional and indicates, if present that the client (at least) supports "roots/list".

THEN, there is the further announcement of whether they ALSO supports "roots/list_changed", as in:

Capability objects can describe sub-capabilities like:
 * `listChanged`: Support for list change notifications...

Pulling it together, the comment is asking: if the client SUPPORTS "roots/list_changed", does it follow that they MAY or that they MUST actually send the notifications?
My question was: if a client SUPPORTS roots, does it follow that they support listChanged - (a) does it follow given the logic in this PR? (b) is that what the spec declares? For (b) I say "no", so if the PR entails "yes", then I believe it is against the spec.

Copy link
Contributor

@ihrpr ihrpr left a comment

Choose a reason for hiding this comment

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

Thank you!

@ihrpr ihrpr merged commit 7f94bef into modelcontextprotocol:main May 29, 2025
10 checks passed
@lorenzocesconetto
Copy link
Contributor Author

@ihrpr I want to implement a pretty large feature, but before actually implementing it and opening a PR, I'd love to chat with you. Is there a way I can reach out to you?

@ihrpr
Copy link
Contributor

ihrpr commented May 29, 2025

@ihrpr I want to implement a pretty large feature, but before actually implementing it and opening a PR, I'd love to chat with you. Is there a way I can reach out to you?

oh, sorry, missed this. I think the best option would be to open an issue and we can discuss there

@lorenzocesconetto
Copy link
Contributor Author

Sounds good! @ihpr

Btw I am working on a fix for the rootListChanged. Thank you @hesreallyhim for pointing that out.

@hesreallyhim
Copy link
Contributor

hesreallyhim commented May 29, 2025

Sounds good! @ihpr

Btw I am working on a fix for the rootListChanged. Thank you @hesreallyhim for pointing that out.

thanks @lorenzocesconetto ! hope I haven't added to the confusion, please see my last comment/essay that I posted for a thorough recap of my position

@lorenzocesconetto
Copy link
Contributor Author

@hesreallyhim imo your understanding is right. The client capabilities declaration was obviously not following the protocol specs, that's why I opened this PR in the first place. This PR addressed only part of the issue, as you've noted the hardcoded piece listChanged=True in the Roots capability still needs to be fixed. I am working on a fix and will open a new PR to address it.

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.

3 participants