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 <rfindley@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
parent
e228d78d19
commit
a6ec3f5f4e
1 changed files with 37 additions and 177 deletions
111
mcp/prompt.go
111
mcp/prompt.go
|
|
@ -6,13 +6,6 @@ package mcp
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"slices"
|
||||
|
||||
"github.com/modelcontextprotocol/go-sdk/internal/util"
|
||||
"github.com/modelcontextprotocol/go-sdk/jsonschema"
|
||||
)
|
||||
|
||||
// A PromptHandler handles a call to prompts/get.
|
||||
|
|
@ -23,107 +16,3 @@ type ServerPrompt struct {
|
|||
Prompt *Prompt
|
||||
Handler PromptHandler
|
||||
}
|
||||
|
||||
// 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
|
||||
// of type string or *string. The argument names for the resulting prompt
|
||||
// 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 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)
|
||||
}
|
||||
if schema.Type != "object" || !reflect.DeepEqual(schema.AdditionalProperties, &jsonschema.Schema{Not: &jsonschema.Schema{}}) {
|
||||
panic(fmt.Sprintf("invalid input type %q: handler input type must be a struct", schema.Type))
|
||||
}
|
||||
resolved, err := schema.Resolve(nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
prompt := &ServerPrompt{
|
||||
Prompt: &Prompt{
|
||||
Name: name,
|
||||
Description: description,
|
||||
},
|
||||
}
|
||||
required := make(map[string]bool)
|
||||
for _, p := range schema.Required {
|
||||
required[p] = true
|
||||
}
|
||||
for name, prop := range util.Sorted(schema.Properties) {
|
||||
if prop.Type != "string" {
|
||||
panic(fmt.Sprintf("invalid property type %q: handler type must consist only of string fields", prop.Type))
|
||||
}
|
||||
prompt.Prompt.Arguments = append(prompt.Prompt.Arguments, &PromptArgument{
|
||||
Name: name,
|
||||
Description: prop.Description,
|
||||
Required: required[name],
|
||||
})
|
||||
}
|
||||
prompt.Handler = func(ctx context.Context, ss *ServerSession, params *GetPromptParams) (*GetPromptResult, error) {
|
||||
// For simplicity, just marshal and unmarshal the arguments.
|
||||
// This could be avoided in the future.
|
||||
rawArgs, err := json.Marshal(params.Arguments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var args In
|
||||
if err := unmarshalSchema(rawArgs, resolved, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return handler(ctx, ss, args, params)
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt.set(prompt)
|
||||
}
|
||||
return prompt
|
||||
}
|
||||
|
||||
// A PromptOption configures the behavior of a Prompt.
|
||||
type PromptOption interface {
|
||||
set(*ServerPrompt)
|
||||
}
|
||||
|
||||
type promptSetter func(*ServerPrompt)
|
||||
|
||||
func (s promptSetter) set(p *ServerPrompt) { s(p) }
|
||||
|
||||
// Argument configures the 'schema' of a prompt argument.
|
||||
// If the argument does not exist, it is added.
|
||||
//
|
||||
// Since prompt arguments are not a full JSON schema, Argument only accepts
|
||||
// Required and Description, and panics when encountering any other option.
|
||||
func Argument(name string, opts ...SchemaOption) PromptOption {
|
||||
return promptSetter(func(p *ServerPrompt) {
|
||||
i := slices.IndexFunc(p.Prompt.Arguments, func(arg *PromptArgument) bool {
|
||||
return arg.Name == name
|
||||
})
|
||||
var arg *PromptArgument
|
||||
if i < 0 {
|
||||
i = len(p.Prompt.Arguments)
|
||||
arg = &PromptArgument{Name: name}
|
||||
p.Prompt.Arguments = append(p.Prompt.Arguments, arg)
|
||||
} else {
|
||||
arg = p.Prompt.Arguments[i]
|
||||
}
|
||||
for _, opt := range opts {
|
||||
switch v := opt.(type) {
|
||||
case required:
|
||||
arg.Required = bool(v)
|
||||
case description:
|
||||
arg.Description = string(v)
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported prompt argument schema option %T", opt))
|
||||
}
|
||||
}
|
||||
p.Prompt.Arguments[i] = arg
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue