A Model Context Protocol® extension adds methods, notifications or metadata without changing the core protocol revision. It is not a C# extension method or an OS URL protocol handler.
dotnet add package \ RecursiveAI.OneModelContext
Requires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.
using RecursiveAI.OneModelContext; using RecursiveAI.OneModelContext .Extensions; var server = new ContextServer( "Extensions", "1.0"); server.Extensions.Add( new ExtensionRegistration( "com.example/echo") .Request("com.example/echo/say", (parameters, _) => ValueTask.FromResult(parameters)));
The snippet is only the registration step; the zip connects a client and calls the method. Only peers that declare the extension can call it.
Unzip the example, then dotnet run.
The full program
The program connects an in-memory client that declares com.example/echo, checks the server’s support, then sends the inline JSON input {"text":"Hello"}.
using RecursiveAI.OneModelContext; using RecursiveAI.OneModelContext.Protocol; using System.Text.Json; using RecursiveAI.OneModelContext.Extensions; var server = new ContextServer("Extensions", "1.0"); server.Extensions.Add( new ExtensionRegistration("com.example/echo") .Request("com.example/echo/say", (parameters, _) => ValueTask.FromResult(parameters))); var toServer = new System.IO.Pipelines.Pipe(); var toClient = new System.IO.Pipelines.Pipe(); var serving = server.RunAsync(ContextTransport.Stream( toServer.Reader.AsStream(), toClient.Writer.AsStream())); await using (var client = await ContextClient.ConnectAsync( ContextTransport.Stream(toClient.Reader.AsStream(), toServer.Writer.AsStream()), new ContextClientOptions { Extensions = [new ClientExtension("com.example/echo")] })) { Console.WriteLine(client.ServerSupportsExtension("com.example/echo")); using var input = JsonDocument.Parse("""{"text":"Hello"}"""); var result = await client.CallExtensionAsync( "com.example/echo/say", input.RootElement); Console.WriteLine(result.GetProperty("text").GetString()); } await serving;
dotnet run prints:
True Hello
A peer that has not declared the extension receives method-not-found (-32601) for its methods.
Variations
To restrict the extension to a core revision, replace its constructor with:
new ExtensionRegistration("com.example/echo", revisions: ["2026-07-28"])
Keep the chained Request registration. The example’s current-revision client still prints True and Hello.
Related recipes
The short answer
Add an ExtensionRegistration to server.Extensions, register its request handlers before adding it, and configure the connecting client with a matching ClientExtension. The client can check ServerSupportsExtension after connection and invoke a registered method with CallExtensionAsync.
Registering a server method alone is not negotiation. The server must offer the extension and the peer must declare support. The runnable example includes both sides, so it demonstrates a negotiated call rather than just a populated registry. Its echo handler returns the parameters as a JSON object; the client prints the text field.
What are protocol extensions?
Extensions add methods, notifications, _meta keys or capability settings without requiring a new core protocol revision. They are opt-in. The protocol extensions overview describes this mechanism; the pinned library documentation gives the API and dispatch behavior used here.
This is not a C# extension method, an operating-system URL protocol handler or a service-container registration. It extends the messages exchanged between a protocol client and server. If all you need is another callable tool, register a tool instead. Use a protocol extension when peers need an additional message contract and agree to implement it.
The echo method is deliberately small. A production extension needs its own documented input, output and metadata contract. Its identifier should use a reversed domain you own, followed by a slash and its name. com.example/echo is an example identifier, not a domain ownership claim.
How do I register a protocol extension in C#?
Build the registration with its identifier, settings and optional supported revisions. Chain Request and Notification handlers onto it before calling Extensions.Add. Adding it freezes the registration; finish constructing it first.
Identifiers use {vendor-prefix}/{name}. Reserved prefixes, including io.modelcontextprotocol and the reserved suffix families documented by the library, are refused for third-party extensions. Use your own vendor prefix rather than imitating a built-in extension.
Settings are advertised JSON objects. For source-generated serialization, ExtensionRegistration.Create<T> accepts serialization metadata. A null revision list means all core revisions; the variation limits this example to one revision. On a revision outside that list, the library does not advertise the extension and its methods answer method-not-found.
How does a client negotiate a protocol extension?
Only entries in ContextClientOptions.Extensions are declared by the client. On core revision 2026-07-28, client capabilities travel in request _meta, and server extensions appear in server/discover. On 2025-11-25 and earlier, both sides use initialize capabilities. The library’s support-checking API abstracts those paths.
Within a handler, context.SupportsExtension(id) checks peer support, and the settings helpers retrieve what it advertised. Do not equate unknown extensions with errors: the library retains and ignores unknown declarations.
Optional behavior is the default. Setting Required = true rejects peers that lack support; the documented error mapping depends on the protocol revision. A required client extension instead makes connection fail with ExtensionRequiredException when the server lacks it. This example leaves required mode off. Server-to-client extension requests and multi-round-trip input requests inside extension calls are not supported by this version.