---
title: "Register a protocol extension in C#"
url: https://recursiveai.org/onemodelcontext/cookbook/register-protocol-extension/
description: "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."
version: "1.0.1"
---

# Register a protocol extension in C#

**OneModelContext** is a free, open-source .NET SDK for the Model Context Protocol® from Recursive AI. MIT or Apache-2.0, no native dependencies.

[NuGet](https://www.nuget.org/packages/RecursiveAI.OneModelContext) · [GitHub](https://github.com/recursive-ai-oss/onemodelcontext-csharp) · [Docs](https://recursiveai.org/onemodelcontext/docs/) · [Download example (.zip)](https://recursiveai.org/onemodelcontext/cookbook/register-protocol-extension/register-protocol-extension.zip)

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.

```shell
dotnet add package \
  RecursiveAI.OneModelContext
```

Requires [.NET 10](https://dotnet.microsoft.com/download/dotnet/10.0) (net10.0); .NET 9 and earlier aren’t supported.

```csharp
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](https://recursiveai.org/onemodelcontext/cookbook/register-protocol-extension/register-protocol-extension.zip), 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"}`.

```csharp
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:

```text
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:

```csharp
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

- [Build a protocol server in C#](https://recursiveai.org/onemodelcontext/cookbook/build-a-server-in-csharp/)
- [Serve over HTTP without ASP.NET Core](https://recursiveai.org/onemodelcontext/cookbook/serve-over-http-without-aspnet/)

---

## 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](https://modelcontextprotocol.io/extensions/overview) describes this mechanism; the [pinned library documentation](https://github.com/recursive-ai-oss/onemodelcontext-csharp/blob/d72d78c39c934697857be92dd8514efd4c415a88/docs/extensions.md) 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.
