2
2
// Use of this source code is governed by a BSD-style
3
3
// license that can be found in the LICENSE file.
4
4
5
+ // TODO: consider passing Transport to NewClient and merging {Connection,Client}Options
5
6
package mcp
6
7
7
8
import (
8
9
"context"
9
10
"encoding/json"
10
11
"fmt"
11
- "iter"
12
- "slices"
13
12
"sync"
14
13
15
14
jsonrpc2 "golang.org/x/tools/internal/jsonrpc2_v2"
16
15
"golang.org/x/tools/internal/mcp/internal/protocol"
17
16
)
18
17
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
20
19
// 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.
24
20
type 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
30
26
}
31
27
32
28
// NewClient creates a new Client.
@@ -41,99 +37,68 @@ func NewClient(name, version string, opts *ClientOptions) *Client {
41
37
}
42
38
}
43
39
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.
55
41
type ClientOptions struct {}
56
42
57
43
// bind implements the binder[*ServerConnection] interface, so that Clients can
58
44
// 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 {
64
46
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
68
50
}
69
51
70
52
// disconnect implements the binder[*ServerConnection] interface, so that
71
53
// 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.
78
57
}
79
58
80
59
// Connect connects the MCP client over the given transport and initializes an
81
60
// MCP session.
82
61
//
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
- //
87
62
// Typically, it is the responsibility of the client to close the connection
88
63
// when it is no longer needed. However, if the connection is closed by the
89
64
// server, calls or notifications will return an error wrapping
90
65
// [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 ) {
92
67
defer func () {
93
- if sc != nil && err != nil {
94
- _ = sc .Close ()
68
+ if err != nil {
69
+ _ = c .Close ()
95
70
}
96
71
}()
97
- sc , err = connect (ctx , t , opts , c )
72
+ _ , err = connect (ctx , t , opts , c )
98
73
if err != nil {
99
- return nil , err
74
+ return err
100
75
}
101
76
params := & protocol.InitializeParams {
102
77
ClientInfo : protocol.Implementation {Name : c .name , Version : c .version },
103
78
}
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
106
81
}
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
109
84
}
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
121
86
}
122
87
123
88
// Close performs a graceful close of the connection, preventing new requests
124
89
// from being handled, and waiting for ongoing requests to return. Close then
125
90
// 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 ()
128
93
}
129
94
130
95
// Wait waits for the connection to be closed by the server.
131
96
// 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 ()
134
99
}
135
100
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 ) {
137
102
// No need to check that the connection is initialized, since we initialize
138
103
// it in Connect.
139
104
switch req .Method {
@@ -145,44 +110,44 @@ func (sc *ServerConnection) handle(ctx context.Context, req *jsonrpc2.Request) (
145
110
}
146
111
147
112
// 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 )
150
115
}
151
116
152
117
// 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 ) {
154
119
var (
155
120
params = & protocol.ListPromptsParams {}
156
121
result protocol.ListPromptsResult
157
122
)
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 {
159
124
return nil , err
160
125
}
161
126
return result .Prompts , nil
162
127
}
163
128
164
129
// 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 ) {
166
131
var (
167
132
params = & protocol.GetPromptParams {
168
133
Name : name ,
169
134
Arguments : args ,
170
135
}
171
136
result = & protocol.GetPromptResult {}
172
137
)
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 {
174
139
return nil , err
175
140
}
176
141
return result , nil
177
142
}
178
143
179
144
// 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 ) {
181
146
var (
182
147
params = & protocol.ListToolsParams {}
183
148
result protocol.ListToolsResult
184
149
)
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 {
186
151
return nil , err
187
152
}
188
153
return result .Tools , nil
@@ -193,7 +158,7 @@ func (sc *ServerConnection) ListTools(ctx context.Context) ([]protocol.Tool, err
193
158
// TODO(jba): make the following true:
194
159
// If the provided arguments do not conform to the schema for the given tool,
195
160
// 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 ) {
197
162
defer func () {
198
163
if err != nil {
199
164
err = fmt .Errorf ("calling tool %q: %w" , name , err )
@@ -214,7 +179,7 @@ func (sc *ServerConnection) CallTool(ctx context.Context, name string, args map[
214
179
}
215
180
result protocol.CallToolResult
216
181
)
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 {
218
183
return nil , err
219
184
}
220
185
return & result , nil
0 commit comments