internal/mcp: rename LocalTransport->InMemoryTransport, and document

Update the design to mention our in-memory transport, since it is
generally useful, and update the implementation to use distinct types
for InMemoryTransport and StdIOTransport (the fact that they are both
implemented as an io.ReadWriteCloser is an implementation detail).

Change-Id: I8228ccf2092a56a730c113c4db105fb492cce112
Reviewed-on: https://go-review.googlesource.com/c/tools/+/672715
Reviewed-by: Sam Thanawalla <samthanawalla@google.com>
Commit-Queue: Robert Findley <rfindley@google.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
TryBot-Bypass: Robert Findley <rfindley@google.com>
This commit is contained in:
Rob Findley 2025-05-14 14:59:59 +00:00 • committed by Robert Findley
commit dbf57af24b
4 changed files with 33 additions and 13 deletions

View file

@ -283,6 +283,14 @@ func NewStreamableClientTransport(url string) *StreamableClientTransport {
func (*StreamableClientTransport) Connect(context.Context) (Stream, error) func (*StreamableClientTransport) Connect(context.Context) (Stream, error)
``` ```
Finally, we also provide an in-memory transport, for scenarios such as testing,
where the MCP client and server are in the same process.
```go
type InMemoryTransport struct { /* ... */ }
func NewInMemoryTransport() (*InMemoryTransport, *InMemoryTransport)
```
**Differences from mcp-go**: The Go team has a battle-tested JSON-RPC **Differences from mcp-go**: The Go team has a battle-tested JSON-RPC
implementation that we use for gopls, our Go LSP server. We are using the new implementation that we use for gopls, our Go LSP server. We are using the new
version of this library as part of our MCP SDK. It handles all JSON-RPC 2.0 version of this library as part of our MCP SDK. It handles all JSON-RPC 2.0

View file

@ -33,7 +33,7 @@ func sayHi(ctx context.Context, cc *ServerSession, v hiParams) ([]*Content, erro
func TestEndToEnd(t *testing.T) { func TestEndToEnd(t *testing.T) {
ctx := context.Background() ctx := context.Background()
ct, st := NewLocalTransport() ct, st := NewInMemoryTransport()
s := NewServer("testServer", "v1.0.0", nil) s := NewServer("testServer", "v1.0.0", nil)
@ -286,7 +286,7 @@ func basicConnection(t *testing.T, tools ...*ServerTool) (*ServerSession, *Clien
t.Helper() t.Helper()
ctx := context.Background() ctx := context.Background()
ct, st := NewLocalTransport() ct, st := NewInMemoryTransport()
s := NewServer("testServer", "v1.0.0", nil) s := NewServer("testServer", "v1.0.0", nil)
@ -329,7 +329,7 @@ func TestServerClosing(t *testing.T) {
func TestBatching(t *testing.T) { func TestBatching(t *testing.T) {
ctx := context.Background() ctx := context.Background()
ct, st := NewLocalTransport() ct, st := NewInMemoryTransport()
s := NewServer("testServer", "v1.0.0", nil) s := NewServer("testServer", "v1.0.0", nil)
_, err := s.Connect(ctx, st, nil) _, err := s.Connect(ctx, st, nil)

View file

@ -24,7 +24,7 @@ func SayHi(ctx context.Context, cc *mcp.ServerSession, params *SayHiParams) ([]*
func ExampleServer() { func ExampleServer() {
ctx := context.Background() ctx := context.Background()
clientTransport, serverTransport := mcp.NewLocalTransport() clientTransport, serverTransport := mcp.NewInMemoryTransport()
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))

View file

@ -48,27 +48,39 @@ type ConnectionOptions struct {
Logger io.Writer // if set, write RPC logs Logger io.Writer // if set, write RPC logs
} }
// An IOTransport is a [Transport] that communicates using newline-delimited // A StdIOTransport is a [Transport] that communicates over stdin/stdout using
// newline-delimited JSON.
type StdIOTransport struct {
ioTransport
}
// An ioTransport is a [Transport] that communicates using newline-delimited
// JSON over an io.ReadWriteCloser. // JSON over an io.ReadWriteCloser.
type IOTransport struct { type ioTransport struct {
rwc io.ReadWriteCloser rwc io.ReadWriteCloser
} }
func (t *IOTransport) Connect(context.Context) (Stream, error) { func (t *ioTransport) Connect(context.Context) (Stream, error) {
return newIOStream(t.rwc), nil return newIOStream(t.rwc), nil
} }
// NewStdIOTransport constructs a transport that communicates over // NewStdIOTransport constructs a transport that communicates over
// stdin/stdout. // stdin/stdout.
func NewStdIOTransport() *IOTransport { func NewStdIOTransport() *StdIOTransport {
return &IOTransport{rwc{os.Stdin, os.Stdout}} return &StdIOTransport{ioTransport{rwc{os.Stdin, os.Stdout}}}
} }
// NewLocalTransport returns two in-memory transports that connect to // An InMemoryTransport is a [Transport] that communicates over an in-memory
// each other, for testing purposes. // network connection, using newline-delimited JSON.
func NewLocalTransport() (*IOTransport, *IOTransport) { type InMemoryTransport struct {
ioTransport
}
// NewInMemoryTransport returns two InMemoryTransports that connect to each
// other.
func NewInMemoryTransport() (*InMemoryTransport, *InMemoryTransport) {
c1, c2 := net.Pipe() c1, c2 := net.Pipe()
return &IOTransport{c1}, &IOTransport{c2} return &InMemoryTransport{ioTransport{c1}}, &InMemoryTransport{ioTransport{c2}}
} }
// handler is an unexported version of jsonrpc2.Handler. // handler is an unexported version of jsonrpc2.Handler.