internal/mcp: export the Transport interface; add a Server example

Export the Transport interface, as there's no reason users shouldn't be
able to define their own transports: they are just bidirectional
streams.

Also, add a godoc example for basic Server usage.

Finally, remove some TODOs that existed due to a bad merge: they are
related to the (as yet unsubmitted) streamable HTTP transport.

Change-Id: Id669793fc80630e667d50611fe2876d09125a025
Reviewed-on: https://go-review.googlesource.com/c/tools/+/668675
TryBot-Bypass: Robert Findley <rfindley@google.com>
Reviewed-by: Sam Thanawalla <samthanawalla@google.com>
Auto-Submit: Robert Findley <rfindley@google.com>
This commit is contained in:
Rob Findley 2025-04-28 20:10:20 +00:00 • committed by Robert Findley
commit 3b86fb5601
3 changed files with 66 additions and 25 deletions

53
mcp/example_test.go Normal file
View file

@ -0,0 +1,53 @@
// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package mcp_test
import (
"context"
"fmt"
"log"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
type SayHiParams struct {
Name string `json:"name" mcp:"the name to say hi to"`
}
func SayHi(ctx context.Context, cc *mcp.ClientConnection, params *SayHiParams) ([]mcp.Content, error) {
return []mcp.Content{
mcp.TextContent{Text: "Hi " + params.Name},
}, nil
}
func ExampleServer() {
ctx := context.Background()
clientTransport, serverTransport := mcp.NewLocalTransport()
server := mcp.NewServer("greeter", "v0.0.1", nil)
server.AddTools(mcp.MakeTool("greet", "say hi", SayHi))
clientConnection, err := server.Connect(ctx, serverTransport, nil)
if err != nil {
log.Fatal(err)
}
client := mcp.NewClient("client", "v0.0.1", nil)
serverConnection, err := client.Connect(ctx, clientTransport, nil)
if err != nil {
log.Fatal(err)
}
content, err := serverConnection.CallTool(ctx, "greet", SayHiParams{Name: "user"})
if err != nil {
log.Fatal(err)
}
fmt.Println(content[0].(mcp.TextContent).Text)
serverConnection.Close()
clientConnection.Wait()
// Output: Hi user
}

View file

@ -37,15 +37,6 @@ import (
// exited.
// - Read reads off a message queue that is pushed to via POST requests.
// - Close causes the hanging GEt to exit.
//
// TODO:
// - support resuming broken streamable sessions
// - support GET channels for unrelated notifications in streamable sessions
// - add client support (and use it to test)
// - properly correlate notifications/requests to an incoming request (using
// requestCtx)
// TODO(rfindley): reorganize this file, and split it into sse_server.go and sse_client.go.
// An event is a server-sent event.
type event struct {
@ -109,8 +100,8 @@ type sseSession struct {
done chan struct{} // closed when the stream is closed
}
// connect returns the receiver, as an sseSession is a logical stream.
func (s *sseSession) connect(context.Context) (stream, error) {
// Connect returns the receiver, as an sseSession is a logical stream.
func (s *sseSession) Connect(context.Context) (Stream, error) {
return s, nil
}
@ -296,8 +287,8 @@ func NewSSEClientTransport(rawURL string) (*SSEClientTransport, error) {
}, nil
}
// connect connects through the client endpoint.
func (c *SSEClientTransport) connect(ctx context.Context) (stream, error) {
// Connect connects through the client endpoint.
func (c *SSEClientTransport) Connect(ctx context.Context) (Stream, error) {
req, err := http.NewRequestWithContext(ctx, "GET", c.sseEndpoint.String(), nil)
if err != nil {
return nil, err

View file

@ -19,9 +19,6 @@ import (
"github.com/modelcontextprotocol/go-sdk/internal/xcontext"
)
// A JSONRPC2 error is an error defined by the JSONRPC2 spec.
type JSONRPC2Error = jsonrpc2.WireError
// ErrConnectionClosed is returned when sending a message to a connection that
// is closed or in the process of closing.
var ErrConnectionClosed = errors.New("connection closed")
@ -32,15 +29,15 @@ var ErrConnectionClosed = errors.New("connection closed")
// Transports should be used for at most one call to [Server.Connect] or
// [Client.Connect].
type Transport interface {
// connect returns the logical stream.
// Connect returns the logical stream.
//
// It is called exactly once by [connect].
connect(ctx context.Context) (stream, error)
// It is called exactly once by [Connect].
Connect(ctx context.Context) (Stream, error)
}
// A stream is an abstract bidirectional jsonrpc2 stream.
// A Stream is an abstract bidirectional jsonrpc2 Stream.
// It is used by [connect] to establish a [jsonrpc2.Connection].
type stream interface {
type Stream interface {
jsonrpc2.Reader
jsonrpc2.Writer
io.Closer
@ -59,7 +56,7 @@ 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
}
@ -94,7 +91,7 @@ func connect[H handler](ctx context.Context, t Transport, opts *ConnectionOption
}
var zero H
stream, err := t.connect(ctx)
stream, err := t.Connect(ctx)
if err != nil {
return zero, err
}
@ -275,8 +272,8 @@ func newIOStream(rwc io.ReadWriteCloser) *ioStream {
}
}
// connect returns the receiver, as a streamTransport is a logical stream.
func (t *ioStream) connect(ctx context.Context) (stream, error) {
// Connect returns the receiver, as a streamTransport is a logical stream.
func (t *ioStream) Connect(ctx context.Context) (Stream, error) {
return t, nil
}