internal/mcp: rename NewXXX->NewServerXXX

The NewTool and NewPrompt constructors were unidiomatic, because they
returned a ServerTool and ServerPrompt, respectively, rather than a Tool
and Prompt. Rename to be consistent with the constructed object name.

Change-Id: Ife3f16f783368401f792660c4272e1bb5ae61638
Reviewed-on: https://go-review.googlesource.com/c/tools/+/682375
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
This commit is contained in:
Rob Findley 2025-06-17 16:18:39 +00:00 • committed by Robert Findley
commit 9453d6bf6c
14 changed files with 76 additions and 74 deletions

View file

@ -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...)

View file

@ -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)

View file

@ -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

View file

@ -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()

View file

@ -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)

View file

@ -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)
}
}
}

View file

@ -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)
}

View file

@ -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 {

View file

@ -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)

View file

@ -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)

View file

@ -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 })

View file

@ -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)
}

View file

@ -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 {

View file

@ -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)
}
}
}