8000 Ensure _meta is available on all requests (#1284) by ccreighton-apptio · Pull Request #1520 · modelcontextprotocol/modelcontextprotocol · GitHub
[go: up one dir, main page]

Skip to content

Conversation

ccreighton-apptio
Copy link
@ccreighton-apptio ccreighton-apptio commented Sep 22, 2025

Adds definitions for params? on all Requests that avoid overwriting extended interfaces.

Motivation and Context

In #1284, it's suggested to use _meta on tools/list & other requests, but this isn't currently possible.

How Has This Been Tested?

Defined a sample ListToolsRequest to ensure that both _meta and cursor were supported params:

const listToolsRequest: ListToolsRequest = {
  id: 'foo',
  jsonrpc: "2.0",
  method: "tools/list",
  params: {
    cursor: 'next',
    badKey: 'typescript error here - invalid key',
    _meta: {
      any: 'no typescript error here',
      thing: {
        complex: 'object'
      }
    }
  }
}

Breaking Changes

Possibly yes:

  • previously, [key: string]: unknown; was part of the params definition for any type which didn't override the definition from Request or Notification. If support for arbitrary params was used, this will no longer be allowed.

This is potentially breaking for the following types:

  • PingRequest
  • ListRootsRequest
  • InitializedNotification
  • ResourceListChangedNotification
  • PromptListChangedNotification
  • ToolListChangedNotification
  • RootsListChangedNotification

Please see further discussion here.

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

Copy link
Author
@ccreighton-apptio ccreighton-apptio left a comment

Choose a reason for hiding this comment

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

Per the comments in #1284 I believe it was intended that progressToken and _meta be available on all Request types. I'm certainly open to other approaches / names / naming conventions, but believe this matches the original intent.

[key: string]: unknown;
};
progressToken?: ProgressToken;
[key: string]: unknown;
Copy link
Author

Choose a reason for hiding this comment

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

Breaking change: this was previously present both inside _meta and outside it. My assumption was that the intent was to show that other interfaces might add properties to params. In practice, such types overwrote this definition, so it only applied to PingRequest and ListRootsRequest which utilized the base definition.

If desired, this could be added back, but if I've understood the intent properly, I don't think that's actually desired behavior.

Copy link
Member

Choose a reason for hiding this comment

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

I think (read: speculate) the intent was to allow MCP to be extended with unofficial RPC methods that merely have to conform to the Request interface, which supports any properties in params. So I think we should leave this in.

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for the initial review! I'm not 100% sure I follow this though. I'll put this back, but would like to make sure I understand how this delivers on that intent (or not?).

In the "test" object from the description, badKey fails type checking, but introducing arbitrary params means the test object should instead look something like:

const listToolsRequest: ListToolsRequest = {
  id: 'foo',
  jsonrpc: "2.0",
  method: "tools/list",
  params: {
    cursor: 'next',
    arbitraryKey: 'NO typescript error here - the spec allows this. Depending on the server implementation, this request might:\n' +
      '      - Fail - but hopefully not since the spec allows this\n' +
      '      - Succeed, with this param ignored\n' +
      '      - Succeed, with this param being handled in some unofficial manner',
    _meta: {
      any: 'no typescript error here',
      thing: {
        complex: 'object'
      }
    }
  }
}

Is the intent that the Succeed, with this param being handled in some unofficial manner case be allowed for the tools/list method? If so, I misunderstood what you meant by allow MCP to be extended with unofficial RPC methods, but the way I interpreted that was more like:

If I wanted to extend MCP with an unofficial RPC method, I think I would need to introduce some new method definition which would have a params definition of its own. As part of that definition, I would define the params it takes similar to how the spec does, and my definition would extend RequestParams with definitions for the specific params needed. For example, I might want to introduce a tools/call/cancel method, which extends existing tool calling functionality to enable requesting cancellation a prior tool call request.

To support this, I want a params definition that lets me specify the id of the Request that wish to cancel:

export interface CancelCallToolRequestParams extends RequestParams {
  /**
   * The id of the previously sent tool call request which cancellation is being requested for.
   */
  id: RequestId;
}

And then define the cancellation request which uses it:

export interface CancelCallToolRequest extends JSONRPCRequest {
  method: "tools/call/cancel";
  params: CancelCallToolRequestParams;
}

This enables me to send cancellation requests which specify the id of the prior request, along with the standard RequestParams, but not arbitrary params.

Which of these understandings is more in line with the intent behind:

allow MCP to be extended with unofficial RPC methods

Copy link
Member

Choose a reason for hiding this comment

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

If I wanted to extend MCP with an unofficial RPC method, I think I would need to introduce some new method definition which would have a params definition of its own. As part of that definition, I would define the params it takes similar to how the spec does, and my definition would extend RequestParams with definitions for the specific params needed.

