internal/mcp: rename ServerConnection->ServerSession
Bring the Server implementation up to date with the spec by renaming ServerConnection to ServerSession. Also update variable names, which were in various states of disarray from the multiple renamings of this type. Change-Id: I4aec883db6d0500fb91e6daa51b702614877cf8a Reviewed-on: https://go-review.googlesource.com/c/tools/+/672655 Auto-Submit: Robert Findley <rfindley@google.com> TryBot-Bypass: Robert Findley <rfindley@google.com> Commit-Queue: Robert Findley <rfindley@google.com> Reviewed-by: Jonathan Amsterdam <jba@google.com>
This commit is contained in:
parent
9f16a99bb1
commit
ac828d5a82
14 changed files with 74 additions and 75 deletions
|
|
@ -59,7 +59,7 @@ type HiParams struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SayHi(ctx context.Context, cc *mcp.ServerConnection, params *HiParams) ([]mcp.Content, error) {
|
func SayHi(ctx context.Context, cc *mcp.ServerSession, params *HiParams) ([]mcp.Content, error) {
|
||||||
return []mcp.Content{
|
return []mcp.Content{
|
||||||
mcp.TextContent{Text: "Hi " + params.Name},
|
mcp.TextContent{Text: "Hi " + params.Name},
|
||||||
}, nil
|
}, nil
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,13 @@ type HiParams struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SayHi(ctx context.Context, cc *mcp.ServerConnection, params *HiParams) ([]*mcp.Content, error) {
|
func SayHi(ctx context.Context, cc *mcp.ServerSession, params *HiParams) ([]*mcp.Content, error) {
|
||||||
return []*mcp.Content{
|
return []*mcp.Content{
|
||||||
mcp.NewTextContent("Hi " + params.Name),
|
mcp.NewTextContent("Hi " + params.Name),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func PromptHi(ctx context.Context, cc *mcp.ServerConnection, params *HiParams) (*mcp.GetPromptResult, error) {
|
func PromptHi(ctx context.Context, cc *mcp.ServerSession, params *HiParams) (*mcp.GetPromptResult, error) {
|
||||||
return &mcp.GetPromptResult{
|
return &mcp.GetPromptResult{
|
||||||
Description: "Code review prompt",
|
Description: "Code review prompt",
|
||||||
Messages: []*mcp.PromptMessage{
|
Messages: []*mcp.PromptMessage{
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ type SayHiParams struct {
|
||||||
Name string `json:"name" mcp:"the name to say hi to"`
|
Name string `json:"name" mcp:"the name to say hi to"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SayHi(ctx context.Context, cc *mcp.ServerConnection, params *SayHiParams) ([]*mcp.Content, error) {
|
func SayHi(ctx context.Context, cc *mcp.ServerSession, params *SayHiParams) ([]*mcp.Content, error) {
|
||||||
return []*mcp.Content{
|
return []*mcp.Content{
|
||||||
mcp.NewTextContent("Hi " + params.Name),
|
mcp.NewTextContent("Hi " + params.Name),
|
||||||
}, nil
|
}, nil
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@
|
||||||
// To get started, create either a [Client] or [Server], and connect it to a
|
// To get started, create either a [Client] or [Server], and connect it to a
|
||||||
// peer using a [Transport]. The diagram below illustrates how this works:
|
// peer using a [Transport]. The diagram below illustrates how this works:
|
||||||
//
|
//
|
||||||
// Client Server
|
// Client Server
|
||||||
// ⇅ (jsonrpc2) ⇅
|
// ⇅ (jsonrpc2) ⇅
|
||||||
// Client Transport ⇄ Server Transport ⇄ ServerConnection
|
// Client Transport ⇄ Server Transport ⇄ ServerSession
|
||||||
//
|
//
|
||||||
// A [Client] is an MCP client, which can be configured with various client
|
// A [Client] is an MCP client, which can be configured with various client
|
||||||
// capabilities. Clients may be connected to a [Server] instance
|
// capabilities. Clients may be connected to a [Server] instance
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
// Similarly, a [Server] is an MCP server, which can be configured with various
|
// Similarly, a [Server] is an MCP server, which can be configured with various
|
||||||
// server capabilities. Servers may be connected to one or more [Client]
|
// server capabilities. Servers may be connected to one or more [Client]
|
||||||
// instances using the [Server.Connect] method, which creates a
|
// instances using the [Server.Connect] method, which creates a
|
||||||
// [ServerConnection].
|
// [ServerSession].
|
||||||
//
|
//
|
||||||
// A [Transport] connects a bidirectional [Stream] of jsonrpc2 messages. In
|
// A [Transport] connects a bidirectional [Stream] of jsonrpc2 messages. In
|
||||||
// practice, transports in the MCP spec are are either client transports or
|
// practice, transports in the MCP spec are are either client transports or
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ type hiParams struct {
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func sayHi(ctx context.Context, cc *ServerConnection, v hiParams) ([]*Content, error) {
|
func sayHi(ctx context.Context, cc *ServerSession, v hiParams) ([]*Content, error) {
|
||||||
if err := cc.Ping(ctx, nil); err != nil {
|
if err := cc.Ping(ctx, nil); err != nil {
|
||||||
return nil, fmt.Errorf("ping failed: %v", err)
|
return nil, fmt.Errorf("ping failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -43,13 +43,13 @@ func TestEndToEnd(t *testing.T) {
|
||||||
// The 'fail' tool returns this error.
|
// The 'fail' tool returns this error.
|
||||||
failure := errors.New("mcp failure")
|
failure := errors.New("mcp failure")
|
||||||
s.AddTools(
|
s.AddTools(
|
||||||
NewTool("fail", "just fail", func(context.Context, *ServerConnection, struct{}) ([]*Content, error) {
|
NewTool("fail", "just fail", func(context.Context, *ServerSession, struct{}) ([]*Content, error) {
|
||||||
return nil, failure
|
return nil, failure
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
s.AddPrompts(
|
s.AddPrompts(
|
||||||
NewPrompt("code_review", "do a code review", func(_ context.Context, _ *ServerConnection, params struct{ Code string }) (*GetPromptResult, error) {
|
NewPrompt("code_review", "do a code review", func(_ context.Context, _ *ServerSession, params struct{ Code string }) (*GetPromptResult, error) {
|
||||||
return &GetPromptResult{
|
return &GetPromptResult{
|
||||||
Description: "Code review prompt",
|
Description: "Code review prompt",
|
||||||
Messages: []*PromptMessage{
|
Messages: []*PromptMessage{
|
||||||
|
|
@ -57,17 +57,17 @@ func TestEndToEnd(t *testing.T) {
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}),
|
}),
|
||||||
NewPrompt("fail", "", func(_ context.Context, _ *ServerConnection, params struct{}) (*GetPromptResult, error) {
|
NewPrompt("fail", "", func(_ context.Context, _ *ServerSession, params struct{}) (*GetPromptResult, error) {
|
||||||
return nil, failure
|
return nil, failure
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Connect the server.
|
// Connect the server.
|
||||||
sc, err := s.Connect(ctx, st, nil)
|
ss, err := s.Connect(ctx, st, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if got := slices.Collect(s.Clients()); len(got) != 1 {
|
if got := slices.Collect(s.Sessions()); len(got) != 1 {
|
||||||
t.Errorf("after connection, Clients() has length %d, want 1", len(got))
|
t.Errorf("after connection, Clients() has length %d, want 1", len(got))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,7 +75,7 @@ func TestEndToEnd(t *testing.T) {
|
||||||
var clientWG sync.WaitGroup
|
var clientWG sync.WaitGroup
|
||||||
clientWG.Add(1)
|
clientWG.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
if err := sc.Wait(); err != nil {
|
if err := ss.Wait(); err != nil {
|
||||||
t.Errorf("server failed: %v", err)
|
t.Errorf("server failed: %v", err)
|
||||||
}
|
}
|
||||||
clientWG.Done()
|
clientWG.Done()
|
||||||
|
|
@ -198,7 +198,7 @@ func TestEndToEnd(t *testing.T) {
|
||||||
URI: "file:///nonexistent.txt",
|
URI: "file:///nonexistent.txt",
|
||||||
}
|
}
|
||||||
|
|
||||||
readHandler := func(_ context.Context, _ *ServerConnection, p *ReadResourceParams) (*ReadResourceResult, error) {
|
readHandler := func(_ context.Context, _ *ServerSession, p *ReadResourceParams) (*ReadResourceResult, error) {
|
||||||
if p.URI == "file:///file1.txt" {
|
if p.URI == "file:///file1.txt" {
|
||||||
return &ReadResourceResult{
|
return &ReadResourceResult{
|
||||||
Contents: &ResourceContents{
|
Contents: &ResourceContents{
|
||||||
|
|
@ -249,9 +249,9 @@ func TestEndToEnd(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.Run("roots", func(t *testing.T) {
|
t.Run("roots", func(t *testing.T) {
|
||||||
// Take the server's first ServerConnection.
|
// Take the server's first ServerSession.
|
||||||
var sc *ServerConnection
|
var sc *ServerSession
|
||||||
for sc = range s.Clients() {
|
for sc = range s.Sessions() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -272,7 +272,7 @@ func TestEndToEnd(t *testing.T) {
|
||||||
|
|
||||||
// After disconnecting, neither client nor server should have any
|
// After disconnecting, neither client nor server should have any
|
||||||
// connections.
|
// connections.
|
||||||
for range s.Clients() {
|
for range s.Sessions() {
|
||||||
t.Errorf("unexpected client after disconnection")
|
t.Errorf("unexpected client after disconnection")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -282,7 +282,7 @@ func TestEndToEnd(t *testing.T) {
|
||||||
//
|
//
|
||||||
// The caller should cancel either the client connection or server connection
|
// The caller should cancel either the client connection or server connection
|
||||||
// when the connections are no longer needed.
|
// when the connections are no longer needed.
|
||||||
func basicConnection(t *testing.T, tools ...*ServerTool) (*ServerConnection, *Client) {
|
func basicConnection(t *testing.T, tools ...*ServerTool) (*ServerSession, *Client) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
@ -292,7 +292,7 @@ func basicConnection(t *testing.T, tools ...*ServerTool) (*ServerConnection, *Cl
|
||||||
|
|
||||||
// The 'greet' tool says hi.
|
// The 'greet' tool says hi.
|
||||||
s.AddTools(tools...)
|
s.AddTools(tools...)
|
||||||
cc, err := s.Connect(ctx, st, nil)
|
ss, err := s.Connect(ctx, st, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -301,7 +301,7 @@ func basicConnection(t *testing.T, tools ...*ServerTool) (*ServerConnection, *Cl
|
||||||
if err := c.Start(ctx); err != nil {
|
if err := c.Start(ctx); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
return cc, c
|
return ss, c
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServerClosing(t *testing.T) {
|
func TestServerClosing(t *testing.T) {
|
||||||
|
|
@ -370,7 +370,7 @@ func TestCancellation(t *testing.T) {
|
||||||
cancelled = make(chan struct{}, 1) // don't block the request
|
cancelled = make(chan struct{}, 1) // don't block the request
|
||||||
)
|
)
|
||||||
|
|
||||||
slowRequest := func(ctx context.Context, cc *ServerConnection, v struct{}) ([]*Content, error) {
|
slowRequest := func(ctx context.Context, cc *ServerSession, v struct{}) ([]*Content, error) {
|
||||||
start <- struct{}{}
|
start <- struct{}{}
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// A PromptHandler handles a call to prompts/get.
|
// A PromptHandler handles a call to prompts/get.
|
||||||
type PromptHandler func(context.Context, *ServerConnection, map[string]string) (*GetPromptResult, error)
|
type PromptHandler func(context.Context, *ServerSession, map[string]string) (*GetPromptResult, error)
|
||||||
|
|
||||||
// A Prompt is a prompt definition bound to a prompt handler.
|
// A Prompt is a prompt definition bound to a prompt handler.
|
||||||
type ServerPrompt struct {
|
type ServerPrompt struct {
|
||||||
|
|
@ -32,7 +32,7 @@ type ServerPrompt struct {
|
||||||
// of type string or *string. The argument names for the resulting prompt
|
// of type string or *string. The argument names for the resulting prompt
|
||||||
// definition correspond to the JSON names of the request fields, and any
|
// definition correspond to the JSON names of the request fields, and any
|
||||||
// fields that are not marked "omitempty" are considered required.
|
// fields that are not marked "omitempty" are considered required.
|
||||||
func NewPrompt[TReq any](name, description string, handler func(context.Context, *ServerConnection, TReq) (*GetPromptResult, error), opts ...PromptOption) *ServerPrompt {
|
func NewPrompt[TReq any](name, description string, handler func(context.Context, *ServerSession, TReq) (*GetPromptResult, error), opts ...PromptOption) *ServerPrompt {
|
||||||
schema, err := jsonschema.For[TReq]()
|
schema, err := jsonschema.For[TReq]()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
@ -60,7 +60,7 @@ func NewPrompt[TReq any](name, description string, handler func(context.Context,
|
||||||
Required: required[name],
|
Required: required[name],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
prompt.Handler = func(ctx context.Context, cc *ServerConnection, args map[string]string) (*GetPromptResult, error) {
|
prompt.Handler = func(ctx context.Context, cc *ServerSession, args map[string]string) (*GetPromptResult, error) {
|
||||||
// For simplicity, just marshal and unmarshal the arguments.
|
// For simplicity, just marshal and unmarshal the arguments.
|
||||||
// This could be avoided in the future.
|
// This could be avoided in the future.
|
||||||
rawArgs, err := json.Marshal(args)
|
rawArgs, err := json.Marshal(args)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// testPromptHandler is used for type inference in TestNewPrompt.
|
// testPromptHandler is used for type inference in TestNewPrompt.
|
||||||
func testPromptHandler[T any](context.Context, *mcp.ServerConnection, T) (*mcp.GetPromptResult, error) {
|
func testPromptHandler[T any](context.Context, *mcp.ServerSession, T) (*mcp.GetPromptResult, error) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ type Server struct {
|
||||||
prompts *featureSet[*ServerPrompt]
|
prompts *featureSet[*ServerPrompt]
|
||||||
tools *featureSet[*ServerTool]
|
tools *featureSet[*ServerTool]
|
||||||
resources *featureSet[*ServerResource]
|
resources *featureSet[*ServerResource]
|
||||||
conns []*ServerConnection
|
conns []*ServerSession
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerOptions is used to configure behavior of the server.
|
// ServerOptions is used to configure behavior of the server.
|
||||||
|
|
@ -121,7 +121,7 @@ const codeResourceNotFound = -31002
|
||||||
|
|
||||||
// A ResourceHandler is a function that reads a resource.
|
// A ResourceHandler is a function that reads a resource.
|
||||||
// If it cannot find the resource, it should return the result of calling [ResourceNotFoundError].
|
// If it cannot find the resource, it should return the result of calling [ResourceNotFoundError].
|
||||||
type ResourceHandler func(context.Context, *ServerConnection, *ReadResourceParams) (*ReadResourceResult, error)
|
type ResourceHandler func(context.Context, *ServerSession, *ReadResourceParams) (*ReadResourceResult, error)
|
||||||
|
|
||||||
// A ServerResource associates a Resource with its handler.
|
// A ServerResource associates a Resource with its handler.
|
||||||
type ServerResource struct {
|
type ServerResource struct {
|
||||||
|
|
@ -157,16 +157,15 @@ func (s *Server) RemoveResources(uris ...string) {
|
||||||
s.resources.remove(uris...)
|
s.resources.remove(uris...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clients returns an iterator that yields the current set of client
|
// Sessions returns an iterator that yields the current set of server sessions.
|
||||||
// connections.
|
func (s *Server) Sessions() iter.Seq[*ServerSession] {
|
||||||
func (s *Server) Clients() iter.Seq[*ServerConnection] {
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
clients := slices.Clone(s.conns)
|
clients := slices.Clone(s.conns)
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
return slices.Values(clients)
|
return slices.Values(clients)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) listPrompts(_ context.Context, _ *ServerConnection, params *ListPromptsParams) (*ListPromptsResult, error) {
|
func (s *Server) listPrompts(_ context.Context, _ *ServerSession, params *ListPromptsParams) (*ListPromptsResult, error) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
res := new(ListPromptsResult)
|
res := new(ListPromptsResult)
|
||||||
|
|
@ -176,7 +175,7 @@ func (s *Server) listPrompts(_ context.Context, _ *ServerConnection, params *Lis
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getPrompt(ctx context.Context, cc *ServerConnection, params *GetPromptParams) (*GetPromptResult, error) {
|
func (s *Server) getPrompt(ctx context.Context, cc *ServerSession, params *GetPromptParams) (*GetPromptResult, error) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
prompt, ok := s.prompts.get(params.Name)
|
prompt, ok := s.prompts.get(params.Name)
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|
@ -187,7 +186,7 @@ func (s *Server) getPrompt(ctx context.Context, cc *ServerConnection, params *Ge
|
||||||
return prompt.Handler(ctx, cc, params.Arguments)
|
return prompt.Handler(ctx, cc, params.Arguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) listTools(_ context.Context, _ *ServerConnection, params *ListToolsParams) (*ListToolsResult, error) {
|
func (s *Server) listTools(_ context.Context, _ *ServerSession, params *ListToolsParams) (*ListToolsResult, error) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
res := new(ListToolsResult)
|
res := new(ListToolsResult)
|
||||||
|
|
@ -197,7 +196,7 @@ func (s *Server) listTools(_ context.Context, _ *ServerConnection, params *ListT
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) callTool(ctx context.Context, cc *ServerConnection, params *CallToolParams) (*CallToolResult, error) {
|
func (s *Server) callTool(ctx context.Context, cc *ServerSession, params *CallToolParams) (*CallToolResult, error) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
tool, ok := s.tools.get(params.Name)
|
tool, ok := s.tools.get(params.Name)
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|
@ -207,7 +206,7 @@ func (s *Server) callTool(ctx context.Context, cc *ServerConnection, params *Cal
|
||||||
return tool.Handler(ctx, cc, params)
|
return tool.Handler(ctx, cc, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) listResources(_ context.Context, _ *ServerConnection, params *ListResourcesParams) (*ListResourcesResult, error) {
|
func (s *Server) listResources(_ context.Context, _ *ServerSession, params *ListResourcesParams) (*ListResourcesResult, error) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
res := new(ListResourcesResult)
|
res := new(ListResourcesResult)
|
||||||
|
|
@ -217,7 +216,7 @@ func (s *Server) listResources(_ context.Context, _ *ServerConnection, params *L
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) readResource(ctx context.Context, ss *ServerConnection, params *ReadResourceParams) (*ReadResourceResult, error) {
|
func (s *Server) readResource(ctx context.Context, ss *ServerSession, params *ReadResourceParams) (*ReadResourceResult, error) {
|
||||||
uri := params.URI
|
uri := params.URI
|
||||||
// Look up the resource URI in the list we have.
|
// Look up the resource URI in the list we have.
|
||||||
// This is a security check as well as an information lookup.
|
// This is a security check as well as an information lookup.
|
||||||
|
|
@ -250,29 +249,29 @@ func (s *Server) readResource(ctx context.Context, ss *ServerConnection, params
|
||||||
//
|
//
|
||||||
// Run blocks until the client terminates the connection.
|
// Run blocks until the client terminates the connection.
|
||||||
func (s *Server) Run(ctx context.Context, t Transport, opts *ConnectionOptions) error {
|
func (s *Server) Run(ctx context.Context, t Transport, opts *ConnectionOptions) error {
|
||||||
cc, err := s.Connect(ctx, t, opts)
|
ss, err := s.Connect(ctx, t, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return cc.Wait()
|
return ss.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
// bind implements the binder[*ServerConnection] interface, so that Servers can
|
// bind implements the binder[*ServerSession] interface, so that Servers can
|
||||||
// be connected using [connect].
|
// be connected using [connect].
|
||||||
func (s *Server) bind(conn *jsonrpc2.Connection) *ServerConnection {
|
func (s *Server) bind(conn *jsonrpc2.Connection) *ServerSession {
|
||||||
cc := &ServerConnection{conn: conn, server: s}
|
cc := &ServerSession{conn: conn, server: s}
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.conns = append(s.conns, cc)
|
s.conns = append(s.conns, cc)
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
return cc
|
return cc
|
||||||
}
|
}
|
||||||
|
|
||||||
// disconnect implements the binder[*ServerConnection] interface, so that
|
// disconnect implements the binder[*ServerSession] interface, so that
|
||||||
// Servers can be connected using [connect].
|
// Servers can be connected using [connect].
|
||||||
func (s *Server) disconnect(cc *ServerConnection) {
|
func (s *Server) disconnect(cc *ServerSession) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
s.conns = slices.DeleteFunc(s.conns, func(cc2 *ServerConnection) bool {
|
s.conns = slices.DeleteFunc(s.conns, func(cc2 *ServerSession) bool {
|
||||||
return cc2 == cc
|
return cc2 == cc
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -283,17 +282,17 @@ func (s *Server) disconnect(cc *ServerConnection) {
|
||||||
// It returns a connection object that may be used to terminate the connection
|
// It returns a connection object that may be used to terminate the connection
|
||||||
// (with [Connection.Close]), or await client termination (with
|
// (with [Connection.Close]), or await client termination (with
|
||||||
// [Connection.Wait]).
|
// [Connection.Wait]).
|
||||||
func (s *Server) Connect(ctx context.Context, t Transport, opts *ConnectionOptions) (*ServerConnection, error) {
|
func (s *Server) Connect(ctx context.Context, t Transport, opts *ConnectionOptions) (*ServerSession, error) {
|
||||||
return connect(ctx, t, opts, s)
|
return connect(ctx, t, opts, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A ServerConnection is a connection from a single MCP client. Its methods can
|
// A ServerSession is a logical connection from a single MCP client. Its
|
||||||
// be used to send requests or notifications to the client. Create a connection
|
// methods can be used to send requests or notifications to the client. Create
|
||||||
// by calling [Server.Connect].
|
// a session by calling [Server.Connect].
|
||||||
//
|
//
|
||||||
// Call [ServerConnection.Close] to close the connection, or await client
|
// Call [ServerSession.Close] to close the connection, or await client
|
||||||
// termination with [ServerConnection.Wait].
|
// termination with [ServerSession.Wait].
|
||||||
type ServerConnection struct {
|
type ServerSession struct {
|
||||||
server *Server
|
server *Server
|
||||||
conn *jsonrpc2.Connection
|
conn *jsonrpc2.Connection
|
||||||
|
|
||||||
|
|
@ -303,15 +302,15 @@ type ServerConnection struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ping makes an MCP "ping" request to the client.
|
// Ping makes an MCP "ping" request to the client.
|
||||||
func (cc *ServerConnection) Ping(ctx context.Context, _ *PingParams) error {
|
func (cc *ServerSession) Ping(ctx context.Context, _ *PingParams) error {
|
||||||
return call(ctx, cc.conn, "ping", nil, nil)
|
return call(ctx, cc.conn, "ping", nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc *ServerConnection) ListRoots(ctx context.Context, params *ListRootsParams) (*ListRootsResult, error) {
|
func (cc *ServerSession) ListRoots(ctx context.Context, params *ListRootsParams) (*ListRootsResult, error) {
|
||||||
return standardCall[ListRootsResult](ctx, cc.conn, "roots/list", params)
|
return standardCall[ListRootsResult](ctx, cc.conn, "roots/list", params)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc *ServerConnection) handle(ctx context.Context, req *jsonrpc2.Request) (any, error) {
|
func (cc *ServerSession) handle(ctx context.Context, req *jsonrpc2.Request) (any, error) {
|
||||||
cc.mu.Lock()
|
cc.mu.Lock()
|
||||||
initialized := cc.initialized
|
initialized := cc.initialized
|
||||||
cc.mu.Unlock()
|
cc.mu.Unlock()
|
||||||
|
|
@ -362,7 +361,7 @@ func (cc *ServerConnection) handle(ctx context.Context, req *jsonrpc2.Request) (
|
||||||
return nil, jsonrpc2.ErrNotHandled
|
return nil, jsonrpc2.ErrNotHandled
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc *ServerConnection) initialize(ctx context.Context, _ *ServerConnection, params *initializeParams) (*initializeResult, error) {
|
func (cc *ServerSession) initialize(ctx context.Context, _ *ServerSession, params *initializeParams) (*initializeResult, error) {
|
||||||
cc.mu.Lock()
|
cc.mu.Lock()
|
||||||
cc.initializeParams = params
|
cc.initializeParams = params
|
||||||
cc.mu.Unlock()
|
cc.mu.Unlock()
|
||||||
|
|
@ -400,12 +399,12 @@ func (cc *ServerConnection) initialize(ctx context.Context, _ *ServerConnection,
|
||||||
// Close performs a graceful shutdown of the connection, preventing new
|
// Close performs a graceful shutdown of the connection, preventing new
|
||||||
// requests from being handled, and waiting for ongoing requests to return.
|
// requests from being handled, and waiting for ongoing requests to return.
|
||||||
// Close then terminates the connection.
|
// Close then terminates the connection.
|
||||||
func (cc *ServerConnection) Close() error {
|
func (cc *ServerSession) Close() error {
|
||||||
return cc.conn.Close()
|
return cc.conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait waits for the connection to be closed by the client.
|
// Wait waits for the connection to be closed by the client.
|
||||||
func (cc *ServerConnection) Wait() error {
|
func (cc *ServerSession) Wait() error {
|
||||||
return cc.conn.Wait()
|
return cc.conn.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -413,7 +412,7 @@ func (cc *ServerConnection) Wait() error {
|
||||||
//
|
//
|
||||||
// Importantly, it returns nil if the handler returned an error, which is a
|
// Importantly, it returns nil if the handler returned an error, which is a
|
||||||
// requirement of the jsonrpc2 package.
|
// requirement of the jsonrpc2 package.
|
||||||
func dispatch[TParams, TResult any](ctx context.Context, conn *ServerConnection, req *jsonrpc2.Request, f func(context.Context, *ServerConnection, TParams) (TResult, error)) (any, error) {
|
func dispatch[TParams, TResult any](ctx context.Context, conn *ServerSession, req *jsonrpc2.Request, f func(context.Context, *ServerSession, TParams) (TResult, error)) (any, error) {
|
||||||
var params TParams
|
var params TParams
|
||||||
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ type SayHiParams struct {
|
||||||
Name string `json:"name" mcp:"the name to say hi to"`
|
Name string `json:"name" mcp:"the name to say hi to"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SayHi(ctx context.Context, cc *mcp.ServerConnection, params *SayHiParams) ([]*mcp.Content, error) {
|
func SayHi(ctx context.Context, cc *mcp.ServerSession, params *SayHiParams) ([]*mcp.Content, error) {
|
||||||
return []*mcp.Content{
|
return []*mcp.Content{
|
||||||
mcp.NewTextContent("Hi " + params.Name),
|
mcp.NewTextContent("Hi " + params.Name),
|
||||||
}, nil
|
}, nil
|
||||||
|
|
@ -29,7 +29,7 @@ func ExampleServer() {
|
||||||
server := mcp.NewServer("greeter", "v0.0.1", nil)
|
server := mcp.NewServer("greeter", "v0.0.1", nil)
|
||||||
server.AddTools(mcp.NewTool("greet", "say hi", SayHi))
|
server.AddTools(mcp.NewTool("greet", "say hi", SayHi))
|
||||||
|
|
||||||
clientConnection, err := server.Connect(ctx, serverTransport, nil)
|
serverSession, err := server.Connect(ctx, serverTransport, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -46,7 +46,7 @@ func ExampleServer() {
|
||||||
fmt.Println(res.Content[0].Text)
|
fmt.Println(res.Content[0].Text)
|
||||||
|
|
||||||
client.Close()
|
client.Close()
|
||||||
clientConnection.Wait()
|
serverSession.Wait()
|
||||||
|
|
||||||
// Output: Hi user
|
// Output: Hi user
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ func writeEvent(w io.Writer, evt event) (int, error) {
|
||||||
// https://modelcontextprotocol.io/specification/2024-11-05/basic/transports
|
// https://modelcontextprotocol.io/specification/2024-11-05/basic/transports
|
||||||
type SSEHandler struct {
|
type SSEHandler struct {
|
||||||
getServer func(request *http.Request) *Server
|
getServer func(request *http.Request) *Server
|
||||||
onConnection func(*ServerConnection) // for testing; must not block
|
onConnection func(*ServerSession) // for testing; must not block
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
sessions map[string]*SSEServerTransport
|
sessions map[string]*SSEServerTransport
|
||||||
|
|
@ -229,15 +229,15 @@ func (h *SSEHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
// TODO(hxjiang): getServer returns nil will panic.
|
// TODO(hxjiang): getServer returns nil will panic.
|
||||||
server := h.getServer(req)
|
server := h.getServer(req)
|
||||||
cc, err := server.Connect(req.Context(), transport, nil)
|
ss, err := server.Connect(req.Context(), transport, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "connection failed", http.StatusInternalServerError)
|
http.Error(w, "connection failed", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if h.onConnection != nil {
|
if h.onConnection != nil {
|
||||||
h.onConnection(cc)
|
h.onConnection(ss)
|
||||||
}
|
}
|
||||||
defer cc.Close() // close the transport when the GET exits
|
defer ss.Close() // close the transport when the GET exits
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-req.Context().Done():
|
case <-req.Context().Done():
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ type AddParams struct {
|
||||||
X, Y int
|
X, Y int
|
||||||
}
|
}
|
||||||
|
|
||||||
func Add(ctx context.Context, cc *mcp.ServerConnection, params *AddParams) ([]*mcp.Content, error) {
|
func Add(ctx context.Context, cc *mcp.ServerSession, params *AddParams) ([]*mcp.Content, error) {
|
||||||
return []*mcp.Content{
|
return []*mcp.Content{
|
||||||
mcp.NewTextContent(fmt.Sprintf("%d", params.X+params.Y)),
|
mcp.NewTextContent(fmt.Sprintf("%d", params.X+params.Y)),
|
||||||
}, nil
|
}, nil
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ func TestSSEServer(t *testing.T) {
|
||||||
|
|
||||||
sseHandler := NewSSEHandler(func(*http.Request) *Server { return server })
|
sseHandler := NewSSEHandler(func(*http.Request) *Server { return server })
|
||||||
|
|
||||||
conns := make(chan *ServerConnection, 1)
|
conns := make(chan *ServerSession, 1)
|
||||||
sseHandler.onConnection = func(cc *ServerConnection) {
|
sseHandler.onConnection = func(cc *ServerSession) {
|
||||||
select {
|
select {
|
||||||
case conns <- cc:
|
case conns <- cc:
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// A ToolHandler handles a call to tools/call.
|
// A ToolHandler handles a call to tools/call.
|
||||||
type ToolHandler func(context.Context, *ServerConnection, *CallToolParams) (*CallToolResult, error)
|
type ToolHandler func(context.Context, *ServerSession, *CallToolParams) (*CallToolResult, error)
|
||||||
|
|
||||||
// A Tool is a tool definition that is bound to a tool handler.
|
// A Tool is a tool definition that is bound to a tool handler.
|
||||||
type ServerTool struct {
|
type ServerTool struct {
|
||||||
|
|
@ -34,12 +34,12 @@ type ServerTool struct {
|
||||||
//
|
//
|
||||||
// TODO: just have the handler return a CallToolResult: returning []Content is
|
// TODO: just have the handler return a CallToolResult: returning []Content is
|
||||||
// going to be inconsistent with other server features.
|
// going to be inconsistent with other server features.
|
||||||
func NewTool[TReq any](name, description string, handler func(context.Context, *ServerConnection, TReq) ([]*Content, error), opts ...ToolOption) *ServerTool {
|
func NewTool[TReq any](name, description string, handler func(context.Context, *ServerSession, TReq) ([]*Content, error), opts ...ToolOption) *ServerTool {
|
||||||
schema, err := jsonschema.For[TReq]()
|
schema, err := jsonschema.For[TReq]()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
wrapped := func(ctx context.Context, cc *ServerConnection, params *CallToolParams) (*CallToolResult, error) {
|
wrapped := func(ctx context.Context, cc *ServerSession, params *CallToolParams) (*CallToolResult, error) {
|
||||||
var v TReq
|
var v TReq
|
||||||
if err := unmarshalSchema(params.Arguments, schema, &v); err != nil {
|
if err := unmarshalSchema(params.Arguments, schema, &v); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// testToolHandler is used for type inference in TestNewTool.
|
// testToolHandler is used for type inference in TestNewTool.
|
||||||
func testToolHandler[T any](context.Context, *mcp.ServerConnection, T) ([]*mcp.Content, error) {
|
func testToolHandler[T any](context.Context, *mcp.ServerSession, T) ([]*mcp.Content, error) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue