Skip to content

mcp: typed custom methods and extension registration - #1031

Open
sambhav wants to merge 2 commits into
modelcontextprotocol:mainfrom
sambhav:custom-method-bidirectional-extension
Open

mcp: typed custom methods and extension registration#1031
sambhav wants to merge 2 commits into
modelcontextprotocol:mainfrom
sambhav:custom-method-bidirectional-extension

Conversation

@sambhav

@sambhav sambhav commented Jun 27, 2026

Copy link
Copy Markdown
Member

Custom method consumers currently repeat the method name and generic types at registration and call sites. This follow-up to #956 packages those details once for extension authors.

As agreed in the review discussion, this PR now covers client-to-server custom methods only. The server-initiated APIs and their additional dispatch maps have been removed.

var Translate = mcp.NewCustomMethod[*TranslateParams, *TranslateResult]("latin/translate")

if err := Translate.RegisterServer(server, handler); err != nil {
    return err
}
if err := Translate.RegisterClient(client); err != nil {
    return err
}
result, err := Translate.Call(ctx, session, &TranslateParams{Text: "hello"})
  • CustomMethod wraps the existing registration and call functions with RegisterServer, RegisterClient, and Call.
  • RegisterExtension supports package-level setup; ServerOptions.Extensions and ClientOptions.Extensions support per-instance setup. Global extensions run first, in registration order; later registrations of a custom method replace earlier ones. Existing instances are unaffected.
  • One helper applies extensions outside the registry lock. Either callback may be nil; callback errors panic during construction.
  • The Latin example demonstrates an extension package exposing a simple Translate helper.
  • Current main is merged and conflicts resolved.

Validation on Go 1.26.1:

  • Custom-method and extension tests pass with -race, including all supported protocol versions, typed-nil params, registration errors, handler errors, ordering, overrides, scope, and concurrent registration.
  • Existing multi-round-trip and server-initiated-request rejection tests pass with -race.
  • go build ./..., go vet ./..., formatting checks, and the Latin example pass.
  • The full local race suite passes with a temporary Go test overlay making the two existing localhost-protection fixtures target 127.0.0.1 instead of their wildcard listener address. This avoids the environment's proxy while preserving their Host headers and assertions; repository test files are unchanged.
  • GitHub CI passes the unmodified full suite on Go 1.25 and 1.26, the full race suite, formatting, vet, and staticcheck. Conformance and CodeQL also pass.

…egistry

Builds on the custom JSON-RPC method support in modelcontextprotocol#956 in three ways:

1. **Bidirectionality** — the server can now send requests to the client,
   and the client can register handlers for them, mirroring the existing
   client→server direction:
   - AddServerSendingCustomMethod / ServerCallCustomMethod
   - AddClientReceivingCustomMethod
   - CustomMethod.RegisterServerSending / .ServerCall
   - CustomMethod.RegisterClientReceiving

2. **CustomMethod[P, R, T]** — a phantom-type wrapper that captures the
   method name and parameter/result types once at package level, so call
   sites never repeat generic arguments or method-name strings:

       var Method = mcp.NewCustomMethod[*Params, *Result]("acme/method")
       result, err := Method.Call(ctx, cs, &Params{...})

3. **Extension registry** — a mechanism for libraries to auto-wire custom
   methods into every Server/Client without requiring callers to do any
   manual setup:
   - RegisterExtension (global, typically called from init)
   - ServerOptions.Extensions / ClientOptions.Extensions (per-instance)

   Global extensions are applied first; per-instance extensions after
   (last writer wins on name collision).

The example is restructured to demonstrate the pattern: a latinext
sub-package is the "extension author" (defines types, registers via
init(), exports a Translate() helper); main.go is the "consumer" (just
imports latinext, no generics or method-name strings visible).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@piyushbag piyushbag left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looked through the extension registry and bidirectional custom-method wiring. The CustomMethod wrapper and latinext example make the #956 follow-up easy to follow.

Local check:

go test ./mcp/... -count=1

All green on my machine.

One non-blocking suggestion: TestCustomMethods only exercises client→server today. A small round-trip test for AddServerSendingCustomMethod + AddClientReceivingCustomMethod (or ServerCallCustomMethod) would lock in the new path.

@guglielmo-san

Copy link
Copy Markdown
Contributor

@sambhav thank you for your contribution.
The new mcp protocol disallows Server initiated requests, and should instead be based on the MultiRoundTripRequest
That's why I would lean for not approving this PR

@sambhav

sambhav commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

@sambhav thank you for your contribution. The new mcp protocol disallows Server initiated requests, and should instead be based on the MultiRoundTripRequest That's why I would lean for not approving this PR

gotcha, let me remove those bits. I think the rest of the syntactic sugar still helps though.

Remove server-initiated custom method registration and calls as agreed in
PR modelcontextprotocol#1031. Retain the typed CustomMethod wrapper with RegisterServer,
RegisterClient, and Call, and consolidate extension application.

Merge current main, refresh the Latin example, and test protocol versions,
registration order and scope, error propagation, and concurrent extension
registration.
@sambhav sambhav changed the title mcp: bidirectional custom methods, CustomMethod type, and extension registry mcp: typed custom methods and extension registration Sep 11, 2026
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.

3 participants