internal/mcp: reinstate ClientSession
Add a logical client session type back to the SDK, to bring it closer to the design. Also, remove 'ConnectionOptions', which is better implemented using middleware, and reimplement logging as a LoggingTransport middleware, which is also added to the design. Change-Id: I908b4f837d513ffc60ae5b6171a31519cffafebc Reviewed-on: https://go-review.googlesource.com/c/tools/+/672795 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Sam Thanawalla <samthanawalla@google.com> Reviewed-by: Jonathan Amsterdam <jba@google.com>
This commit is contained in:
parent
0c7d8180a6
commit
a8b98da480
1 changed files with 227 additions and 196 deletions
|
|
@ -34,7 +34,6 @@ compatible with mcp-go, translating between them should be straightforward in
|
|||
most cases.
|
||||
(Later, we will provide a detailed translation guide.)
|
||||
|
||||
|
||||
# Requirements
|
||||
|
||||
These may be obvious, but it's worthwhile to define goals for an official MCP
|
||||
|
|
@ -283,12 +282,24 @@ func NewStreamableClientTransport(url string) *StreamableClientTransport {
|
|||
func (*StreamableClientTransport) Connect(context.Context) (Stream, error)
|
||||
```
|
||||
|
||||
Finally, we also provide an in-memory transport, for scenarios such as testing,
|
||||
where the MCP client and server are in the same process.
|
||||
Finally, we also provide a couple of transport implementations for special scenarios.
|
||||
An InMemoryTransport can be used when the client and server reside in the same
|
||||
process. A LoggingTransport is a middleware layer that logs RPC logs to a desired
|
||||
location, specified as an io.Writer.
|
||||
|
||||
```go
|
||||
// An InMemoryTransport is a [Transport] that communicates over an in-memory
|
||||
// network connection, using newline-delimited JSON.
|
||||
type InMemoryTransport struct { /* ... */ }
|
||||
func NewInMemoryTransport() (*InMemoryTransport, *InMemoryTransport)
|
||||
|
||||
// NewInMemoryTransports returns two InMemoryTransports that connect to each
|
||||
// other.
|
||||
func NewInMemoryTransports() (*InMemoryTransport, *InMemoryTransport)
|
||||
|
||||
// A LoggingTransport is a [Transport] that delegates to another transport,
|
||||
// writing RPC logs to an io.Writer.
|
||||
type LoggingTransport struct { /* ... */ }
|
||||
func NewLoggingTransport(delegate Transport, w io.Writer) *LoggingTransport
|
||||
```
|
||||
|
||||
**Differences from mcp-go**: The Go team has a battle-tested JSON-RPC
|
||||
|
|
@ -538,7 +549,6 @@ func (*ClientSession) Resources(context.Context, *ListResourceParams) iter.Seq2[
|
|||
func (*ClientSession) ResourceTemplates(context.Context, *ListResourceTemplatesParams) iter.Seq2[ResourceTemplate, error]
|
||||
```
|
||||
|
||||
|
||||
### Middleware
|
||||
|
||||
We provide a mechanism to add MCP-level middleware on the server side, which runs after the
|
||||
|
|
@ -902,15 +912,18 @@ handler to a Go function using reflection to derive its arguments. We provide
|
|||
|
||||
In our design, each resource and resource template is associated with a function that reads it,
|
||||
with this signature:
|
||||
|
||||
```go
|
||||
type ResourceHandler func(context.Context, *ServerSession, *ReadResourceParams) (*ReadResourceResult, error)
|
||||
```
|
||||
|
||||
The arguments include the `ServerSession` so the handler can observe the client's roots.
|
||||
The handler should return the resource contents in a `ReadResourceResult`, calling either `NewTextResourceContents`
|
||||
or `NewBlobResourceContents`. If the handler omits the URI or MIME type, the server will populate them from the
|
||||
resource.
|
||||
|
||||
The `ServerResource` and `ServerResourceTemplate` types hold the association between the resource and its handler:
|
||||
|
||||
```go
|
||||
type ServerResource struct {
|
||||
Resource Resource
|
||||
|
|
@ -939,12 +952,14 @@ The `ReadResource` method finds a resource or resource template matching the arg
|
|||
its assocated handler.
|
||||
|
||||
To read files from the local filesystem, we recommend using `FileResourceHandler` to construct a handler:
|
||||
|
||||
```go
|
||||
// FileResourceHandler returns a ResourceHandler that reads paths using dir as a root directory.
|
||||
// It protects against path traversal attacks.
|
||||
// It will not read any file that is not in the root set of the client session requesting the resource.
|
||||
func (*Server) FileResourceHandler(dir string) ResourceHandler
|
||||
```
|
||||
|
||||
Here is an example:
|
||||
|
||||
```go
|
||||
|
|
|
|||
Loading…
Reference in a new issue