Skip to content

Commit c6b2a87

Browse files
committed
internal/jsonrpc2: remove unused server, network, and framing infrastructure
Remove code inherited from x/tools/internal/jsonrpc2_v2 that is not used by MCP transports. MCP transports implement their own framing and connection management, using only the Connection engine and message encode/decode from this package. Removed files: - serve.go: Server, Listener, Dialer, idleListener, Dial, NewServer - net.go: NetListener, NetDialer, NetPipeListener - serve_test.go, jsonrpc2_test.go: tests for removed infrastructure Removed from remaining files: - frame.go: Framer interface, RawFramer, HeaderFramer implementations - conn.go: Binder, BinderFunc, ConnectionOptions, bindConnection - jsonrpc2.go: ErrIdleTimeout, PreempterFunc, defaultHandler, async helper - wire.go: ErrServerOverloaded - conn.go: AsyncCall.IsReady (unused method)
1 parent 35f1b07 commit c6b2a87

8 files changed

Lines changed: 8 additions & 1552 deletions

File tree

internal/jsonrpc2/conn.go

Lines changed: 1 addition & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -16,45 +16,6 @@ import (
1616
"github.com/modelcontextprotocol/go-sdk/internal/json"
1717
)
1818

19-
// Binder builds a connection configuration.
20-
// This may be used in servers to generate a new configuration per connection.
21-
// ConnectionOptions itself implements Binder returning itself unmodified, to
22-
// allow for the simple cases where no per connection information is needed.
23-
type Binder interface {
24-
// Bind returns the ConnectionOptions to use when establishing the passed-in
25-
// Connection.
26-
//
27-
// The connection is not ready to use when Bind is called,
28-
// but Bind may close it without reading or writing to it.
29-
Bind(context.Context, *Connection) ConnectionOptions
30-
}
31-
32-
// A BinderFunc implements the Binder interface for a standalone Bind function.
33-
type BinderFunc func(context.Context, *Connection) ConnectionOptions
34-
35-
func (f BinderFunc) Bind(ctx context.Context, c *Connection) ConnectionOptions {
36-
return f(ctx, c)
37-
}
38-
39-
var _ Binder = BinderFunc(nil)
40-
41-
// ConnectionOptions holds the options for new connections.
42-
type ConnectionOptions struct {
43-
// Framer allows control over the message framing and encoding.
44-
// If nil, HeaderFramer will be used.
45-
Framer Framer
46-
// Preempter allows registration of a pre-queue message handler.
47-
// If nil, no messages will be preempted.
48-
Preempter Preempter
49-
// Handler is used as the queued message handler for inbound messages.
50-
// If nil, all responses will be ErrNotHandled.
51-
Handler Handler
52-
// OnInternalError, if non-nil, is called with any internal errors that occur
53-
// while serving the connection, such as protocol errors or invariant
54-
// violations. (If nil, internal errors result in panics.)
55-
OnInternalError func(error)
56-
}
57-
5819
// Connection manages the jsonrpc2 protocol, connecting responses back to their
5920
// calls. Connection is bidirectional; it does not have a designated server or
6021
// client end.
@@ -197,11 +158,6 @@ type incomingRequest struct {
197158
cancel context.CancelFunc
198159
}
199160

