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)
```
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
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

View file

@ -33,7 +33,7 @@ func sayHi(ctx context.Context, cc *ServerSession, v hiParams) ([]*Content, erro
func TestEndToEnd(t *testing.T) {
ctx := context.Background()
ct, st := NewLocalTransport()
ct, st := NewInMemoryTransport()
s := NewServer("testServer", "v1.0.0", nil)
@ -286,7 +286,7 @@ func basicConnection(t *testing.T, tools ...*ServerTool) (*ServerSession, *Clien
t.Helper()
ctx := context.Background()
ct, st := NewLocalTransport()
ct, st := NewInMemoryTransport()
s := NewServer("testServer", "v1.0.0", nil)
@ -329,7 +329,7 @@ func TestServerClosing(t *testing.T) {
func TestBatching(t *testing.T) {
ctx := context.Background()
ct, st := NewLocalTransport()
ct, st := NewInMemoryTransport()
s := NewServer("testServer", "v1.0.0", 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() {
ctx := context.Background()
clientTransport, serverTransport := mcp.NewLocalTransport()
clientTransport, serverTransport := mcp.NewInMemoryTransport()
server := mcp.NewServer("greeter", "v0.0.1", nil)
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
}
// 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.
type IOTransport struct {
type ioTransport struct {
rwc io.ReadWriteCloser
}
func (t *IOTransport) Connect(context.Context) (Stream, error) {
func (t *ioTransport) Connect(context.Context) (Stream, error) {
return newIOStream(t.rwc), nil
}
// NewStdIOTransport constructs a transport that communicates over
// stdin/stdout.
func NewStdIOTransport() *IOTransport {
return &IOTransport{rwc{os.Stdin, os.Stdout}}
func NewStdIOTransport() *StdIOTransport {
return &StdIOTransport{ioTransport{rwc{os.Stdin, os.Stdout}}}
}
// NewLocalTransport returns two in-memory transports that connect to
// each other, for testing purposes.
func NewLocalTransport() (*IOTransport, *IOTransport) {
// An InMemoryTransport is a [Transport] that communicates over an in-memory
// network connection, using newline-delimited JSON.
type InMemoryTransport struct {
ioTransport
}
// NewInMemoryTransport returns two InMemoryTransports that connect to each
// other.
func NewInMemoryTransport() (*InMemoryTransport, *InMemoryTransport) {
c1, c2 := net.Pipe()
return &IOTransport{c1}, &IOTransport{c2}
return &InMemoryTransport{ioTransport{c1}}, &InMemoryTransport{ioTransport{c2}}
}
// handler is an unexported version of jsonrpc2.Handler.