Skip to content

Commit aebd244

Browse files
authored
mcp: new API for features (#88)
BREAKING CHANGE Change the API for adding tools, prompts, resources and resource templates to a server. The ServerXXX types are gone along with the plural Add methods (AddTools, AddPrompts etc.) Instead there are single Add methods (AddTool, AddPrompt). In addition, instead of NewServerTool, there is AddTool[In, Out]. Fixes #73.
1 parent 328a25d commit aebd244

File tree

20 files changed

+366
-612
lines changed

20 files changed

+366
-612
lines changed

README.md

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
<!-- Autogenerated by weave; DO NOT EDIT -->
22
# MCP Go SDK
33

4+
***BREAKING CHANGES***
5+
6+
The latest version contains breaking changes:
7+
8+
- Server.AddTools is replaced by Server.AddTool.
9+
10+
- NewServerTool is replaced by AddTool. AddTool takes a Tool rather than a name and description, so you can
11+
set any field on the Tool that you want before associating it with a handler.
12+
13+
- Tool options have been removed. If you don't want AddTool to infer a JSON Schema for you, you can construct one
14+
as a struct literal, or using any other code that suits you.
15+
16+
- AddPrompts, AddResources and AddResourceTemplates are similarly replaced by singular methods which pair the
17+
feature with a handler. The ServerXXX types have been removed.
18+
419
[![PkgGoDev](https://pkg.go.dev/badge/github.com/modelcontextprotocol/go-sdk)](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk)
520

621
This repository contains an unreleased implementation of the official Go
@@ -99,7 +114,7 @@ import (
99114
)
100115

101116
type HiParams struct {
102-
Name string `json:"name"`
117+
Name string `json:"name", mcp:"the name of the person to greet"`
103118
}
104119

105120
func SayHi(ctx context.Context, cc *mcp.ServerSession, params *mcp.CallToolParamsFor[HiParams]) (*mcp.CallToolResultFor[any], error) {
@@ -111,11 +126,8 @@ func SayHi(ctx context.Context, cc *mcp.ServerSession, params *mcp.CallToolParam
111126
func main() {
112127
// Create a server with a single tool.
113128
server := mcp.NewServer("greeter", "v1.0.0", nil)
114-
server.AddTools(
115-
mcp.NewServerTool("greet", "say hi", SayHi, mcp.Input(
116-
mcp.Property("name", mcp.Description("the name of the person to greet")),
117-
)),
118-
)
129+
130+
mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)
119131
// Run the server over stdin/stdout, until the client disconnects
120132
if err := server.Run(context.Background(), mcp.NewStdioTransport()); err != nil {
121133
log.Fatal(err)

design/design.md

Lines changed: 50 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -372,12 +372,11 @@ A server that can handle that client call would look like this:
372372
```go
373373
// Create a server with a single tool.
374374
server := mcp.NewServer("greeter", "v1.0.0", nil)
375-
server.AddTools(mcp.NewServerTool("greet", "say hi", SayHi))
375+
mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)
376376
// Run the server over stdin/stdout, until the client disconnects.
377-
transport := mcp.NewStdioTransport()
378-
session, err := server.Connect(ctx, transport)
379-
...
380-
return session.Wait()
377+
if err := server.Run(context.Background(), mcp.NewStdioTransport()); err != nil {
378+
log.Fatal(err)
379+
}
381380
```
382381
383382
For convenience, we provide `Server.Run` to handle the common case of running a session until the client disconnects:
@@ -603,14 +602,14 @@ type ClientOptions struct {
603602
604603
### Tools
605604
606-
A `Tool` is a logical MCP tool, generated from the MCP spec, and a `ServerTool` is a tool bound to a tool handler.
605+
A `Tool` is a logical MCP tool, generated from the MCP spec.
607606
608-
A tool handler accepts `CallToolParams` and returns a `CallToolResult`. However, since we want to bind tools to Go input types, it is convenient in associated APIs to make `CallToolParams` generic, with a type parameter `TArgs` for the tool argument type. This allows tool APIs to manage the marshalling and unmarshalling of tool inputs for their caller. The bound `ServerTool` type expects a `json.RawMessage` for its tool arguments, but the `NewServerTool` constructor described below provides a mechanism to bind a typed handler.
607+
A tool handler accepts `CallToolParams` and returns a `CallToolResult`. However, since we want to bind tools to Go input types, it is convenient in associated APIs to have a generic version of `CallToolParams`, with a type parameter `In` for the tool argument type, as well as a generic version of for `CallToolResult`. This allows tool APIs to manage the marshalling and unmarshalling of tool inputs for their caller.
609608
610609
```go
611-
type CallToolParams[TArgs any] struct {
610+
type CallToolParamsFor[In any] struct {
612611
Meta Meta `json:"_meta,omitempty"`
613-
Arguments TArgs `json:"arguments,omitempty"`
612+
Arguments In `json:"arguments,omitempty"`
614613
Name string `json:"name"`
615614
}
616615

@@ -621,23 +620,31 @@ type Tool struct {
621620
Name string `json:"name"`
622621
}
623622

624-
type ToolHandler[TArgs] func(context.Context, *ServerSession, *CallToolParams[TArgs]) (*CallToolResult, error)
623+
type ToolHandlerFor[In, Out any] func(context.Context, *ServerSession, *CallToolParamsFor[In]) (*CallToolResultFor[Out], error)
624+
type ToolHandler = ToolHandlerFor[map[string]any, any]
625+
```
625626
626-
type ServerTool struct {
627-
Tool Tool
628-
Handler ToolHandler[json.RawMessage]
629-
}
627+
Add tools to a server with the `AddTool` method or function. The function is generic and infers schemas from the handler
628+
arguments:
629+
630+
```go
631+
func (s *Server) AddTool(t *Tool, h ToolHandler)
632+
func AddTool[In, Out any](s *Server, t *Tool, h ToolHandlerFor[In, Out])
630633
```
631634
632-
Add tools to a server with `AddTools`:
635+
```go
636+
mcp.AddTool(server, &mcp.Tool{Name: "add", Description: "add numbers"}, addHandler)
637+
mcp.AddTool(server, &mcp.Tool{Name: "subtract", Description: "subtract numbers"}, subHandler)
638+
```
633639
640+
The `AddTool` method requires an input schema, and optionally an output one. It will not modify them.
641+
The handler should accept a `CallToolParams` and return a `CallToolResult`.
634642
```go
635-
server.AddTools(
636-
mcp.NewServerTool("add", "add numbers", addHandler),
637-
mcp.NewServerTool("subtract, subtract numbers", subHandler))
643+
t := &Tool{Name: ..., Description: ..., InputSchema: &jsonschema.Schema{...}}
644+
server.AddTool(t, myHandler)
638645
```
639646
640-
Remove them by name with `RemoveTools`:
647+
Tools can be removed by name with `RemoveTools`:
641648
642649
```go
643650
server.RemoveTools("add", "subtract")
@@ -650,53 +657,30 @@ A tool's input schema, expressed as a [JSON Schema](https://json-schema.org), pr
650657
651658
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.
652659
653-
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 `NewServerTool` 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.
654-
655-
```go
656-
// NewServerTool creates a Tool using reflection on the given handler.
657-
func NewServerTool[TArgs any](name, description string, handler ToolHandler[TArgs], opts …ToolOption) *ServerTool
660+
We provide both ways. The `jsonschema.For[T]` function will infer a schema, and it is called by the `AddTool` generic function.
661+
Users can also call it themselves, or build a schema directly as a struct literal. They can still use the `AddTool` function to
662+
create a typed handler, since `AddTool` doesn't touch schemas that are already present.
658663
659-
type ToolOption interface { /* ... */ }
660-
```
661664
662-
`NewServerTool` determines the input schema for a Tool from the `TArgs` type. Each struct field that would be marshaled by `encoding/json.Marshal` becomes a property of the schema. The property is required unless the field's `json` tag specifies "omitempty" or "omitzero" (new in Go 1.24). For example, given this struct:
665+
If the tool's `InputSchema` is nil, it is inferred from the `In` type parameter. If the `OutputSchema` is nil, it is inferred from the `Out` type parameter (unless `Out` is `any`).
663666
667+
For example, given this handler:
664668
```go
665-
struct {
666-
Name string `json:"name"`
667-
Count int `json:"count,omitempty"`
668-
Choices []string
669-
Password []byte `json:"-"`
669+
type AddParams struct {
670+
X int `json:"x"`
671+
Y int `json:"y"`
670672
}
671-
```
672-
673-
"name" and "Choices" are required, while "count" is optional.
674-
675-
As of this writing, the only `ToolOption` is `Input`, 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.
676-
677-
```go
678-
func Input(...SchemaOption) ToolOption
679-
680-
type Property(name string, opts ...SchemaOption) SchemaOption
681-
type Description(desc string) SchemaOption
682-
// etc.
683-
```
684-
685-
For example:
686673

687-
```go
688-
NewServerTool(name, description, handler,
689-
Input(Property("count", Description("size of the inventory"))))
674+
func addHandler(ctx context.Context, ss *mcp.ServerSession, params *mcp.CallToolParamsFor[AddParams]) (*mcp.CallToolResultFor[int], error) {
675+
return &mcp.CallToolResultFor[int]{StructuredContent: params.Arguments.X + params.Arguments.Y}, nil
676+
}
690677
```
691678
692-
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:
693-
679+
You can add it to a server like this:
694680
```go
695-
NewServerTool(name, description, handler,
696-
Input(Property("Choices", Schema(&jsonschema.Schema{UniqueItems: true}))))
681+
mcp.AddTool(server, &mcp.Tool{Name: "add", Description: "add numbers"}, addHandler)
697682
```
698-
699-
Schemas are validated on the server before the tool handler is called.
683+
The input schema will be inferred from `AddParams`, and the output schema from `int`.
700684
701685
Since all the fields of the Tool struct are exported, a Tool can also be created directly with assignment or a struct literal.
702686
@@ -718,27 +702,20 @@ For registering tools, we provide only `AddTools`; mcp-go's `SetTools`, `AddTool
718702
719703
### Prompts
720704
721-
Use `NewServerPrompt` to create a prompt. As with tools, prompt argument schemas can be inferred from a struct, or obtained from options.
722-
723-
```go
724-
func NewServerPrompt[TReq any](name, description string,
725-
handler func(context.Context, *ServerSession, TReq) (*GetPromptResult, error),
726-
opts ...PromptOption) *ServerPrompt
727-
```
728-
729-
Use `AddPrompts` to add prompts to the server, and `RemovePrompts`
705+
Use `AddPrompt` to add a prompt to the server, and `RemovePrompts`
730706
to remove them by name.
731707
732708
```go
733709
type codeReviewArgs struct {
734710
Code string `json:"code"`
735711
}
736712

737-
func codeReviewHandler(context.Context, *ServerSession, codeReviewArgs) {...}
713+
func codeReviewHandler(context.Context, *ServerSession, *mcp.GetPromptParams) (*mcp.GetPromptResult, error) {...}
738714

739-
server.AddPrompts(
740-
NewServerPrompt("code_review", "review code", codeReviewHandler,
741-
Argument("code", Description("the code to review"))))
715+
server.AddPrompt(
716+
&mcp.Prompt{Name: "code_review", Description: "review code"},
717+
codeReviewHandler,
718+
)
742719

743720
server.RemovePrompts("code_review")
744721
```
@@ -757,25 +734,11 @@ type ResourceHandler func(context.Context, *ServerSession, *ReadResourceParams)
757734
758735
The arguments include the `ServerSession` so the handler can observe the client's roots. The handler should return the resource contents in a `ReadResourceResult`, calling either `NewTextResourceContents` or `NewBlobResourceContents`. If the handler omits the URI or MIME type, the server will populate them from the resource.
759736
760-
The `ServerResource` and `ServerResourceTemplate` types hold the association between the resource and its handler:
761-
762-
```go
763-
type ServerResource struct {
764-
Resource Resource
765-
Handler ResourceHandler
766-
}
767-
768-
type ServerResourceTemplate struct {
769-
Template ResourceTemplate
770-
Handler ResourceHandler
771-
}
772-
```
773-
774-
To add a resource or resource template to a server, users call the `AddResources` and `AddResourceTemplates` methods with one or more `ServerResource`s or `ServerResourceTemplate`s. We also provide methods to remove them.
737+
To add a resource or resource template to a server, users call the `AddResource` and `AddResourceTemplate` methods. We also provide methods to remove them.
775738
776739
```go
777-
func (*Server) AddResources(...*ServerResource)
778-
func (*Server) AddResourceTemplates(...*ServerResourceTemplate)
740+
func (*Server) AddResource(*Resource, ResourceHandler)
741+
func (*Server) AddResourceTemplate(*ResourceTemplate, ResourceHandler)
779742

780743
func (s *Server) RemoveResources(uris ...string)
781744
func (s *Server) RemoveResourceTemplates(uriTemplates ...string)
@@ -796,9 +759,7 @@ Here is an example:
796759
797760
```go
798761
// Safely read "/public/puppies.txt".
799-
s.AddResources(&mcp.ServerResource{
800-
Resource: mcp.Resource{URI: "file:///puppies.txt"},
801-
Handler: s.FileReadResourceHandler("/public")})
762+
s.AddResource(&mcp.Resource{URI: "file:///puppies.txt"}, s.FileReadResourceHandler("/public"))
802763
```
803764
804765
Server sessions also support the spec methods `ListResources` and `ListResourceTemplates`, and the corresponding iterator methods `Resources` and `ResourceTemplates`.

examples/hello/main.go

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
var httpAddr = flag.String("http", "", "if set, use streamable HTTP at this address, instead of stdin/stdout")
2020

2121
type HiArgs struct {
22-
Name string `json:"name"`
22+
Name string `json:"name" mcp:"the name to say hi to"`
2323
}
2424

2525
func SayHi(ctx context.Context, ss *mcp.ServerSession, params *mcp.CallToolParamsFor[HiArgs]) (*mcp.CallToolResultFor[struct{}], error) {
@@ -43,21 +43,13 @@ func main() {
4343
flag.Parse()
4444

4545
server := mcp.NewServer("greeter", "v0.0.1", nil)
46-
server.AddTools(mcp.NewServerTool("greet", "say hi", SayHi, mcp.Input(
47-
mcp.Property("name", mcp.Description("the name to say hi to")),
48-
)))
49-
server.AddPrompts(&mcp.ServerPrompt{
50-
Prompt: &mcp.Prompt{Name: "greet"},
51-
Handler: PromptHi,
52-
})
53-
server.AddResources(&mcp.ServerResource{
54-
Resource: &mcp.Resource{
55-
Name: "info",
56-
MIMEType: "text/plain",
57-
URI: "embedded:info",
58-
},
59-
Handler: handleEmbeddedResource,
60-
})
46+
mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)
47+
server.AddPrompt(&mcp.Prompt{Name: "greet"}, PromptHi)
48+
server.AddResource(&mcp.Resource{
49+
Name: "info",
50+
MIMEType: "text/plain",
51+
URI: "embedded:info",
52+
}, handleEmbeddedResource)
6153

6254
if *httpAddr != "" {
6355
handler := mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server {

examples/sse/main.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,12 @@ func main() {
3535
}
3636

3737
server1 := mcp.NewServer("greeter1", "v0.0.1", nil)
38-
server1.AddTools(mcp.NewServerTool("greet1", "say hi", SayHi))
38+
mcp.AddTool(server1, &mcp.Tool{Name: "greet1", Description: "say hi"}, SayHi)
3939

4040
server2 := mcp.NewServer("greeter2", "v0.0.1", nil)
41-
server2.AddTools(mcp.NewServerTool("greet2", "say hello", SayHi))
41+
mcp.AddTool(server2, &mcp.Tool{Name: "greet2", Description: "say hello"}, SayHi)
4242

43-
log.Printf("MCP servers serving at %s\n", *httpAddr)
43+
log.Printf("MCP servers serving at %s", *httpAddr)
4444
handler := mcp.NewSSEHandler(func(request *http.Request) *mcp.Server {
4545
url := request.URL.Path
4646
log.Printf("Handling request for URL %s\n", url)

internal/readme/README.src.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# MCP Go SDK
22

3+
***BREAKING CHANGES***
4+
5+
The latest version contains breaking changes:
6+
7+
- Server.AddTools is replaced by Server.AddTool.
8+
9+
- NewServerTool is replaced by AddTool. AddTool takes a Tool rather than a name and description, so you can
10+
set any field on the Tool that you want before associating it with a handler.
11+
12+
- Tool options have been removed. If you don't want AddTool to infer a JSON Schema for you, you can construct one
13+
as a struct literal, or using any other code that suits you.
14+
15+
- AddPrompts, AddResources and AddResourceTemplates are similarly replaced by singular methods which pair the
16+
feature with a handler. The ServerXXX types have been removed.
17+
318
[![PkgGoDev](https://pkg.go.dev/badge/github.com/modelcontextprotocol/go-sdk)](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk)
419

520
This repository contains an unreleased implementation of the official Go

internal/readme/server/server.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
)
1414

1515
type HiParams struct {
16-
Name string `json:"name"`
16+
Name string `json:"name", mcp:"the name of the person to greet"`
1717
}
1818

1919
func SayHi(ctx context.Context, cc *mcp.ServerSession, params *mcp.CallToolParamsFor[HiParams]) (*mcp.CallToolResultFor[any], error) {
@@ -25,11 +25,8 @@ func SayHi(ctx context.Context, cc *mcp.ServerSession, params *mcp.CallToolParam
2525
func main() {
2626
// Create a server with a single tool.
2727
server := mcp.NewServer("greeter", "v1.0.0", nil)
28-
server.AddTools(
29-
mcp.NewServerTool("greet", "say hi", SayHi, mcp.Input(
30-
mcp.Property("name", mcp.Description("the name of the person to greet")),
31-
)),
32-
)
28+
29+
mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)
3330
// Run the server over stdin/stdout, until the client disconnects
3431
if err := server.Run(context.Background(), mcp.NewStdioTransport()); err != nil {
3532
log.Fatal(err)

0 commit comments

Comments
 (0)