1
1
# Go MCP SDK design
2
2
3
- This file discusses the design of a Go SDK for the [ model context
3
+ This document discusses the design of a Go SDK for the [ model context
4
4
protocol] ( https://modelcontextprotocol.io/specification/2025-03-26 ) . It is
5
5
intended to seed a GitHub discussion about the official Go MCP SDK.
6
6
@@ -18,7 +18,7 @@ writing, it is imported by over 400 packages that span over 200 modules.
18
18
We admire mcp-go, and seriously considered simply adopting it as a starting
19
19
point for this SDK. However, as we looked at doing so, we realized that a
20
20
significant amount of its API would probably need to change. In some cases,
21
- mcp-go has older APIs that predated newer variations-- an obvious opportunity
21
+ mcp-go has older APIs that predated newer variations— an obvious opportunity
22
22
for cleanup. In others, it took a batteries-included approach that is probably
23
23
not viable for an official SDK. In yet others, we simply think there is room for
24
24
API refinement, and we should take this opportunity to consider our options.
@@ -332,7 +332,7 @@ marshalling/unmarshalling can be delegated to the business logic of the client
332
332
or server.
333
333
334
334
For union types, which can't be represented in Go (specifically ` Content` and
335
- ` Resource ` ), we prefer distinguished unions: struct types with fields
335
+ ` ResourceContents ` ), we prefer distinguished unions: struct types with fields
336
336
corresponding to the union of all properties for union elements.
337
337
338
338
For brevity, only a few examples are shown here:
@@ -353,11 +353,11 @@ type CallToolResult struct {
353
353
// The Type field distinguishes the type of the content.
354
354
// At most one of Text, MIMEType, Data, and Resource is non-zero.
355
355
type Content struct {
356
- Type string ` json:"type"`
357
- Text string ` json:"text,omitempty"`
358
- MIMEType string ` json:"mimeType,omitempty"`
359
- Data []byte ` json:"data,omitempty"`
360
- Resource *Resource ` json:"resource,omitempty"`
356
+ Type string ` json:"type"`
357
+ Text string ` json:"text,omitempty"`
358
+ MIMEType string ` json:"mimeType,omitempty"`
359
+ Data []byte ` json:"data,omitempty"`
360
+ Resource *ResourceContents ` json:"resource,omitempty"`
361
361
}
362
362
` ` `
363
363
@@ -386,7 +386,7 @@ change.
386
386
387
387
Following the terminology of the
388
388
[spec](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#session-management),
389
- we call the logical connection between a client and server a "session". There
389
+ we call the logical connection between a client and server a "session." There
390
390
must necessarily be a ` ClientSession` and a ` ServerSession` , corresponding to
391
391
the APIs available from the client and server perspective, respectively.
392
392
@@ -436,7 +436,7 @@ transport := mcp.NewCommandTransport(exec.Command("myserver"))
436
436
session , err := client.Connect (ctx, transport)
437
437
if err != nil { ... }
438
438
// Call a tool on the server.
439
- content , err := session.CallTool (ctx, " greet" , map [string ]any{" name" : " you" }, nil } )
439
+ content , err := session.CallTool (ctx, " greet" , map [string ]any{" name" : " you" }, nil )
440
440
...
441
441
return session.Close ()
442
442
` ` `
@@ -446,7 +446,7 @@ A server that can handle that client call would look like this:
446
446
` ` ` go
447
447
// Create a server with a single tool.
448
448
server := mcp.NewServer (" greeter" , " v1.0.0" , nil )
449
- server.AddTool (mcp.NewTool (" greet" , " say hi" , SayHi))
449
+ server.AddTools (mcp.NewTool (" greet" , " say hi" , SayHi))
450
450
// Run the server over stdin/stdout, until the client disconnects.
451
451
transport := mcp.NewStdIOTransport ()
452
452
session , err := server.Connect (ctx, transport)
@@ -541,8 +541,8 @@ func (*ClientSession) ResourceTemplates(context.Context, *ListResourceTemplatesP
541
541
542
542
### Middleware
543
543
544
- We provide a mechanism to add MCP-level middleware, which runs after the
545
- request has been parsed, but before any normal handling.
544
+ We provide a mechanism to add MCP-level middleware on the server side , which runs after the
545
+ request has been parsed but before any normal handling.
546
546
547
547
` ` ` go
548
548
// A Dispatcher dispatches an MCP message to the appropriate handler.
@@ -551,14 +551,14 @@ request has been parsed, but before any normal handling.
551
551
type Dispatcher func(ctx context.Context, s *ServerSession, method string, params any) (result any, err error)
552
552
553
553
// AddDispatchers calls each function from right to left on the previous result, beginning
554
- // with the server's current dispatcher, and installs the result as the new handler .
555
- func (*Server) AddDispatchers(middleware ...func(Handler) Handler ))
554
+ // with the server's current dispatcher, and installs the result as the new dispatcher .
555
+ func (*Server) AddDispatchers(middleware ...func(Dispatcher) Dispatcher ))
556
556
` ` `
557
557
558
558
As an example, this code adds server-side logging:
559
559
560
560
` ` ` go
561
- func withLogging(h mcp.Handler ) mcp.Handler {
561
+ func withLogging(h mcp.Dispatcher ) mcp.Dispatcher {
562
562
return func(ctx context.Context, s *mcp.ServerSession, method string, params any) (res any, err error) {
563
563
log.Printf("request: %s %v", method, params)
564
564
defer func() { log.Printf("response: %v, %v", res, err) }()
@@ -621,11 +621,9 @@ The server observes a client cancellation as a cancelled context.
621
621
A caller can request progress notifications by setting the ` ProgressToken` field on any request.
622
622
623
623
` ` ` go
624
- type ProgressToken any // string or int
625
-
626
624
type XXXParams struct { // where XXX is each type of call
627
625
...
628
- ProgressToken ProgressToken
626
+ ProgressToken any // string or int
629
627
}
630
628
` ` `
631
629
@@ -681,15 +679,15 @@ Roots can be added and removed from a `Client` with `AddRoots` and `RemoveRoots`
681
679
// AddRoots adds the given roots to the client,
682
680
// replacing any with the same URIs,
683
681
// and notifies any connected servers.
684
- func (*Client) AddRoots(roots ...Root)
682
+ func (*Client) AddRoots(roots ...* Root)
685
683
686
684
// RemoveRoots removes the roots with the given URIs.
687
685
// and notifies any connected servers if the list has changed.
688
686
// It is not an error to remove a nonexistent root.
689
687
func (*Client) RemoveRoots(uris ...string)
690
688
` ` `
691
689
692
- Servers can call the spec method ` ListRoots` to get the roots. If a server installs a
690
+ Server sessions can call the spec method ` ListRoots` to get the roots. If a server installs a
693
691
` RootsChangedHandler` , it will be called when the client sends a roots-changed
694
692
notification, which happens whenever the list of roots changes after a
695
693
connection has been established.
@@ -705,7 +703,7 @@ type ServerOptions {
705
703
### Sampling
706
704
707
705
Clients that support sampling are created with a ` CreateMessageHandler` option
708
- for handling server calls. To perform sampling, a server calls the spec method ` CreateMessage` .
706
+ for handling server calls. To perform sampling, a server session calls the spec method ` CreateMessage` .
709
707
710
708
` ` ` go
711
709
type ClientOptions struct {
@@ -742,7 +740,7 @@ Add tools to a server with `AddTools`:
742
740
` ` ` go
743
741
server.AddTools(
744
742
mcp.NewTool("add", "add numbers", addHandler),
745
- mcp.NewTools ("subtract, subtract numbers", subHandler))
743
+ mcp.NewTool ("subtract, subtract numbers", subHandler))
746
744
` ` `
747
745
748
746
Remove them by name with ` RemoveTools` :
@@ -836,7 +834,7 @@ Schemas are validated on the server before the tool handler is called.
836
834
Since all the fields of the Tool struct are exported, a Tool can also be created
837
835
directly with assignment or a struct literal.
838
836
839
- Clients can call the spec method ` ListTools` or an iterator method ` Tools`
837
+ Client sessions can call the spec method ` ListTools` or an iterator method ` Tools`
840
838
to list the available tools.
841
839
842
840
**Differences from mcp-go **: using variadic options to configure tools was
@@ -862,7 +860,7 @@ each occur only once (and in an SDK that wraps mcp-go).
862
860
863
861
For registering tools, we provide only `AddTools`; mcp-go' s ` SetTools` ,
864
862
` AddTool` , ` AddSessionTool` , and ` AddSessionTools` are deemed unnecessary.
865
- (similarly for Delete/Remove).
863
+ (Similarly for Delete/Remove).
866
864
867
865
### Prompts
868
866
@@ -893,8 +891,8 @@ server.AddPrompts(
893
891
server.RemovePrompts("code_review")
894
892
` ` `
895
893
896
- Clients can call the spec method ` ListPrompts` or an iterator method ` Prompts`
897
- to list the available prompts and the spec method ` GetPrompt` to get one.
894
+ Client sessions can call the spec method ` ListPrompts` or the iterator method ` Prompts`
895
+ to list the available prompts, and the spec method ` GetPrompt` to get one.
898
896
899
897
**Differences from mcp-go **: We provide a ` NewPrompt` helper to bind a prompt
900
898
handler to a Go function using reflection to derive its arguments. We provide
@@ -926,7 +924,8 @@ type ServerResourceTemplate struct {
926
924
```
927
925
928
926
To add a resource or resource template to a server, users call the `AddResources` and
929
- `AddResourceTemplates` methods with one or more `ServerResource`s or `ServerResourceTemplate`s:
927
+ `AddResourceTemplates` methods with one or more `ServerResource`s or `ServerResourceTemplate`s.
928
+ We also provide methods to remove them.
930
929
931
930
```go
932
931
func (*Server) AddResources(...*ServerResource)
@@ -938,14 +937,12 @@ func (s *Server) RemoveResourceTemplates(uriTemplates ...string)
938
937
939
938
The `ReadResource` method finds a resource or resource template matching the argument URI and calls
940
939
its assocated handler.
941
- If the argument URI matches a template, the `Resource` argument to the handler is constructed
942
- from the fields in the `ResourceTemplate`.
943
940
944
941
To read files from the local filesystem, we recommend using `FileResourceHandler` to construct a handler:
945
942
```go
946
943
// FileResourceHandler returns a ResourceHandler that reads paths using dir as a root directory.
947
944
// It protects against path traversal attacks.
948
- // It will not read any file that is not in the root set of the client requesting the resource.
945
+ // It will not read any file that is not in the root set of the client session requesting the resource.
949
946
func (*Server) FileResourceHandler(dir string) ResourceHandler
950
947
```
951
948
Here is an example:
@@ -957,17 +954,8 @@ s.AddResources(&mcp.ServerResource{
957
954
Handler: s.FileReadResourceHandler("/public")})
958
955
```
959
956
960
- Servers support all of the resource-related spec methods:
961
-
962
- - `ListResources` and `ListResourceTemplates` for listings.
963
- - `ReadResource` to get the contents of a resource.
964
- - `Subscribe` and `Unsubscribe` to manage subscriptions on resources.
965
-
966
- We also provide iterator methods `Resources` and `ResourceTemplates`.
967
-
968
- `ReadResource` checks the incoming URI against the server' s list of
969
- resources and resource templates to make sure it matches one of them,
970
- then returns the result of calling the associated reader function.
957
+ Server sessions also support the spec methods `ListResources` and `ListResourceTemplates`,
958
+ and the corresponding iterator methods `Resources` and `ResourceTemplates`.
971
959
972
960
#### Subscriptions
973
961
@@ -1017,6 +1005,7 @@ type ClientOptions struct {
1017
1005
...
1018
1006
ToolListChangedHandler func(context.Context, *ClientSession, *ToolListChangedParams)
1019
1007
PromptListChangedHandler func(context.Context, *ClientSession, *PromptListChangedParams)
1008
+ // For both resources and resource templates.
1020
1009
ResourceListChangedHandler func(context.Context, *ClientSession, *ResourceListChangedParams)
1021
1010
}
1022
1011
` ` `
@@ -1049,8 +1038,8 @@ type ServerOptions {
1049
1038
}
1050
1039
` ` `
1051
1040
1052
- ServerSessions have access to a `slog.Logger` that writes to the client. A call to
1053
- a log method like `Info` is translated to a `LoggingMessageNotification` as
1041
+ Server sessions have a field ` Logger ` holding a ` slog.Logger` that writes to the client session.
1042
+ A call to a log method like ` Info` is translated to a ` LoggingMessageNotification` as
1054
1043
follows:
1055
1044
1056
1045
- The attributes and the message populate the " data" property with the
@@ -1060,11 +1049,11 @@ follows:
1060
1049
- If the ` LoggerName` server option is set, it populates the " logger" property.
1061
1050
1062
1051
- The standard slog levels ` Info` , ` Debug` , ` Warn` and ` Error` map to the
1063
- corresponding levels in the MCP spec. The other spec levels will be mapped
1052
+ corresponding levels in the MCP spec. The other spec levels map
1064
1053
to integers between the slog levels. For example, " notice" is level 2 because
1065
1054
it is between " warning" (slog value 4 ) and " info" (slog value 0 ).
1066
1055
The ` mcp` package defines consts for these levels. To log at the " notice"
1067
- level, a handler would call `session.Log(ctx, mcp.LevelNotice, "message")`.
1056
+ level, a handler would call ` session.Logger. Log(ctx, mcp.LevelNotice, "message")` .
1068
1057
1069
1058
A client that wishes to receive log messages must provide a handler:
1070
1059
@@ -1080,7 +1069,7 @@ type ClientOptions struct {
1080
1069
Servers initiate pagination for ` ListTools` , ` ListPrompts` , ` ListResources` ,
1081
1070
and ` ListResourceTemplates` , dictating the page size and providing a
1082
1071
` NextCursor` field in the Result if more pages exist. The SDK implements keyset
1083
- pagination, using the ` unique ID` as the key for a stable sort order and encoding
1072
+ pagination, using the unique ID of the feature as the key for a stable sort order and encoding
1084
1073
the cursor as an opaque string .
1085
1074
1086
1075
For server implementations, the page size for the list operation may be
0 commit comments