200-
// Bind returns the options unmodified.
201-
func (o ConnectionOptions) Bind(context.Context, *Connection) ConnectionOptions {
202-
return o
203-
}
204-
205161
// A ConnectionConfig configures a bidirectional jsonrpc2 connection.
206162
type ConnectionConfig struct {
207163
Reader Reader // required
@@ -230,59 +186,16 @@ func NewConnection(ctx context.Context, cfg ConnectionConfig) *Connection {
230186
return c
231187
}
232188

233-
// bindConnection creates a new connection and runs it.
234-
//
235-
// This is used by the Dial and Serve functions to build the actual connection.
236-
//
237-
// The connection is closed automatically (and its resources cleaned up) when
238-
// the last request has completed after the underlying ReadWriteCloser breaks,
239-
// but it may be stopped earlier by calling Close (for a clean shutdown).
240-
func bindConnection(bindCtx context.Context, rwc io.ReadWriteCloser, binder Binder, onDone func()) *Connection {
241-
// TODO: Should we create a new event span here?
242-
// This will propagate cancellation from ctx; should it?
243-
ctx := notDone{bindCtx}
244-
245-
c := &Connection{
246-
state: inFlightState{closer: rwc},
247-
done: make(chan struct{}),
248-
onDone: onDone,
249-
}
250-
// It's tempting to set a finalizer on c to verify that the state has gone
251-
// idle when the connection becomes unreachable. Unfortunately, the Binder
252-
// interface makes that unsafe: it allows the Handler to close over the
253-
// Connection, which could create a reference cycle that would cause the
254-
// Connection to become uncollectable.
255-
256-
options := binder.Bind(bindCtx, c)
257-
framer := options.Framer
258-
if framer == nil {
259-
framer = HeaderFramer()
260-
}
261-
c.handler = options.Handler
262-
if c.handler == nil {
263-
c.handler = defaultHandler{}
264-
}
265-
c.onInternalError = options.OnInternalError
266-
267-
c.writer = framer.Writer(rwc)
268-
reader := framer.Reader(rwc)
269-
c.start(ctx, reader, options.Preempter)
270-
return c
271-
}
272-
273189
func (c *Connection) start(ctx context.Context, reader Reader, preempter Preempter) {
274190
c.updateInFlight(func(s *inFlightState) {
275191
select {
276192
case <-c.done:
277-
// Bind already closed the connection; don't start a goroutine to read it.
193+
// The connection was already closed; don't start a goroutine to read it.
278194
return
279195
default:
280196
}
281197

282198
// The goroutine started here will continue until the underlying stream is closed.
283-
//
284-
// (If the Binder closed the Connection already, this should error out and
285-
// return almost immediately.)
286199
s.reading = true
287200
go c.readIncoming(ctx, reader, preempter)
288201
})
@@ -439,18 +352,6 @@ type AsyncCall struct {
439352
// This can be used to cancel the call if needed.
440353
func (ac *AsyncCall) ID() ID { return ac.id }
441354

442-
// IsReady can be used to check if the result is already prepared.
443-
// This is guaranteed to return true on a result for which Await has already
444-
// returned, or a call that failed to send in the first place.
445-
func (ac *AsyncCall) IsReady() bool {
446-
select {
447-
case <-ac.ready:
448-
return true
449-
default:
450-
return false
451-
}
452-
}
453-
454355
// retire processes the response to the call.
455356
//
456357
// It is an error to call retire more than once: retire is guarded by the

internal/jsonrpc2/frame.go

Lines changed: 1 addition & 178 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,7 @@
44

55
package jsonrpc2
66

7-
import (
8-
"bufio"
9-
"context"
10-
"encoding/json"
11-
"fmt"
12-
"io"
13-
"strconv"
14-
"strings"
15-
"sync"
16-
)
7+
import "context"
178

189
// Reader abstracts the transport mechanics from the JSON RPC protocol.
1910
// A Conn reads messages from the reader it was provided on construction,
@@ -38,171 +29,3 @@ type Writer interface {
3829
// Write sends a message to the stream.
3930
Write(context.Context, Message) error
4031
}
41-
42-
// Framer wraps low level byte readers and writers into jsonrpc2 message
43-
// readers and writers.
44-
// It is responsible for the framing and encoding of messages into wire form.
45-
//
46-
// TODO(rfindley): rethink the framer interface, as with JSONRPC2 batching
47-
// there is a need for Reader and Writer to be correlated, and while the
48-
// implementation of framing here allows that, it is not made explicit by the
49-
// interface.
50-
//
51-
// Perhaps a better interface would be
52-
//
53-
// Frame(io.ReadWriteCloser) (Reader, Writer).
54-
type Framer interface {
55-
// Reader wraps a byte reader into a message reader.
56-
Reader(io.Reader) Reader
57-
// Writer wraps a byte writer into a message writer.
58-
Writer(io.Writer) Writer
59-
}
60-
61-
// RawFramer returns a new Framer.
62-
// The messages are sent with no wrapping, and rely on json decode consistency
63-
// to determine message boundaries.
64-
func RawFramer() Framer { return rawFramer{} }
65-
66-
type rawFramer struct{}
67-
type rawReader struct{ in *json.Decoder }
68-
type rawWriter struct {
69-
mu sync.Mutex
70-
out io.Writer
71-
}
72-
73-
func (rawFramer) Reader(rw io.Reader) Reader {
74-
return &rawReader{in: json.NewDecoder(rw)}
75-
}
76-
77-
func (rawFramer) Writer(rw io.Writer) Writer {
78-
return &rawWriter{out: rw}
79-
}
80-
81-
func (r *rawReader) Read(ctx context.Context) (Message, error) {
82-
select {
83-
case <-ctx.Done():
84-
return nil, ctx.Err()
85-
default:
86-
}
87-
var raw json.RawMessage
88-
if err := r.in.Decode(&raw); err != nil {
89-
return nil, err
90-
}
91-
msg, err := DecodeMessage(raw)
92-
return msg, err
93-
}
94-
95-
func (w *rawWriter) Write(ctx context.Context, msg Message) error {
96-
select {
97-
case <-ctx.Done():
98-
return ctx.Err()
99-
default:
100-
}
101-
102-
data, err := EncodeMessage(msg)
103-
if err != nil {
104-
return fmt.Errorf("marshaling message: %v", err)
105-
}
106-
107-
w.mu.Lock()
108-
defer w.mu.Unlock()
109-
_, err = w.out.Write(data)
110-
return err
111-
}
112-
113-
// HeaderFramer returns a new Framer.
114-
// The messages are sent with HTTP content length and MIME type headers.
115-
// This is the format used by LSP and others.
116-
func HeaderFramer() Framer { return headerFramer{} }
117-
118-
type headerFramer struct{}
119-
type headerReader struct{ in *bufio.Reader }
120-
type headerWriter struct {
121-
mu sync.Mutex
122-
out io.Writer
123-
}
124-
125-
func (headerFramer) Reader(rw io.Reader) Reader {
126-
return &headerReader{in: bufio.NewReader(rw)}
127-
}
128-
129-
func (headerFramer) Writer(rw io.Writer) Writer {
130-
return &headerWriter{out: rw}
131-
}
132-
133-
func (r *headerReader) Read(ctx context.Context) (Message, error) {
134-
select {
135-
case <-ctx.Done():
136-
return nil, ctx.Err()
137-
default:
138-
}
139-
140-
firstRead := true // to detect a clean EOF below
141-
var contentLength int64
142-
// read the header, stop on the first empty line
143-
for {
144-
line, err := r.in.ReadString('\n')
145-
if err != nil {
146-
if err == io.EOF {
147-
if firstRead && line == "" {
148-
return nil, io.EOF // clean EOF
149-
}
150-
err = io.ErrUnexpectedEOF
151-
}
152-
return nil, fmt.Errorf("failed reading header line: %w", err)
153-
}
154-
firstRead = false
155-
156-
line = strings.TrimSpace(line)
157-
// check we have a header line
158-
if line == "" {
159-
break
160-
}
161-
colon := strings.IndexRune(line, ':')
162-
if colon < 0 {
163-
return nil, fmt.Errorf("invalid header line %q", line)
164-
}
165-
name, value := line[:colon], strings.TrimSpace(line[colon+1:])
166-
switch {
167-
case strings.EqualFold(name, "Content-Length"):
168-
if contentLength, err = strconv.ParseInt(value, 10, 32); err != nil {
169-
return nil, fmt.Errorf("failed parsing Content-Length: %v", value)
170-
}
171-
if contentLength <= 0 {
172-
return nil, fmt.Errorf("invalid Content-Length: %v", contentLength)
173-
}
174-
default:
175-
// ignoring unknown headers
176-
}
177-
}
178-
if contentLength == 0 {
179-
return nil, fmt.Errorf("missing Content-Length header")
180-
}
181-
data := make([]byte, contentLength)
182-
_, err := io.ReadFull(r.in, data)
183-
if err != nil {
184-
return nil, err
185-
}
186-
msg, err := DecodeMessage(data)
187-
return msg, err
188-
}
189-
190-
func (w *headerWriter) Write(ctx context.Context, msg Message) error {
191-
select {
192-
case <-ctx.Done():
193-
return ctx.Err()
194-
default:
195-
}
196-
w.mu.Lock()
197-
defer w.mu.Unlock()
198-
199-
data, err := EncodeMessage(msg)
200-
if err != nil {
201-
return fmt.Errorf("marshaling message: %v", err)
202-
}
203-
_, err = fmt.Fprintf(w.out, "Content-Length: %v\r\n\r\n", len(data))
204-
if err == nil {
205-
_, err = w.out.Write(data)
206-
}
207-
return err
208-
}

0 commit comments

Comments
 (0)