Skip to content

Commit 91ddba5

Browse files
authored
feat(protocol): allow additional fields in meta (#293)
1 parent eb835b9 commit 91ddba5

File tree

3 files changed

+110
-18
lines changed

3 files changed

+110
-18
lines changed

mcp/tools.go

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,7 @@ type CallToolRequest struct {
4646
Params struct {
4747
Name string `json:"name"`
4848
Arguments map[string]any `json:"arguments,omitempty"`
49-
Meta *struct {
50-
// If specified, the caller is requesting out-of-band progress
51-
// notifications for this request (as represented by
52-
// notifications/progress). The value of this parameter is an
53-
// opaque token that will be attached to any subsequent
54-
// notifications. The receiver is not obligated to provide these
55-
// notifications.
56-
ProgressToken ProgressToken `json:"progressToken,omitempty"`
57-
} `json:"_meta,omitempty"`
49+
Meta *Meta `json:"_meta,omitempty"`
5850
} `json:"params"`
5951
}
6052

mcp/types.go

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package mcp
44

55
import (
66
"encoding/json"
7+
"maps"
78

89
"github.com/yosida95/uritemplate/v3"
910
)
@@ -100,18 +101,47 @@ type ProgressToken any
100101
// Cursor is an opaque token used to represent a cursor for pagination.
101102
type Cursor string
102103

104+
// Meta is metadata attached to a request's parameters. This can include fields
105+
// formally defined by the protocol or other arbitrary data.
106+
type Meta struct {
107+
// If specified, the caller is requesting out-of-band progress
108+
// notifications for this request (as represented by
109+
// notifications/progress). The value of this parameter is an
110+
// opaque token that will be attached to any subsequent
111+
// notifications. The receiver is not obligated to provide these
112+
// notifications.
113+
ProgressToken ProgressToken
114+
115+
// AdditionalFields are any fields present in the Meta that are not
116+
// otherwise defined in the protocol.
117+
AdditionalFields map[string]any
118+
}
119+
120+
func (m *Meta) MarshalJSON() ([]byte, error) {
121+
raw := make(map[string]any)
122+
if m.ProgressToken != nil {
123+
raw["progressToken"] = m.ProgressToken
124+
}
125+
maps.Copy(raw, m.AdditionalFields)
126+
127+
return json.Marshal(raw)
128+
}
129+
130+
func (m *Meta) UnmarshalJSON(data []byte) error {
131+
raw := make(map[string]any)
132+
if err := json.Unmarshal(data, &raw); err != nil {
133+
return err
134+
}
135+
m.ProgressToken = raw["progressToken"]
136+
delete(raw, "progressToken")
137+
m.AdditionalFields = raw
138+
return nil
139+
}
140+
103141
type Request struct {
104142
Method string `json:"method"`
105143
Params struct {
106-
Meta *struct {
107-
// If specified, the caller is requesting out-of-band progress
108-
// notifications for this request (as represented by
109-
// notifications/progress). The value of this parameter is an
110-
// opaque token that will be attached to any subsequent
111-
// notifications. The receiver is not obligated to provide these
112-
// notifications.
113-
ProgressToken ProgressToken `json:"progressToken,omitempty"`
114-
} `json:"_meta,omitempty"`
144+
Meta *Meta `json:"_meta,omitempty"`
115145
} `json:"params,omitempty"`
116146
}
117147

mcp/types_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package mcp
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestMetaMarshalling(t *testing.T) {
12+
tests := []struct {
13+
name string
14+
json string
15+
meta *Meta
16+
expMeta *Meta
17+
}{
18+
{
19+
name: "empty",
20+
json: "{}",
21+
meta: &Meta{},
22+
expMeta: &Meta{AdditionalFields: map[string]any{}},
23+
},
24+
{
25+
name: "empty additional fields",
26+
json: "{}",
27+
meta: &Meta{AdditionalFields: map[string]any{}},
28+
expMeta: &Meta{AdditionalFields: map[string]any{}},
29+
},
30+
{
31+
name: "string token only",
32+
json: `{"progressToken":"123"}`,
33+
meta: &Meta{ProgressToken: "123"},
34+
expMeta: &Meta{ProgressToken: "123", AdditionalFields: map[string]any{}},
35+
},
36+
{
37+
name: "string token only, empty additional fields",
38+
json: `{"progressToken":"123"}`,
39+
meta: &Meta{ProgressToken: "123", AdditionalFields: map[string]any{}},
40+
expMeta: &Meta{ProgressToken: "123", AdditionalFields: map[string]any{}},
41+
},
42+
{
43+
name: "additional fields only",
44+
json: `{"a":2,"b":"1"}`,
45+
meta: &Meta{AdditionalFields: map[string]any{"a": 2, "b": "1"}},
46+
// For untyped map, numbers are always float64
47+
expMeta: &Meta{AdditionalFields: map[string]any{"a": float64(2), "b": "1"}},
48+
},
49+
{
50+
name: "progress token and additional fields",
51+
json: `{"a":2,"b":"1","progressToken":"123"}`,
52+
meta: &Meta{ProgressToken: "123", AdditionalFields: map[string]any{"a": 2, "b": "1"}},
53+
// For untyped map, numbers are always float64
54+
expMeta: &Meta{ProgressToken: "123", AdditionalFields: map[string]any{"a": float64(2), "b": "1"}},
55+
},
56+
}
57+
58+
for _, tc := range tests {
59+
t.Run(tc.name, func(t *testing.T) {
60+
data, err := json.Marshal(tc.meta)
61+
require.NoError(t, err)
62+
assert.Equal(t, tc.json, string(data))
63+
64+
meta := &Meta{}
65+
err = json.Unmarshal([]byte(tc.json), meta)
66+
require.NoError(t, err)
67+
assert.Equal(t, tc.expMeta, meta)
68+
})
69+
}
70+
}

0 commit comments

Comments
 (0)