mcp: move to 2025-06-18 schema (#8)

* mcp: move to 2025-06-18 schema

Add types and fields for the latest MCP schema.

The main challenge here was dealing with the presence of _meta fields
on many types, like Tool, Prompt and Resource.

- Distinguish between the _meta field of a request's params, which
  includes a progress token, from the _meta field on other types,
  which is simply a map.

- Redefine GetMeta to return a pointer to the map. We wanted to avoid
  that, but I don't see a clean way to do so. An alternative would be
  to have GetMeta return a map, and SetMeta to set it.

- Provide GetProgressToken for requests only (not notifications or other
  types).

- Still to do: deal with the meta and annotation fields of ContentBlock,
  which are currently duplicated for Resource and ResourceContents.

* Meta and progress token redesign
This commit is contained in:
Jonathan Amsterdam 2025-06-25 09:48:33 -04:00 • committed by GitHub
commit 3da831b0c9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -242,51 +242,62 @@ func notifySessions[S Session](sessions []S, method string, params Params) {
}
}
type Meta struct {
Data map[string]any `json:",omitempty"`
// For params, the progress token can be nil, a string or an integer.
// It should be nil for results.
ProgressToken any `json:"progressToken,omitempty"`
type Meta map[string]any
func (m Meta) GetMeta() map[string]any { return m }
func (m *Meta) SetMeta(x map[string]any) { *m = x }
const progressTokenKey = "progressToken"
func getProgressToken(p Params) any {
return p.GetMeta()[progressTokenKey]
}
type metaSansMethods Meta // avoid infinite recursion during marshaling
func (m Meta) MarshalJSON() ([]byte, error) {
if p := m.ProgressToken; p != nil {
if k := reflect.ValueOf(p).Kind(); k != reflect.Int && k != reflect.String {
return nil, fmt.Errorf("bad type %T for Meta.ProgressToken: must be int or string", p)
}
func setProgressToken(p Params, pt any) {
switch pt.(type) {
// Support int32 and int64 for atomic.IntNN.
case int, int32, int64, string:
default:
panic(fmt.Sprintf("progress token %v is of type %[1]T, not int or string", pt))
}
// If ProgressToken is nil, accept Data["progressToken"]. We can't call marshalStructWithMap
// in that case because it will complain about duplicate fields. (We'd have to
// make it much smarter to avoid that; not worth it.)
if m.ProgressToken == nil && len(m.Data) > 0 {
return json.Marshal(m.Data)
m := p.GetMeta()
if m == nil {
m = map[string]any{}
}
return marshalStructWithMap((*metaSansMethods)(&m), "Data")
}
func (m *Meta) UnmarshalJSON(data []byte) error {
return unmarshalStructWithMap(data, (*metaSansMethods)(m), "Data")
m[progressTokenKey] = pt
}
// Params is a parameter (input) type for an MCP call or notification.
type Params interface {
// Returns a pointer to the params's Meta field.
GetMeta() *Meta
GetMeta() map[string]any
SetMeta(map[string]any)
}
// RequestParams is a parameter (input) type for an MCP request.
type RequestParams interface {
Params
// GetProgressToken returns the progress token from the params' Meta field, or nil
// if there is none.
GetProgressToken() any
// SetProgressToken sets the given progress token into the params' Meta field.
// It panics if its argument is not an int or a string.
SetProgressToken(any)
}
// Result is a result of an MCP call.
type Result interface {
// Returns a pointer to the result's Meta field.
GetMeta() *Meta
GetMeta() map[string]any
SetMeta(map[string]any)
}
// emptyResult is returned by methods that have no result, like ping.
// Those methods cannot return nil, because jsonrpc2 cannot handle nils.
type emptyResult struct{}
func (*emptyResult) GetMeta() *Meta { panic("should never be called") }
func (*emptyResult) GetMeta() map[string]any { panic("should never be called") }
func (*emptyResult) SetMeta(map[string]any) { panic("should never be called") }
type listParams interface {
// Returns a pointer to the param's Cursor field.