mcp: implement a concurrency model for calls

Implement the concurrency model described in #26: notifications are
synchronous, but calls are asynchronous (except for 'initialize'). To
achieve this, implement jsonrpc2.Async(ctx) to signal asynchronous
handling. This is simpler to use than returning ErrAsyncResponse and
calling Respond, and since this is an internal detail we don't need to
worry too much about whether it's idiomatic.

Add tests that verify both features, for both client and server.

Also:
- replace req.ID.IsValid with req.IsCall
- remove the methodHandler type as we can just use MethodHandler

Fixes #26
This commit is contained in:
Rob Findley 2025-08-18 18:15:06 +00:00 • committed by Robert Findley
commit 3db848abec

View file

@ -183,7 +183,7 @@ func runServerTest(t *testing.T, test *conformanceTest) {
return nil, err, false
}
serverMessages = append(serverMessages, msg)
if req, ok := msg.(*jsonrpc.Request); ok && req.ID.IsValid() {
if req, ok := msg.(*jsonrpc.Request); ok && req.IsCall() {
// Pair up the next outgoing response with this request.
// We assume requests arrive in the same order every time.
if len(outResponses) == 0 {
@ -201,8 +201,8 @@ func runServerTest(t *testing.T, test *conformanceTest) {
// Synthetic peer interacts with real peer.
for _, req := range outRequests {
writeMsg(req)
if req.ID.IsValid() {
// A request (as opposed to a notification). Wait for the response.
if req.IsCall() {
// A call (as opposed to a notification). Wait for the response.
res, err, ok := nextResponse()
if err != nil {
t.Fatalf("reading server messages failed: %v", err)