My thinking (again, speculation) is that while a custom server / client might validate against a new request type that has its own dedicated params type, the SDK or other intermediary code would still validate against the Request type. So Request.params would need to allow arbitrary key-values in order to support that.

Is the intent that the Succeed, with this param being handled in some unofficial manner case be allowed for the tools/list method?

No, I don't think so. So perhaps I rescind my suggestion about renaming BaseRequestParams, and instead we could have both BaseRequestParams and RequestParams types.

All *RequestParams types would extend BaseRequestParams, which would define _meta. Additionally, the RequestParams type, which would be the type for Request.params, would allow arbitrary key-values. That way, we preserve backward compatibility.

Does that make sense?

Copy link
Author

Choose a reason for hiding this comment

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

IIUC, as described, that will introduce TypeScript errors like:

2430: Interface InitializeRequest incorrectly extends interface JSONRPCRequest
  Types of property params are incompatible.
    Type InitializeRequestParams is not assignable to type RequestParams
      Index signature for type string is missing in type InitializeRequestParams

Copy link
Author

Choose a reason for hiding this comment

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

How about?

/** @internal */
export interface Request {
  method: string;
  params?: RequestParams | { [key: string]: unknown };
}

This allows arbitrary params on Request without imposing that requirement on RequestParams and avoids re-introducing the Base nomenclature.

Copy link
Author

Choose a reason for hiding this comment

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

I've pushed a commit with this proposed change & addressing this same issue with notifications, but if there's an alternate route I should pursue that doesn't cause type errors, I can certainly revisit this.

Copy link
Member

Choose a reason for hiding this comment

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

IIUC, as described, that will introduce TypeScript errors like:

Ah, looks like that's due to microsoft/TypeScript#15300. TL;DR: if we used type aliases instead of interfaces, it would work.

How about?

/** @internal */
export interface Request {
  method: string;
  params?: RequestParams | { [key: string]: unknown };
}

That works, though the JSON schema ends up being:

{
    "Request": {
        "properties": {
            "method": {
                "type": "string"
            },
            "params": {
                "anyOf": [
                    {
                        "$ref": "#/definitions/RequestParams"
                    },
                    {
                        "additionalProperties": {},
                        "type": "object"
                    }
                ]
            }
        },
        "required": [
            "method"
        ],
        "type": "object"
    }
}

Which might be fine?

Another option is simply defining Request as:

/** @internal */
export interface Request {
  method: string;
  params?: { [key: string]: any };
}

Then JSON schema ends up as:

{
    "Request": {
        "properties": {
            "method": {
                "type": "string"
            },
            "params": {
                "additionalProperties": {},
                "type": "object"
            }
        },
        "required": [
            "method"
        ],
        "type": "object"
    }
}

Which feels slightly more accurate (in a nitpicky way).

I generally prefer unknown to any, but since we are contending with TypeScript's design decision more so than type safety, I'm inclined to just go with any. Thoughts?

Copy link
Author
@ccreighton-apptio ccreighton-apptio Oct 6, 2025

Choose a reason for hiding this comment

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

Done!

E: I will leave this convo unresolved for visibility in future reviews, since it concerns the behavior called out as breaking in the description.

Copy link
Member
@jonathanhefner jonathanhefner left a comment

Choose a reason for hiding this comment

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

There is some overlap here with #1318, which addresses SEP-1319.

I think there is value in this refactoring / fix, and, being a refactoring / fix, it's not clear to me that an SEP would actually be necessary.

Anyway, 👍 from me.

[key: string]: unknown;
};
progressToken?: ProgressToken;
[key: string]: unknown;
Copy link
Member

Choose a reason for hiding this comment

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

I think (read: speculate) the intent was to allow MCP to be extended with unofficial RPC methods that merely have to conform to the Request interface, which supports any properties in params. So I think we should leave this in.

@ccreighton-apptio
Copy link
Author
ccreighton-apptio commented Oct 6, 2025

@jonathanhefner thanks again for reviewing! For easy diff visibility, all changes are in a 2nd commit. Please see my comment about my concerns/understanding regarding arbitrary params & unofficial RPC methods.

After we're certain that arbitrary params keys are desirable, I'd be happy to open a separate PR with changes for notifications unless you'd really like to see them addressed in this one.

jonathanhefner
jonathanhefner previously approved these changes Oct 7, 2025
Copy link
Member
@jonathanhefner jonathanhefner left a comment

Choose a reason for hiding this comment

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

Excellent! Thank you, @ccreighton-apptio! ❤️

Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
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.

Missing _meta in schema.json for CallToolRequest
2 participants
0