CARVIEW |
Navigation Menu
Replies: 22 comments · 57 replies
-
Thank you so much for hard work on this! Excited to try this out at GitHub. π₯ |
Beta Was this translation helpful? Give feedback.
All reactions
-
β€οΈ 8
-
This looks great, thank you for your work. As I read the function signatures, I had a similar question as: mark3labs/mcp-go#294, which perhaps you also have a good answer for. You say:
And the type ToolHandler func(context.Context, *ServerSession, *CallToolParams) (*CallToolResult, error) Under what circumstances would you expect a Tool to return a non-nil |
Beta Was this translation helpful? Give feedback.
All reactions
-
@williammartin here's the relevant implementation in our prototype: If you use the |
Beta Was this translation helpful? Give feedback.
All reactions
-
Some concrete example of returned error would be, in the But, I want to clarify something to @findleyr. Based on the implementation you share above, if the handler implementation is returning error, the client that calling it would NOT receive a Go error, but instead a |
Beta Was this translation helpful? Give feedback.
All reactions
-
@MegaGrindStone yes, that's right. That was my reading of the spec: a missing tool or parse error results in a JSON-RPC error, but a failure of the business logic of the tool (the Go function) is lifted into a normal result with |
Beta Was this translation helpful? Give feedback.
All reactions
-
π 1
-
Thanks @findleyr for sharing that code! That's pretty much exactly what I imagined earlier in a conversation when I wrote to @SamMorrowDrums about separating our tool handling from the specific server module:
Except in this implementation you've chosen to union fields rather than types (totally fine, I'm just a sum typer at heart!). Also, a big fan of giving access to the raw json for input, thats what I would have used in the signature above if it wouldn't have meant marshaling the mcp-go args again. |
Beta Was this translation helpful? Give feedback.
All reactions
-
β€οΈ 3
-
@findleyr, as valuable as this document is for noodling on, I'm very eager to get my hands on the implementation to give feedback based on empirical use. At the top of the discussion you said:
Please let me know if you start executing in such a way that I can start trying it out. |
Beta Was this translation helpful? Give feedback.
All reactions
-
π 4 -
π 1
-
I vote for creating github.com/modelcontextprotocol/go-sdk with at least a README on it including pointers to this thread and the dev repo at google to point the search-gods to the right direction. |
Beta Was this translation helpful? Give feedback.
All reactions
-
Really excited to see this moving forward β the design looks thoughtful and well aligned with Go idioms. Iβd be happy to help with early testing, especially around session handling, transport abstractions, and tool integration. Please feel free to loop me in where hands-on feedback would be most useful. Looking forward to contributing! |
Beta Was this translation helpful? Give feedback.
All reactions
-
π 1
-
Thanks for the doc @findleyr. As you know I am mostly looking at the observability aspect of the SDK, especially propagating distributed context. Some initial thoughts
|
Beta Was this translation helpful? Give feedback.
All reactions
-
Thanks - I read through the latest code and see the symmetric middleware and the request handling side looks pretty good. I am still curious what it'll look like to inject data into messages before going over the wire, I think it's wrap Also, for requests from server to client, or arbitrary request from client/server, I think it can be simply added with |
Beta Was this translation helpful? Give feedback.
All reactions
-
I went ahead and prototyped what it would look like to inject metadata by wrapping That being said, since it is doable, once meta is available on the server side things look ready for context propagation to work so nice to see that. type contextTransport struct {
delegate mcp.Transport
}
func (c contextTransport) Connect(ctx context.Context) (mcp.Stream, error) {
stream, err := c.delegate.Connect(ctx)
if err != nil {
return nil, err
}
return contextStream{delegate: stream}, nil
}
type contextStream struct {
delegate mcp.Stream
}
// Read implements mcp.Stream.
func (c contextStream) Read(ctx context.Context) (jsonrpc2.Message, int64, error) {
return c.delegate.Read(ctx)
}
// Write implements mcp.Stream.
func (c contextStream) Write(ctx context.Context, msg jsonrpc2.Message) (int64, error) {
if req, ok := msg.(*jsonrpc2.Request); ok {
var params map[string]any
if err := json.Unmarshal(req.Params, ¶ms); err == nil {
meta := params["_meta"]
if meta == nil {
meta = make(map[string]any)
params["_meta"] = meta
}
otel.GetTextMapPropagator().Inject(ctx, stringAnyMapCarrier{meta.(map[string]any)})
if newParams, err := json.Marshal(params); err == nil {
req.Params = newParams
}
}
}
return c.delegate.Write(ctx, msg)
}
// Close implements mcp.Stream.
func (c contextStream) Close() error {
return c.delegate.Close()
} |
Beta Was this translation helpful? Give feedback.
All reactions
-
Ah, I see: our middleware intercepts on the receiving side (like all HTTP middleware). You need to intercept on the sending side. There are other use cases for that too, like adding a progress token to every outgoing request. |
Beta Was this translation helpful? Give feedback.
All reactions
-
π 1
-
@anuraaga please take a look at https://go.googlesource.com/tools/+/refs/heads/master/internal/mcp/client.go#197. |
Beta Was this translation helpful? Give feedback.
All reactions
-
Thanks @jba! Can confirm it works great for propagation, and definitely the easiest of any MCP SDK I've tried so far. I've tried with a basic example and will try some more advanced use cases such as calling back from server to client, but I don't see any reason this wouldn't work. The only notes are less about this SDK and Go generics themselves, and probably well known, but I'll write them down just in case - definitely not blockers since even if improved, it would be in a possibly distant future Go version.
func OTelReceivingMiddleware[S mcp.Session]() mcp.Middleware[S] {
return func(mh mcp.MethodHandler[S]) mcp.MethodHandler[S] {
return func(ctx context.Context, sess S, method string, params mcp.Params) (result mcp.Result, err error) {
ctx = otel.GetTextMapPropagator().Extract(ctx, stringAnyMapCarrier{params.GetMeta().Data})
return mh(ctx, sess, method, params)
}
}
}
func OTelSendingMiddleware[S mcp.Session]() mcp.Middleware[S] {
return func(mh mcp.MethodHandler[S]) mcp.MethodHandler[S] {
return func(ctx context.Context, sess S, method string, params mcp.Params) (result mcp.Result, err error) {
meta := params.GetMeta()
if meta.Data == nil {
meta.Data = make(map[string]any)
}
otel.GetTextMapPropagator().Inject(ctx, stringAnyMapCarrier{meta.Data})
return mh(ctx, sess, method, params)
}
}
}
server.AddSendingMiddleware(OTelSendingMiddleware[*mcp.ServerSession]())
server.AddReceivingMiddleware(OTelReceivingMiddleware[*mcp.ServerSession]())
client.AddReceivingMiddleware(OTelReceivingMiddleware[*mcp.ClientSession]())
client.AddSendingMiddleware(OTelSendingMiddleware[*mcp.ClientSession]()) |
Beta Was this translation helpful? Give feedback.
All reactions
-
Hello @findleyr and fellow contributors, Thanks for the very thoughtful design proposal and discussion. I have an alternate proposal and discussion that I kickstarted with the rust sdk authors here Reproducing the gist of that discussion here: Folks, as MCPβs language ecosystem expands, weβre spending more time reβimplementing the same deterministicβstate logic than shipping new ideas.β―Iβd like us to flip that ratio by standardizing on a single, Rust core (rustβsdk) that owns the workflow engineβ sessions, peering, transport, serializationβwhile each language surface (Python, JS/TS, Go, etc.) stays 100β―% idiomatic and lightweight.β― Rust gives us memoryβsafe, noβGC performance overhead, a stable FFI, and a proven path.β―The payoff is immediate: one quality, security and performance audit instead of five, feature parity across languages on dayβ―1, and p99 latencies that drop from milliseconds to microsecondsβwithout asking Python or JavaScript developers to learn Rust.β― In short, one deterministic engine, many friendly faces.β―We have built a V 0.1 Python and Typescript SDK based on this architecture so that we write less boilerplate and innovate faster on the parts that matter. I know that as Golang stewards and enthusiasts you may want to write the SDK from scratch but would love to hear your views and counterpoints to this. The Python Bindings PR is here The Typescript Bindings PR is here |
Beta Was this translation helpful? Give feedback.
All reactions
-
π 2 -
π 4 -
π 2
-
Thanks @tarunanand-dev for the idea. I think a good rule of thumb is that cgo (or other FFI) should be avoided unless it's infeasible to implement in pure Go, especially for something like an SDK which will be included in many builds. One of the reasons we think Go is a great fit for MCP is that it's a lightweight, portable, fast-compiling glue language, and any FFI would significantly reduce that value proposition. Additionally, as of writing I don't think the implementation of the SDK is particularly difficult. That may change in the future, but right now I think the benefits of a shared implementation would not offset the downsides. |
Beta Was this translation helpful? Give feedback.
All reactions
-
π 11 -
β€οΈ 3
-
I also agree that Go should be Go unless a very good reason, and I think MCP spec is simple enough and there will be enough contributors to ward off most reasons. While folks have discussed performance (and some security hints), there are many other reasons to stay in Go. cross-compilation, coherent stack traces, easy debugging, ability to run on any platform including one without libc... all the joys folks are used in go. let's keep MCP as joyful as other key libraries in the Go ecosystem! |
Beta Was this translation helpful? Give feedback.
All reactions
-
Thanks for the idea but, as others have said, I see no need for external bindings to Rust or C or another systems language / compiled objects. Go's rock solid static binary, cross-compilation support makes for a good reason to not have external dependencies on system libraries or other bindings. I'd expect that if I |
Beta Was this translation helpful? Give feedback.
All reactions
-
π 2
-
I tend to agree. While there may be learnings from the rust community on their progression on libraries, this is a go-lang discussion for as pure a golang support for mcp as possible, with as few external interactions or dependencies as possible, but sticking to the |
Beta Was this translation helpful? Give feedback.
All reactions
-
Interop and cross pollination of ideas are two different things. Unless you believe that all servers will be written in golang for e.g. interop will be in play whether you consider ideas from other languages or not. So yes, its a good idea to have pure go but keep the integration tax in mind. We can take this away to https://github.com/orgs/modelcontextprotocol/discussions/354 if you are interested and leave this thread for the go sdk. |
Beta Was this translation helpful? Give feedback.
All reactions
-
I think this might be consistent with other MCP SDKs, but I did want to mention that having the client by default always claim to have roots support is sometimes not ideal, because while the protocol may be "supported", that doesn't mean the client actually ever will set any roots. This makes it difficult for a server to handle clients which don't actually ever set roots, for instance they may want to conditionally add a tool to manage roots. I ran into this developing the MCP server for Dart/Flutter tooling, cursor claims to support roots but does not actually support them, so I had to add a flag to force enable a fallback mode instead of relying on detection of the supported features. |
Beta Was this translation helpful? Give feedback.
All reactions
-
π 1
-
I agree with this. But, from the current implementation, it looks like the client always declare to NOT support the Roots feature. Because, in that code, the |
Beta Was this translation helpful? Give feedback.
All reactions
-
Yeah my comment is based only on the design proposed, it looks to me as well like the current implementation doesn't ever declare roots support at all, but I assume that is just a work in progress, or I am missing something as well. For dart_mcp I chose to take a more object oriented approach, where you use mixins for each feature you support. It is a bit more tricky to fit that cleanly into a functional style though - because you want some actual nice apis for adding/removing roots. The mixins can both operate sort of as middleware for handling requests and altering the capabilities, as well as adding members (add/removeRoots). |
Beta Was this translation helpful? Give feedback.
All reactions
-
π 1
-
@jakemac53 thanks for the feedback. We should include a way to suppress client and server capabilities. I think it's just a bug of our type wasn't extracting root capabilities to a named type like it does for server capabilities, and therefore root capabilities were generated as an embedded struct rather than a nillable (and therefore optional) field. So @MegaGrindStone while you are right that there's something wrong with the zero value in the implementation, it's not quite what you conclude: we were indeed always advertising root support. @jakemac53 Just checking my understanding of what you're saying: if a hypothetical client used this SDK, and yet didn't have a UI element for users to add roots, it would be buggy for servers to assume that the user has chosen not to add roots. I agree that makes sense. On the other hand, it seems less important to be able to suppress server capabilities, but we should allow it anyway. Is there any reason to support tools (or roots) and not advertise support of |
Beta Was this translation helpful? Give feedback.
All reactions
-
Yes, exactly
This assumes at least one root will be set prior to any servers being connected - the capabilities can only be given during initialization. So, I don't think that is really a good solution.
I don't think there is, for dart_mcp I chose to just always support the change notifications with no opt-out. |
Beta Was this translation helpful? Give feedback.
All reactions
-
Yeah, I think it's necessary but perhaps not sufficient. |
Beta Was this translation helpful? Give feedback.
All reactions
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as spam.
This comment was marked as spam.
-
Had a random thought, I was wondering what the future of the design is in terms of ownership. I found the section on package layout
which seems to indicate the future is in the If the intention is to be I think it would be quite helpful to clarify what the goals are in terms of ownership and community engagement in this design, probably more so than even the technical details which will all somehow be resolved before a v1 release by the community. My advice would be to add a |
Beta Was this translation helpful? Give feedback.
All reactions
-
The intention is to contribute this to github.com/modelcontextprotocol/go-sdk. I like the idea of a |
Beta Was this translation helpful? Give feedback.
All reactions
-
π 1
This comment was marked as disruptive content.
This comment was marked as disruptive content.
-
There's a lot of overlap between the functionality in this (generated?) comment and the existing extensibility mechanisms of the Please provide concrete arguments for the three bullets in the 'Why this is important' section. For example, the whole point of the existing getServer factory is to provide per-session customization. Though I do think we should consider passing options to NewSSEHandler, for future-proofing. |
Beta Was this translation helpful? Give feedback.
All reactions
-
π 1
This comment was marked as disruptive content.
This comment was marked as disruptive content.
-
π₯ Design looks impeccable, as I'd expect from the Go team!!!!!! Great work Robert! Can't wait to get my hands on it re: Governance and ownershipOne area I see as missing from this discussion is how the SDK will be governed and managed - Go code is one thing, governing the Go community around the SDK is another. From what I've inferred:
Open questions from this structure:
I don't think this is blocking, just would be good to iron out now. And maybe some of these questions are things that are better left for the core |
Beta Was this translation helpful? Give feedback.
All reactions
-
π 11 -
β€οΈ 2
-
Thanks very much for your review and suggestions. You're right, there's a lot to address here, and we should include it in the design doc. (After all, it's almost more important to define how the SDK will evolve over time, since there are many spec changes on the horizon). I've started writing this up in https://go.dev/cl/677540. If you're able, please feel free to review that CL. |
Beta Was this translation helpful? Give feedback.
All reactions
-
β€οΈ 1
-
Very much looking forward to an official Golang SDK, for anyone hanging out for this in the mean time I've found https://github.com/mark3labs/mcp-go/ to be the best alternative. |
Beta Was this translation helpful? Give feedback.
All reactions
-
why should we need It seems dangerous, because anyone can get Server in |
Beta Was this translation helpful? Give feedback.
All reactions
-
Thank you, that's an interesting point. I think if you are executing malicious code on the server, there are bigger problems. |
Beta Was this translation helpful? Give feedback.
All reactions
-
β€οΈ 2
-
I'm looking forward to switching from github.com/mark3labs/mcp-go to this when it's ready. |
Beta Was this translation helpful? Give feedback.
All reactions
-
π 5
-
me too!!!! |
Beta Was this translation helpful? Give feedback.
All reactions
-
Thanks for the thoughtful design! I'm concerned about the tight coupling between tools and the MCP implementation via the *ServerSession parameter (among other things). type ToolHandler[TArgs] func(context.Context, *ServerSession, *CallToolParams[TArgs]) (*CallToolResult, error) The core issue: The *ServerSession parameter tightly couples every tool to this specific MCP implementation. This means tools can't be reused across different contexts - you can't take a search tool from your MCP server and drop it into a custom chatbot or headless agent flow without rewriting it. This is also a problem in mcp-go. An alternative approach would be to use a standard interface so that tools can be reused outside of this specific implementation. At its simplest: type Tool interface {
Name() string
Description() string
Parameters() *jsonschema.Schema
Execute(ctx context.Context, params json.RawMessage) (*ToolResult, error)
} For more flexibility, we could use execution options to handle session features like progress notification or logging: type ExecuteOptions struct {
ProgressReporter ProgressReporter
Logger Logger
// ... future extensions
}
type Tool interface {
Name() string
Description() string
Parameters() *jsonschema.Schema
Execute(ctx context.Context, params json.RawMessage, opts ExecuteOptions) (*ToolResult, error)
}
// Usage would be clean and explicit:
// tool.Execute(ctx, params, EmptyOptions()) // no progress/logging
// tool.Execute(ctx, params, ExecuteOptions{ProgressReporter: session}) // with MCP features In this way, MCP-specific features become optional capabilities rather than required dependencies. This enables a richer ecosystem of reusable tools while maintaining full MCP functionality when needed. There are multiple ways to do this but execution options are my preferred approach, perhaps allowing for empty options like we see in the context package (context.Background() or context.TODO()) This is just an example - I am not certain these are the exact right contracts and abstractions. But, in general, with really strong foundational structs and abstractions, a project like this can have influence in the overall structure of tool using LLMs in Go. I understand this adds a layer of abstraction, but I believe the ecosystem benefits outweigh the complexity cost. Thoughts on this direction? Happy to contribute a more detailed proposal if there's interest although seeing the implementation details will help a lot in thinking through what would be required. |
Beta Was this translation helpful? Give feedback.
All reactions
-
π 8
-
Thanks for clarifying. CC @jba has been revisiting the tool API as he adds jsonschema validation. Right now, The requirement to have a However, I do think it's worth exploring the idea of passing an interface to the tool, rather than the ServerSession itself. This would also solve a related problem I've been looking at for implementing the Streamable HTTP server. Let me explore that idea a bit further. From your perspective, would it be sufficient to simply modify the current API to pass an interface like ΒΉ aside: we're planning to rename |
Beta Was this translation helpful? Give feedback.
All reactions
-
π 1
-
Yes, that would go a long way to allaying my concerns. Those design decisions around structs versus interfaces makes sense to me. I understand the aim to use generics here - it makes the tool creation elegant: type WebSearchTool struct {
client brave.Brave
}
type WebSearchToolQuery struct {
Query string `json:"query" description:"The search query to send to Brave Search"`
}
func (t *WebSearchToolMCP) ToMCPTool() *mcp.ServerTool {
return mcp.NewServerTool(
"brave_web_search",
"Search the web using Brave Search API for current information",
t.HandleSearch,
)
}
func (t *WebSearchTool) HandleSearch(
ctx context.Context,
resources mcp.CallableResources,
params *mcp.CallToolParams[WebSearchToolQuery]) (*mcp.CallToolResult, error) {
// Log search using resources.LoggingMessage (or whatever)
// Notify client of progress with resources.NotifyProgress
// Or don't do anything with CallableResources
results, _ := t.client.WebSearch(ctx, params.Arguments.Query)
resultJSON, _ := json.Marshal(results)
return &mcp.CallToolResult{
Content: []*mcp.Content{
mcp.NewTextContent(string(resultJSON)),
},
}, nil
} Thanks again, looking forward to seeing the final version. Please feel free to reach out if you want any further comment or feedback. |
Beta Was this translation helpful? Give feedback.
All reactions
-
I have to think more about this, but one point jumps out at me. Go has no security model for code in the same process. It doesn't matter whether you use interfaces, structs, unexported fields, or what have you. The unsafe package, and other techniques like exploiting race conditions, allow any piece of code in the same process to access any piece of memory in that process. To support true sandboxing of tools, we would have to run them in a separate process, and presumably over some sort of virtual machine like gvisor. I don't think anything like that is in scope for this SDK. |
Beta Was this translation helpful? Give feedback.
All reactions
-
I have to think more about this, but one point jumps out at me. Go has no security model for code in the same process. It doesn't matter whether you use interfaces, structs, unexported fields, or what have you. The unsafe package, and other techniques like exploiting race conditions, allow any piece of code in the same process to access any piece of memory in that process. To support true sandboxing of tools, we would have to run them in a separate process, and presumably over some sort of virtual machine like gvisor. I don't think anything like that is in scope for this SDK. |
Beta Was this translation helpful? Give feedback.
All reactions
-
You do have a point about ServerSession. It exports |
Beta Was this translation helpful? Give feedback.
All reactions
-
π 1
-
Quick update: at this point, I think the discussion here has run its course (thanks!), though I expect we'll get a lot more feedback once we move code to the official repo. We will do this as soon as possible--we're just blocked on finalizing the antitrust policy mentioned in the governance section. In the meantime:
|
Beta Was this translation helpful? Give feedback.
All reactions
-
π 11 -
π 25 -
β€οΈ 1
-
@findleyr amazing, very excited to try. Do you expect Completions with the updated context spec to be supported soon? I (roughly) implemented it in a fork of MCP-go, and for template resources in particular the completions spec is virtually useless without, as it's pretty much always the case that subsequent completions are dependent on the prior values. Especially when there are many millions of values, as is the case for GitHub repository content. I built it before @connor4312 and I submitted the spec suggestion, so naming is old but happy to link. I was planning to upstream, just got sidetracked. Code is here: https://github.com/SamMorrowDrums/mcp-go Ignore the rename, I was rushing to get a demo working. |
Beta Was this translation helpful? Give feedback.
All reactions
-
Super coolοΌ |
Beta Was this translation helpful? Give feedback.
All reactions
-
π 1
-
I'm looking forward to it! Thanks everyone involved for making it possible! |
Beta Was this translation helpful? Give feedback.
All reactions
-
@SamMorrowDrums yes, completions are one of the major outstanding features, and we plan to implement it soon. |
Beta Was this translation helpful? Give feedback.
All reactions
-
β€οΈ 1
-
github.com/modelcontextprotocol/go-sdk now exists (we spent the first couple days of the week setting it up). It's should still be considered unreleased, but we should continue design discussions there. I'll close out this discussion later today or tomorrow. |
Beta Was this translation helpful? Give feedback.
All reactions
-
π 4
-
Hello @findleyr and community, Following up on my previous post - I have released a Milestone 0 of the MCP Bench at https://github.com/unimcp/mcpbench MCPBench is the open standard for compatibility testing of various Model Context Protocol (MCP https://github.com/modelcontextprotocol) client and server SDK implementations. As the MCP ecosystem grows, not every server or client will be written in the same language, creating an NΓN compatibility matrix where N represents the number of language SDKs available. Our goal is to ensure seamless interoperability between different MCP implementations, regardless of the programming language used. Initial SDK Support: Python SDK (Official) Future Expansion: Additional language SDKs will be added as the MCP ecosystem grows. The rest of the details are in the README. I would love to add support for Go SDK as it becomes available this week or later I would appreciate any insights from the Golang community into what should go into the benchmark in general or specific to golang. |
Beta Was this translation helpful? Give feedback.
All reactions
-
π 1 -
β€οΈ 4
-
Thanks, this could be really useful for the community. We've already discovered some bugs testing conformance against different SDKs, and have been meaning to write some automated conformance tests. If you don't already, you'll probably want to pin a version, as I expect there will be breaking API changes in the next few weeks. |
Beta Was this translation helpful? Give feedback.
All reactions
-
Apologies if this has already been discussed, but I don't see how to implement a server that does not manage a long running connection under the proposed implementation. Based on my reading of the spec:
The server is allowed to specifically decide if it wants to start a long running connection ( In mcp-go, this is controlled by Perhaps adding an explicit option to |
Beta Was this translation helpful? Give feedback.
All reactions
-
@rwjblue-glean we can certainly add this option -- the Streamable HTTP implementation needs several options related to session management. I didn't implement this initially, because with batching removed I didn't understand how a client is supposed to make stateless requests. The spec requires that the client initialize the session. If the client then wants to make a request, it must send another POST request. How can it do that without a session id? It seems like other SDKs just allow uninitialized requests in stateless mode, but this doesn't seem compliant with the spec, unless I'm misunderstanding. |
Beta Was this translation helpful? Give feedback.
All reactions
-
BTW, this concern is effectively modelcontextprotocol/modelcontextprotocol#282. |
Beta Was this translation helpful? Give feedback.
All reactions
-
@findleyr - Makes sense! It's a bit unfortunate (the hosting side of things is definitely more complicated having to manage the long running connection and dealing with coordination across servers, redeployments, &c &c), but I totally agree with your take on the spec (upon re-reading the lifecycle section). Thank you for sharing the link, I'll read through that issue in more detail. |
Beta Was this translation helpful? Give feedback.
All reactions
-
Looking into this more at your suggestion, it seems that especially in the context of modelcontextprotocol/modelcontextprotocol#478, there is an implicit 'stateless' mode, not mentioned in the spec, where there is no capability negotiation and where protocol version negotiation occurs via HTTP headers. We'll need to implement this, of course, but I'd like to get some clarification on the spec first to ensure we don't misinterpret the undocumented specification. Thanks for bringing this up. |
Beta Was this translation helpful? Give feedback.
All reactions
-
β€οΈ 3
-
Perfect! I'm also happy to help on the implementation side of things once we figure out what the best way forward on the spec side. |
Beta Was this translation helpful? Give feedback.
All reactions
-
β€οΈ 1
-
@findleyr I wanted to discuss your code comment on the error interface and make a proposal. // TODO(rfindley): investigate why server errors are embedded in this strange way,
// rather than returned as jsonrpc2 server errors.
if err != nil {
return &CallToolResult{
Content: []*Content{NewTextContent(err.Error())},
IsError: true,
}, nil
}
return res, nil Building a remote server, I have wanted middleware to have access to errors that I also want to return to the end user as an error string. I still also want to make coding errors go via the err value. In order to do this I think it would be great if we could use an interface like so:
And enable us to do things like:
Almost all errors in a tool are user-facing, and not coding errors and so should be returning as an error response, with a deliberate message so I get why MCP Go returns them as content, I just want to delay the serialization so I can use real types that preserve the information until serialization is actually necessary. I thought about just using the error return for this, but held back because:
With this interface you could then provide some obvious helpers like What do you think? edit: I also think that this would be useful for implementing custom typed returns from tool calls, where they just need to fulfil the interface and the SDK is not involved beyond that. |
Beta Was this translation helpful? Give feedback.
All reactions
-
We were planning to support custom return types via We could theoretically have both ( Do you want to move this discussion to a new issue on github.com/modelcontextprotocol/go-sdk? |
Beta Was this translation helpful? Give feedback.
All reactions
-
π 1
-
Let's move discussion to modelcontextprotocol/go-sdk#64, where I propose a different solution. |
Beta Was this translation helpful? Give feedback.
All reactions
-
β€οΈ 1
-
Hi all, thanks again for the feedback. As noted, since discussion quieted down, we've moved on with the implementation at https://github.com/modelcontextprotocol/go-sdk. But that doesn't mean the design period is closed. In fact, I encourage you to raise design issues in that repo as either concrete poposals or GitHub discussions (see CONTRIBUTING.md). For example, we're currently having a discussion revisiting the tool binding API in modelcontextprotocol/go-sdk#37. |
Beta Was this translation helpful? Give feedback.
All reactions
-
β€οΈ 9
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Pre-submission Checklist
Your Idea
Hi everyone π! This is a design discussion for the Go MCP SDK, following up on /discussions/224, where we (the Go team) volunteered to help contribute an official SDK.
In the original discussion, many people expressed support for a Go SDK, which is fantastic. With so much interest, we felt that it was more important than ever to start with a concrete design; otherwise weβd risk a lot of churn as we all worked out the APIs incrementally.
We analyzed a number of SDKs in Go and other languages to arrive at the design below. Wherever possible, we tried to align with the very successful mark3labs/mcp-go. However, as youβll see, we diverged in a number of ways in order to keep the APIs minimal and to be cautious about future spec evolution. We address the differences between this design and mcp-go throughout the document.
Our goal is to make sure that there is a good official SDK for Go. We want your feedback. If discussion reveals that some of the APIs below arenβt right, weβll change them.
To achieve this, letβs proceed as follows:
Thanks!
-Rob
Changelog
Meta
type for metadata, a section on "Community and Governance", and revised the signature ofToolHandler
andCallTool
. changes.slog.logger
. changesGo SDK Design
This document discusses the design of a Go SDK for the model context protocol. The golang.org/x/tools/internal/mcp package contains a prototype that we built to explore the MCP design space. Many of the ideas there are present in this document. However, we have diverged from and expanded on the APIs of that prototype, and this document should be considered canonical.
Similarities and differences with mark3labs/mcp-go (and others)
The most popular unofficial MCP SDK for Go is mark3labs/mcp-go. As of this writing, it is imported by over 400 packages that span over 200 modules.
We admire mcp-go, and where possible tried to align with its design. However, the APIs here diverge in a number of ways in order to keep the official SDK minimal, allow for future spec evolution, and support additional features. We have noted significant differences from mcp-go in the sections below. Although the API here is not compatible with mcp-go, translating between them should be straightforward in most cases. (Later, we will provide a detailed translation guide.)
Thank you to everyone who contributes to mcp-go and other Go SDKs. We hope that we can collaborate to leverage all that we've learned about MCP and Go in an official SDK.
Requirements
These may be obvious, but it's worthwhile to define goals for an official MCP SDK. An official SDK should aim to be:
Design
In the sections below, we visit each aspect of the MCP spec, in approximately the order they are presented by the official spec For each, we discuss considerations for the Go implementation, and propose a Go API.
Foundations
Package layout
In the sections that follow, it is assumed that most of the MCP API lives in a single shared package, the
mcp
package. This is inconsistent with other MCP SDKs, but is consistent with Go packages likenet/http
,net/rpc
, orgoogle.golang.org/grpc
. We believe that having a single package aids discoverability in package documentation and in the IDE. Furthermore, it avoids arbitrary decisions about package structure that may be rendered inaccurate by future evolution of the spec.Functionality that is not directly related to MCP (like jsonschema or jsonrpc2) belongs in a separate package.
Therefore, this is the core package layout, assuming github.com/modelcontextprotocol/go-sdk as the module path.
github.com/modelcontextprotocol/go-sdk/mcp
: the bulk of the user facing APIgithub.com/modelcontextprotocol/go-sdk/jsonschema
: a jsonschema implementation, with validationgithub.com/modelcontextprotocol/go-sdk/internal/jsonrpc2
: a fork of x/tools/internal/jsonrpc2_v2The JSON-RPC implementation is hidden, to avoid tight coupling. As described in the next section, the only aspects of JSON-RPC that need to be exposed in the SDK are the message types, for the purposes of defining custom transports. We can expose these types by promoting them from the
mcp
package using aliases or wrappers.Difference from mcp-go: Our
mcp
package includes all the functionality of mcp-go'smcp
,client
,server
andtransport
packages.JSON-RPC and Transports
The MCP is defined in terms of client-server communication over bidirectional JSON-RPC message streams. Specifically, version
2025-03-26
of the spec defines two transports:Additionally, version
2024-11-05
of the spec defined a simpler (yet stateful) HTTP transport:text/event-stream
, and sends messages via POST to a session endpoint.Furthermore, the spec states that it must be possible for users to define their own custom transports.
Given the diversity of the transport implementations, they can be challenging to abstract. However, since JSON-RPC requires a bidirectional stream, we can use this to model the MCP transport abstraction:
Methods accept a Go
Context
and return anerror
, as is idiomatic for APIs that do I/O.A
Transport
is something that connects a logical JSON-RPC stream, and nothing more. Streams must be closeable in order to implement client and server shutdown, and therefore conform to theio.Closer
interface.Other SDKs define higher-level transports, with, for example, methods to send a notification or make a call. Those are jsonrpc2 operations on top of the logical stream, and the lower-level interface is easier to implement in most cases, which means it is easier to implement custom transports.
For our prototype, we've used an internal
jsonrpc2
package based on the Go language servergopls
, which we propose to fork for the MCP SDK. It already handles concerns like client/server connection, request lifecycle, cancellation, and shutdown.Differences from mcp-go: The Go team has a battle-tested JSON-RPC implementation that we use for gopls, our Go LSP server. We are using the new version of this library as part of our MCP SDK. It handles all JSON-RPC 2.0 features, including cancellation.
The
Transport
interface here is lower-level than that of mcp-go, but serves a similar purpose. We believe the lower-level interface is easier to implement.stdio transports
In the MCP Spec, the stdio transport uses newline-delimited JSON to communicate over stdin/stdout. It's possible to model both client side and server side of this communication with a shared type that communicates over an
io.ReadWriteCloser
. However, for the purposes of future-proofing, we should use a different types for client and server stdio transport.The
CommandTransport
is the client side of the stdio transport, and connects by starting a command and binding its jsonrpc2 stream to its stdin/stdout.The
StdIOTransport
is the server side of the stdio transport, and connects by binding toos.Stdin
andos.Stdout
.HTTP transports
The HTTP transport APIs are even more asymmetrical. Since connections are initiated via HTTP requests, the client developer will create a transport, but the server developer will typically install an HTTP handler. Internally, the HTTP handler will create a logical transport for each new client connection.
Importantly, since they serve many connections, the HTTP handlers must accept a callback to get an MCP server for each new session. As described below, MCP servers can optionally connect to multiple clients. This allows customization of per-session servers: if the MCP server is stateless, the user can return the same MCP server for each connection. On the other hand, if any per-session customization is required, it is possible by returning a different
Server
instance for each connection.Notably absent are options to hook into low-level request handling for the purposes of authentication or context injection. These concerns are instead handled using standard HTTP middleware patterns. For middleware at the level of the MCP protocol, see Middleware below.
By default, the SSE handler creates messages endpoints with the
?sessionId=...
query parameter. Users that want more control over the management of sessions and session endpoints may write their own handler, and createSSEServerTransport
instances themselves for incoming GET requests.The SSE client transport is simpler, and hopefully self-explanatory.
The Streamable HTTP transports are similar to the SSE transport, albeit with a
more complicated implementation. For brevity, we summarize only the differences
from the equivalent SSE types:
Differences from mcp-go: In mcp-go, server authors create an
MCPServer
, populate it with tools, resources and so on, and then wrap it in anSSEServer
orStdioServer
. Users can manage their own sessions withRegisterSession
andUnregisterSession
. Rather than use a server constructor to get a distinct server for each connection, there is a concept of a "session tool" that overlays tools for a specific session.Here, we tried to differentiate the concept of a
Server
,HTTPHandler
, andTransport
, and provide per-session customization through either thegetServer
constructor or middleware. Additionally, individual handlers and transports here have a minimal API, and do not expose internal details. (Open question: are we oversimplifying?)Other transports
We also provide a couple of transport implementations for special scenarios. An InMemoryTransport can be used when the client and server reside in the same process. A LoggingTransport is a middleware layer that logs RPC logs to a desired location, specified as an io.Writer.
Protocol types
Types needed for the protocol are generated from the JSON schema of the MCP spec.
These types will be included in the
mcp
package, but will be unexported unless they are needed for the user-facing API. Notably, JSON-RPC request types are elided, since they are handled by thejsonrpc2
package and should not be observed by the user.For user-provided data, we use
json.RawMessage
ormap[string]any
, depending on the use case.For union types, which can't be represented in Go (specifically
Content
andResourceContents
), we prefer distinguished unions: struct types with fields corresponding to the union of all properties for union elements.For brevity, only a few examples are shown here:
The
Meta
type includes amap[string]any
for arbitrary data, and aProgressToken
field.Differences from mcp-go: these types are largely similar, but our type generator flattens types rather than using struct embedding.
Clients and Servers
Generally speaking, the SDK is used by creating a
Client
orServer
instance, adding features to it, and connecting it to a peer.However, the SDK must make a non-obvious choice in these APIs: are clients 1:1 with their logical connections? What about servers? Both clients and servers are stateful: users may add or remove roots from clients, and tools, prompts, and resources from servers. Additionally, handlers for these features may themselves be stateful, for example if a tool handler caches state from earlier requests in the session.
We believe that in the common case, any change to a client or server, such as adding a tool, is intended for all its peers. It is therefore more useful to allow multiple connections from a client, and to a server. This is similar to the
net/http
packages, in which anhttp.Client
andhttp.Server
each may handle multiple unrelated connections. When users add features to a client or server, all connected peers are notified of the change.Supporting multiple connections to servers (and from clients) still allows for stateful components, as it is up to the user to decide whether or not to create distinct servers/clients for each connection. For example, if the user wants to create a distinct server for each new connection, they can do so in the
getServer
factory passed to transport handlers.Following the terminology of the spec, we call the logical connection between a client and server a "session." There must necessarily be a
ClientSession
and aServerSession
, corresponding to the APIs available from the client and server perspective, respectively.Sessions are created from either
Client
orServer
using theConnect
method.Here's an example of these APIs from the client side:
A server that can handle that client call would look like this:
For convenience, we provide
Server.Run
to handle the common case of running a session until the client disconnects:Differences from mcp-go: the Server APIs are similar to mcp-go, though the association between servers and transports is different. In mcp-go, a single server is bound to what we would call an
SSEHTTPHandler
, and reused for all sessions. Per-session behavior is implemented though a 'session tool' overlay. As discussed above, the transport abstraction here is differentiated from HTTP serving, and theServer.Connect
method provides a consistent API for binding to an arbitrary transport. Servers here do not have methods for sending notifications or calls, because they are logically distinct from theServerSession
. In mcp-go, servers aren:1
, but there is no abstraction of a server session: sessions are addressed in Server APIs through theirsessionID
:SendNotificationToAllClients
,SendNotificationToClient
,SendNotificationToSpecificClient
.The client API here is different, since clients and client sessions are conceptually distinct. The
ClientSession
is closer to mcp-go's notion of Client.For both clients and servers, mcp-go uses variadic options to customize behavior, whereas an options struct is used here. We felt that in this case, an options struct would be more readable, and result in simpler package documentation.
Spec Methods
In our SDK, RPC methods that are defined in the specification take a context and a params pointer as arguments, and return a result pointer and error:
Our SDK has a method for every RPC in the spec, their signatures all share this form. We do this, rather than providing more convenient shortcut signatures, to maintain backward compatibility if the spec makes backward-compatible changes such as adding a new property to the request parameters (as in this commit, for example). To avoid boilerplate, we don't repeat this signature for RPCs defined in the spec; readers may assume it when we mention a "spec method."
CallTool
is the only exception: for convenience when binding to Go argument types,*CallToolParams[TArgs]
is generic, with a type parameter providing the Go type of the tool arguments. The spec method accepts a*CallToolParams[json.RawMessage]
, but we provide a generic helper function. See the section on Tools below for details.Why do we use params instead of the full JSON-RPC request? As much as possible, we endeavor to hide JSON-RPC details when they are not relevant to the business logic of your client or server. In this case, the additional information in the JSON-RPC request is just the request ID and method name; the request ID is irrelevant, and the method name is implied by the name of the Go method providing the API.
We believe that any change to the spec that would require callers to pass a new a parameter is not backward compatible. Therefore, it will always work to pass
nil
for anyXXXParams
argument that isn't currently necessary. For example, it is okay to callPing
like so:Iterator Methods
For convenience, iterator methods handle pagination for the
List
spec methods automatically, traversing all pages. If Params are supplied, iteration begins from the provided cursor (if present).Middleware
We provide a mechanism to add MCP-level middleware on the both the client and server side, which runs after the request has been parsed but before any normal handling.
As an example, this code adds server-side logging:
Differences from mcp-go: Version 0.26.0 of mcp-go defines 24 server hooks. Each hook consists of a field in the
Hooks
struct, aHooks.Add
method, and a type for the hook function. These are rarely used. The most common isOnError
, which occurs fewer than ten times in open-source code.Errors
With the exception of tool handler errors, protocol errors are handled transparently as Go errors: errors in server-side feature handlers are propagated as errors from calls from the
ClientSession
, and vice-versa.Protocol errors wrap a
JSONRPCError
type which exposes its underlying error code.As described by the spec, tool execution errors are reported in tool results.
Differences from mcp-go: the
JSONRPCError
type here does not include ID and Method, which can be inferred from the caller. Otherwise, this behavior is similar.Cancellation
Cancellation is implemented transparently using context cancellation. The user can cancel an operation by cancelling the associated context:
When this client call is cancelled, a
"notifications/cancelled"
notification is sent to the server. However, the client call returns immediately withctx.Err()
: it does not wait for the result from the server.The server observes a client cancellation as a cancelled context.
Progress handling
A caller can request progress notifications by setting the
Meta.ProgressToken
field on any request.Handlers can notify their peer about progress by calling the
NotifyProgress
method. The notification is only sent if the peer requested it by providing a progress token.Ping / KeepAlive
Both
ClientSession
andServerSession
expose aPing
method to call "ping" on their peer.Additionally, client and server sessions can be configured with automatic keepalive behavior. If the
KeepAlive
option is set to a non-zero duration, it defines an interval for regular "ping" requests. If the peer fails to respond to pings originating from the keepalive check, the session is automatically closed.Differences from mcp-go: in mcp-go the
Ping
method is only provided for client, not server, and the keepalive option is only provided for SSE servers (as a variadic option).Client Features
Roots
Clients support the MCP Roots feature, including roots-changed notifications. Roots can be added and removed from a
Client
withAddRoots
andRemoveRoots
:Server sessions can call the spec method
ListRoots
to get the roots. If a server installs aRootsChangedHandler
, it will be called when the client sends a roots-changed notification, which happens whenever the list of roots changes after a connection has been established.The
Roots
method provides a cached iterator of the root set, invalidated when roots change.Sampling
Clients that support sampling are created with a
CreateMessageHandler
option for handling server calls. To perform sampling, a server session calls the spec methodCreateMessage
.Server Features
Tools
A
Tool
is a logical MCP tool, generated from the MCP spec, and aServerTool
is a tool bound to a tool handler.A tool handler accepts
CallToolParams
and returns aCallToolResult
. However, since we want to bind tools to Go input types, it is convenient in associated APIs to makeCallToolParams
generic, with a type parameterTArgs
for the tool argument type. This allows tool APIs to manage the marshalling and unmarshalling of tool inputs for their caller. The boundServerTool
type expects ajson.RawMessage
for its tool arguments, but theNewTool
constructor described below provides a mechanism to bind a typed handler.Add tools to a server with
AddTools
:Remove them by name with
RemoveTools
:A tool's input schema, expressed as a JSON Schema, provides a way to validate the tool's input. One of the challenges in defining tools is the need to associate them with a Go function, yet support the arbitrary complexity of JSON Schema. To achieve this, we have seen two primary approaches:
metoro-io/mcp-golang
)mark3labs/mcp-go
).Both of these have their advantages and disadvantages. Reflection is nice, because it allows you to bind directly to a Go API, and means that the JSON schema of your API is compatible with your Go types by construction. It also means that concerns like parsing and validation can be handled automatically. However, it can become cumbersome to express the full breadth of JSON schema using Go types or struct tags, and sometimes you want to express things that arenβt naturally modeled by Go types, like unions. Explicit schemas are simple and readable, and give the caller full control over their tool definition, but involve significant boilerplate.
We have found that a hybrid model works well, where the initial schema is derived using reflection, but any customization on top of that schema is applied using variadic options. We achieve this using a
NewTool
helper, which generates the schema from the input type, and wraps the handler to provide parsing and validation. The schema (and potentially other features) can be customized using ToolOptions.NewTool
determines the input schema for a Tool from theTArgs
type. Each struct field that would be marshaled byencoding/json.Marshal
becomes a property of the schema. The property is required unless the field'sjson
tag specifies "omitempty" or "omitzero" (new in Go 1.24). For example, given this struct:"name" and "Choices" are required, while "count" is optional.
As of this writing, the only
ToolOption
isInput
, which allows customizing the input schema of the tool using schema options. These schema options are recursive, in the sense that they may also be applied to properties.For example:
The most recent JSON Schema spec defines over 40 keywords. Providing them all as options would bloat the API despite the fact that most would be very rarely used. For less common keywords, use the
Schema
option to set the schema explicitly:Schemas are validated on the server before the tool handler is called.
Since all the fields of the Tool struct are exported, a Tool can also be created directly with assignment or a struct literal.
Client sessions can call the spec method
ListTools
or an iterator methodTools
to list the available tools, and use spec methodCallTool
to call tools. Similar toServerTool.Handler
,CallTool
expects*CallToolParams[json.RawMessage]
, but we provide a genericCallTool
helper to operate on typed arguments.Differences from mcp-go: using variadic options to configure tools was significantly inspired by mcp-go. However, the distinction between
ToolOption
andSchemaOption
allows for recursive application of schema options. For example, that limitation is visible in this code, which must resort to untyped maps to express a nested schema.Additionally, the
NewTool
helper provides a means for building a tool from a Go function using reflection, that automatically handles parsing and validation of inputs.We provide a full JSON Schema implementation for validating tool input schemas against incoming arguments. The
jsonschema.Schema
type provides exported features for all keywords in the JSON Schema draft2020-12 spec. Tool definers can use it to construct any schema they want, so there is no need to provide options for all of them. When combined with schema inference from input structs, we found that we needed only three options to cover the common cases, instead of mcp-go's 23. For example, we will provideEnum
, which occurs 125 times in open source code, but not MinItems, MinLength or MinProperties, which each occur only once (and in an SDK that wraps mcp-go).For registering tools, we provide only
AddTools
; mcp-go'sSetTools
,AddTool
,AddSessionTool
, andAddSessionTools
are deemed unnecessary. (Similarly for Delete/Remove).Prompts
Use
NewPrompt
to create a prompt. As with tools, prompt argument schemas can be inferred from a struct, or obtained from options.Use
AddPrompts
to add prompts to the server, andRemovePrompts
to remove them by name.
Client sessions can call the spec method
ListPrompts
or the iterator methodPrompts
to list the available prompts, and the spec methodGetPrompt
to get one.Differences from mcp-go: We provide a
NewPrompt
helper to bind a prompt handler to a Go function using reflection to derive its arguments. We provideRemovePrompts
to remove prompts from the server.Resources and resource templates
In our design, each resource and resource template is associated with a function that reads it, with this signature:
The arguments include the
ServerSession
so the handler can observe the client's roots. The handler should return the resource contents in aReadResourceResult
, calling eitherNewTextResourceContents
orNewBlobResourceContents
. If the handler omits the URI or MIME type, the server will populate them from the resource.The
ServerResource
andServerResourceTemplate
types hold the association between the resource and its handler:To add a resource or resource template to a server, users call the
AddResources
andAddResourceTemplates
methods with one or moreServerResource
s orServerResourceTemplate
s. We also provide methods to remove them.The
ReadResource
method finds a resource or resource template matching the argument URI and calls its associated handler.To read files from the local filesystem, we recommend using
FileResourceHandler
to construct a handler:Here is an example:
Server sessions also support the spec methods
ListResources
andListResourceTemplates
, and the corresponding iterator methodsResources
andResourceTemplates
.Differences from mcp-go: for symmetry with tools and prompts, we use
AddResources
rather thanAddResource
. Additionally, theResourceHandler
returns aReadResourceResult
, rather than just its content, for compatibility with future evolution of the spec.Subscriptions
ClientSessions can manage change notifications on particular resources:
The server does not implement resource subscriptions. It passes along subscription requests to the user, and supplies a method to notify clients of changes. It tracks which sessions have subscribed to which resources so the user doesn't have to.
If a server author wants to support resource subscriptions, they must provide handlers to be called when clients subscribe and unsubscribe. It is an error to provide only one of these handlers.
User code should call
ResourceUpdated
when a subscribed resource changes.The server routes these notifications to the server sessions that subscribed to the resource.
ListChanged notifications
When a list of tools, prompts or resources changes as the result of an AddXXX or RemoveXXX call, the server informs all its connected clients by sending the corresponding type of notification. A client will receive these notifications if it was created with the corresponding option:
Differences from mcp-go: mcp-go instead provides a general
OnNotification
handler. For type-safety, and to hide JSON RPC details, we provide feature-specific handlers here.Completion
Clients call the spec method
Complete
to request completions. Servers automatically handle these requests based on their collections of prompts and resources.Differences from mcp-go: the client API is similar. mcp-go has not yet defined its server-side behavior.
Logging
MCP specifies a notification for servers to log to clients. Server sessions implement this with the
LoggingMessage
method. It honors the minimum log level established by the client session'sSetLevel
call.As a convenience, we also provide a
slog.Handler
that allows server authors to write logs with thelog/slog
package::Server-to-client logging is configured with
ServerOptions
:A call to a log method like
Info
is translated to aLoggingMessageNotification
as follows:The attributes and the message populate the "data" property with the output of a
slog.JSONHandler
: The result is always a JSON object, with the key "msg" for the message.If the
LoggerName
server option is set, it populates the "logger" property.The standard slog levels
Info
,Debug
,Warn
andError
map to the corresponding levels in the MCP spec. The other spec levels map to integers between the slog levels. For example, "notice" is level 2 because it is between "warning" (slog value 4) and "info" (slog value 0). Themcp
package defines consts for these levels. To log at the "notice" level, a handler would callLog(ctx, mcp.LevelNotice, "message")
.A client that wishes to receive log messages must provide a handler:
Pagination
Servers initiate pagination for
ListTools
,ListPrompts
,ListResources
, andListResourceTemplates
, dictating the page size and providing aNextCursor
field in the Result if more pages exist. The SDK implements keyset pagination, using the unique ID of the feature as the key for a stable sort order and encoding the cursor as an opaque string.For server implementations, the page size for the list operation may be configured via the
ServerOptions.PageSize
field. PageSize must be a non-negative integer. If zero, a sensible default is used.Client requests for List methods include an optional Cursor field for pagination. Server responses for List methods include a
NextCursor
field if more pages exist.In addition to the
List
methods, the SDK provides an iterator method for each list operation. This simplifies pagination for clients by automatically handling the underlying pagination logic. See Iterator Methods above.Differences with mcp-go: the PageSize configuration is set with a configuration field rather than a variadic option. Additionally, this design proposes pagination by default, as this is likely desirable for most servers
Governance and Community
While the sections above propose an initial implementation of the Go SDK, MCP is evolving rapidly. SDKs need to keep pace, by implementing changes to the spec, fixing bugs, and accomodating new and emerging use-cases. This section proposes how the SDK project can be managed so that it can change safely and transparently.
Initially, the Go SDK repository will be administered by the Go team and Anthropic, and they will be the Approvers (the set of people able to merge PRs to the SDK). The policies here are also intended to satisfy necessary constraints of the Go team's participation in the project.
The content in this section will also be included in a CONTRIBUTING.md file in the repo root.
Hosting, copyright, and license
The SDK will be hosted under github.com/modelcontextprotocol/go-sdk, MIT license, copyright "Go SDK Authors". Each Go file in the repository will have a standard copyright header. For example:
Issues and Contributing
The SDK will use its GitHub issue tracker for bug tracking, and pull requests for contributions.
Contributions to the SDK will be welcomed, and will be accepted provided they are high quality and consistent with the direction and philosophy of the SDK outlined above. An official SDK must be conservative in the changes it accepts, to defend against compatibility problems, security vulnerabilities, and churn. To avoid being declined, PRs should be associated with open issues, and those issues should either be labeled 'Help Wanted', or the PR author should ask on the issue before contributing.
Proposals
A proposal is an issue that proposes a new API for the SDK, or a change to the signature or behavior of an existing API. Proposals will be labeled with the 'Proposal' label, and require an explicit approval before being accepted (applied through the 'Proposal-Accepted' label). Proposals will remain open for at least a week to allow discussion before being accepted or declined by an Approver.
Proposals that are straightforward and uncontroversial may be approved based on GitHub discussion. However, proposals that are deemed to be sufficiently unclear or complicated will be deferred to a regular steering meeting (see below).
This process is similar to the Go proposal process, but is necessarily lighter weight to accomodate the greater rate of change expected for the SDK.
Steering meetings
On a regular basis, we will host a virtual steering meeting to discuss outstanding proposals and other changes to the SDK. These 1hr meetings and their agenda will be announced in advance, and open to all to join. The meetings will be recorded, and recordings and meeting notes will be made available afterward.
This process is similar to the Go Tools call, though it is expected that meetings will at least initially occur on a more frequent basis (likely biweekly).
Discord
Discord (either the public or private Anthropic discord servers) should only be used for logistical coordination or answering questions. Design discussion and decisions should occur in GitHub issues or public steering meetings.
Antitrust considerations
It is important that the SDK avoids bias toward specific integration paths or providers. Therefore, the CONTRIBUTING.md file will include an antitrust policy that outlines terms and practices intended to avoid such bias, or the appearance thereof. (The details of this policy will be determined by Google and Anthropic lawyers).
Releases and Versioning
The SDK will consist of a single Go module, and will be released through versioned Git tags. Accordingly, it will follow semantic versioning.
Up until the v1.0.0 release, the SDK may be unstable and may change in breaking ways. An initial v1.0.0 release will occur when the SDK is deemed by Approvers to be stable, production ready, and sufficiently complete (though some unimplemented features may remain). Subsequent to that release, new APIs will be added in minor versions, and breaking changes will require a v2 release of the module (and therefore should be avoided). All releases will have corresponding release notes in GitHub.
It is desirable that releases occur frequently, and that a v1.0.0 release is achieved as quickly as possible.
If feasible, the SDK will support all versions of the MCP spec. However, if breaking changes to the spec make this infeasible, preference will be given to the most recent version of the MCP spec.
Ongoing evaluation
On an ongoing basis, the administrators of the SDK will evaluate whether it is keeping pace with changes to the MCP spec and meeting its goals of openness and transparency. If it is not meeting these goals, either because it exceeds the bandwidth of its current Approvers, or because the processes here are inadequate, these processes will be re-evaluated. At this time, the Approvers set may be expanded to include additional community members, based on their history of strong contribution.
Scope
Beta Was this translation helpful? Give feedback.
All reactions