fix(go.sum): update ResolveSpec dependency to v1.0.87
CI / build-and-test (push) Failing after 1s
Release / release (push) Failing after 19m26s

This commit is contained in:
Hein
2026-06-23 13:17:16 +02:00
parent 0227912325
commit 1adf50e3db
2436 changed files with 1078758 additions and 114 deletions
+19
View File
@@ -0,0 +1,19 @@
// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
// Use of this source code is governed by the license
// that can be found in the LICENSE file.
// Package json provides internal JSON utilities.
package json
import (
"bytes"
"github.com/segmentio/encoding/json"
)
func Unmarshal(data []byte, v any) error {
dec := json.NewDecoder(bytes.NewReader(data))
dec.DontMatchCaseInsensitiveStructFields()
return dec.Decode(v)
}
+842
View File
@@ -0,0 +1,842 @@
// Copyright 2018 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 jsonrpc2
import (
"context"
"errors"
"fmt"
"io"
"sync"
"sync/atomic"
"time"
"github.com/modelcontextprotocol/go-sdk/internal/json"
)
// Binder builds a connection configuration.
// This may be used in servers to generate a new configuration per connection.
// ConnectionOptions itself implements Binder returning itself unmodified, to
// allow for the simple cases where no per connection information is needed.
type Binder interface {
// Bind returns the ConnectionOptions to use when establishing the passed-in
// Connection.
//
// The connection is not ready to use when Bind is called,
// but Bind may close it without reading or writing to it.
Bind(context.Context, *Connection) ConnectionOptions
}
// A BinderFunc implements the Binder interface for a standalone Bind function.
type BinderFunc func(context.Context, *Connection) ConnectionOptions
func (f BinderFunc) Bind(ctx context.Context, c *Connection) ConnectionOptions {
return f(ctx, c)
}
var _ Binder = BinderFunc(nil)
// ConnectionOptions holds the options for new connections.
type ConnectionOptions struct {
// Framer allows control over the message framing and encoding.
// If nil, HeaderFramer will be used.
Framer Framer
// Preempter allows registration of a pre-queue message handler.
// If nil, no messages will be preempted.
Preempter Preempter
// Handler is used as the queued message handler for inbound messages.
// If nil, all responses will be ErrNotHandled.
Handler Handler
// OnInternalError, if non-nil, is called with any internal errors that occur
// while serving the connection, such as protocol errors or invariant
// violations. (If nil, internal errors result in panics.)
OnInternalError func(error)
}
// Connection manages the jsonrpc2 protocol, connecting responses back to their
// calls. Connection is bidirectional; it does not have a designated server or
// client end.
//
// Note that the word 'Connection' is overloaded: the mcp.Connection represents
// the bidirectional stream of messages between client an server. The
// jsonrpc2.Connection layers RPC logic on top of that stream, dispatching RPC
// handlers, and correlating requests with responses from the peer.
//
// Some of the complexity of the Connection type is grown out of its usage in
// gopls: it could probably be simplified based on our usage in MCP.
type Connection struct {
seq int64 // must only be accessed using atomic operations
stateMu sync.Mutex
state inFlightState // accessed only in updateInFlight
done chan struct{} // closed (under stateMu) when state.closed is true and all goroutines have completed
writer Writer
handler Handler
onInternalError func(error)
onDone func()
}
// inFlightState records the state of the incoming and outgoing calls on a
// Connection.
type inFlightState struct {
connClosing bool // true when the Connection's Close method has been called
reading bool // true while the readIncoming goroutine is running
readErr error // non-nil when the readIncoming goroutine exits (typically io.EOF)
writeErr error // non-nil if a call to the Writer has failed with a non-canceled Context
// closer shuts down and cleans up the Reader and Writer state, ideally
// interrupting any Read or Write call that is currently blocked. It is closed
// when the state is idle and one of: connClosing is true, readErr is non-nil,
// or writeErr is non-nil.
//
// After the closer has been invoked, the closer field is set to nil
// and the closeErr field is simultaneously set to its result.
closer io.Closer
closeErr error // error returned from closer.Close
outgoingCalls map[ID]*AsyncCall // calls only
outgoingNotifications int // # of notifications awaiting "write"
// incoming stores the total number of incoming calls and notifications
// that have not yet written or processed a result.
incoming int
incomingByID map[ID]*incomingRequest // calls only
// handlerQueue stores the backlog of calls and notifications that were not
// already handled by a preempter.
// The queue does not include the request currently being handled (if any).
handlerQueue []*incomingRequest
handlerRunning bool
}
// updateInFlight locks the state of the connection's in-flight requests, allows
// f to mutate that state, and closes the connection if it is idle and either
// is closing or has a read or write error.
func (c *Connection) updateInFlight(f func(*inFlightState)) {
c.stateMu.Lock()
defer c.stateMu.Unlock()
s := &c.state
f(s)
select {
case <-c.done:
// The connection was already completely done at the start of this call to
// updateInFlight, so it must remain so. (The call to f should have noticed
// that and avoided making any updates that would cause the state to be
// non-idle.)
if !s.idle() {
panic("jsonrpc2: updateInFlight transitioned to non-idle when already done")
}
return
default:
}
if s.idle() && s.shuttingDown(ErrUnknown) != nil {
if s.closer != nil {
s.closeErr = s.closer.Close()
s.closer = nil // prevent duplicate Close calls
}
if s.reading {
// The readIncoming goroutine is still running. Our call to Close should
// cause it to exit soon, at which point it will make another call to
// updateInFlight, set s.reading to false, and mark the Connection done.
} else {
// The readIncoming goroutine has exited, or never started to begin with.
// Since everything else is idle, we're completely done.
if c.onDone != nil {
c.onDone()
}
close(c.done)
}
}
}
// idle reports whether the connection is in a state with no pending calls or
// notifications.
//
// If idle returns true, the readIncoming goroutine may still be running,
// but no other goroutines are doing work on behalf of the connection.
func (s *inFlightState) idle() bool {
return len(s.outgoingCalls) == 0 && s.outgoingNotifications == 0 && s.incoming == 0 && !s.handlerRunning
}
// shuttingDown reports whether the connection is in a state that should
// disallow new (incoming and outgoing) calls. It returns either nil or
// an error that is or wraps the provided errClosing.
func (s *inFlightState) shuttingDown(errClosing error) error {
if s.connClosing {
// If Close has been called explicitly, it doesn't matter what state the
// Reader and Writer are in: we shouldn't be starting new work because the
// caller told us not to start new work.
return errClosing
}
if s.readErr != nil {
// If the read side of the connection is broken, we cannot read new call
// requests, and cannot read responses to our outgoing calls.
return fmt.Errorf("%w: %v", errClosing, s.readErr)
}
if s.writeErr != nil {
// If the write side of the connection is broken, we cannot write responses
// for incoming calls, and cannot write requests for outgoing calls.
return fmt.Errorf("%w: %v", errClosing, s.writeErr)
}
return nil
}
// incomingRequest is used to track an incoming request as it is being handled
type incomingRequest struct {
*Request // the request being processed
ctx context.Context
cancel context.CancelFunc
}
// Bind returns the options unmodified.
func (o ConnectionOptions) Bind(context.Context, *Connection) ConnectionOptions {
return o
}
// A ConnectionConfig configures a bidirectional jsonrpc2 connection.
type ConnectionConfig struct {
Reader Reader // required
Writer Writer // required
Closer io.Closer // required
Preempter Preempter // optional
Bind func(*Connection) Handler // required
OnDone func() // optional
OnInternalError func(error) // optional
}
// NewConnection creates a new [Connection] object and starts processing
// incoming messages.
func NewConnection(ctx context.Context, cfg ConnectionConfig) *Connection {
ctx = notDone{ctx}
c := &Connection{
state: inFlightState{closer: cfg.Closer},
done: make(chan struct{}),
writer: cfg.Writer,
onDone: cfg.OnDone,
onInternalError: cfg.OnInternalError,
}
c.handler = cfg.Bind(c)
c.start(ctx, cfg.Reader, cfg.Preempter)
return c
}
// bindConnection creates a new connection and runs it.
//
// This is used by the Dial and Serve functions to build the actual connection.
//
// The connection is closed automatically (and its resources cleaned up) when
// the last request has completed after the underlying ReadWriteCloser breaks,
// but it may be stopped earlier by calling Close (for a clean shutdown).
func bindConnection(bindCtx context.Context, rwc io.ReadWriteCloser, binder Binder, onDone func()) *Connection {
// TODO: Should we create a new event span here?
// This will propagate cancellation from ctx; should it?
ctx := notDone{bindCtx}
c := &Connection{
state: inFlightState{closer: rwc},
done: make(chan struct{}),
onDone: onDone,
}
// It's tempting to set a finalizer on c to verify that the state has gone
// idle when the connection becomes unreachable. Unfortunately, the Binder
// interface makes that unsafe: it allows the Handler to close over the
// Connection, which could create a reference cycle that would cause the
// Connection to become uncollectable.
options := binder.Bind(bindCtx, c)
framer := options.Framer
if framer == nil {
framer = HeaderFramer()
}
c.handler = options.Handler
if c.handler == nil {
c.handler = defaultHandler{}
}
c.onInternalError = options.OnInternalError
c.writer = framer.Writer(rwc)
reader := framer.Reader(rwc)
c.start(ctx, reader, options.Preempter)
return c
}
func (c *Connection) start(ctx context.Context, reader Reader, preempter Preempter) {
c.updateInFlight(func(s *inFlightState) {
select {
case <-c.done:
// Bind already closed the connection; don't start a goroutine to read it.
return
default:
}
// The goroutine started here will continue until the underlying stream is closed.
//
// (If the Binder closed the Connection already, this should error out and
// return almost immediately.)
s.reading = true
go c.readIncoming(ctx, reader, preempter)
})
}
// Notify invokes the target method but does not wait for a response.
// The params will be marshaled to JSON before sending over the wire, and will
// be handed to the method invoked.
func (c *Connection) Notify(ctx context.Context, method string, params any) (err error) {
attempted := false
defer func() {
if attempted {
c.updateInFlight(func(s *inFlightState) {
s.outgoingNotifications--
})
}
}()
c.updateInFlight(func(s *inFlightState) {
// If the connection is shutting down, allow outgoing notifications only if
// there is at least one call still in flight. The number of calls in flight
// cannot increase once shutdown begins, and allowing outgoing notifications
// may permit notifications that will cancel in-flight calls.
if len(s.outgoingCalls) == 0 && len(s.incomingByID) == 0 {
err = s.shuttingDown(ErrClientClosing)
if err != nil {
return
}
}
s.outgoingNotifications++
attempted = true
})
if err != nil {
return err
}
notify, err := NewNotification(method, params)
if err != nil {
return fmt.Errorf("marshaling notify parameters: %v", err)
}
return c.write(ctx, notify)
}
// Call invokes the target method and returns an object that can be used to await the response.
// The params will be marshaled to JSON before sending over the wire, and will
// be handed to the method invoked.
// You do not have to wait for the response, it can just be ignored if not needed.
// If sending the call failed, the response will be ready and have the error in it.
func (c *Connection) Call(ctx context.Context, method string, params any) *AsyncCall {
// Generate a new request identifier.
id := Int64ID(atomic.AddInt64(&c.seq, 1))
ac := &AsyncCall{
id: id,
ready: make(chan struct{}),
}
// When this method returns, either ac is retired, or the request has been
// written successfully and the call is awaiting a response (to be provided by
// the readIncoming goroutine).
call, err := NewCall(ac.id, method, params)
if err != nil {
ac.retire(&Response{ID: id, Error: fmt.Errorf("marshaling call parameters: %w", err)})
return ac
}
c.updateInFlight(func(s *inFlightState) {
err = s.shuttingDown(ErrClientClosing)
if err != nil {
return
}
if s.outgoingCalls == nil {
s.outgoingCalls = make(map[ID]*AsyncCall)
}
s.outgoingCalls[ac.id] = ac
})
if err != nil {
ac.retire(&Response{ID: id, Error: err})
return ac
}
if err := c.write(ctx, call); err != nil {
// Sending failed. We will never get a response, so deliver a fake one if it
// wasn't already retired by the connection breaking.
c.Retire(ac, err)
}
return ac
}
// Retire stops tracking the call, and reports err as its terminal error.
//
// Retire is safe to call multiple times: if the call is already no longer
// tracked, Retire is a no op.
func (c *Connection) Retire(ac *AsyncCall, err error) {
c.updateInFlight(func(s *inFlightState) {
if s.outgoingCalls[ac.id] == ac {
delete(s.outgoingCalls, ac.id)
ac.retire(&Response{ID: ac.id, Error: err})
} else {
// ac was already retired elsewhere.
}
})
}
// Async, signals that the current jsonrpc2 request may be handled
// asynchronously to subsequent requests, when ctx is the request context.
//
// Async must be called at most once on each request's context (and its
// descendants).
func Async(ctx context.Context) {
if r, ok := ctx.Value(asyncKey).(*releaser); ok {
r.release(false)
}
}
type asyncKeyType struct{}
var asyncKey = asyncKeyType{}
// A releaser implements concurrency safe 'releasing' of async requests. (A
// request is released when it is allowed to run concurrent with other
// requests, via a call to [Async].)
type releaser struct {
mu sync.Mutex
ch chan struct{}
released bool
}
// release closes the associated channel. If soft is set, multiple calls to
// release are allowed.
func (r *releaser) release(soft bool) {
r.mu.Lock()
defer r.mu.Unlock()
if r.released {
if !soft {
panic("jsonrpc2.Async called multiple times")
}
} else {
close(r.ch)
r.released = true
}
}
type AsyncCall struct {
id ID
ready chan struct{} // closed after response has been set
response *Response
}
// ID used for this call.
// This can be used to cancel the call if needed.
func (ac *AsyncCall) ID() ID { return ac.id }
// IsReady can be used to check if the result is already prepared.
// This is guaranteed to return true on a result for which Await has already
// returned, or a call that failed to send in the first place.
func (ac *AsyncCall) IsReady() bool {
select {
case <-ac.ready:
return true
default:
return false
}
}
// retire processes the response to the call.
//
// It is an error to call retire more than once: retire is guarded by the
// connection's outgoingCalls map.
func (ac *AsyncCall) retire(response *Response) {
select {
case <-ac.ready:
panic(fmt.Sprintf("jsonrpc2: retire called twice for ID %v", ac.id))
default:
}
ac.response = response
close(ac.ready)
}
// Await waits for (and decodes) the results of a Call.
// The response will be unmarshaled from JSON into the result.
//
// If the call is cancelled due to context cancellation, the result is
// ctx.Err().
func (ac *AsyncCall) Await(ctx context.Context, result any) error {
select {
case <-ctx.Done():
return ctx.Err()
case <-ac.ready:
}
if ac.response.Error != nil {
return ac.response.Error
}
if result == nil {
return nil
}
return json.Unmarshal(ac.response.Result, result)
}
// Cancel cancels the Context passed to the Handle call for the inbound message
// with the given ID.
//
// Cancel will not complain if the ID is not a currently active message, and it
// will not cause any messages that have not arrived yet with that ID to be
// cancelled.
func (c *Connection) Cancel(id ID) {
var req *incomingRequest
c.updateInFlight(func(s *inFlightState) {
req = s.incomingByID[id]
})
if req != nil {
req.cancel()
}
}
// Wait blocks until the connection is fully closed, but does not close it.
func (c *Connection) Wait() error {
return c.wait(true)
}
// wait for the connection to close, and aggregates the most cause of its
// termination, if abnormal.
//
// The fromWait argument allows this logic to be shared with Close, where we
// only want to expose the closeErr.
//
// (Previously, Wait also only returned the closeErr, which was misleading if
// the connection was broken for another reason).
func (c *Connection) wait(fromWait bool) error {
var err error
<-c.done
c.updateInFlight(func(s *inFlightState) {
if fromWait {
if !errors.Is(s.readErr, io.EOF) {
err = s.readErr
}
if err == nil && !errors.Is(s.writeErr, io.EOF) {
err = s.writeErr
}
}
if err == nil {
err = s.closeErr
}
})
return err
}
// Close stops accepting new requests, waits for in-flight requests and enqueued
// Handle calls to complete, and then closes the underlying stream.
//
// After the start of a Close, notification requests (that lack IDs and do not
// receive responses) will continue to be passed to the Preempter, but calls
// with IDs will receive immediate responses with ErrServerClosing, and no new
// requests (not even notifications!) will be enqueued to the Handler.
func (c *Connection) Close() error {
// Stop handling new requests, and interrupt the reader (by closing the
// connection) as soon as the active requests finish.
c.updateInFlight(func(s *inFlightState) { s.connClosing = true })
return c.wait(false)
}
// readIncoming collects inbound messages from the reader and delivers them, either responding
// to outgoing calls or feeding requests to the queue.
func (c *Connection) readIncoming(ctx context.Context, reader Reader, preempter Preempter) {
var err error
for {
var msg Message
msg, err = reader.Read(ctx)
if err != nil {
break
}
switch msg := msg.(type) {
case *Request:
c.acceptRequest(ctx, msg, preempter)
case *Response:
c.updateInFlight(func(s *inFlightState) {
if ac, ok := s.outgoingCalls[msg.ID]; ok {
delete(s.outgoingCalls, msg.ID)
ac.retire(msg)
} else {
// TODO: How should we report unexpected responses?
}
})
default:
c.internalErrorf("Read returned an unexpected message of type %T", msg)
}
}
c.updateInFlight(func(s *inFlightState) {
s.reading = false
s.readErr = err
// Retire any outgoing requests that were still in flight: with the Reader no
// longer being processed, they necessarily cannot receive a response.
for id, ac := range s.outgoingCalls {
ac.retire(&Response{ID: id, Error: err})
}
s.outgoingCalls = nil
})
}
// acceptRequest either handles msg synchronously or enqueues it to be handled
// asynchronously.
func (c *Connection) acceptRequest(ctx context.Context, msg *Request, preempter Preempter) {
// In theory notifications cannot be cancelled, but we build them a cancel
// context anyway.
reqCtx, cancel := context.WithCancel(ctx)
req := &incomingRequest{
Request: msg,
ctx: reqCtx,
cancel: cancel,
}
// If the request is a call, add it to the incoming map so it can be
// cancelled (or responded) by ID.
var err error
c.updateInFlight(func(s *inFlightState) {
s.incoming++
if req.IsCall() {
if s.incomingByID[req.ID] != nil {
err = fmt.Errorf("%w: request ID %v already in use", ErrInvalidRequest, req.ID)
req.ID = ID{} // Don't misattribute this error to the existing request.
return
}
if s.incomingByID == nil {
s.incomingByID = make(map[ID]*incomingRequest)
}
s.incomingByID[req.ID] = req
// When shutting down, reject all new Call requests, even if they could
// theoretically be handled by the preempter. The preempter could return
// ErrAsyncResponse, which would increase the amount of work in flight
// when we're trying to ensure that it strictly decreases.
err = s.shuttingDown(ErrServerClosing)
}
})
if err != nil {
c.processResult("acceptRequest", req, nil, err)
return
}
if preempter != nil {
result, err := preempter.Preempt(req.ctx, req.Request)
if !errors.Is(err, ErrNotHandled) {
c.processResult("Preempt", req, result, err)
return
}
}
c.updateInFlight(func(s *inFlightState) {
// If the connection is shutting down, don't enqueue anything to the
// handler — not even notifications. That ensures that if the handler
// continues to make progress, it will eventually become idle and
// close the connection.
err = s.shuttingDown(ErrServerClosing)
if err != nil {
return
}
// We enqueue requests that have not been preempted to an unbounded slice.
// Unfortunately, we cannot in general limit the size of the handler
// queue: we have to read every response that comes in on the wire
// (because it may be responding to a request issued by, say, an
// asynchronous handler), and in order to get to that response we have
// to read all of the requests that came in ahead of it.
s.handlerQueue = append(s.handlerQueue, req)
if !s.handlerRunning {
// We start the handleAsync goroutine when it has work to do, and let it
// exit when the queue empties.
//
// Otherwise, in order to synchronize the handler we would need some other
// goroutine (probably readIncoming?) to explicitly wait for handleAsync
// to finish, and that would complicate error reporting: either the error
// report from the goroutine would be blocked on the handler emptying its
// queue (which was tried, and introduced a deadlock detected by
// TestCloseCallRace), or the error would need to be reported separately
// from synchronizing completion. Allowing the handler goroutine to exit
// when idle seems simpler than trying to implement either of those
// alternatives correctly.
s.handlerRunning = true
go c.handleAsync()
}
})
if err != nil {
c.processResult("acceptRequest", req, nil, err)
}
}
// handleAsync invokes the handler on the requests in the handler queue
// sequentially until the queue is empty.
func (c *Connection) handleAsync() {
for {
var req *incomingRequest
c.updateInFlight(func(s *inFlightState) {
if len(s.handlerQueue) > 0 {
req, s.handlerQueue = s.handlerQueue[0], s.handlerQueue[1:]
} else {
s.handlerRunning = false
}
})
if req == nil {
return
}
// Only deliver to the Handler if not already canceled.
if err := req.ctx.Err(); err != nil {
c.updateInFlight(func(s *inFlightState) {
if s.writeErr != nil {
// Assume that req.ctx was canceled due to s.writeErr.
// TODO(#51365): use a Context API to plumb this through req.ctx.
err = fmt.Errorf("%w: %v", ErrServerClosing, s.writeErr)
}
})
c.processResult("handleAsync", req, nil, err)
continue
}
releaser := &releaser{ch: make(chan struct{})}
ctx := context.WithValue(req.ctx, asyncKey, releaser)
go func() {
defer releaser.release(true)
result, err := c.handler.Handle(ctx, req.Request)
c.processResult(c.handler, req, result, err)
}()
<-releaser.ch
}
}
// processResult processes the result of a request and, if appropriate, sends a response.
func (c *Connection) processResult(from any, req *incomingRequest, result any, err error) error {
switch err {
case ErrNotHandled, ErrMethodNotFound:
// Add detail describing the unhandled method.
err = fmt.Errorf("%w: %q", ErrMethodNotFound, req.Method)
}
if result != nil && err != nil {
c.internalErrorf("%#v returned a non-nil result with a non-nil error for %s:\n%v\n%#v", from, req.Method, err, result)
result = nil // Discard the spurious result and respond with err.
}
if req.IsCall() {
if result == nil && err == nil {
err = c.internalErrorf("%#v returned a nil result and nil error for a %q Request that requires a Response", from, req.Method)
}
response, respErr := NewResponse(req.ID, result, err)
// The caller could theoretically reuse the request's ID as soon as we've
// sent the response, so ensure that it is removed from the incoming map
// before sending.
c.updateInFlight(func(s *inFlightState) {
delete(s.incomingByID, req.ID)
})
if respErr == nil {
writeErr := c.write(notDone{req.ctx}, response)
if err == nil {
err = writeErr
}
} else {
err = c.internalErrorf("%#v returned a malformed result for %q: %w", from, req.Method, respErr)
}
} else { // req is a notification
if result != nil {
err = c.internalErrorf("%#v returned a non-nil result for a %q Request without an ID", from, req.Method)
} else if err != nil {
err = fmt.Errorf("%w: %q notification failed: %v", ErrInternal, req.Method, err)
}
}
if err != nil {
// TODO: can/should we do anything with this error beyond writing it to the event log?
// (Is this the right label to attach to the log?)
}
// Cancel the request to free any associated resources.
req.cancel()
c.updateInFlight(func(s *inFlightState) {
if s.incoming == 0 {
panic("jsonrpc2: processResult called when incoming count is already zero")
}
s.incoming--
})
return nil
}
// write is used by all things that write outgoing messages, including replies.
// it makes sure that writes are atomic
func (c *Connection) write(ctx context.Context, msg Message) error {
var err error
// Fail writes immediately if the connection is shutting down.
//
// TODO(rfindley): should we allow cancellation notifications through? It
// could be the case that writes can still succeed.
c.updateInFlight(func(s *inFlightState) {
err = s.shuttingDown(ErrServerClosing)
})
if err == nil {
err = c.writer.Write(ctx, msg)
}
// For cancelled or rejected requests, we don't set the writeErr (which would
// break the connection). They can just be returned to the caller.
if err != nil && ctx.Err() == nil && !errors.Is(err, ErrRejected) {
// The call to Write failed, and since ctx.Err() is nil we can't attribute
// the failure (even indirectly) to Context cancellation. The writer appears
// to be broken, and future writes are likely to also fail.
//
// If the read side of the connection is also broken, we might not even be
// able to receive cancellation notifications. Since we can't reliably write
// the results of incoming calls and can't receive explicit cancellations,
// cancel the calls now.
c.updateInFlight(func(s *inFlightState) {
if s.writeErr == nil {
s.writeErr = err
for _, r := range s.incomingByID {
r.cancel()
}
}
})
}
return err
}
// internalErrorf reports an internal error. By default it panics, but if
// c.onInternalError is non-nil it instead calls that and returns an error
// wrapping ErrInternal.
func (c *Connection) internalErrorf(format string, args ...any) error {
err := fmt.Errorf(format, args...)
if c.onInternalError == nil {
panic("jsonrpc2: " + err.Error())
}
c.onInternalError(err)
return fmt.Errorf("%w: %v", ErrInternal, err)
}
// notDone is a context.Context wrapper that returns a nil Done channel.
type notDone struct{ ctx context.Context }
func (ic notDone) Value(key any) any {
return ic.ctx.Value(key)
}
func (notDone) Done() <-chan struct{} { return nil }
func (notDone) Err() error { return nil }
func (notDone) Deadline() (time.Time, bool) { return time.Time{}, false }
@@ -0,0 +1,208 @@
// Copyright 2018 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 jsonrpc2
import (
"bufio"
"context"
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"sync"
)
// Reader abstracts the transport mechanics from the JSON RPC protocol.
// A Conn reads messages from the reader it was provided on construction,
// and assumes that each call to Read fully transfers a single message,
// or returns an error.
//
// A reader is not safe for concurrent use, it is expected it will be used by
// a single Conn in a safe manner.
type Reader interface {
// Read gets the next message from the stream.
Read(context.Context) (Message, error)
}
// Writer abstracts the transport mechanics from the JSON RPC protocol.
// A Conn writes messages using the writer it was provided on construction,
// and assumes that each call to Write fully transfers a single message,
// or returns an error.
//
// A writer must be safe for concurrent use, as writes may occur concurrently
// in practice: libraries may make calls or respond to requests asynchronously.
type Writer interface {
// Write sends a message to the stream.
Write(context.Context, Message) error
}
// Framer wraps low level byte readers and writers into jsonrpc2 message
// readers and writers.
// It is responsible for the framing and encoding of messages into wire form.
//
// TODO(rfindley): rethink the framer interface, as with JSONRPC2 batching
// there is a need for Reader and Writer to be correlated, and while the
// implementation of framing here allows that, it is not made explicit by the
// interface.
//
// Perhaps a better interface would be
//
// Frame(io.ReadWriteCloser) (Reader, Writer).
type Framer interface {
// Reader wraps a byte reader into a message reader.
Reader(io.Reader) Reader
// Writer wraps a byte writer into a message writer.
Writer(io.Writer) Writer
}
// RawFramer returns a new Framer.
// The messages are sent with no wrapping, and rely on json decode consistency
// to determine message boundaries.
func RawFramer() Framer { return rawFramer{} }
type rawFramer struct{}
type rawReader struct{ in *json.Decoder }
type rawWriter struct {
mu sync.Mutex
out io.Writer
}
func (rawFramer) Reader(rw io.Reader) Reader {
return &rawReader{in: json.NewDecoder(rw)}
}
func (rawFramer) Writer(rw io.Writer) Writer {
return &rawWriter{out: rw}
}
func (r *rawReader) Read(ctx context.Context) (Message, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
var raw json.RawMessage
if err := r.in.Decode(&raw); err != nil {
return nil, err
}
msg, err := DecodeMessage(raw)
return msg, err
}
func (w *rawWriter) Write(ctx context.Context, msg Message) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
data, err := EncodeMessage(msg)
if err != nil {
return fmt.Errorf("marshaling message: %v", err)
}
w.mu.Lock()
defer w.mu.Unlock()
_, err = w.out.Write(data)
return err
}
// HeaderFramer returns a new Framer.
// The messages are sent with HTTP content length and MIME type headers.
// This is the format used by LSP and others.
func HeaderFramer() Framer { return headerFramer{} }
type headerFramer struct{}
type headerReader struct{ in *bufio.Reader }
type headerWriter struct {
mu sync.Mutex
out io.Writer
}
func (headerFramer) Reader(rw io.Reader) Reader {
return &headerReader{in: bufio.NewReader(rw)}
}
func (headerFramer) Writer(rw io.Writer) Writer {
return &headerWriter{out: rw}
}
func (r *headerReader) Read(ctx context.Context) (Message, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
firstRead := true // to detect a clean EOF below
var contentLength int64
// read the header, stop on the first empty line
for {
line, err := r.in.ReadString('\n')
if err != nil {
if err == io.EOF {
if firstRead && line == "" {
return nil, io.EOF // clean EOF
}
err = io.ErrUnexpectedEOF
}
return nil, fmt.Errorf("failed reading header line: %w", err)
}
firstRead = false
line = strings.TrimSpace(line)
// check we have a header line
if line == "" {
break
}
colon := strings.IndexRune(line, ':')
if colon < 0 {
return nil, fmt.Errorf("invalid header line %q", line)
}
name, value := line[:colon], strings.TrimSpace(line[colon+1:])
switch {
case strings.EqualFold(name, "Content-Length"):
if contentLength, err = strconv.ParseInt(value, 10, 32); err != nil {
return nil, fmt.Errorf("failed parsing Content-Length: %v", value)
}
if contentLength <= 0 {
return nil, fmt.Errorf("invalid Content-Length: %v", contentLength)
}
default:
// ignoring unknown headers
}
}
if contentLength == 0 {
return nil, fmt.Errorf("missing Content-Length header")
}
data := make([]byte, contentLength)
_, err := io.ReadFull(r.in, data)
if err != nil {
return nil, err
}
msg, err := DecodeMessage(data)
return msg, err
}
func (w *headerWriter) Write(ctx context.Context, msg Message) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
w.mu.Lock()
defer w.mu.Unlock()
data, err := EncodeMessage(msg)
if err != nil {
return fmt.Errorf("marshaling message: %v", err)
}
_, err = fmt.Fprintf(w.out, "Content-Length: %v\r\n\r\n", len(data))
if err == nil {
_, err = w.out.Write(data)
}
return err
}
@@ -0,0 +1,121 @@
// Copyright 2018 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 jsonrpc2 is a minimal implementation of the JSON RPC 2 spec.
// https://www.jsonrpc.org/specification
// It is intended to be compatible with other implementations at the wire level.
package jsonrpc2
import (
"context"
"errors"
)
var (
// ErrIdleTimeout is returned when serving timed out waiting for new connections.
ErrIdleTimeout = errors.New("timed out waiting for new connections")
// ErrNotHandled is returned from a Handler or Preempter to indicate it did
// not handle the request.
//
// If a Handler returns ErrNotHandled, the server replies with
// ErrMethodNotFound.
ErrNotHandled = errors.New("JSON RPC not handled")
)
// Preempter handles messages on a connection before they are queued to the main
// handler.
// Primarily this is used for cancel handlers or notifications for which out of
// order processing is not an issue.
type Preempter interface {
// Preempt is invoked for each incoming request before it is queued for handling.
//
// If Preempt returns ErrNotHandled, the request will be queued,
// and eventually passed to a Handle call.
//
// Otherwise, the result and error are processed as if returned by Handle.
//
// Preempt must not block. (The Context passed to it is for Values only.)
Preempt(ctx context.Context, req *Request) (result any, err error)
}
// A PreempterFunc implements the Preempter interface for a standalone Preempt function.
type PreempterFunc func(ctx context.Context, req *Request) (any, error)
func (f PreempterFunc) Preempt(ctx context.Context, req *Request) (any, error) {
return f(ctx, req)
}
var _ Preempter = PreempterFunc(nil)
// Handler handles messages on a connection.
type Handler interface {
// Handle is invoked sequentially for each incoming request that has not
// already been handled by a Preempter.
//
// If the Request has a nil ID, Handle must return a nil result,
// and any error may be logged but will not be reported to the caller.
//
// If the Request has a non-nil ID, Handle must return either a
// non-nil, JSON-marshalable result, or a non-nil error.
//
// The Context passed to Handle will be canceled if the
// connection is broken or the request is canceled or completed.
// (If Handle returns ErrAsyncResponse, ctx will remain uncanceled
// until either Cancel or Respond is called for the request's ID.)
Handle(ctx context.Context, req *Request) (result any, err error)
}
type defaultHandler struct{}
func (defaultHandler) Preempt(context.Context, *Request) (any, error) {
return nil, ErrNotHandled
}
func (defaultHandler) Handle(context.Context, *Request) (any, error) {
return nil, ErrNotHandled
}
// A HandlerFunc implements the Handler interface for a standalone Handle function.
type HandlerFunc func(ctx context.Context, req *Request) (any, error)
func (f HandlerFunc) Handle(ctx context.Context, req *Request) (any, error) {
return f(ctx, req)
}
var _ Handler = HandlerFunc(nil)
// async is a small helper for operations with an asynchronous result that you
// can wait for.
type async struct {
ready chan struct{} // closed when done
firstErr chan error // 1-buffered; contains either nil or the first non-nil error
}
func newAsync() *async {
var a async
a.ready = make(chan struct{})
a.firstErr = make(chan error, 1)
a.firstErr <- nil
return &a
}
func (a *async) done() {
close(a.ready)
}
func (a *async) wait() error {
<-a.ready
err := <-a.firstErr
a.firstErr <- err
return err
}
func (a *async) setError(err error) {
storedErr := <-a.firstErr
if storedErr == nil {
storedErr = err
}
a.firstErr <- storedErr
}
@@ -0,0 +1,242 @@
// Copyright 2018 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 jsonrpc2
import (
"bytes"
"encoding/json"
"errors"
"fmt"
internaljson "github.com/modelcontextprotocol/go-sdk/internal/json"
"github.com/modelcontextprotocol/go-sdk/internal/mcpgodebug"
)
// ID is a Request identifier, which is defined by the spec to be a string, integer, or null.
// https://www.jsonrpc.org/specification#request_object
type ID struct {
value any
}
// MakeID coerces the given Go value to an ID. The value should be the
// default JSON marshaling of a Request identifier: nil, float64, or string.
//
// Returns an error if the value type was not a valid Request ID type.
//
// TODO: ID can't be a json.Marshaler/Unmarshaler, because we want to omitzero.
// Simplify this package by making ID json serializable once we can rely on
// omitzero.
func MakeID(v any) (ID, error) {
switch v := v.(type) {
case nil:
return ID{}, nil
case float64:
return Int64ID(int64(v)), nil
case string:
return StringID(v), nil
}
return ID{}, fmt.Errorf("%w: invalid ID type %T", ErrParse, v)
}
// Message is the interface to all jsonrpc2 message types.
// They share no common functionality, but are a closed set of concrete types
// that are allowed to implement this interface. The message types are *Request
// and *Response.
type Message interface {
// marshal builds the wire form from the API form.
// It is private, which makes the set of Message implementations closed.
marshal(to *wireCombined)
}
// Request is a Message sent to a peer to request behavior.
// If it has an ID it is a call, otherwise it is a notification.
type Request struct {
// ID of this request, used to tie the Response back to the request.
// This will be nil for notifications.
ID ID
// Method is a string containing the method name to invoke.
Method string
// Params is either a struct or an array with the parameters of the method.
Params json.RawMessage
// Extra is additional information that does not appear on the wire. It can be
// used to pass information from the application to the underlying transport.
Extra any
}
// Response is a Message used as a reply to a call Request.
// It will have the same ID as the call it is a response to.
type Response struct {
// result is the content of the response.
Result json.RawMessage
// err is set only if the call failed.
Error error
// id of the request this is a response to.
ID ID
// Extra is additional information that does not appear on the wire. It can be
// used to pass information from the underlying transport to the application.
Extra any
}
// StringID creates a new string request identifier.
func StringID(s string) ID { return ID{value: s} }
// Int64ID creates a new integer request identifier.
func Int64ID(i int64) ID { return ID{value: i} }
// IsValid returns true if the ID is a valid identifier.
// The default value for ID will return false.
func (id ID) IsValid() bool { return id.value != nil }
// Raw returns the underlying value of the ID.
func (id ID) Raw() any { return id.value }
// NewNotification constructs a new Notification message for the supplied
// method and parameters.
func NewNotification(method string, params any) (*Request, error) {
p, merr := marshalToRaw(params)
return &Request{Method: method, Params: p}, merr
}
// NewCall constructs a new Call message for the supplied ID, method and
// parameters.
func NewCall(id ID, method string, params any) (*Request, error) {
p, merr := marshalToRaw(params)
return &Request{ID: id, Method: method, Params: p}, merr
}
func (msg *Request) IsCall() bool { return msg.ID.IsValid() }
func (msg *Request) marshal(to *wireCombined) {
to.ID = msg.ID.value
to.Method = msg.Method
to.Params = msg.Params
}
// NewResponse constructs a new Response message that is a reply to the
// supplied. If err is set result may be ignored.
func NewResponse(id ID, result any, rerr error) (*Response, error) {
r, merr := marshalToRaw(result)
return &Response{ID: id, Result: r, Error: rerr}, merr
}
func (msg *Response) marshal(to *wireCombined) {
to.ID = msg.ID.value
to.Error = toWireError(msg.Error)
to.Result = msg.Result
}
func toWireError(err error) *WireError {
if err == nil {
// no error, the response is complete
return nil
}
if err, ok := err.(*WireError); ok {
// already a wire error, just use it
return err
}
result := &WireError{Message: err.Error()}
var wrapped *WireError
if errors.As(err, &wrapped) {
// if we wrapped a wire error, keep the code from the wrapped error
// but the message from the outer error
result.Code = wrapped.Code
}
return result
}
func EncodeMessage(msg Message) ([]byte, error) {
wire := wireCombined{VersionTag: wireVersion}
msg.marshal(&wire)
data, err := jsonMarshal(&wire)
if err != nil {
return nil, fmt.Errorf("marshaling jsonrpc message: %w", err)
}
return data, nil
}
// EncodeIndent is like EncodeMessage, but honors indents.
// TODO(rfindley): refactor so that this concern is handled independently.
// Perhaps we should pass in a json.Encoder?
func EncodeIndent(msg Message, prefix, indent string) ([]byte, error) {
wire := wireCombined{VersionTag: wireVersion}
msg.marshal(&wire)
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
enc.SetIndent(prefix, indent)
if err := enc.Encode(&wire); err != nil {
return nil, fmt.Errorf("marshaling jsonrpc message: %w", err)
}
return bytes.TrimRight(buf.Bytes(), "\n"), nil
}
func DecodeMessage(data []byte) (Message, error) {
msg := wireCombined{}
if err := internaljson.Unmarshal(data, &msg); err != nil {
return nil, fmt.Errorf("unmarshaling jsonrpc message: %w", err)
}
if msg.VersionTag != wireVersion {
return nil, fmt.Errorf("invalid message version tag %q; expected %q", msg.VersionTag, wireVersion)
}
id, err := MakeID(msg.ID)
if err != nil {
return nil, err
}
if msg.Method != "" {
// has a method, must be a call
return &Request{
Method: msg.Method,
ID: id,
Params: msg.Params,
}, nil
}
// no method, should be a response
if !id.IsValid() {
return nil, ErrInvalidRequest
}
resp := &Response{
ID: id,
Result: msg.Result,
}
// we have to check if msg.Error is nil to avoid a typed error
if msg.Error != nil {
resp.Error = msg.Error
}
return resp, nil
}
func marshalToRaw(obj any) (json.RawMessage, error) {
if obj == nil {
return nil, nil
}
data, err := jsonMarshal(obj)
if err != nil {
return nil, err
}
return json.RawMessage(data), nil
}
// jsonescaping is a compatibility parameter that allows to restore
// JSON escaping in the JSON marshaling, which stopped being the default
// in the 1.4.0 version of the SDK. See the documentation for the
// mcpgodebug package for instructions how to enable it.
// The option will be removed in the 1.6.0 version of the SDK.
var jsonescaping = mcpgodebug.Value("jsonescaping")
// jsonMarshal marshals obj to JSON like json.Marshal but without HTML escaping.
func jsonMarshal(obj any) ([]byte, error) {
if jsonescaping == "1" {
return json.Marshal(obj)
}
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
if err := enc.Encode(obj); err != nil {
return nil, err
}
// json.Encoder.Encode adds a trailing newline. Trim it to be consistent with json.Marshal.
return bytes.TrimRight(buf.Bytes(), "\n"), nil
}
+138
View File
@@ -0,0 +1,138 @@
// Copyright 2018 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 jsonrpc2
import (
"context"
"io"
"net"
"os"
)
// This file contains implementations of the transport primitives that use the standard network
// package.
// NetListenOptions is the optional arguments to the NetListen function.
type NetListenOptions struct {
NetListenConfig net.ListenConfig
NetDialer net.Dialer
}
// NetListener returns a new Listener that listens on a socket using the net package.
func NetListener(ctx context.Context, network, address string, options NetListenOptions) (Listener, error) {
ln, err := options.NetListenConfig.Listen(ctx, network, address)
if err != nil {
return nil, err
}
return &netListener{net: ln}, nil
}
// netListener is the implementation of Listener for connections made using the net package.
type netListener struct {
net net.Listener
}
// Accept blocks waiting for an incoming connection to the listener.
func (l *netListener) Accept(context.Context) (io.ReadWriteCloser, error) {
return l.net.Accept()
}
// Close will cause the listener to stop listening. It will not close any connections that have
// already been accepted.
func (l *netListener) Close() error {
addr := l.net.Addr()
err := l.net.Close()
if addr.Network() == "unix" {
rerr := os.Remove(addr.String())
if rerr != nil && err == nil {
err = rerr
}
}
return err
}
// Dialer returns a dialer that can be used to connect to the listener.
func (l *netListener) Dialer() Dialer {
return NetDialer(l.net.Addr().Network(), l.net.Addr().String(), net.Dialer{})
}
// NetDialer returns a Dialer using the supplied standard network dialer.
func NetDialer(network, address string, nd net.Dialer) Dialer {
return &netDialer{
network: network,
address: address,
dialer: nd,
}
}
type netDialer struct {
network string
address string
dialer net.Dialer
}
func (n *netDialer) Dial(ctx context.Context) (io.ReadWriteCloser, error) {
return n.dialer.DialContext(ctx, n.network, n.address)
}
// NetPipeListener returns a new Listener that listens using net.Pipe.
// It is only possibly to connect to it using the Dialer returned by the
// Dialer method, each call to that method will generate a new pipe the other
// side of which will be returned from the Accept call.
func NetPipeListener(ctx context.Context) (Listener, error) {
return &netPiper{
done: make(chan struct{}),
dialed: make(chan io.ReadWriteCloser),
}, nil
}
// netPiper is the implementation of Listener build on top of net.Pipes.
type netPiper struct {
done chan struct{}
dialed chan io.ReadWriteCloser
}
// Accept blocks waiting for an incoming connection to the listener.
func (l *netPiper) Accept(context.Context) (io.ReadWriteCloser, error) {
// Block until the pipe is dialed or the listener is closed,
// preferring the latter if already closed at the start of Accept.
select {
case <-l.done:
return nil, net.ErrClosed
default:
}
select {
case rwc := <-l.dialed:
return rwc, nil
case <-l.done:
return nil, net.ErrClosed
}
}
// Close will cause the listener to stop listening. It will not close any connections that have
// already been accepted.
func (l *netPiper) Close() error {
// unblock any accept calls that are pending
close(l.done)
return nil
}
func (l *netPiper) Dialer() Dialer {
return l
}
func (l *netPiper) Dial(ctx context.Context) (io.ReadWriteCloser, error) {
client, server := net.Pipe()
select {
case l.dialed <- server:
return client, nil
case <-l.done:
client.Close()
server.Close()
return nil, net.ErrClosed
}
}
@@ -0,0 +1,330 @@
// Copyright 2020 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 jsonrpc2
import (
"context"
"fmt"
"io"
"runtime"
"sync"
"sync/atomic"
"time"
)
// Listener is implemented by protocols to accept new inbound connections.
type Listener interface {
// Accept accepts an inbound connection to a server.
// It blocks until either an inbound connection is made, or the listener is closed.
Accept(context.Context) (io.ReadWriteCloser, error)
// Close closes the listener.
// Any blocked Accept or Dial operations will unblock and return errors.
Close() error
// Dialer returns a dialer that can be used to connect to this listener
// locally.
// If a listener does not implement this it will return nil.
Dialer() Dialer
}
// Dialer is used by clients to dial a server.
type Dialer interface {
// Dial returns a new communication byte stream to a listening server.
Dial(ctx context.Context) (io.ReadWriteCloser, error)
}
// Server is a running server that is accepting incoming connections.
type Server struct {
listener Listener
binder Binder
async *async
shutdownOnce sync.Once
closing int32 // atomic: set to nonzero when Shutdown is called
}
// Dial uses the dialer to make a new connection, wraps the returned
// reader and writer using the framer to make a stream, and then builds
// a connection on top of that stream using the binder.
//
// The returned Connection will operate independently using the Preempter and/or
// Handler provided by the Binder, and will release its own resources when the
// connection is broken, but the caller may Close it earlier to stop accepting
// (or sending) new requests.
//
// If non-nil, the onDone function is called when the connection is closed.
func Dial(ctx context.Context, dialer Dialer, binder Binder, onDone func()) (*Connection, error) {
// dial a server
rwc, err := dialer.Dial(ctx)
if err != nil {
return nil, err
}
return bindConnection(ctx, rwc, binder, onDone), nil
}
// NewServer starts a new server listening for incoming connections and returns
// it.
// This returns a fully running and connected server, it does not block on
// the listener.
// You can call Wait to block on the server, or Shutdown to get the sever to
// terminate gracefully.
// To notice incoming connections, use an intercepting Binder.
func NewServer(ctx context.Context, listener Listener, binder Binder) *Server {
server := &Server{
listener: listener,
binder: binder,
async: newAsync(),
}
go server.run(ctx)
return server
}
// Wait returns only when the server has shut down.
func (s *Server) Wait() error {
return s.async.wait()
}
// Shutdown informs the server to stop accepting new connections.
func (s *Server) Shutdown() {
s.shutdownOnce.Do(func() {
atomic.StoreInt32(&s.closing, 1)
s.listener.Close()
})
}
// run accepts incoming connections from the listener,
// If IdleTimeout is non-zero, run exits after there are no clients for this
// duration, otherwise it exits only on error.
func (s *Server) run(ctx context.Context) {
defer s.async.done()
var activeConns sync.WaitGroup
for {
rwc, err := s.listener.Accept(ctx)
if err != nil {
// Only Shutdown closes the listener. If we get an error after Shutdown is
// called, assume that was the cause and don't report the error;
// otherwise, report the error in case it is unexpected.
if atomic.LoadInt32(&s.closing) == 0 {
s.async.setError(err)
}
// We are done generating new connections for good.
break
}
// A new inbound connection.
activeConns.Add(1)
_ = bindConnection(ctx, rwc, s.binder, activeConns.Done) // unregisters itself when done
}
activeConns.Wait()
}
// NewIdleListener wraps a listener with an idle timeout.
//
// When there are no active connections for at least the timeout duration,
// calls to Accept will fail with ErrIdleTimeout.
//
// A connection is considered inactive as soon as its Close method is called.
func NewIdleListener(timeout time.Duration, wrap Listener) Listener {
l := &idleListener{
wrapped: wrap,
timeout: timeout,
active: make(chan int, 1),
timedOut: make(chan struct{}),
idleTimer: make(chan *time.Timer, 1),
}
l.idleTimer <- time.AfterFunc(l.timeout, l.timerExpired)
return l
}
type idleListener struct {
wrapped Listener
timeout time.Duration
// Only one of these channels is receivable at any given time.
active chan int // count of active connections; closed when Close is called if not timed out
timedOut chan struct{} // closed when the idle timer expires
idleTimer chan *time.Timer // holds the timer only when idle
}
// Accept accepts an incoming connection.
//
// If an incoming connection is accepted concurrent to the listener being closed
// due to idleness, the new connection is immediately closed.
func (l *idleListener) Accept(ctx context.Context) (io.ReadWriteCloser, error) {
rwc, err := l.wrapped.Accept(ctx)
select {
case n, ok := <-l.active:
if err != nil {
if ok {
l.active <- n
}
return nil, err
}
if ok {
l.active <- n + 1
} else {
// l.wrapped.Close Close has been called, but Accept returned a
// connection. This race can occur with concurrent Accept and Close calls
// with any net.Listener, and it is benign: since the listener was closed
// explicitly, it can't have also timed out.
}
return l.newConn(rwc), nil
case <-l.timedOut:
if err == nil {
// Keeping the connection open would leave the listener simultaneously
// active and closed due to idleness, which would be contradictory and
// confusing. Close the connection and pretend that it never happened.
rwc.Close()
} else {
// In theory the timeout could have raced with an unrelated error return
// from Accept. However, ErrIdleTimeout is arguably still valid (since we
// would have closed due to the timeout independent of the error), and the
// harm from returning a spurious ErrIdleTimeout is negligible anyway.
}
return nil, ErrIdleTimeout
case timer := <-l.idleTimer:
if err != nil {
// The idle timer doesn't run until it receives itself from the idleTimer
// channel, so it can't have called l.wrapped.Close yet and thus err can't
// be ErrIdleTimeout. Leave the idle timer as it was and return whatever
// error we got.
l.idleTimer <- timer
return nil, err
}
if !timer.Stop() {
// Failed to stop the timer — the timer goroutine is in the process of
// firing. Send the timer back to the timer goroutine so that it can
// safely close the timedOut channel, and then wait for the listener to
// actually be closed before we return ErrIdleTimeout.
l.idleTimer <- timer
rwc.Close()
<-l.timedOut
return nil, ErrIdleTimeout
}
l.active <- 1
return l.newConn(rwc), nil
}
}
func (l *idleListener) Close() error {
select {
case _, ok := <-l.active:
if ok {
close(l.active)
}
case <-l.timedOut:
// Already closed by the timer; take care not to double-close if the caller
// only explicitly invokes this Close method once, since the io.Closer
// interface explicitly leaves doubled Close calls undefined.
return ErrIdleTimeout
case timer := <-l.idleTimer:
if !timer.Stop() {
// Couldn't stop the timer. It shouldn't take long to run, so just wait
// (so that the Listener is guaranteed to be closed before we return)
// and pretend that this call happened afterward.
// That way we won't leak any timers or goroutines when Close returns.
l.idleTimer <- timer
<-l.timedOut
return ErrIdleTimeout
}
close(l.active)
}
return l.wrapped.Close()
}
func (l *idleListener) Dialer() Dialer {
return l.wrapped.Dialer()
}
func (l *idleListener) timerExpired() {
select {
case n, ok := <-l.active:
if ok {
panic(fmt.Sprintf("jsonrpc2: idleListener idle timer fired with %d connections still active", n))
} else {
panic("jsonrpc2: Close finished with idle timer still running")
}
case <-l.timedOut:
panic("jsonrpc2: idleListener idle timer fired more than once")
case <-l.idleTimer:
// The timer for this very call!
}
// Close the Listener with all channels still blocked to ensure that this call
// to l.wrapped.Close doesn't race with the one in l.Close.
defer close(l.timedOut)
l.wrapped.Close()
}
func (l *idleListener) connClosed() {
select {
case n, ok := <-l.active:
if !ok {
// l is already closed, so it can't close due to idleness,
// and we don't need to track the number of active connections any more.
return
}
n--
if n == 0 {
l.idleTimer <- time.AfterFunc(l.timeout, l.timerExpired)
} else {
l.active <- n
}
case <-l.timedOut:
panic("jsonrpc2: idleListener idle timer fired before last active connection was closed")
case <-l.idleTimer:
panic("jsonrpc2: idleListener idle timer active before last active connection was closed")
}
}
type idleListenerConn struct {
wrapped io.ReadWriteCloser
l *idleListener
closeOnce sync.Once
}
func (l *idleListener) newConn(rwc io.ReadWriteCloser) *idleListenerConn {
c := &idleListenerConn{
wrapped: rwc,
l: l,
}
// A caller that forgets to call Close may disrupt the idleListener's
// accounting, even though the file descriptor for the underlying connection
// may eventually be garbage-collected anyway.
//
// Set a (best-effort) finalizer to verify that a Close call always occurs.
// (We will clear the finalizer explicitly in Close.)
runtime.SetFinalizer(c, func(c *idleListenerConn) {
panic("jsonrpc2: IdleListener connection became unreachable without a call to Close")
})
return c
}
func (c *idleListenerConn) Read(p []byte) (int, error) { return c.wrapped.Read(p) }
func (c *idleListenerConn) Write(p []byte) (int, error) { return c.wrapped.Write(p) }
func (c *idleListenerConn) Close() error {
defer c.closeOnce.Do(func() {
c.l.connClosed()
runtime.SetFinalizer(c, nil)
})
return c.wrapped.Close()
}
@@ -0,0 +1,97 @@
// Copyright 2018 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 jsonrpc2
import (
"encoding/json"
)
// This file contains the go forms of the wire specification.
// see http://www.jsonrpc.org/specification for details
var (
// ErrParse is used when invalid JSON was received by the server.
ErrParse = NewError(-32700, "parse error")
// ErrInvalidRequest is used when the JSON sent is not a valid Request object.
ErrInvalidRequest = NewError(-32600, "invalid request")
// ErrMethodNotFound should be returned by the handler when the method does
// not exist / is not available.
ErrMethodNotFound = NewError(-32601, "method not found")
// ErrInvalidParams should be returned by the handler when method
// parameter(s) were invalid.
ErrInvalidParams = NewError(-32602, "invalid params")
// ErrInternal indicates a failure to process a call correctly
ErrInternal = NewError(-32603, "internal error")
// The following errors are not part of the json specification, but
// compliant extensions specific to this implementation.
// ErrServerOverloaded is returned when a message was refused due to a
// server being temporarily unable to accept any new messages.
ErrServerOverloaded = NewError(-32000, "overloaded")
// ErrUnknown should be used for all non coded errors.
ErrUnknown = NewError(-32001, "unknown error")
// ErrServerClosing is returned for calls that arrive while the server is closing.
ErrServerClosing = NewError(-32004, "server is closing")
// ErrClientClosing is a dummy error returned for calls initiated while the client is closing.
ErrClientClosing = NewError(-32003, "client is closing")
// The following errors have special semantics for MCP transports
// ErrRejected may be wrapped to return errors from calls to Writer.Write
// that signal that the request was rejected by the transport layer as
// invalid.
//
// Such failures do not indicate that the connection is broken, but rather
// should be returned to the caller to indicate that the specific request is
// invalid in the current context.
ErrRejected = NewError(-32005, "rejected by transport")
)
const wireVersion = "2.0"
// wireCombined has all the fields of both Request and Response.
// We can decode this and then work out which it is.
type wireCombined struct {
VersionTag string `json:"jsonrpc"`
ID any `json:"id,omitempty"`
Method string `json:"method,omitempty"`
Params json.RawMessage `json:"params,omitempty"`
Result json.RawMessage `json:"result,omitempty"`
Error *WireError `json:"error,omitempty"`
}
// WireError represents a structured error in a Response.
type WireError struct {
// Code is an error code indicating the type of failure.
Code int64 `json:"code"`
// Message is a short description of the error.
Message string `json:"message"`
// Data is optional structured data containing additional information about the error.
Data json.RawMessage `json:"data,omitempty"`
}
// NewError returns an error that will encode on the wire correctly.
// The standard codes are made available from this package, this function should
// only be used to build errors for application specific codes as allowed by the
// specification.
func NewError(code int64, message string) error {
return &WireError{
Code: code,
Message: message,
}
}
func (err *WireError) Error() string {
return err.Message
}
func (err *WireError) Is(other error) bool {
w, ok := other.(*WireError)
if !ok {
return false
}
return err.Code == w.Code
}
@@ -0,0 +1,52 @@
// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
// Use of this source code is governed by the license
// that can be found in the LICENSE file.
// Package mcpgodebug provides a mechanism to configure compatibility parameters
// via the MCPGODEBUG environment variable.
//
// The value of MCPGODEBUG is a comma-separated list of key=value pairs.
// For example:
//
// MCPGODEBUG=someoption=1,otheroption=value
package mcpgodebug
import (
"fmt"
"os"
"strings"
)
const compatibilityEnvKey = "MCPGODEBUG"
var compatibilityParams map[string]string
func init() {
var err error
compatibilityParams, err = parseCompatibility(os.Getenv(compatibilityEnvKey))
if err != nil {
panic(err)
}
}
// Value returns the value of the compatibility parameter with the given key.
// It returns an empty string if the key is not set.
func Value(key string) string {
return compatibilityParams[key]
}
func parseCompatibility(envValue string) (map[string]string, error) {
if envValue == "" {
return nil, nil
}
params := make(map[string]string)
for part := range strings.SplitSeq(envValue, ",") {
k, v, ok := strings.Cut(part, "=")
if !ok {
return nil, fmt.Errorf("MCPGODEBUG: invalid format: %q", part)
}
params[strings.TrimSpace(k)] = strings.TrimSpace(v)
}
return params, nil
}
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
// Use of this source code is governed by the license
// that can be found in the LICENSE file.
package util
import (
"net"
"net/netip"
"strings"
)
func IsLoopback(addr string) bool {
host, _, err := net.SplitHostPort(addr)
if err != nil {
// If SplitHostPort fails, it might be just a host without a port.
host = strings.Trim(addr, "[]")
}
if host == "localhost" {
return true
}
ip, err := netip.ParseAddr(host)
if err != nil {
return false
}
return ip.IsLoopback()
}
+44
View File
@@ -0,0 +1,44 @@
// 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 util
import (
"cmp"
"fmt"
"iter"
"slices"
)
// Helpers below are copied from gopls' moremaps package.
// Sorted returns an iterator over the entries of m in key order.
func Sorted[M ~map[K]V, K cmp.Ordered, V any](m M) iter.Seq2[K, V] {
// TODO(adonovan): use maps.Sorted if proposal #68598 is accepted.
return func(yield func(K, V) bool) {
keys := KeySlice(m)
slices.Sort(keys)
for _, k := range keys {
if !yield(k, m[k]) {
break
}
}
}
}
// KeySlice returns the keys of the map M, like slices.Collect(maps.Keys(m)).
func KeySlice[M ~map[K]V, K comparable, V any](m M) []K {
r := make([]K, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}
// Wrapf wraps *errp with the given formatted message if *errp is not nil.
func Wrapf(errp *error, format string, args ...any) {
if *errp != nil {
*errp = fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), *errp)
}
}
@@ -0,0 +1,23 @@
// Copyright 2019 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 xcontext is a package to offer the extra functionality we need
// from contexts that is not available from the standard context package.
package xcontext
import (
"context"
"time"
)
// Detach returns a context that keeps all the values of its parent context
// but detaches from the cancellation and error handling.
func Detach(ctx context.Context) context.Context { return detachedContext{ctx} }
type detachedContext struct{ parent context.Context }
func (v detachedContext) Deadline() (time.Time, bool) { return time.Time{}, false }
func (v detachedContext) Done() <-chan struct{} { return nil }
func (v detachedContext) Err() error { return nil }
func (v detachedContext) Value(key any) any { return v.parent.Value(key) }