Skip to content

Commit 0f6a53f

Browse files
jbafindleyr
authored andcommitted
mcp: remove ServerConnection
Remove ServerConnection. - It's not in typescript or mcpgo APIs. - Amortizing a client over multiple servers doesn't have any value. Change-Id: I725b42569733d6836864d96b16898a10ae91e570 Reviewed-on: https://go-review.googlesource.com/c/tools/+/669575 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent ca54d59 commit 0f6a53f

File tree

8 files changed

+83
-134
lines changed

8 files changed

+83
-134
lines changed

internal/mcp/client.go

+40-75
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,27 @@
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
56
package mcp
67

78
import (
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.
2420
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
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.
5541
type 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

internal/mcp/cmd_test.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,10 @@ func TestCmdTransport(t *testing.T) {
5050
cmd.Env = append(os.Environ(), runAsServer+"=true")
5151

5252
client := mcp.NewClient("client", "v0.0.1", nil)
53-
serverConn, err := client.Connect(ctx, mcp.NewCommandTransport(cmd), nil)
54-
if err != nil {
53+
if err := client.Connect(ctx, mcp.NewCommandTransport(cmd), nil); err != nil {
5554
log.Fatal(err)
5655
}
57-
got, err := serverConn.CallTool(ctx, "greet", map[string]any{"name": "user"})
56+
got, err := client.CallTool(ctx, "greet", map[string]any{"name": "user"})
5857
if err != nil {
5958
log.Fatal(err)
6059
}
@@ -64,7 +63,7 @@ func TestCmdTransport(t *testing.T) {
6463
if diff := cmp.Diff(want, got); diff != "" {
6564
t.Errorf("greet returned unexpected content (-want +got):\n%s", diff)
6665
}
67-
if err := serverConn.Close(); err != nil {
66+
if err := client.Close(); err != nil {
6867
t.Fatalf("closing server: %v", err)
6968
}
7069
}

internal/mcp/mcp.go

+7-8
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99
// To get started, create either a [Client] or [Server], and connect it to a
1010
// peer using a [Transport]. The diagram below illustrates how this works:
1111
//
12-
// Client Server
13-
// ⇅ (jsonrpc2) ⇅
14-
// ServerConnection ⇄ Client Transport ⇄ Server Transport ⇄ ClientConnection
12+
// Client Server
13+
// ⇅ (jsonrpc2) ⇅
14+
// Client Transport ⇄ Server Transport ⇄ ClientConnection
1515
//
1616
// A [Client] is an MCP client, which can be configured with various client
17-
// capabilities. Clients may be connected to one or more [Server] instances
18-
// using the [Client.Connect] method, which creates a [ServerConnection].
17+
// capabilities. Clients may be connected to a [Server] instance
18+
// using the [Client.Connect] method.
1919
//
2020
// Similarly, a [Server] is an MCP server, which can be configured with various
2121
// server capabilities. Servers may be connected to one or more [Client]
@@ -44,12 +44,11 @@
4444
// client := mcp.NewClient("mcp-client", "v1.0.0", nil)
4545
// // Connect to a server over stdin/stdout
4646
// transport := mcp.NewCommandTransport(exec.Command("myserver"))
47-
// serverConn, err := client.Connect(ctx, transport, nil)
48-
// if err != nil {
47+
// if err := client.Connect(ctx, transport, nil); err != nil {
4948
// log.Fatal(err)
5049
// }
5150
// // Call a tool on the server.
52-
// content, err := serverConn.CallTool(ctx, "greet", map[string]any{"name": "you"})
51+
// content, err := client.CallTool(ctx, "greet", map[string]any{"name": "you"})
5352
//
5453
// Here is an example of the corresponding server, connected over stdin/stdout:
5554
//

0 commit comments

Comments
 (0)