internal/mcp: implement HTTP SSE transport
Add support for SSE transport, as defined by version 2024-11-05 and 2025-03-26 of the MCP spec. In order to achieve this, the jsonrpc2 stream must be abstracted so that we can implement it across multiple HTTP requests. This required some refactoring of the jsonrpc2_v2 package, so that we can avoid the Dialer/Binder interfaces and construct the Connection directly from its constituent primitives. Change-Id: Id627b5b5a474d92f2c613cfad904d14611e7c768 Reviewed-on: https://go-review.googlesource.com/c/tools/+/667475 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Jonathan Amsterdam <jba@google.com>
This commit is contained in:
parent
1204b93a85
commit
ef446f82d7
2 changed files with 771 additions and 223 deletions
|
|
@ -201,14 +201,43 @@ func (o ConnectionOptions) Bind(context.Context, *Connection) ConnectionOptions
|
|||
return o
|
||||
}
|
||||
|
||||
// newConnection creates a new connection and runs it.
|
||||
// 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: make(chan Writer, 1),
|
||||
onDone: cfg.OnDone,
|
||||
onInternalError: cfg.OnInternalError,
|
||||
}
|
||||
c.handler = cfg.Bind(c)
|
||||
c.writer <- cfg.Writer
|
||||
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 newConnection(bindCtx context.Context, rwc io.ReadWriteCloser, binder Binder, onDone func()) *Connection {
|
||||
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}
|
||||
|
|
@ -238,7 +267,11 @@ func newConnection(bindCtx context.Context, rwc io.ReadWriteCloser, binder Binde
|
|||
|
||||
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:
|
||||
|
|
@ -252,9 +285,8 @@ func newConnection(bindCtx context.Context, rwc io.ReadWriteCloser, binder Binde
|
|||
// (If the Binder closed the Connection already, this should error out and
|
||||
// return almost immediately.)
|
||||
s.reading = true
|
||||
go c.readIncoming(ctx, reader, options.Preempter)
|
||||
go c.readIncoming(ctx, reader, preempter)
|
||||
})
|
||||
return c
|
||||
}
|
||||
|
||||
// Notify invokes the target method but does not wait for a response.
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ func Dial(ctx context.Context, dialer Dialer, binder Binder, onDone func()) (*Co
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newConnection(ctx, rwc, binder, onDone), nil
|
||||
return bindConnection(ctx, rwc, binder, onDone), nil
|
||||
}
|
||||
|
||||
// NewServer starts a new server listening for incoming connections and returns
|
||||
|
|
@ -117,7 +117,7 @@ func (s *Server) run(ctx context.Context) {
|
|||
|
||||
// A new inbound connection.
|
||||
activeConns.Add(1)
|
||||
_ = newConnection(ctx, rwc, s.binder, activeConns.Done) // unregisters itself when done
|
||||
_ = bindConnection(ctx, rwc, s.binder, activeConns.Done) // unregisters itself when done
|
||||
}
|
||||
activeConns.Wait()
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue