-
Notifications
You must be signed in to change notification settings - Fork 32
Expand file tree
/
Copy pathmcp_types.go
More file actions
410 lines (349 loc) · 11.7 KB
/
Copy pathmcp_types.go
File metadata and controls
410 lines (349 loc) · 11.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
// Tencent is pleased to support the open source community by making trpc-mcp-go available.
//
// Copyright (C) 2025 Tencent. All rights reserved.
//
// trpc-mcp-go is licensed under the Apache License Version 2.0.
package mcp
import (
"encoding/json"
"strings"
"sync"
)
const (
// ContentTypeText represents text content type
ContentTypeText = "text"
// ContentTypeImage represents image content type
ContentTypeImage = "image"
// ContentTypeAudio represents audio content type
ContentTypeAudio = "audio"
// ContentTypeEmbeddedResource represents embedded resource content type
ContentTypeEmbeddedResource = "embedded_resource"
)
// MCP protcol Layer
// Meta represents metadata attached to a request's parameters.
// This can include fields formally defined by the protocol (like ProgressToken)
// or other arbitrary data for custom use cases.
// Based on mcp-go implementation for MCP 2025-06-18 protocol support.
type Meta struct {
// ProgressToken is used to request out-of-band progress notifications.
// If specified, the caller is requesting progress notifications for this
// request (as represented by notifications/progress). The value is an
// opaque token that will be attached to any subsequent notifications.
// The receiver is not obligated to provide these notifications.
ProgressToken ProgressToken `json:"-"`
// AdditionalFields are any fields present in the Meta that are not
// otherwise defined in the protocol. This allows for custom metadata
// to be passed between clients and servers.
AdditionalFields map[string]interface{} `json:"-"`
}
// MarshalJSON implements custom JSON marshaling for Meta.
// It flattens ProgressToken and AdditionalFields into a single JSON object.
func (m *Meta) MarshalJSON() ([]byte, error) {
if m == nil {
return []byte("null"), nil
}
raw := make(map[string]interface{})
// Add progressToken if present
if m.ProgressToken != nil {
raw["progressToken"] = m.ProgressToken
}
// Add all additional fields
for k, v := range m.AdditionalFields {
raw[k] = v
}
return json.Marshal(raw)
}
// UnmarshalJSON implements custom JSON unmarshaling for Meta.
// It extracts progressToken and puts all other fields into AdditionalFields.
func (m *Meta) UnmarshalJSON(data []byte) error {
raw := make(map[string]interface{})
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
// Extract progressToken
if pt, ok := raw["progressToken"]; ok {
m.ProgressToken = pt
delete(raw, "progressToken")
}
// Store remaining fields as additional fields
m.AdditionalFields = raw
return nil
}
// Get retrieves a value from AdditionalFields by key.
// Returns nil if the key doesn't exist or AdditionalFields is nil.
func (m *Meta) Get(key string) interface{} {
if m == nil || m.AdditionalFields == nil {
return nil
}
return m.AdditionalFields[key]
}
// Set sets a value in AdditionalFields.
// Initializes AdditionalFields if it's nil.
func (m *Meta) Set(key string, value interface{}) {
if m.AdditionalFields == nil {
m.AdditionalFields = make(map[string]interface{})
}
m.AdditionalFields[key] = value
}
// Request is the base request struct for all MCP requests.
type Request struct {
Method string `json:"method"`
Params struct {
Meta *Meta `json:"_meta,omitempty"`
} `json:"params,omitempty"`
}
// Notification is the base notification struct for all MCP notifications.
type Notification struct {
Method string `json:"method"`
Params NotificationParams `json:"params,omitempty"`
}
// NotificationParams is the base notification params struct for all MCP notifications.
type NotificationParams struct {
Meta map[string]interface{} `json:"_meta,omitempty"`
AdditionalFields map[string]interface{} `json:"-"` // Additional fields that are not part of the MCP protocol.
}
// MarshalJSON implements custom JSON marshaling for NotificationParams.
// It flattens the AdditionalFields into the main JSON object.
func (p NotificationParams) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{})
// Add Meta if it exists and is not empty
if len(p.Meta) > 0 {
m["_meta"] = p.Meta
}
// Add all additional fields
if p.AdditionalFields != nil {
for k, v := range p.AdditionalFields {
// Ensure we don't override the _meta field if it was already set from p.Meta
// This check is important if AdditionalFields could also contain a "_meta" key,
// though generally, _meta should be handled by the dedicated Meta field.
if k != "_meta" {
m[k] = v
} else if _, metaExists := m["_meta"]; !metaExists {
// If _meta was not set from p.Meta but exists in AdditionalFields, use it.
// This case might be rare if p.Meta is the designated place for _meta.
m[k] = v
}
}
}
if len(m) == 0 {
// Return JSON representation of an empty object {} instead of null for empty params
return []byte("{}"), nil
}
return json.Marshal(m)
}
// UnmarshalJSON implements custom JSON unmarshaling for NotificationParams.
// It separates '_meta' from other fields which are placed into AdditionalFields.
func (p *NotificationParams) UnmarshalJSON(data []byte) error {
// Handle null or empty JSON object correctly for params
sData := string(data)
if sData == "null" || sData == "{}" {
// If params is null or an empty object, initialize and return
p.AdditionalFields = make(map[string]interface{})
p.Meta = make(map[string]interface{}) // Initialize Meta as well
return nil
}
var m map[string]interface{}
if err := json.Unmarshal(data, &m); err != nil {
return err
}
if p.AdditionalFields == nil {
p.AdditionalFields = make(map[string]interface{})
}
for k, v := range m {
if k == "_meta" {
if metaMap, ok := v.(map[string]interface{}); ok {
// Initialize p.Meta only if it's nil and metaMap is not nil and not empty
if p.Meta == nil && metaMap != nil && len(metaMap) > 0 {
p.Meta = make(map[string]interface{})
}
// Populate p.Meta. This handles case where p.Meta was nil or already existed.
if p.Meta != nil { // ensure p.Meta is not nil before assigning to it
for mk, mv := range metaMap {
p.Meta[mk] = mv
}
}
}
} else {
p.AdditionalFields[k] = v
}
}
return nil
}
// Result is the base result struct for all MCP results.
type Result struct {
Meta map[string]interface{} `json:"_meta,omitempty"`
}
// PaginatedResult is the base paginated result struct for all MCP paginated results.
type PaginatedResult struct {
Result
NextCursor Cursor `json:"nextCursor,omitempty"`
}
// ProgressToken is the base progress token struct for all MCP progress tokens.
type ProgressToken interface{}
// Cursor is the base cursor struct for all MCP cursors.
type Cursor string
// Role represents the sender or recipient of a message.
type Role string
const (
// RoleUser represents the user role
RoleUser Role = "user"
// RoleAssistant represents the assistant role
RoleAssistant Role = "assistant"
)
// Annotated describes an annotated resource.
type Annotated struct {
// Annotations (optional)
Annotations *struct {
Audience []Role `json:"audience,omitempty"`
Priority float64 `json:"priority,omitempty"`
} `json:"annotations,omitempty"`
}
// Content represents different types of message content (text, image, audio, embedded resource).
type Content interface {
isContent()
}
// TextContent represents text content
type TextContent struct {
Type string `json:"type"`
Text string `json:"text"`
Annotated
}
func (TextContent) isContent() {}
// ImageContent represents image content
type ImageContent struct {
Type string `json:"type"`
Data string `json:"data"` // base64 encoded image data
MimeType string `json:"mimeType"`
Annotated
}
func (ImageContent) isContent() {}
// AudioContent represents audio content
type AudioContent struct {
Type string `json:"type"`
Data string `json:"data"` // base64 encoded audio data
MimeType string `json:"mimeType"`
Annotated
}
func (AudioContent) isContent() {}
// EmbeddedResource represents an embedded resource
type EmbeddedResource struct {
Resource ResourceContents `json:"resource"` // Using generic interface type
Type string `json:"type"`
Annotated
}
func (EmbeddedResource) isContent() {}
// NewTextContent helpe functions for content creation
func NewTextContent(text string) TextContent {
return TextContent{
Type: ContentTypeText,
Text: text,
}
}
// NewImageContent creates a new image content
func NewImageContent(data string, mimeType string) ImageContent {
return ImageContent{
Type: ContentTypeImage,
Data: data,
MimeType: mimeType,
}
}
// NewAudioContent creates a new audio content
func NewAudioContent(data string, mimeType string) AudioContent {
return AudioContent{
Type: ContentTypeAudio,
Data: data,
MimeType: mimeType,
}
}
// NewEmbeddedResource creates a new embedded resource
func NewEmbeddedResource(resource ResourceContents) EmbeddedResource {
return EmbeddedResource{
Type: ContentTypeEmbeddedResource,
Resource: resource,
}
}
// RootsProvider defines the interface for root directory providers.
type RootsProvider interface {
// GetRoots returns the list of currently available root directories.
GetRoots() []Root
}
// Root represents a filesystem root directory that a client provides to servers.
type Root struct {
// The URI of the root directory. Must be a file:// URI.
URI string `json:"uri"`
// An optional name for the root directory.
Name string `json:"name,omitempty"`
}
// ListRootsResult represents the client's response to a roots/list request from the server.
type ListRootsResult struct {
Result
Roots []Root `json:"roots"`
}
// DefaultRootsProvider implements a simple root directory provider.
type DefaultRootsProvider struct {
mu sync.RWMutex
roots []Root
}
// NewDefaultRootsProvider creates a new default root directory provider.
func NewDefaultRootsProvider(roots ...Root) *DefaultRootsProvider {
return &DefaultRootsProvider{
roots: append([]Root{}, roots...),
}
}
// AddRoot adds a root directory to the provider.
// If the URI doesn't start with "file://", it will be automatically prefixed.
// For local filesystem paths, this ensures proper file:/// format per MCP specification.
func (p *DefaultRootsProvider) AddRoot(uri, name string) {
p.mu.Lock()
defer p.mu.Unlock()
// Ensure URI format is correct according to MCP specification.
if !strings.HasPrefix(uri, "file://") {
// For local filesystem paths, use file:/// format as per MCP spec.
if strings.HasPrefix(uri, "/") {
// Absolute path: file:/// + path
uri = "file://" + uri
} else {
// Relative path: convert to absolute then add file:///
// This ensures proper file:/// format for local filesystem.
uri = "file:///" + uri
}
}
p.roots = append(p.roots, Root{
URI: uri,
Name: name,
})
}
// RemoveRoot removes a root directory from the provider.
// If the URI doesn't start with "file://", it will be automatically prefixed for comparison.
func (p *DefaultRootsProvider) RemoveRoot(uri string) {
p.mu.Lock()
defer p.mu.Unlock()
// Standardize URI format for comparison.
if !strings.HasPrefix(uri, "file://") {
// Apply same logic as AddRoot for consistent formatting.
if strings.HasPrefix(uri, "/") {
// Absolute path: file:/// + path.
uri = "file://" + uri
} else {
// Relative path: convert to file:/// format.
uri = "file:///" + uri
}
}
newRoots := make([]Root, 0, len(p.roots))
for _, root := range p.roots {
if root.URI != uri {
newRoots = append(newRoots, root)
}
}
p.roots = newRoots
}
// GetRoots implements the RootsProvider interface.
// It returns a copy of the current root directories to prevent external modification.
func (p *DefaultRootsProvider) GetRoots() []Root {
p.mu.RLock()
defer p.mu.RUnlock()
// Return a copy of the roots.
result := make([]Root, len(p.roots))
copy(result, p.roots)
return result
}