22// Use of this source code is governed by a BSD-style
33// license that can be found in the LICENSE file.
44
5+ // TODO: consider passing Transport to NewClient and merging {Connection,Client}Options
56package mcp
67
78import (
89 "context"
910 "encoding/json"
1011 "fmt"
11- "iter"
12- "slices"
1312 "sync"
1413
1514 jsonrpc2 "golang.org/x/tools/internal/jsonrpc2_v2"
1615 "golang.org/x/tools/internal/mcp/internal/protocol"
1716)
1817
19- // A Client is an MCP client, which may be connected to one or more MCP servers
18+ // A Client is an MCP client, which may be connected to an MCP server
2019// using the [Client.Connect] method.
21- //
22- // TODO(rfindley): revisit the many-to-one relationship of clients and servers.
23- // It is a bit odd.
2420type Client struct {
25- name string
26- version string
27-
28- mu sync. Mutex
29- servers [] * ServerConnection
21+ name string
22+ version string
23+ mu sync. Mutex
24+ conn * jsonrpc2. Connection
25+ initializeResult * protocol. InitializeResult
3026}
3127
3228// NewClient creates a new Client.
@@ -41,99 +37,68 @@ func NewClient(name, version string, opts *ClientOptions) *Client {
4137 }
4238}
4339
44- // Servers returns an iterator that yields the current set of server
45- // connections.
46- func (c * Client ) Servers () iter.Seq [* ServerConnection ] {
47- c .mu .Lock ()
48- clients := slices .Clone (c .servers )
49- c .mu .Unlock ()
50- return slices .Values (clients )
51- }
52-
53- // ClientOptions configures the behavior of the client, and apply to every
54- // client-server connection created using [Client.Connect].
40+ // ClientOptions configures the behavior of the client.
5541type ClientOptions struct {}
5642
5743// bind implements the binder[*ServerConnection] interface, so that Clients can
5844// be connected using [connect].
59- func (c * Client ) bind (conn * jsonrpc2.Connection ) * ServerConnection {
60- sc := & ServerConnection {
61- conn : conn ,
62- client : c ,
63- }
45+ func (c * Client ) bind (conn * jsonrpc2.Connection ) * Client {
6446 c .mu .Lock ()
65- c . servers = append ( c . servers , sc )
66- c .mu . Unlock ()
67- return sc
47+ defer c . mu . Unlock ( )
48+ c .conn = conn
49+ return c
6850}
6951
7052// disconnect implements the binder[*ServerConnection] interface, so that
7153// Clients can be connected using [connect].
72- func (c * Client ) disconnect (sc * ServerConnection ) {
73- c .mu .Lock ()
74- defer c .mu .Unlock ()
75- c .servers = slices .DeleteFunc (c .servers , func (sc2 * ServerConnection ) bool {
76- return sc2 == sc
77- })
54+ func (c * Client ) disconnect (* Client ) {
55+ // Do nothing. In particular, do not set conn to nil: it needs to exist so it can
56+ // return an error.
7857}
7958
8059// Connect connects the MCP client over the given transport and initializes an
8160// MCP session.
8261//
83- // It returns an initialized [ServerConnection] object that may be used to
84- // query the MCP server, terminate the connection (with [Connection.Close]), or
85- // await server termination (with [Connection.Wait]).
86- //
8762// Typically, it is the responsibility of the client to close the connection
8863// when it is no longer needed. However, if the connection is closed by the
8964// server, calls or notifications will return an error wrapping
9065// [ErrConnectionClosed].
91- func (c * Client ) Connect (ctx context.Context , t Transport , opts * ConnectionOptions ) (sc * ServerConnection , err error ) {
66+ func (c * Client ) Connect (ctx context.Context , t Transport , opts * ConnectionOptions ) (err error ) {
9267 defer func () {
93- if sc != nil && err != nil {
94- _ = sc .Close ()
68+ if err != nil {
69+ _ = c .Close ()
9570 }
9671 }()
97- sc , err = connect (ctx , t , opts , c )
72+ _ , err = connect (ctx , t , opts , c )
9873 if err != nil {
99- return nil , err
74+ return err
10075 }
10176 params := & protocol.InitializeParams {
10277 ClientInfo : protocol.Implementation {Name : c .name , Version : c .version },
10378 }
104- if err := call (ctx , sc .conn , "initialize" , params , & sc .initializeResult ); err != nil {
105- return nil , err
79+ if err := call (ctx , c .conn , "initialize" , params , & c .initializeResult ); err != nil {
80+ return err
10681 }
107- if err := sc .conn .Notify (ctx , "notifications/initialized" , & protocol.InitializedParams {}); err != nil {
108- return nil , err
82+ if err := c .conn .Notify (ctx , "notifications/initialized" , & protocol.InitializedParams {}); err != nil {
83+ return err
10984 }
110- return sc , nil
111- }
112-
113- // A ServerConnection is a connection with an MCP server.
114- //
115- // It handles messages from the client, and can be used to send messages to the
116- // client. Create a connection by calling [Server.Connect].
117- type ServerConnection struct {
118- conn * jsonrpc2.Connection
119- client * Client
120- initializeResult * protocol.InitializeResult
85+ return nil
12186}
12287
12388// Close performs a graceful close of the connection, preventing new requests
12489// from being handled, and waiting for ongoing requests to return. Close then
12590// terminates the connection.
126- func (cc * ServerConnection ) Close () error {
127- return cc .conn .Close ()
91+ func (c * Client ) Close () error {
92+ return c .conn .Close ()
12893}
12994
13095// Wait waits for the connection to be closed by the server.
13196// Generally, clients should be responsible for closing the connection.
132- func (cc * ServerConnection ) Wait () error {
133- return cc .conn .Wait ()
97+ func (c * Client ) Wait () error {
98+ return c .conn .Wait ()
13499}
135100
136- func (sc * ServerConnection ) handle (ctx context.Context , req * jsonrpc2.Request ) (any , error ) {
101+ func (* Client ) handle (ctx context.Context , req * jsonrpc2.Request ) (any , error ) {
137102 // No need to check that the connection is initialized, since we initialize
138103 // it in Connect.
139104 switch req .Method {
@@ -145,44 +110,44 @@ func (sc *ServerConnection) handle(ctx context.Context, req *jsonrpc2.Request) (
145110}
146111
147112// Ping makes an MCP "ping" request to the server.
148- func (sc * ServerConnection ) Ping (ctx context.Context ) error {
149- return call (ctx , sc .conn , "ping" , nil , nil )
113+ func (c * Client ) Ping (ctx context.Context ) error {
114+ return call (ctx , c .conn , "ping" , nil , nil )
150115}
151116
152117// ListPrompts lists prompts that are currently available on the server.
153- func (sc * ServerConnection ) ListPrompts (ctx context.Context ) ([]protocol.Prompt , error ) {
118+ func (c * Client ) ListPrompts (ctx context.Context ) ([]protocol.Prompt , error ) {
154119 var (
155120 params = & protocol.ListPromptsParams {}
156121 result protocol.ListPromptsResult
157122 )
158- if err := call (ctx , sc .conn , "prompts/list" , params , & result ); err != nil {
123+ if err := call (ctx , c .conn , "prompts/list" , params , & result ); err != nil {
159124 return nil , err
160125 }
161126 return result .Prompts , nil
162127}
163128
164129// GetPrompt gets a prompt from the server.
165- func (sc * ServerConnection ) GetPrompt (ctx context.Context , name string , args map [string ]string ) (* protocol.GetPromptResult , error ) {
130+ func (c * Client ) GetPrompt (ctx context.Context , name string , args map [string ]string ) (* protocol.GetPromptResult , error ) {
166131 var (
167132 params = & protocol.GetPromptParams {
168133 Name : name ,
169134 Arguments : args ,
170135 }
171136 result = & protocol.GetPromptResult {}
172137 )
173- if err := call (ctx , sc .conn , "prompts/get" , params , result ); err != nil {
138+ if err := call (ctx , c .conn , "prompts/get" , params , result ); err != nil {
174139 return nil , err
175140 }
176141 return result , nil
177142}
178143
179144// ListTools lists tools that are currently available on the server.
180- func (sc * ServerConnection ) ListTools (ctx context.Context ) ([]protocol.Tool , error ) {
145+ func (c * Client ) ListTools (ctx context.Context ) ([]protocol.Tool , error ) {
181146 var (
182147 params = & protocol.ListToolsParams {}
183148 result protocol.ListToolsResult
184149 )
185- if err := call (ctx , sc .conn , "tools/list" , params , & result ); err != nil {
150+ if err := call (ctx , c .conn , "tools/list" , params , & result ); err != nil {
186151 return nil , err
187152 }
188153 return result .Tools , nil
@@ -193,7 +158,7 @@ func (sc *ServerConnection) ListTools(ctx context.Context) ([]protocol.Tool, err
193158// TODO(jba): make the following true:
194159// If the provided arguments do not conform to the schema for the given tool,
195160// the call fails.
196- func (sc * ServerConnection ) CallTool (ctx context.Context , name string , args map [string ]any ) (_ * protocol.CallToolResult , err error ) {
161+ func (c * Client ) CallTool (ctx context.Context , name string , args map [string ]any ) (_ * protocol.CallToolResult , err error ) {
197162 defer func () {
198163 if err != nil {
199164 err = fmt .Errorf ("calling tool %q: %w" , name , err )
@@ -214,7 +179,7 @@ func (sc *ServerConnection) CallTool(ctx context.Context, name string, args map[
214179 }
215180 result protocol.CallToolResult
216181 )
217- if err := call (ctx , sc .conn , "tools/call" , params , & result ); err != nil {
182+ if err := call (ctx , c .conn , "tools/call" , params , & result ); err != nil {
218183 return nil , err
219184 }
220185 return & result , nil
0 commit comments