Skip to content

Conversation

@jba
Copy link
Contributor

@jba jba commented Aug 8, 2025

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.

@jba jba requested review from findleyr and samthanawalla August 8, 2025 11:49
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 {
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor

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
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

@jba jba requested a review from findleyr August 8, 2025 18:35
@jba jba requested a review from findleyr August 11, 2025 14:28
Copy link
Contributor

@findleyr findleyr left a 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 {
Copy link
Contributor

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.

@jba jba requested a review from findleyr August 12, 2025 17:30
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.
Copy link
Contributor

@findleyr findleyr left a 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)
Copy link
Contributor

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.

Copy link
Contributor Author

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.

@jba jba merged commit 6e03217 into modelcontextprotocol:main Aug 12, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants