jsonrpc: package with JSON-RPC symbols (#117)
Move the aliases to the internal/jsonrpc2 package into their own package. Fixes #115.
This commit is contained in:
parent
2c577e564b
commit
bfa5e30cd1
9 changed files with 112 additions and 95 deletions
20
jsonrpc/jsonrpc.go
Normal file
20
jsonrpc/jsonrpc.go
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
// 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 jsonrpc exposes part of a JSON-RPC v2 implementation
|
||||||
|
// for use by mcp transport authors.
|
||||||
|
package jsonrpc
|
||||||
|
|
||||||
|
import "github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2"
|
||||||
|
|
||||||
|
type (
|
||||||
|
// ID is a JSON-RPC request ID.
|
||||||
|
ID = jsonrpc2.ID
|
||||||
|
// Message is a JSON-RPC message.
|
||||||
|
Message = jsonrpc2.Message
|
||||||
|
// Request is a JSON-RPC request.
|
||||||
|
Request = jsonrpc2.Request
|
||||||
|
// Response is a JSON-RPC response.
|
||||||
|
Response = jsonrpc2.Response
|
||||||
|
)
|
||||||
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2"
|
"github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2"
|
||||||
|
"github.com/modelcontextprotocol/go-sdk/jsonrpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A Client is an MCP client, which may be connected to an MCP server
|
// A Client is an MCP client, which may be connected to an MCP server
|
||||||
|
|
@ -301,7 +302,7 @@ func (cs *ClientSession) receivingMethodInfos() map[string]methodInfo {
|
||||||
return clientMethodInfos
|
return clientMethodInfos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *ClientSession) handle(ctx context.Context, req *JSONRPCRequest) (any, error) {
|
func (cs *ClientSession) handle(ctx context.Context, req *jsonrpc.Request) (any, error) {
|
||||||
return handleReceive(ctx, cs, req)
|
return handleReceive(ctx, cs, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
|
|
||||||
"github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2"
|
"github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2"
|
||||||
"github.com/modelcontextprotocol/go-sdk/internal/util"
|
"github.com/modelcontextprotocol/go-sdk/internal/util"
|
||||||
|
"github.com/modelcontextprotocol/go-sdk/jsonrpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DefaultPageSize = 1000
|
const DefaultPageSize = 1000
|
||||||
|
|
@ -610,7 +611,7 @@ func (ss *ServerSession) receivingMethodHandler() methodHandler {
|
||||||
func (ss *ServerSession) getConn() *jsonrpc2.Connection { return ss.conn }
|
func (ss *ServerSession) getConn() *jsonrpc2.Connection { return ss.conn }
|
||||||
|
|
||||||
// handle invokes the method described by the given JSON RPC request.
|
// handle invokes the method described by the given JSON RPC request.
|
||||||
func (ss *ServerSession) handle(ctx context.Context, req *JSONRPCRequest) (any, error) {
|
func (ss *ServerSession) handle(ctx context.Context, req *jsonrpc.Request) (any, error) {
|
||||||
ss.mu.Lock()
|
ss.mu.Lock()
|
||||||
initialized := ss.initialized
|
initialized := ss.initialized
|
||||||
ss.mu.Unlock()
|
ss.mu.Unlock()
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2"
|
"github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2"
|
||||||
|
"github.com/modelcontextprotocol/go-sdk/jsonrpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// latestProtocolVersion is the latest protocol version that this version of the SDK supports.
|
// latestProtocolVersion is the latest protocol version that this version of the SDK supports.
|
||||||
|
|
@ -121,7 +122,7 @@ func defaultReceivingMethodHandler[S Session](ctx context.Context, session S, me
|
||||||
return info.handleMethod.(MethodHandler[S])(ctx, session, method, params)
|
return info.handleMethod.(MethodHandler[S])(ctx, session, method, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleReceive[S Session](ctx context.Context, session S, req *JSONRPCRequest) (Result, error) {
|
func handleReceive[S Session](ctx context.Context, session S, req *jsonrpc.Request) (Result, error) {
|
||||||
info, ok := session.receivingMethodInfos()[req.Method]
|
info, ok := session.receivingMethodInfos()[req.Method]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, jsonrpc2.ErrNotHandled
|
return nil, jsonrpc2.ErrNotHandled
|
||||||
|
|
|
||||||
13
mcp/sse.go
13
mcp/sse.go
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2"
|
"github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2"
|
||||||
|
"github.com/modelcontextprotocol/go-sdk/jsonrpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This file implements support for SSE (HTTP with server-sent events)
|
// This file implements support for SSE (HTTP with server-sent events)
|
||||||
|
|
@ -111,7 +112,7 @@ func NewSSEHandler(getServer func(request *http.Request) *Server) *SSEHandler {
|
||||||
// - Close terminates the hanging GET.
|
// - Close terminates the hanging GET.
|
||||||
type SSEServerTransport struct {
|
type SSEServerTransport struct {
|
||||||
endpoint string
|
endpoint string
|
||||||
incoming chan JSONRPCMessage // queue of incoming messages; never closed
|
incoming chan jsonrpc.Message // queue of incoming messages; never closed
|
||||||
|
|
||||||
// We must guard both pushes to the incoming queue and writes to the response
|
// We must guard both pushes to the incoming queue and writes to the response
|
||||||
// writer, because incoming POST requests are arbitrarily concurrent and we
|
// writer, because incoming POST requests are arbitrarily concurrent and we
|
||||||
|
|
@ -138,7 +139,7 @@ func NewSSEServerTransport(endpoint string, w http.ResponseWriter) *SSEServerTra
|
||||||
return &SSEServerTransport{
|
return &SSEServerTransport{
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
w: w,
|
w: w,
|
||||||
incoming: make(chan JSONRPCMessage, 100),
|
incoming: make(chan jsonrpc.Message, 100),
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -267,7 +268,7 @@ type sseServerConn struct {
|
||||||
func (s sseServerConn) SessionID() string { return "" }
|
func (s sseServerConn) SessionID() string { return "" }
|
||||||
|
|
||||||
// Read implements jsonrpc2.Reader.
|
// Read implements jsonrpc2.Reader.
|
||||||
func (s sseServerConn) Read(ctx context.Context) (JSONRPCMessage, error) {
|
func (s sseServerConn) Read(ctx context.Context) (jsonrpc.Message, error) {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
|
|
@ -279,7 +280,7 @@ func (s sseServerConn) Read(ctx context.Context) (JSONRPCMessage, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write implements jsonrpc2.Writer.
|
// Write implements jsonrpc2.Writer.
|
||||||
func (s sseServerConn) Write(ctx context.Context, msg JSONRPCMessage) error {
|
func (s sseServerConn) Write(ctx context.Context, msg jsonrpc.Message) error {
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
@ -532,7 +533,7 @@ func (c *sseClientConn) isDone() bool {
|
||||||
return c.closed
|
return c.closed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *sseClientConn) Read(ctx context.Context) (JSONRPCMessage, error) {
|
func (c *sseClientConn) Read(ctx context.Context) (jsonrpc.Message, error) {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
|
|
@ -553,7 +554,7 @@ func (c *sseClientConn) Read(ctx context.Context) (JSONRPCMessage, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *sseClientConn) Write(ctx context.Context, msg JSONRPCMessage) error {
|
func (c *sseClientConn) Write(ctx context.Context, msg jsonrpc.Message) error {
|
||||||
data, err := jsonrpc2.EncodeMessage(msg)
|
data, err := jsonrpc2.EncodeMessage(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2"
|
"github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2"
|
||||||
|
"github.com/modelcontextprotocol/go-sdk/jsonrpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -157,12 +158,12 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque
|
||||||
func NewStreamableServerTransport(sessionID string) *StreamableServerTransport {
|
func NewStreamableServerTransport(sessionID string) *StreamableServerTransport {
|
||||||
return &StreamableServerTransport{
|
return &StreamableServerTransport{
|
||||||
id: sessionID,
|
id: sessionID,
|
||||||
incoming: make(chan JSONRPCMessage, 10),
|
incoming: make(chan jsonrpc.Message, 10),
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
outgoingMessages: make(map[streamID][]*streamableMsg),
|
outgoingMessages: make(map[streamID][]*streamableMsg),
|
||||||
signals: make(map[streamID]chan struct{}),
|
signals: make(map[streamID]chan struct{}),
|
||||||
requestStreams: make(map[JSONRPCID]streamID),
|
requestStreams: make(map[jsonrpc.ID]streamID),
|
||||||
streamRequests: make(map[streamID]map[JSONRPCID]struct{}),
|
streamRequests: make(map[streamID]map[jsonrpc.ID]struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,7 +177,7 @@ type StreamableServerTransport struct {
|
||||||
nextStreamID atomic.Int64 // incrementing next stream ID
|
nextStreamID atomic.Int64 // incrementing next stream ID
|
||||||
|
|
||||||
id string
|
id string
|
||||||
incoming chan JSONRPCMessage // messages from the client to the server
|
incoming chan jsonrpc.Message // messages from the client to the server
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
|
||||||
|
|
@ -226,7 +227,7 @@ type StreamableServerTransport struct {
|
||||||
// Lifecycle: requestStreams persists for the duration of the session.
|
// Lifecycle: requestStreams persists for the duration of the session.
|
||||||
//
|
//
|
||||||
// TODO(rfindley): clean up once requests are handled.
|
// TODO(rfindley): clean up once requests are handled.
|
||||||
requestStreams map[JSONRPCID]streamID
|
requestStreams map[jsonrpc.ID]streamID
|
||||||
|
|
||||||
// streamRequests tracks the set of unanswered incoming RPCs for each logical
|
// streamRequests tracks the set of unanswered incoming RPCs for each logical
|
||||||
// stream.
|
// stream.
|
||||||
|
|
@ -237,7 +238,7 @@ type StreamableServerTransport struct {
|
||||||
// Lifecycle: streamRequests values persist as until the requests have been
|
// Lifecycle: streamRequests values persist as until the requests have been
|
||||||
// replied to by the server. Notably, NOT until they are sent to an HTTP
|
// replied to by the server. Notably, NOT until they are sent to an HTTP
|
||||||
// response, as delivery is not guaranteed.
|
// response, as delivery is not guaranteed.
|
||||||
streamRequests map[streamID]map[JSONRPCID]struct{}
|
streamRequests map[streamID]map[jsonrpc.ID]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type streamID int64
|
type streamID int64
|
||||||
|
|
@ -271,7 +272,7 @@ func (s *StreamableServerTransport) Connect(context.Context) (Connection, error)
|
||||||
// 2. Expose a 'HandlerTransport' interface that allows transports to provide
|
// 2. Expose a 'HandlerTransport' interface that allows transports to provide
|
||||||
// a handler middleware, so that we don't hard-code this behavior in
|
// a handler middleware, so that we don't hard-code this behavior in
|
||||||
// ServerSession.handle.
|
// ServerSession.handle.
|
||||||
// 3. Add a `func ForRequest(context.Context) JSONRPCID` accessor that lets
|
// 3. Add a `func ForRequest(context.Context) jsonrpc.ID` accessor that lets
|
||||||
// any transport access the incoming request ID.
|
// any transport access the incoming request ID.
|
||||||
//
|
//
|
||||||
// For now, by giving only the StreamableServerTransport access to the request
|
// For now, by giving only the StreamableServerTransport access to the request
|
||||||
|
|
@ -340,9 +341,9 @@ func (t *StreamableServerTransport) servePOST(w http.ResponseWriter, req *http.R
|
||||||
http.Error(w, fmt.Sprintf("malformed payload: %v", err), http.StatusBadRequest)
|
http.Error(w, fmt.Sprintf("malformed payload: %v", err), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
requests := make(map[JSONRPCID]struct{})
|
requests := make(map[jsonrpc.ID]struct{})
|
||||||
for _, msg := range incoming {
|
for _, msg := range incoming {
|
||||||
if req, ok := msg.(*JSONRPCRequest); ok && req.ID.IsValid() {
|
if req, ok := msg.(*jsonrpc.Request); ok && req.ID.IsValid() {
|
||||||
requests[req.ID] = struct{}{}
|
requests[req.ID] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -352,7 +353,7 @@ func (t *StreamableServerTransport) servePOST(w http.ResponseWriter, req *http.R
|
||||||
signal := make(chan struct{}, 1)
|
signal := make(chan struct{}, 1)
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
if len(requests) > 0 {
|
if len(requests) > 0 {
|
||||||
t.streamRequests[id] = make(map[JSONRPCID]struct{})
|
t.streamRequests[id] = make(map[jsonrpc.ID]struct{})
|
||||||
}
|
}
|
||||||
for reqID := range requests {
|
for reqID := range requests {
|
||||||
t.requestStreams[reqID] = id
|
t.requestStreams[reqID] = id
|
||||||
|
|
@ -484,7 +485,7 @@ func parseEventID(eventID string) (sid streamID, idx int, ok bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read implements the [Connection] interface.
|
// Read implements the [Connection] interface.
|
||||||
func (t *StreamableServerTransport) Read(ctx context.Context) (JSONRPCMessage, error) {
|
func (t *StreamableServerTransport) Read(ctx context.Context) (jsonrpc.Message, error) {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
|
|
@ -499,10 +500,10 @@ func (t *StreamableServerTransport) Read(ctx context.Context) (JSONRPCMessage, e
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write implements the [Connection] interface.
|
// Write implements the [Connection] interface.
|
||||||
func (t *StreamableServerTransport) Write(ctx context.Context, msg JSONRPCMessage) error {
|
func (t *StreamableServerTransport) Write(ctx context.Context, msg jsonrpc.Message) error {
|
||||||
// Find the incoming request that this write relates to, if any.
|
// Find the incoming request that this write relates to, if any.
|
||||||
var forRequest, replyTo JSONRPCID
|
var forRequest, replyTo jsonrpc.ID
|
||||||
if resp, ok := msg.(*JSONRPCResponse); ok {
|
if resp, ok := msg.(*jsonrpc.Response); ok {
|
||||||
// If the message is a response, it relates to its request (of course).
|
// If the message is a response, it relates to its request (of course).
|
||||||
forRequest = resp.ID
|
forRequest = resp.ID
|
||||||
replyTo = resp.ID
|
replyTo = resp.ID
|
||||||
|
|
@ -511,7 +512,7 @@ func (t *StreamableServerTransport) Write(ctx context.Context, msg JSONRPCMessag
|
||||||
// ongoing request. This may not be the case if the request way made with
|
// ongoing request. This may not be the case if the request way made with
|
||||||
// an unrelated context.
|
// an unrelated context.
|
||||||
if v := ctx.Value(idContextKey{}); v != nil {
|
if v := ctx.Value(idContextKey{}); v != nil {
|
||||||
forRequest = v.(JSONRPCID)
|
forRequest = v.(jsonrpc.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -661,7 +662,7 @@ func (c *streamableClientConn) SessionID() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read implements the [Connection] interface.
|
// Read implements the [Connection] interface.
|
||||||
func (s *streamableClientConn) Read(ctx context.Context) (JSONRPCMessage, error) {
|
func (s *streamableClientConn) Read(ctx context.Context) (jsonrpc.Message, error) {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
|
|
@ -673,7 +674,7 @@ func (s *streamableClientConn) Read(ctx context.Context) (JSONRPCMessage, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write implements the [Connection] interface.
|
// Write implements the [Connection] interface.
|
||||||
func (s *streamableClientConn) Write(ctx context.Context, msg JSONRPCMessage) error {
|
func (s *streamableClientConn) Write(ctx context.Context, msg jsonrpc.Message) error {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
if s.err != nil {
|
if s.err != nil {
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|
@ -709,7 +710,7 @@ func (s *streamableClientConn) Write(ctx context.Context, msg JSONRPCMessage) er
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *streamableClientConn) postMessage(ctx context.Context, sessionID string, msg JSONRPCMessage) (string, error) {
|
func (s *streamableClientConn) postMessage(ctx context.Context, sessionID string, msg jsonrpc.Message) (string, error) {
|
||||||
data, err := jsonrpc2.EncodeMessage(msg)
|
data, err := jsonrpc2.EncodeMessage(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
"github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2"
|
"github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2"
|
||||||
|
"github.com/modelcontextprotocol/go-sdk/jsonrpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStreamableTransports(t *testing.T) {
|
func TestStreamableTransports(t *testing.T) {
|
||||||
|
|
@ -126,16 +127,16 @@ func TestStreamableServerTransport(t *testing.T) {
|
||||||
// Redundant with OnRequest: all OnRequest steps are asynchronous.
|
// Redundant with OnRequest: all OnRequest steps are asynchronous.
|
||||||
Async bool
|
Async bool
|
||||||
|
|
||||||
Method string // HTTP request method
|
Method string // HTTP request method
|
||||||
Send []JSONRPCMessage // messages to send
|
Send []jsonrpc.Message // messages to send
|
||||||
CloseAfter int // if nonzero, close after receiving this many messages
|
CloseAfter int // if nonzero, close after receiving this many messages
|
||||||
StatusCode int // expected status code
|
StatusCode int // expected status code
|
||||||
Recv []JSONRPCMessage // expected messages to receive
|
Recv []jsonrpc.Message // expected messages to receive
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON-RPC message constructors.
|
// JSON-RPC message constructors.
|
||||||
req := func(id int64, method string, params any) *JSONRPCRequest {
|
req := func(id int64, method string, params any) *jsonrpc.Request {
|
||||||
r := &JSONRPCRequest{
|
r := &jsonrpc.Request{
|
||||||
Method: method,
|
Method: method,
|
||||||
Params: mustMarshal(t, params),
|
Params: mustMarshal(t, params),
|
||||||
}
|
}
|
||||||
|
|
@ -144,8 +145,8 @@ func TestStreamableServerTransport(t *testing.T) {
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
resp := func(id int64, result any, err error) *JSONRPCResponse {
|
resp := func(id int64, result any, err error) *jsonrpc.Response {
|
||||||
return &JSONRPCResponse{
|
return &jsonrpc.Response{
|
||||||
ID: jsonrpc2.Int64ID(id),
|
ID: jsonrpc2.Int64ID(id),
|
||||||
Result: mustMarshal(t, result),
|
Result: mustMarshal(t, result),
|
||||||
Error: err,
|
Error: err,
|
||||||
|
|
@ -168,13 +169,13 @@ func TestStreamableServerTransport(t *testing.T) {
|
||||||
initializedMsg := req(0, "initialized", &InitializedParams{})
|
initializedMsg := req(0, "initialized", &InitializedParams{})
|
||||||
initialize := step{
|
initialize := step{
|
||||||
Method: "POST",
|
Method: "POST",
|
||||||
Send: []JSONRPCMessage{initReq},
|
Send: []jsonrpc.Message{initReq},
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
Recv: []JSONRPCMessage{initResp},
|
Recv: []jsonrpc.Message{initResp},
|
||||||
}
|
}
|
||||||
initialized := step{
|
initialized := step{
|
||||||
Method: "POST",
|
Method: "POST",
|
||||||
Send: []JSONRPCMessage{initializedMsg},
|
Send: []jsonrpc.Message{initializedMsg},
|
||||||
StatusCode: http.StatusAccepted,
|
StatusCode: http.StatusAccepted,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -190,9 +191,9 @@ func TestStreamableServerTransport(t *testing.T) {
|
||||||
initialized,
|
initialized,
|
||||||
{
|
{
|
||||||
Method: "POST",
|
Method: "POST",
|
||||||
Send: []JSONRPCMessage{req(2, "tools/call", &CallToolParams{Name: "tool"})},
|
Send: []jsonrpc.Message{req(2, "tools/call", &CallToolParams{Name: "tool"})},
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
Recv: []JSONRPCMessage{resp(2, &CallToolResult{}, nil)},
|
Recv: []jsonrpc.Message{resp(2, &CallToolResult{}, nil)},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -209,11 +210,11 @@ func TestStreamableServerTransport(t *testing.T) {
|
||||||
initialized,
|
initialized,
|
||||||
{
|
{
|
||||||
Method: "POST",
|
Method: "POST",
|
||||||
Send: []JSONRPCMessage{
|
Send: []jsonrpc.Message{
|
||||||
req(2, "tools/call", &CallToolParams{Name: "tool"}),
|
req(2, "tools/call", &CallToolParams{Name: "tool"}),
|
||||||
},
|
},
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
Recv: []JSONRPCMessage{
|
Recv: []jsonrpc.Message{
|
||||||
req(0, "notifications/progress", &ProgressNotificationParams{}),
|
req(0, "notifications/progress", &ProgressNotificationParams{}),
|
||||||
resp(2, &CallToolResult{}, nil),
|
resp(2, &CallToolResult{}, nil),
|
||||||
},
|
},
|
||||||
|
|
@ -234,18 +235,18 @@ func TestStreamableServerTransport(t *testing.T) {
|
||||||
{
|
{
|
||||||
Method: "POST",
|
Method: "POST",
|
||||||
OnRequest: 1,
|
OnRequest: 1,
|
||||||
Send: []JSONRPCMessage{
|
Send: []jsonrpc.Message{
|
||||||
resp(1, &ListRootsResult{}, nil),
|
resp(1, &ListRootsResult{}, nil),
|
||||||
},
|
},
|
||||||
StatusCode: http.StatusAccepted,
|
StatusCode: http.StatusAccepted,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Method: "POST",
|
Method: "POST",
|
||||||
Send: []JSONRPCMessage{
|
Send: []jsonrpc.Message{
|
||||||
req(2, "tools/call", &CallToolParams{Name: "tool"}),
|
req(2, "tools/call", &CallToolParams{Name: "tool"}),
|
||||||
},
|
},
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
Recv: []JSONRPCMessage{
|
Recv: []jsonrpc.Message{
|
||||||
req(1, "roots/list", &ListRootsParams{}),
|
req(1, "roots/list", &ListRootsParams{}),
|
||||||
resp(2, &CallToolResult{}, nil),
|
resp(2, &CallToolResult{}, nil),
|
||||||
},
|
},
|
||||||
|
|
@ -275,7 +276,7 @@ func TestStreamableServerTransport(t *testing.T) {
|
||||||
{
|
{
|
||||||
Method: "POST",
|
Method: "POST",
|
||||||
OnRequest: 1,
|
OnRequest: 1,
|
||||||
Send: []JSONRPCMessage{
|
Send: []jsonrpc.Message{
|
||||||
resp(1, &ListRootsResult{}, nil),
|
resp(1, &ListRootsResult{}, nil),
|
||||||
},
|
},
|
||||||
StatusCode: http.StatusAccepted,
|
StatusCode: http.StatusAccepted,
|
||||||
|
|
@ -285,18 +286,18 @@ func TestStreamableServerTransport(t *testing.T) {
|
||||||
Async: true,
|
Async: true,
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
CloseAfter: 2,
|
CloseAfter: 2,
|
||||||
Recv: []JSONRPCMessage{
|
Recv: []jsonrpc.Message{
|
||||||
req(0, "notifications/progress", &ProgressNotificationParams{}),
|
req(0, "notifications/progress", &ProgressNotificationParams{}),
|
||||||
req(1, "roots/list", &ListRootsParams{}),
|
req(1, "roots/list", &ListRootsParams{}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Method: "POST",
|
Method: "POST",
|
||||||
Send: []JSONRPCMessage{
|
Send: []jsonrpc.Message{
|
||||||
req(2, "tools/call", &CallToolParams{Name: "tool"}),
|
req(2, "tools/call", &CallToolParams{Name: "tool"}),
|
||||||
},
|
},
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
Recv: []JSONRPCMessage{
|
Recv: []jsonrpc.Message{
|
||||||
resp(2, &CallToolResult{}, nil),
|
resp(2, &CallToolResult{}, nil),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -315,9 +316,9 @@ func TestStreamableServerTransport(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Method: "POST",
|
Method: "POST",
|
||||||
Send: []JSONRPCMessage{req(2, "tools/call", &CallToolParams{Name: "tool"})},
|
Send: []jsonrpc.Message{req(2, "tools/call", &CallToolParams{Name: "tool"})},
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
Recv: []JSONRPCMessage{resp(2, nil, &jsonrpc2.WireError{
|
Recv: []jsonrpc.Message{resp(2, nil, &jsonrpc2.WireError{
|
||||||
Message: `method "tools/call" is invalid during session initialization`,
|
Message: `method "tools/call" is invalid during session initialization`,
|
||||||
})},
|
})},
|
||||||
},
|
},
|
||||||
|
|
@ -344,7 +345,7 @@ func TestStreamableServerTransport(t *testing.T) {
|
||||||
httpServer := httptest.NewServer(handler)
|
httpServer := httptest.NewServer(handler)
|
||||||
defer httpServer.Close()
|
defer httpServer.Close()
|
||||||
|
|
||||||
// blocks records request blocks by JSONRPC ID.
|
// blocks records request blocks by jsonrpc. ID.
|
||||||
//
|
//
|
||||||
// When an OnRequest step is encountered, it waits on the corresponding
|
// When an OnRequest step is encountered, it waits on the corresponding
|
||||||
// block. When a request with that ID is received, the block is closed.
|
// block. When a request with that ID is received, the block is closed.
|
||||||
|
|
@ -382,8 +383,8 @@ func TestStreamableServerTransport(t *testing.T) {
|
||||||
|
|
||||||
// Collect messages received during this request, unblock other steps
|
// Collect messages received during this request, unblock other steps
|
||||||
// when requests are received.
|
// when requests are received.
|
||||||
var got []JSONRPCMessage
|
var got []jsonrpc.Message
|
||||||
out := make(chan JSONRPCMessage)
|
out := make(chan jsonrpc.Message)
|
||||||
// Cancel the step if we encounter a request that isn't going to be
|
// Cancel the step if we encounter a request that isn't going to be
|
||||||
// handled.
|
// handled.
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
@ -394,7 +395,7 @@ func TestStreamableServerTransport(t *testing.T) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
for m := range out {
|
for m := range out {
|
||||||
if req, ok := m.(*JSONRPCRequest); ok && req.ID.IsValid() {
|
if req, ok := m.(*jsonrpc.Request); ok && req.ID.IsValid() {
|
||||||
// Encountered a server->client request. We should have a
|
// Encountered a server->client request. We should have a
|
||||||
// response queued. Otherwise, we may deadlock.
|
// response queued. Otherwise, we may deadlock.
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
|
|
@ -427,7 +428,7 @@ func TestStreamableServerTransport(t *testing.T) {
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
transform := cmpopts.AcyclicTransformer("jsonrpcid", func(id JSONRPCID) any { return id.Raw() })
|
transform := cmpopts.AcyclicTransformer("jsonrpcid", func(id jsonrpc.ID) any { return id.Raw() })
|
||||||
if diff := cmp.Diff(step.Recv, got, transform); diff != "" {
|
if diff := cmp.Diff(step.Recv, got, transform); diff != "" {
|
||||||
t.Errorf("received unexpected messages (-want +got):\n%s", diff)
|
t.Errorf("received unexpected messages (-want +got):\n%s", diff)
|
||||||
}
|
}
|
||||||
|
|
@ -469,7 +470,7 @@ func TestStreamableServerTransport(t *testing.T) {
|
||||||
// Returns the sessionID and http status code from the response. If an error is
|
// Returns the sessionID and http status code from the response. If an error is
|
||||||
// returned, sessionID and status code may still be set if the error occurs
|
// returned, sessionID and status code may still be set if the error occurs
|
||||||
// after the response headers have been received.
|
// after the response headers have been received.
|
||||||
func streamingRequest(ctx context.Context, serverURL, sessionID, method string, in []JSONRPCMessage, out chan<- JSONRPCMessage) (string, int, error) {
|
func streamingRequest(ctx context.Context, serverURL, sessionID, method string, in []jsonrpc.Message, out chan<- jsonrpc.Message) (string, int, error) {
|
||||||
defer close(out)
|
defer close(out)
|
||||||
|
|
||||||
var body []byte
|
var body []byte
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import (
|
||||||
|
|
||||||
"github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2"
|
"github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2"
|
||||||
"github.com/modelcontextprotocol/go-sdk/internal/xcontext"
|
"github.com/modelcontextprotocol/go-sdk/internal/xcontext"
|
||||||
|
"github.com/modelcontextprotocol/go-sdk/jsonrpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrConnectionClosed is returned when sending a message to a connection that
|
// ErrConnectionClosed is returned when sending a message to a connection that
|
||||||
|
|
@ -34,21 +35,10 @@ type Transport interface {
|
||||||
Connect(ctx context.Context) (Connection, error)
|
Connect(ctx context.Context) (Connection, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
|
||||||
// JSONRPCID is a JSON-RPC request ID.
|
|
||||||
JSONRPCID = jsonrpc2.ID
|
|
||||||
// JSONRPCMessage is a JSON-RPC message.
|
|
||||||
JSONRPCMessage = jsonrpc2.Message
|
|
||||||
// JSONRPCRequest is a JSON-RPC request.
|
|
||||||
JSONRPCRequest = jsonrpc2.Request
|
|
||||||
// JSONRPCResponse is a JSON-RPC response.
|
|
||||||
JSONRPCResponse = jsonrpc2.Response
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Connection is a logical bidirectional JSON-RPC connection.
|
// A Connection is a logical bidirectional JSON-RPC connection.
|
||||||
type Connection interface {
|
type Connection interface {
|
||||||
Read(context.Context) (JSONRPCMessage, error)
|
Read(context.Context) (jsonrpc.Message, error)
|
||||||
Write(context.Context, JSONRPCMessage) error
|
Write(context.Context, jsonrpc.Message) error
|
||||||
Close() error // may be called concurrently by both peers
|
Close() error // may be called concurrently by both peers
|
||||||
SessionID() string
|
SessionID() string
|
||||||
}
|
}
|
||||||
|
|
@ -100,7 +90,7 @@ type binder[T handler] interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type handler interface {
|
type handler interface {
|
||||||
handle(ctx context.Context, req *JSONRPCRequest) (any, error)
|
handle(ctx context.Context, req *jsonrpc.Request) (any, error)
|
||||||
setConn(Connection)
|
setConn(Connection)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,7 +133,7 @@ type canceller struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preempt implements jsonrpc2.Preempter.
|
// Preempt implements jsonrpc2.Preempter.
|
||||||
func (c *canceller) Preempt(ctx context.Context, req *JSONRPCRequest) (result any, err error) {
|
func (c *canceller) Preempt(ctx context.Context, req *jsonrpc.Request) (result any, err error) {
|
||||||
if req.Method == "notifications/cancelled" {
|
if req.Method == "notifications/cancelled" {
|
||||||
var params CancelledParams
|
var params CancelledParams
|
||||||
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
||||||
|
|
@ -212,7 +202,7 @@ type loggingConn struct {
|
||||||
func (c *loggingConn) SessionID() string { return c.delegate.SessionID() }
|
func (c *loggingConn) SessionID() string { return c.delegate.SessionID() }
|
||||||
|
|
||||||
// loggingReader is a stream middleware that logs incoming messages.
|
// loggingReader is a stream middleware that logs incoming messages.
|
||||||
func (s *loggingConn) Read(ctx context.Context) (JSONRPCMessage, error) {
|
func (s *loggingConn) Read(ctx context.Context) (jsonrpc.Message, error) {
|
||||||
msg, err := s.delegate.Read(ctx)
|
msg, err := s.delegate.Read(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(s.w, "read error: %v", err)
|
fmt.Fprintf(s.w, "read error: %v", err)
|
||||||
|
|
@ -227,7 +217,7 @@ func (s *loggingConn) Read(ctx context.Context) (JSONRPCMessage, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// loggingWriter is a stream middleware that logs outgoing messages.
|
// loggingWriter is a stream middleware that logs outgoing messages.
|
||||||
func (s *loggingConn) Write(ctx context.Context, msg JSONRPCMessage) error {
|
func (s *loggingConn) Write(ctx context.Context, msg jsonrpc.Message) error {
|
||||||
err := s.delegate.Write(ctx, msg)
|
err := s.delegate.Write(ctx, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(s.w, "write error: %v", err)
|
fmt.Fprintf(s.w, "write error: %v", err)
|
||||||
|
|
@ -265,7 +255,7 @@ func (r rwc) Close() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// An ioConn is a transport that delimits messages with newlines across
|
// An ioConn is a transport that delimits messages with newlines across
|
||||||
// a bidirectional stream, and supports JSONRPC2 message batching.
|
// a bidirectional stream, and supports jsonrpc.2 message batching.
|
||||||
//
|
//
|
||||||
// See https://github.com/ndjson/ndjson-spec for discussion of newline
|
// See https://github.com/ndjson/ndjson-spec for discussion of newline
|
||||||
// delimited JSON.
|
// delimited JSON.
|
||||||
|
|
@ -277,11 +267,11 @@ type ioConn struct {
|
||||||
|
|
||||||
// If outgoiBatch has a positive capacity, it will be used to batch requests
|
// If outgoiBatch has a positive capacity, it will be used to batch requests
|
||||||
// and notifications before sending.
|
// and notifications before sending.
|
||||||
outgoingBatch []JSONRPCMessage
|
outgoingBatch []jsonrpc.Message
|
||||||
|
|
||||||
// Unread messages in the last batch. Since reads are serialized, there is no
|
// Unread messages in the last batch. Since reads are serialized, there is no
|
||||||
// need to guard here.
|
// need to guard here.
|
||||||
queue []JSONRPCMessage
|
queue []jsonrpc.Message
|
||||||
|
|
||||||
// batches correlate incoming requests to the batch in which they arrived.
|
// batches correlate incoming requests to the batch in which they arrived.
|
||||||
// Since writes may be concurrent to reads, we need to guard this with a mutex.
|
// Since writes may be concurrent to reads, we need to guard this with a mutex.
|
||||||
|
|
@ -325,7 +315,7 @@ func (t *ioConn) addBatch(batch *msgBatch) error {
|
||||||
// The second result reports whether resp was part of a batch. If this is true,
|
// The second result reports whether resp was part of a batch. If this is true,
|
||||||
// the first result is nil if the batch is still incomplete, or the full set of
|
// the first result is nil if the batch is still incomplete, or the full set of
|
||||||
// batch responses if resp completed the batch.
|
// batch responses if resp completed the batch.
|
||||||
func (t *ioConn) updateBatch(resp *JSONRPCResponse) ([]*JSONRPCResponse, bool) {
|
func (t *ioConn) updateBatch(resp *jsonrpc.Response) ([]*jsonrpc.Response, bool) {
|
||||||
t.batchMu.Lock()
|
t.batchMu.Lock()
|
||||||
defer t.batchMu.Unlock()
|
defer t.batchMu.Unlock()
|
||||||
|
|
||||||
|
|
@ -345,9 +335,9 @@ func (t *ioConn) updateBatch(resp *JSONRPCResponse) ([]*JSONRPCResponse, bool) {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// A msgBatch records information about an incoming batch of JSONRPC2 calls.
|
// A msgBatch records information about an incoming batch of jsonrpc.2 calls.
|
||||||
//
|
//
|
||||||
// The JSONRPC2 spec (https://www.jsonrpc.org/specification#batch) says:
|
// The jsonrpc.2 spec (https://www.jsonrpc.org/specification#batch) says:
|
||||||
//
|
//
|
||||||
// "The Server should respond with an Array containing the corresponding
|
// "The Server should respond with an Array containing the corresponding
|
||||||
// Response objects, after all of the batch Request objects have been
|
// Response objects, after all of the batch Request objects have been
|
||||||
|
|
@ -360,14 +350,14 @@ func (t *ioConn) updateBatch(resp *JSONRPCResponse) ([]*JSONRPCResponse, bool) {
|
||||||
// When there are no unresolved calls, the response payload is sent.
|
// When there are no unresolved calls, the response payload is sent.
|
||||||
type msgBatch struct {
|
type msgBatch struct {
|
||||||
unresolved map[jsonrpc2.ID]int
|
unresolved map[jsonrpc2.ID]int
|
||||||
responses []*JSONRPCResponse
|
responses []*jsonrpc.Response
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ioConn) Read(ctx context.Context) (JSONRPCMessage, error) {
|
func (t *ioConn) Read(ctx context.Context) (jsonrpc.Message, error) {
|
||||||
return t.read(ctx, t.in)
|
return t.read(ctx, t.in)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ioConn) read(ctx context.Context, in *json.Decoder) (JSONRPCMessage, error) {
|
func (t *ioConn) read(ctx context.Context, in *json.Decoder) (jsonrpc.Message, error) {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
|
|
@ -392,7 +382,7 @@ func (t *ioConn) read(ctx context.Context, in *json.Decoder) (JSONRPCMessage, er
|
||||||
if batch {
|
if batch {
|
||||||
var respBatch *msgBatch // track incoming requests in the batch
|
var respBatch *msgBatch // track incoming requests in the batch
|
||||||
for _, msg := range msgs {
|
for _, msg := range msgs {
|
||||||
if req, ok := msg.(*JSONRPCRequest); ok {
|
if req, ok := msg.(*jsonrpc.Request); ok {
|
||||||
if respBatch == nil {
|
if respBatch == nil {
|
||||||
respBatch = &msgBatch{
|
respBatch = &msgBatch{
|
||||||
unresolved: make(map[jsonrpc2.ID]int),
|
unresolved: make(map[jsonrpc2.ID]int),
|
||||||
|
|
@ -417,7 +407,7 @@ func (t *ioConn) read(ctx context.Context, in *json.Decoder) (JSONRPCMessage, er
|
||||||
|
|
||||||
// readBatch reads batch data, which may be either a single JSON-RPC message,
|
// readBatch reads batch data, which may be either a single JSON-RPC message,
|
||||||
// or an array of JSON-RPC messages.
|
// or an array of JSON-RPC messages.
|
||||||
func readBatch(data []byte) (msgs []JSONRPCMessage, isBatch bool, _ error) {
|
func readBatch(data []byte) (msgs []jsonrpc.Message, isBatch bool, _ error) {
|
||||||
// Try to read an array of messages first.
|
// Try to read an array of messages first.
|
||||||
var rawBatch []json.RawMessage
|
var rawBatch []json.RawMessage
|
||||||
if err := json.Unmarshal(data, &rawBatch); err == nil {
|
if err := json.Unmarshal(data, &rawBatch); err == nil {
|
||||||
|
|
@ -435,10 +425,10 @@ func readBatch(data []byte) (msgs []JSONRPCMessage, isBatch bool, _ error) {
|
||||||
}
|
}
|
||||||
// Try again with a single message.
|
// Try again with a single message.
|
||||||
msg, err := jsonrpc2.DecodeMessage(data)
|
msg, err := jsonrpc2.DecodeMessage(data)
|
||||||
return []JSONRPCMessage{msg}, false, err
|
return []jsonrpc.Message{msg}, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ioConn) Write(ctx context.Context, msg JSONRPCMessage) error {
|
func (t *ioConn) Write(ctx context.Context, msg jsonrpc.Message) error {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
|
|
@ -449,7 +439,7 @@ func (t *ioConn) Write(ctx context.Context, msg JSONRPCMessage) error {
|
||||||
// check that first. Otherwise, it is a request or notification, and we may
|
// check that first. Otherwise, it is a request or notification, and we may
|
||||||
// want to collect it into a batch before sending, if we're configured to use
|
// want to collect it into a batch before sending, if we're configured to use
|
||||||
// outgoing batches.
|
// outgoing batches.
|
||||||
if resp, ok := msg.(*JSONRPCResponse); ok {
|
if resp, ok := msg.(*jsonrpc.Response); ok {
|
||||||
if batch, ok := t.updateBatch(resp); ok {
|
if batch, ok := t.updateBatch(resp); ok {
|
||||||
if len(batch) > 0 {
|
if len(batch) > 0 {
|
||||||
data, err := marshalMessages(batch)
|
data, err := marshalMessages(batch)
|
||||||
|
|
@ -489,7 +479,7 @@ func (t *ioConn) Close() error {
|
||||||
return t.rwc.Close()
|
return t.rwc.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func marshalMessages[T JSONRPCMessage](msgs []T) ([]byte, error) {
|
func marshalMessages[T jsonrpc.Message](msgs []T) ([]byte, error) {
|
||||||
var rawMsgs []json.RawMessage
|
var rawMsgs []json.RawMessage
|
||||||
for _, msg := range msgs {
|
for _, msg := range msgs {
|
||||||
raw, err := jsonrpc2.EncodeMessage(msg)
|
raw, err := jsonrpc2.EncodeMessage(msg)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2"
|
"github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2"
|
||||||
|
"github.com/modelcontextprotocol/go-sdk/jsonrpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBatchFraming(t *testing.T) {
|
func TestBatchFraming(t *testing.T) {
|
||||||
|
|
@ -22,10 +23,10 @@ func TestBatchFraming(t *testing.T) {
|
||||||
|
|
||||||
r, w := io.Pipe()
|
r, w := io.Pipe()
|
||||||
tport := newIOConn(rwc{r, w})
|
tport := newIOConn(rwc{r, w})
|
||||||
tport.outgoingBatch = make([]JSONRPCMessage, 0, 2)
|
tport.outgoingBatch = make([]jsonrpc.Message, 0, 2)
|
||||||
|
|
||||||
// Read the two messages into a channel, for easy testing later.
|
// Read the two messages into a channel, for easy testing later.
|
||||||
read := make(chan JSONRPCMessage)
|
read := make(chan jsonrpc.Message)
|
||||||
go func() {
|
go func() {
|
||||||
for range 2 {
|
for range 2 {
|
||||||
msg, _ := tport.Read(ctx)
|
msg, _ := tport.Read(ctx)
|
||||||
|
|
@ -34,7 +35,7 @@ func TestBatchFraming(t *testing.T) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// The first write should not yet be observed by the reader.
|
// The first write should not yet be observed by the reader.
|
||||||
tport.Write(ctx, &JSONRPCRequest{ID: jsonrpc2.Int64ID(1), Method: "test"})
|
tport.Write(ctx, &jsonrpc.Request{ID: jsonrpc2.Int64ID(1), Method: "test"})
|
||||||
select {
|
select {
|
||||||
case got := <-read:
|
case got := <-read:
|
||||||
t.Fatalf("after one write, got message %v", got)
|
t.Fatalf("after one write, got message %v", got)
|
||||||
|
|
@ -42,10 +43,10 @@ func TestBatchFraming(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ...but the second write causes both messages to be observed.
|
// ...but the second write causes both messages to be observed.
|
||||||
tport.Write(ctx, &JSONRPCRequest{ID: jsonrpc2.Int64ID(2), Method: "test"})
|
tport.Write(ctx, &jsonrpc.Request{ID: jsonrpc2.Int64ID(2), Method: "test"})
|
||||||
for _, want := range []int64{1, 2} {
|
for _, want := range []int64{1, 2} {
|
||||||
got := <-read
|
got := <-read
|
||||||
if got := got.(*JSONRPCRequest).ID.Raw(); got != want {
|
if got := got.(*jsonrpc.Request).ID.Raw(); got != want {
|
||||||
t.Errorf("got message #%d, want #%d", got, want)
|
t.Errorf("got message #%d, want #%d", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue