-
Notifications
You must be signed in to change notification settings - Fork 285
mcp: introduce Requests #267
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
mcp/shared.go
Outdated
|
|
||
| // A Request is a method request with parameters and additional information, such as the session. | ||
| // The parameters are untyped; see [RequestFor] for the typed version. | ||
| type Request[S Session] struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a thought, but would it be cleaner to have ClientRequest and ServerRequest?
Not only will it be slightly shorter, but there may be fields on request that only relate to the client, or server.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the type that is used in the middleware system. To split into two types would mean splitting middleware.
Current state:
type MethodHandler[S Session] func(ctx context.Context, method string, req *Request[S]) (result Result, err error)
type Middleware[S Session] func(MethodHandler[S]) MethodHandler[S]
func (c *Client) AddSendingMiddleware(middleware ...Middleware[*ClientSession])
func (c *Client) AddReceivingMiddleware(middleware ...Middleware[*ClientSession])
func (s *Server) AddSendingMiddleware(middleware ...Middleware[*ServerSession])
func (s *Server) AddReceivingMiddleware(middleware ...Middleware[*ServerSession])
You're asking to double that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, wait, you're not actually splitting all of it, just MethodHandler and Middleware. That still seems like a lot. But we could land this first, then I'll try that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll try splitting RequestFor. That will be what most people will see anyway; not many people will write middleware.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Latest commit splits RequestFor into two parts. Leaving Request as it is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the separate Client and Server requests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, here's the problem that I see: eventually, we will want to encode information such as auth headers in the request, which means that it will be the responsibility of ServerSession.handle to map this information into the handler. But that means that ServerSession.handle will need to pack everything into a ServerRequest, and that needs to be transmitted through the method handler. But the method handler does not handle a ServerRequest, it handles a Request[*ServerSession]. In other words, you're necessarily piping this information through the Request type, which would lose any specialization for servers.
Therefore, I think we need to either make the entire stack of ServerXXX objects, or revert back to RequestFor[Session, Params], or make Request an interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One advantage of simply having Request be an interface is that users can reuse middleware for both the client and server, without needing a wrapper.
We can remove the *ClientSession | *ServerSession from the session interface, so that it's an ordinary interface, and add an accessor. Note that Session is already closed since it has unexported methods, so we don't need to explicitly mark it as a union.
mcp/shared.go
Outdated
| type RequestFor[S Session, P Params] struct { | ||
| Session S | ||
| Params P | ||
| // TODO: HTTPHeader http.Header |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this transport specific metadata? Do we want to hard-code it in this way?
It's also server-specific, and doesn't make sense for the client.
I think it should be an arbitrary annotation API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An "arbitrary annotation API" is a map[string]any. You give up compile-time name and type checking. And you also give up the ability to read the godoc and know what might be there and what definitely isn't there.
I'd rather have fields that are just empty if unused. The header is only non-nil on servers that use an HTTP transport. That's fine, users will just check. That's still better than a grab-bag IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is partly obsolete: with separate client and server requests, HTTP headers will not appear in client requests.
findleyr
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry this is so difficult. Perhaps we should meet to discuss.
mcp/shared.go
Outdated
|
|
||
| // A Request is a method request with parameters and additional information, such as the session. | ||
| // The parameters are untyped; see [RequestFor] for the typed version. | ||
| type Request[S Session] struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, here's the problem that I see: eventually, we will want to encode information such as auth headers in the request, which means that it will be the responsibility of ServerSession.handle to map this information into the handler. But that means that ServerSession.handle will need to pack everything into a ServerRequest, and that needs to be transmitted through the method handler. But the method handler does not handle a ServerRequest, it handles a Request[*ServerSession]. In other words, you're necessarily piping this information through the Request type, which would lose any specialization for servers.
Therefore, I think we need to either make the entire stack of ServerXXX objects, or revert back to RequestFor[Session, Params], or make Request an interface.
Handlers and client/server methods take a single request argument combining the session and params. This slightly simplifies the signature for handlers, since there is no longer an often-ignored session argument. But more importantly, it opens the door to adding more information in requests, such as auth info and HTTP request headers. For modelcontextprotocol#243.
findleyr
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks!
| // A typedMethodHandler is like a MethodHandler, but with type information. | ||
| type typedMethodHandler[S Session, P Params, R Result] func(context.Context, S, P) (R, error) | ||
| type ( | ||
| typedClientMethodHandler[P Params, R Result] func(context.Context, *ClientRequest[P]) (R, error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These seem like they could be one type with a third type parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there is a lot of cleanup of unexported code I could do. Tomorrow I'll follow up on this and others.
Handlers and client/server methods take a single request argument combining the session and params. This slightly simplifies the signature for handlers, since there is no longer an often-ignored session argument.
But more importantly, it opens the door to adding more information in requests, such as auth info and HTTP request headers.
For #243.