Skip to content

Commit 15c16a5

Browse files
jbafindleyr
authored andcommitted
internal/mcp: simplify prompt API
Remove NewServerPrompt. Schema inference is irrelevant for prompts. All arguments are strings. So using a struct is not as valuable as with tools. The only advantage is catching misspellings at compile time. It's not worth it. Change-Id: I45aaa5d55c8940cc9e32dc277bd543f79d932519 Reviewed-on: https://go-review.googlesource.com/c/tools/+/682797 Reviewed-by: Robert Findley <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 84508c7 commit 15c16a5

File tree

5 files changed

+37
-177
lines changed

5 files changed

+37
-177
lines changed

internal/mcp/client_list_test.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,9 @@ func TestList(t *testing.T) {
8888
})
8989

9090
t.Run("prompts", func(t *testing.T) {
91-
promptA := mcp.NewServerPrompt("apple", "apple prompt", testPromptHandler[struct{}])
92-
promptB := mcp.NewServerPrompt("banana", "banana prompt", testPromptHandler[struct{}])
93-
promptC := mcp.NewServerPrompt("cherry", "cherry prompt", testPromptHandler[struct{}])
91+
promptA := newServerPrompt("apple", "apple prompt")
92+
promptB := newServerPrompt("banana", "banana prompt")
93+
promptC := newServerPrompt("cherry", "cherry prompt")
9494
wantPrompts := []*mcp.Prompt{promptA.Prompt, promptB.Prompt, promptC.Prompt}
9595
prompts := []*mcp.ServerPrompt{promptA, promptB, promptC}
9696
server.AddPrompts(prompts...)
@@ -122,3 +122,15 @@ func testIterator[T any](ctx context.Context, t *testing.T, seq iter.Seq2[*T, er
122122
t.Fatalf("mismatch (-want +got):\n%s", diff)
123123
}
124124
}
125+
126+
// testPromptHandler is used for type inference newServerPrompt.
127+
func testPromptHandler(context.Context, *mcp.ServerSession, *mcp.GetPromptParams) (*mcp.GetPromptResult, error) {
128+
panic("not implemented")
129+
}
130+
131+
func newServerPrompt(name, desc string) *mcp.ServerPrompt {
132+
return &mcp.ServerPrompt{
133+
Prompt: &mcp.Prompt{Name: name, Description: desc},
134+
Handler: testPromptHandler,
135+
}
136+
}

internal/mcp/examples/hello/main.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ func SayHi(ctx context.Context, ss *mcp.ServerSession, params *mcp.CallToolParam
3232

3333
// TODO(jba): it should be OK for args to be a pointer, but this fails in
3434
// jsonschema. Needs investigation.
35-
func PromptHi(ctx context.Context, ss *mcp.ServerSession, args HiArgs, _ *mcp.GetPromptParams) (*mcp.GetPromptResult, error) {
35+
func PromptHi(ctx context.Context, ss *mcp.ServerSession, params *mcp.GetPromptParams) (*mcp.GetPromptResult, error) {
3636
return &mcp.GetPromptResult{
3737
Description: "Code review prompt",
3838
Messages: []*mcp.PromptMessage{
39-
{Role: "user", Content: mcp.NewTextContent("Say hi to " + args.Name)},
39+
{Role: "user", Content: mcp.NewTextContent("Say hi to " + params.Arguments["name"])},
4040
},
4141
}, nil
4242
}
@@ -48,7 +48,10 @@ func main() {
4848
server.AddTools(mcp.NewServerTool("greet", "say hi", SayHi, mcp.Input(
4949
mcp.Property("name", mcp.Description("the name to say hi to")),
5050
)))
51-
server.AddPrompts(mcp.NewServerPrompt("greet", "", PromptHi))
51+
server.AddPrompts(&mcp.ServerPrompt{
52+
Prompt: &mcp.Prompt{Name: "greet"},
53+
Handler: PromptHi,
54+
})
5255
server.AddResources(&mcp.ServerResource{
5356
Resource: &mcp.Resource{
5457
Name: "info",

internal/mcp/mcp_test.go

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -441,18 +441,27 @@ var (
441441
}
442442

443443
prompts = map[string]*ServerPrompt{
444-
"code_review": NewServerPrompt("code_review", "do a code review",
445-
func(_ context.Context, _ *ServerSession, params struct{ Code string }, _ *GetPromptParams) (*GetPromptResult, error) {
444+
"code_review": {
445+
Prompt: &Prompt{
446+
Name: "code_review",
447+
Description: "do a code review",
448+
Arguments: []*PromptArgument{{Name: "Code", Required: true}},
449+
},
450+
Handler: func(_ context.Context, _ *ServerSession, params *GetPromptParams) (*GetPromptResult, error) {
446451
return &GetPromptResult{
447452
Description: "Code review prompt",
448453
Messages: []*PromptMessage{
449-
{Role: "user", Content: NewTextContent("Please review the following code: " + params.Code)},
454+
{Role: "user", Content: NewTextContent("Please review the following code: " + params.Arguments["Code"])},
450455
},
451456
}, nil
452-
}),
453-
"fail": NewServerPrompt("fail", "", func(_ context.Context, _ *ServerSession, args struct{}, _ *GetPromptParams) (*GetPromptResult, error) {
454-
return nil, errTestFailure
455-
}),
457+
},
458+
},
459+
"fail": {
460+
Prompt: &Prompt{Name: "fail"},
461+
Handler: func(_ context.Context, _ *ServerSession, _ *GetPromptParams) (*GetPromptResult, error) {
462+
return nil, errTestFailure
463+
},
464+
},
456465
}
457466

458467
resource1 = &Resource{

internal/mcp/prompt.go

Lines changed: 0 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,6 @@ package mcp
66

77
import (
88
"context"
9-
"encoding/json"
10-
"fmt"
11-
"reflect"
12-
"slices"
13-
14-
"golang.org/x/tools/internal/mcp/internal/util"
15-
"golang.org/x/tools/internal/mcp/jsonschema"
169
)
1710

1811
// A PromptHandler handles a call to prompts/get.
@@ -23,107 +16,3 @@ type ServerPrompt struct {
2316
Prompt *Prompt
2417
Handler PromptHandler
2518
}
26-
27-
// NewServerPrompt is a helper that uses reflection to create a prompt for the
28-
// given handler.
29-
//
30-
// The arguments for the prompt are extracted from the request type for the
31-
// handler. The handler request type must be a struct consisting only of fields
32-
// of type string or *string. The argument names for the resulting prompt
33-
// definition correspond to the JSON names of the request fields, and any
34-
// fields that are not marked "omitempty" are considered required.
35-
//
36-
// The handler is passed [GetPromptParams] so it can have access to prompt
37-
// parameters other than name and arguments. At present, there are no such
38-
// parameters.
39-
func NewServerPrompt[In any](name, description string, handler func(context.Context, *ServerSession, In, *GetPromptParams) (*GetPromptResult, error), opts ...PromptOption) *ServerPrompt {
40-
schema, err := jsonschema.For[In]()
41-
if err != nil {
42-
panic(err)
43-
}
44-
if schema.Type != "object" || !reflect.DeepEqual(schema.AdditionalProperties, &jsonschema.Schema{Not: &jsonschema.Schema{}}) {
45-
panic(fmt.Sprintf("invalid input type %q: handler input type must be a struct", schema.Type))
46-
}
47-
resolved, err := schema.Resolve(nil)
48-
if err != nil {
49-
panic(err)
50-
}
51-
prompt := &ServerPrompt{
52-
Prompt: &Prompt{
53-
Name: name,
54-
Description: description,
55-
},
56-
}
57-
required := make(map[string]bool)
58-
for _, p := range schema.Required {
59-
required[p] = true
60-
}
61-
for name, prop := range util.Sorted(schema.Properties) {
62-
if prop.Type != "string" {
63-
panic(fmt.Sprintf("invalid property type %q: handler type must consist only of string fields", prop.Type))
64-
}
65-
prompt.Prompt.Arguments = append(prompt.Prompt.Arguments, &PromptArgument{
66-
Name: name,
67-
Description: prop.Description,
68-
Required: required[name],
69-
})
70-
}
71-
prompt.Handler = func(ctx context.Context, ss *ServerSession, params *GetPromptParams) (*GetPromptResult, error) {
72-
// For simplicity, just marshal and unmarshal the arguments.
73-
// This could be avoided in the future.
74-
rawArgs, err := json.Marshal(params.Arguments)
75-
if err != nil {
76-
return nil, err
77-
}
78-
var args In
79-
if err := unmarshalSchema(rawArgs, resolved, &args); err != nil {
80-
return nil, err
81-
}
82-
return handler(ctx, ss, args, params)
83-
}
84-
for _, opt := range opts {
85-
opt.set(prompt)
86-
}
87-
return prompt
88-
}
89-
90-
// A PromptOption configures the behavior of a Prompt.
91-
type PromptOption interface {
92-
set(*ServerPrompt)
93-
}
94-
95-
type promptSetter func(*ServerPrompt)
96-
97-
func (s promptSetter) set(p *ServerPrompt) { s(p) }
98-
99-
// Argument configures the 'schema' of a prompt argument.
100-
// If the argument does not exist, it is added.
101-
//
102-
// Since prompt arguments are not a full JSON schema, Argument only accepts
103-
// Required and Description, and panics when encountering any other option.
104-
func Argument(name string, opts ...SchemaOption) PromptOption {
105-
return promptSetter(func(p *ServerPrompt) {
106-
i := slices.IndexFunc(p.Prompt.Arguments, func(arg *PromptArgument) bool {
107-
return arg.Name == name
108-
})
109-
var arg *PromptArgument
110-
if i < 0 {
111-
i = len(p.Prompt.Arguments)
112-
arg = &PromptArgument{Name: name}
113-
p.Prompt.Arguments = append(p.Prompt.Arguments, arg)
114-
} else {
115-
arg = p.Prompt.Arguments[i]
116-
}
117-
for _, opt := range opts {
118-
switch v := opt.(type) {
119-
case required:
120-
arg.Required = bool(v)
121-
case description:
122-
arg.Description = string(v)
123-
default:
124-
panic(fmt.Sprintf("unsupported prompt argument schema option %T", opt))
125-
}
126-
}
127-
p.Prompt.Arguments[i] = arg
128-
})
129-
}

internal/mcp/prompt_test.go

Lines changed: 0 additions & 53 deletions
This file was deleted.

0 commit comments

Comments
 (0)