diff --git a/README.md b/README.md index 11b05b4..b9e705c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ + # MCP SDK prototype [![PkgGoDev](https://pkg.go.dev/badge/golang.org/x/tools)](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp) @@ -91,7 +92,7 @@ func SayHi(ctx context.Context, cc *mcp.ServerSession, params *mcp.CallToolParam func main() { // Create a server with a single tool. server := mcp.NewServer("greeter", "v1.0.0", nil) - server.AddTools(mcp.NewTool("greet", "say hi", SayHi)) + server.AddTools(mcp.NewServerTool("greet", "say hi", SayHi)) // Run the server over stdin/stdout, until the client disconnects _ = server.Run(context.Background(), mcp.NewStdIOTransport()) } diff --git a/design/design.md b/design/design.md index 0219567..abe7d8d 100644 --- a/design/design.md +++ b/design/design.md @@ -359,7 +359,7 @@ A server that can handle that client call would look like this: ```go // Create a server with a single tool. server := mcp.NewServer("greeter", "v1.0.0", nil) -server.AddTools(mcp.NewTool("greet", "say hi", SayHi)) +server.AddTools(mcp.NewServerTool("greet", "say hi", SayHi)) // Run the server over stdin/stdout, until the client disconnects. transport := mcp.NewStdIOTransport() session, err := server.Connect(ctx, transport) @@ -588,7 +588,7 @@ type ClientOptions struct { A `Tool` is a logical MCP tool, generated from the MCP spec, and a `ServerTool` is a tool bound to a tool handler. -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 `NewTool` constructor described below provides a mechanism to bind a typed handler. +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. ```go type CallToolParams[TArgs any] struct { @@ -616,8 +616,8 @@ Add tools to a server with `AddTools`: ```go server.AddTools( - mcp.NewTool("add", "add numbers", addHandler), - mcp.NewTool("subtract, subtract numbers", subHandler)) + mcp.NewServerTool("add", "add numbers", addHandler), + mcp.NewServerTool("subtract, subtract numbers", subHandler)) ``` Remove them by name with `RemoveTools`: @@ -633,16 +633,16 @@ A tool's input schema, expressed as a [JSON Schema](https://json-schema.org), pr 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. +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. ```go -// NewTool creates a Tool using reflection on the given handler. -func NewTool[TArgs any](name, description string, handler ToolHandler[TArgs], opts …ToolOption) *ServerTool +// NewServerTool creates a Tool using reflection on the given handler. +func NewServerTool[TArgs any](name, description string, handler ToolHandler[TArgs], opts …ToolOption) *ServerTool type ToolOption interface { /* ... */ } ``` -`NewTool` 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: +`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: ```go struct { @@ -668,14 +668,14 @@ type Description(desc string) SchemaOption For example: ```go -NewTool(name, description, handler, +NewServerTool(name, description, handler, Input(Property("count", Description("size of the inventory")))) ``` 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: ```go -NewTool(name, description, handler, +NewServerTool(name, description, handler, Input(Property("Choices", Schema(&jsonschema.Schema{UniqueItems: true})))) ``` @@ -693,7 +693,7 @@ func CallTool[TArgs any](context.Context, *ClientSession, *CallToolParams[TArgs] **Differences from mcp-go**: using variadic options to configure tools was significantly inspired by mcp-go. However, the distinction between `ToolOption` and `SchemaOption` allows for recursive application of schema options. For example, that limitation is visible in [this code](https://github.com/DCjanus/dida365-mcp-server/blob/master/cmd/mcp/tools.go#L315), 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. +Additionally, the `NewServerTool` 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 provide `Enum`, 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). @@ -701,10 +701,10 @@ For registering tools, we provide only `AddTools`; mcp-go's `SetTools`, `AddTool ### Prompts -Use `NewPrompt` to create a prompt. As with tools, prompt argument schemas can be inferred from a struct, or obtained from options. +Use `NewServerPrompt` to create a prompt. As with tools, prompt argument schemas can be inferred from a struct, or obtained from options. ```go -func NewPrompt[TReq any](name, description string, +func NewServerPrompt[TReq any](name, description string, handler func(context.Context, *ServerSession, TReq) (*GetPromptResult, error), opts ...PromptOption) *ServerPrompt ``` @@ -720,7 +720,7 @@ type codeReviewArgs struct { func codeReviewHandler(context.Context, *ServerSession, codeReviewArgs) {...} server.AddPrompts( - NewPrompt("code_review", "review code", codeReviewHandler, + NewServerPrompt("code_review", "review code", codeReviewHandler, Argument("code", Description("the code to review")))) server.RemovePrompts("code_review") @@ -728,7 +728,7 @@ server.RemovePrompts("code_review") Client sessions can call the spec method `ListPrompts` or the iterator method `Prompts` to list the available prompts, and the spec method `GetPrompt` 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 provide `RemovePrompts` to remove prompts from the server. +**Differences from mcp-go**: We provide a `NewServerPrompt` helper to bind a prompt handler to a Go function using reflection to derive its arguments. We provide `RemovePrompts` to remove prompts from the server. ### Resources and resource templates diff --git a/examples/hello/main.go b/examples/hello/main.go index 0491d16..77fc191 100644 --- a/examples/hello/main.go +++ b/examples/hello/main.go @@ -45,10 +45,10 @@ func main() { flag.Parse() server := mcp.NewServer("greeter", "v0.0.1", nil) - server.AddTools(mcp.NewTool("greet", "say hi", SayHi, mcp.Input( + server.AddTools(mcp.NewServerTool("greet", "say hi", SayHi, mcp.Input( mcp.Property("name", mcp.Description("the name to say hi to")), ))) - server.AddPrompts(mcp.NewPrompt("greet", "", PromptHi)) + server.AddPrompts(mcp.NewServerPrompt("greet", "", PromptHi)) server.AddResources(&mcp.ServerResource{ Resource: &mcp.Resource{ Name: "info", diff --git a/examples/sse/main.go b/examples/sse/main.go index b778d9c..010d545 100644 --- a/examples/sse/main.go +++ b/examples/sse/main.go @@ -35,10 +35,10 @@ func main() { } server1 := mcp.NewServer("greeter1", "v0.0.1", nil) - server1.AddTools(mcp.NewTool("greet1", "say hi", SayHi)) + server1.AddTools(mcp.NewServerTool("greet1", "say hi", SayHi)) server2 := mcp.NewServer("greeter2", "v0.0.1", nil) - server2.AddTools(mcp.NewTool("greet2", "say hello", SayHi)) + server2.AddTools(mcp.NewServerTool("greet2", "say hello", SayHi)) log.Printf("MCP servers serving at %s\n", *httpAddr) handler := mcp.NewSSEHandler(func(request *http.Request) *mcp.Server { diff --git a/internal/readme/server/server.go b/internal/readme/server/server.go index d01d170..149bd94 100644 --- a/internal/readme/server/server.go +++ b/internal/readme/server/server.go @@ -24,7 +24,7 @@ func SayHi(ctx context.Context, cc *mcp.ServerSession, params *mcp.CallToolParam func main() { // Create a server with a single tool. server := mcp.NewServer("greeter", "v1.0.0", nil) - server.AddTools(mcp.NewTool("greet", "say hi", SayHi)) + server.AddTools(mcp.NewServerTool("greet", "say hi", SayHi)) // Run the server over stdin/stdout, until the client disconnects _ = server.Run(context.Background(), mcp.NewStdIOTransport()) } diff --git a/mcp/client_list_test.go b/mcp/client_list_test.go index cf8d192..1ea435b 100644 --- a/mcp/client_list_test.go +++ b/mcp/client_list_test.go @@ -22,9 +22,9 @@ func TestList(t *testing.T) { defer serverSession.Close() t.Run("tools", func(t *testing.T) { - toolA := mcp.NewTool("apple", "apple tool", SayHi) - toolB := mcp.NewTool("banana", "banana tool", SayHi) - toolC := mcp.NewTool("cherry", "cherry tool", SayHi) + toolA := mcp.NewServerTool("apple", "apple tool", SayHi) + toolB := mcp.NewServerTool("banana", "banana tool", SayHi) + toolC := mcp.NewServerTool("cherry", "cherry tool", SayHi) tools := []*mcp.ServerTool{toolA, toolB, toolC} wantTools := []*mcp.Tool{toolA.Tool, toolB.Tool, toolC.Tool} server.AddTools(tools...) @@ -88,9 +88,9 @@ func TestList(t *testing.T) { }) t.Run("prompts", func(t *testing.T) { - promptA := mcp.NewPrompt("apple", "apple prompt", testPromptHandler[struct{}]) - promptB := mcp.NewPrompt("banana", "banana prompt", testPromptHandler[struct{}]) - promptC := mcp.NewPrompt("cherry", "cherry prompt", testPromptHandler[struct{}]) + promptA := mcp.NewServerPrompt("apple", "apple prompt", testPromptHandler[struct{}]) + promptB := mcp.NewServerPrompt("banana", "banana prompt", testPromptHandler[struct{}]) + promptC := mcp.NewServerPrompt("cherry", "cherry prompt", testPromptHandler[struct{}]) wantPrompts := []*mcp.Prompt{promptA.Prompt, promptB.Prompt, promptC.Prompt} prompts := []*mcp.ServerPrompt{promptA, promptB, promptC} server.AddPrompts(prompts...) diff --git a/mcp/cmd_test.go b/mcp/cmd_test.go index 9a96d25..cde70ff 100644 --- a/mcp/cmd_test.go +++ b/mcp/cmd_test.go @@ -31,7 +31,7 @@ func runServer() { ctx := context.Background() server := mcp.NewServer("greeter", "v0.0.1", nil) - server.AddTools(mcp.NewTool("greet", "say hi", SayHi)) + server.AddTools(mcp.NewServerTool("greet", "say hi", SayHi)) if err := server.Run(ctx, mcp.NewStdIOTransport()); err != nil { log.Fatal(err) diff --git a/mcp/features_test.go b/mcp/features_test.go index ba836ce..cdff4fb 100644 --- a/mcp/features_test.go +++ b/mcp/features_test.go @@ -27,9 +27,9 @@ func SayHi(ctx context.Context, cc *ServerSession, params *CallToolParamsFor[Say } func TestFeatureSetOrder(t *testing.T) { - toolA := NewTool("apple", "apple tool", SayHi).Tool - toolB := NewTool("banana", "banana tool", SayHi).Tool - toolC := NewTool("cherry", "cherry tool", SayHi).Tool + toolA := NewServerTool("apple", "apple tool", SayHi).Tool + toolB := NewServerTool("banana", "banana tool", SayHi).Tool + toolC := NewServerTool("cherry", "cherry tool", SayHi).Tool testCases := []struct { tools []*Tool @@ -52,9 +52,9 @@ func TestFeatureSetOrder(t *testing.T) { } func TestFeatureSetAbove(t *testing.T) { - toolA := NewTool("apple", "apple tool", SayHi).Tool - toolB := NewTool("banana", "banana tool", SayHi).Tool - toolC := NewTool("cherry", "cherry tool", SayHi).Tool + toolA := NewServerTool("apple", "apple tool", SayHi).Tool + toolB := NewServerTool("banana", "banana tool", SayHi).Tool + toolC := NewServerTool("cherry", "cherry tool", SayHi).Tool testCases := []struct { tools []*Tool diff --git a/mcp/mcp_test.go b/mcp/mcp_test.go index ca958a6..fc2be19 100644 --- a/mcp/mcp_test.go +++ b/mcp/mcp_test.go @@ -433,7 +433,7 @@ var ( errTestFailure = errors.New("mcp failure") tools = map[string]*ServerTool{ - "greet": NewTool("greet", "say hi", sayHi), + "greet": NewServerTool("greet", "say hi", sayHi), "fail": { Tool: &Tool{Name: "fail"}, Handler: func(context.Context, *ServerSession, *CallToolParamsFor[map[string]any]) (*CallToolResult, error) { @@ -443,7 +443,7 @@ var ( } prompts = map[string]*ServerPrompt{ - "code_review": NewPrompt("code_review", "do a code review", + "code_review": NewServerPrompt("code_review", "do a code review", func(_ context.Context, _ *ServerSession, params struct{ Code string }, _ *GetPromptParams) (*GetPromptResult, error) { return &GetPromptResult{ Description: "Code review prompt", @@ -452,7 +452,7 @@ var ( }, }, nil }), - "fail": NewPrompt("fail", "", func(_ context.Context, _ *ServerSession, args struct{}, _ *GetPromptParams) (*GetPromptResult, error) { + "fail": NewServerPrompt("fail", "", func(_ context.Context, _ *ServerSession, args struct{}, _ *GetPromptParams) (*GetPromptResult, error) { return nil, errTestFailure }), } @@ -556,7 +556,7 @@ func basicConnection(t *testing.T, tools ...*ServerTool) (*ServerSession, *Clien } func TestServerClosing(t *testing.T) { - cc, cs := basicConnection(t, NewTool("greet", "say hi", sayHi)) + cc, cs := basicConnection(t, NewServerTool("greet", "say hi", sayHi)) defer cs.Close() ctx := context.Background() diff --git a/mcp/prompt.go b/mcp/prompt.go index fce2e5e..c690325 100644 --- a/mcp/prompt.go +++ b/mcp/prompt.go @@ -24,7 +24,8 @@ type ServerPrompt struct { Handler PromptHandler } -// NewPrompt is a helper that uses reflection to create a prompt for the given handler. +// NewServerPrompt is a helper that uses reflection to create a prompt for the +// given handler. // // The arguments for the prompt are extracted from the request type for the // handler. The handler request type must be a struct consisting only of fields @@ -32,9 +33,10 @@ type ServerPrompt struct { // definition correspond to the JSON names of the request fields, and any // fields that are not marked "omitempty" are considered required. // -// The handler is passed [GetPromptParams] so it can have access to prompt parameters other than name and arguments. -// At present, there are no such parameters. -func NewPrompt[In any](name, description string, handler func(context.Context, *ServerSession, In, *GetPromptParams) (*GetPromptResult, error), opts ...PromptOption) *ServerPrompt { +// The handler is passed [GetPromptParams] so it can have access to prompt +// parameters other than name and arguments. At present, there are no such +// parameters. +func NewServerPrompt[In any](name, description string, handler func(context.Context, *ServerSession, In, *GetPromptParams) (*GetPromptResult, error), opts ...PromptOption) *ServerPrompt { schema, err := jsonschema.For[In]() if err != nil { panic(err) diff --git a/mcp/prompt_test.go b/mcp/prompt_test.go index 55a2b0c..b74aec4 100644 --- a/mcp/prompt_test.go +++ b/mcp/prompt_test.go @@ -12,26 +12,26 @@ import ( "github.com/modelcontextprotocol/go-sdk/mcp" ) -// testPromptHandler is used for type inference in TestNewPrompt. +// testPromptHandler is used for type inference in TestNewServerPrompt. func testPromptHandler[T any](context.Context, *mcp.ServerSession, T, *mcp.GetPromptParams) (*mcp.GetPromptResult, error) { panic("not implemented") } -func TestNewPrompt(t *testing.T) { +func TestNewServerPrompt(t *testing.T) { tests := []struct { prompt *mcp.ServerPrompt want []*mcp.PromptArgument }{ { - mcp.NewPrompt("empty", "", testPromptHandler[struct{}]), + mcp.NewServerPrompt("empty", "", testPromptHandler[struct{}]), nil, }, { - mcp.NewPrompt("add_arg", "", testPromptHandler[struct{}], mcp.Argument("x")), + mcp.NewServerPrompt("add_arg", "", testPromptHandler[struct{}], mcp.Argument("x")), []*mcp.PromptArgument{{Name: "x"}}, }, { - mcp.NewPrompt("combo", "", testPromptHandler[struct { + mcp.NewServerPrompt("combo", "", testPromptHandler[struct { Name string `json:"name"` Country string `json:"country,omitempty"` State string @@ -47,7 +47,7 @@ func TestNewPrompt(t *testing.T) { } for _, test := range tests { if diff := cmp.Diff(test.want, test.prompt.Prompt.Arguments); diff != "" { - t.Errorf("NewPrompt(%v) mismatch (-want +got):\n%s", test.prompt.Prompt.Name, diff) + t.Errorf("NewServerPrompt(%v) mismatch (-want +got):\n%s", test.prompt.Prompt.Name, diff) } } } diff --git a/mcp/server.go b/mcp/server.go index 15e7cdc..198d9d3 100644 --- a/mcp/server.go +++ b/mcp/server.go @@ -116,7 +116,6 @@ func (s *Server) RemovePrompts(names ...string) { // The arguments must not be modified after this call. // // AddTools panics if errors are detected. -// Call [AddToolsErr] to obtain an error instead. func (s *Server) AddTools(tools ...*ServerTool) { if err := s.addToolsErr(tools...); err != nil { panic(err) @@ -132,7 +131,7 @@ func (s *Server) addToolsErr(tools ...*ServerTool) error { // Wrap the user's Handlers with rawHandlers that take a json.RawMessage. for _, st := range tools { if st.rawHandler == nil { - // This ServerTool was not created with NewTool. + // This ServerTool was not created with NewServerTool. if st.Handler == nil { return fmt.Errorf("AddTools: tool %q has no handler", st.Tool.Name) } diff --git a/mcp/server_example_test.go b/mcp/server_example_test.go index 3f0e40a..152eb5b 100644 --- a/mcp/server_example_test.go +++ b/mcp/server_example_test.go @@ -29,7 +29,7 @@ func ExampleServer() { clientTransport, serverTransport := mcp.NewInMemoryTransports() server := mcp.NewServer("greeter", "v0.0.1", nil) - server.AddTools(mcp.NewTool("greet", "say hi", SayHi)) + server.AddTools(mcp.NewServerTool("greet", "say hi", SayHi)) serverSession, err := server.Connect(ctx, serverTransport) if err != nil { diff --git a/mcp/shared_test.go b/mcp/shared_test.go index 7aa7b60..a2ed9b6 100644 --- a/mcp/shared_test.go +++ b/mcp/shared_test.go @@ -71,8 +71,8 @@ func roundTrip[T any](t *testing.T, v T) T { } // TODO(jba): this shouldn't be in this file, but tool_test.go doesn't have access to unexported symbols. -func TestNewToolValidate(t *testing.T) { - // Check that the tool returned from NewTool properly validates its input schema. +func TestNewServerToolValidate(t *testing.T) { + // Check that the tool returned from NewServerTool properly validates its input schema. type req struct { I int @@ -85,7 +85,7 @@ func TestNewToolValidate(t *testing.T) { return nil, nil } - tool := NewTool("test", "test", dummyHandler) + tool := NewServerTool("test", "test", dummyHandler) // Need to add the tool to a server to get resolved schemas. // s := NewServer("", "", nil) diff --git a/mcp/sse_example_test.go b/mcp/sse_example_test.go index 12cb91e..4602acd 100644 --- a/mcp/sse_example_test.go +++ b/mcp/sse_example_test.go @@ -26,7 +26,7 @@ func Add(ctx context.Context, cc *mcp.ServerSession, params *mcp.CallToolParamsF func ExampleSSEHandler() { server := mcp.NewServer("adder", "v0.0.1", nil) - server.AddTools(mcp.NewTool("add", "add two numbers", Add)) + server.AddTools(mcp.NewServerTool("add", "add two numbers", Add)) handler := mcp.NewSSEHandler(func(*http.Request) *mcp.Server { return server }) httpServer := httptest.NewServer(handler) diff --git a/mcp/sse_test.go b/mcp/sse_test.go index efbadcc..aed9982 100644 --- a/mcp/sse_test.go +++ b/mcp/sse_test.go @@ -20,7 +20,7 @@ func TestSSEServer(t *testing.T) { t.Run(fmt.Sprintf("closeServerFirst=%t", closeServerFirst), func(t *testing.T) { ctx := context.Background() server := NewServer("testServer", "v1.0.0", nil) - server.AddTools(NewTool("greet", "say hi", sayHi)) + server.AddTools(NewServerTool("greet", "say hi", sayHi)) sseHandler := NewSSEHandler(func(*http.Request) *Server { return server }) diff --git a/mcp/streamable_test.go b/mcp/streamable_test.go index 0b13256..8aad124 100644 --- a/mcp/streamable_test.go +++ b/mcp/streamable_test.go @@ -30,7 +30,7 @@ func TestStreamableTransports(t *testing.T) { // 1. Create a server with a simple "greet" tool. server := NewServer("testServer", "v1.0.0", nil) - server.AddTools(NewTool("greet", "say hi", sayHi)) + server.AddTools(NewServerTool("greet", "say hi", sayHi)) // 2. Start an httptest.Server with the StreamableHTTPHandler. handler := NewStreamableHTTPHandler(func(req *http.Request) *Server { return server }, nil) @@ -290,7 +290,7 @@ func TestStreamableServerTransport(t *testing.T) { // Create a server containing a single tool, which runs the test tool // behavior, if any. server := NewServer("testServer", "v1.0.0", nil) - tool := NewTool("tool", "test tool", func(ctx context.Context, ss *ServerSession, params *CallToolParamsFor[any]) (*CallToolResultFor[any], error) { + tool := NewServerTool("tool", "test tool", func(ctx context.Context, ss *ServerSession, params *CallToolParamsFor[any]) (*CallToolResultFor[any], error) { if test.tool != nil { test.tool(t, ctx, ss) } diff --git a/mcp/tool.go b/mcp/tool.go index 116eac0..a8b5557 100644 --- a/mcp/tool.go +++ b/mcp/tool.go @@ -30,30 +30,30 @@ type rawToolHandler = func(context.Context, *ServerSession, *CallToolParamsFor[j type ServerTool struct { Tool *Tool Handler ToolHandler - // Set in NewTool or Server.AddToolsErr. + // Set in NewServerTool or Server.AddToolsErr. rawHandler rawToolHandler // Resolved tool schemas. Set in Server.AddToolsErr. inputResolved, outputResolved *jsonschema.Resolved } -// NewTool is a helper to make a tool using reflection on the given type parameters. -// When the tool is called, CallToolParams.Arguments will be of type TReq. +// NewServerTool is a helper to make a tool using reflection on the given type parameters. +// When the tool is called, CallToolParams.Arguments will be of type In. // // If provided, variadic [ToolOption] values may be used to customize the tool. // // The input schema for the tool is extracted from the request type for the // handler, and used to unmmarshal and validate requests to the handler. This // schema may be customized using the [Input] option. -func NewTool[In, Out any](name, description string, handler ToolHandlerFor[In, Out], opts ...ToolOption) *ServerTool { - st, err := newToolErr[In, Out](name, description, handler, opts...) +func NewServerTool[In, Out any](name, description string, handler ToolHandlerFor[In, Out], opts ...ToolOption) *ServerTool { + st, err := newServerToolErr[In, Out](name, description, handler, opts...) if err != nil { - panic(fmt.Errorf("NewTool(%q): %w", name, err)) + panic(fmt.Errorf("NewServerTool(%q): %w", name, err)) } return st } -func newToolErr[In, Out any](name, description string, handler ToolHandlerFor[In, Out], opts ...ToolOption) (*ServerTool, error) { - // TODO: check that TReq is a struct. +func newServerToolErr[In, Out any](name, description string, handler ToolHandlerFor[In, Out], opts ...ToolOption) (*ServerTool, error) { + // TODO: check that In is a struct. ischema, err := jsonschema.For[In]() if err != nil { return nil, err @@ -106,7 +106,7 @@ func newToolErr[In, Out any](name, description string, handler ToolHandlerFor[In return t, nil } -// newRawHandler creates a rawToolHandler for tools not created through NewTool. +// newRawHandler creates a rawToolHandler for tools not created through NewServerTool. // It unmarshals the arguments into a map[string]any and validates them against the // schema, then calls the ServerTool's handler. func newRawHandler(st *ServerTool) rawToolHandler { diff --git a/mcp/tool_test.go b/mcp/tool_test.go index 128147e..85775e9 100644 --- a/mcp/tool_test.go +++ b/mcp/tool_test.go @@ -15,18 +15,18 @@ import ( "github.com/modelcontextprotocol/go-sdk/jsonschema" ) -// testToolHandler is used for type inference in TestNewTool. +// testToolHandler is used for type inference in TestNewServerTool. func testToolHandler[T any](context.Context, *ServerSession, *CallToolParamsFor[T]) (*CallToolResultFor[any], error) { panic("not implemented") } -func TestNewTool(t *testing.T) { +func TestNewServerTool(t *testing.T) { tests := []struct { tool *ServerTool want *jsonschema.Schema }{ { - NewTool("basic", "", testToolHandler[struct { + NewServerTool("basic", "", testToolHandler[struct { Name string `json:"name"` }]), &jsonschema.Schema{ @@ -39,7 +39,7 @@ func TestNewTool(t *testing.T) { }, }, { - NewTool("enum", "", testToolHandler[struct{ Name string }], Input( + NewServerTool("enum", "", testToolHandler[struct{ Name string }], Input( Property("Name", Enum("x", "y", "z")), )), &jsonschema.Schema{ @@ -52,7 +52,7 @@ func TestNewTool(t *testing.T) { }, }, { - NewTool("required", "", testToolHandler[struct { + NewServerTool("required", "", testToolHandler[struct { Name string `json:"name"` Language string `json:"language"` X int `json:"x,omitempty"` @@ -72,7 +72,7 @@ func TestNewTool(t *testing.T) { }, }, { - NewTool("set_schema", "", testToolHandler[struct { + NewServerTool("set_schema", "", testToolHandler[struct { X int `json:"x,omitempty"` Y int `json:"y,omitempty"` }], Input( @@ -85,7 +85,7 @@ func TestNewTool(t *testing.T) { } for _, test := range tests { if diff := cmp.Diff(test.want, test.tool.Tool.InputSchema, cmpopts.IgnoreUnexported(jsonschema.Schema{})); diff != "" { - t.Errorf("NewTool(%v) mismatch (-want +got):\n%s", test.tool.Tool.Name, diff) + t.Errorf("NewServerTool(%v) mismatch (-want +got):\n%s", test.tool.Tool.Name, diff) } } }