ManagedCode.MCPGateway 0.3.1

Prefix Reserved
dotnet add package ManagedCode.MCPGateway --version 0.3.1
                    
NuGet\Install-Package ManagedCode.MCPGateway -Version 0.3.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ManagedCode.MCPGateway" Version="0.3.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ManagedCode.MCPGateway" Version="0.3.1" />
                    
Directory.Packages.props
<PackageReference Include="ManagedCode.MCPGateway" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ManagedCode.MCPGateway --version 0.3.1
                    
#r "nuget: ManagedCode.MCPGateway, 0.3.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ManagedCode.MCPGateway@0.3.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ManagedCode.MCPGateway&version=0.3.1
                    
Install as a Cake Addin
#tool nuget:?package=ManagedCode.MCPGateway&version=0.3.1
                    
Install as a Cake Tool

ManagedCode.MCPGateway

CI Release CodeQL NuGet License: MIT

ManagedCode.MCPGateway is a .NET 10 library that turns local AITool instances and remote MCP servers into one searchable execution surface.

It is built on:

  • Microsoft.Extensions.AI
  • the official ModelContextProtocol .NET SDK

Install

dotnet add package ManagedCode.MCPGateway

What It Gives You

  • one gateway for local AITool instances and MCP tools
  • one search surface with vector ranking when embeddings are available and lexical fallback when they are not
  • one invoke surface for both local tools and MCP tools
  • runtime registration through IMcpGatewayRegistry
  • reusable gateway meta-tools for chat clients and agents
  • staged tool auto-discovery for chat loops, so models do not need to see the whole catalog at once

Core Services

After services.AddMcpGateway(...), the container exposes:

  • IMcpGateway for build, list, search, invoke, and meta-tool creation
  • IMcpGatewayRegistry for adding tools or MCP sources after the container is built
  • McpGatewayToolSet for reusable AITool integration helpers

Quickstart

using ManagedCode.MCPGateway;
using ManagedCode.MCPGateway.Abstractions;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();

services.AddMcpGateway(options =>
{
    options.AddTool(
        "local",
        AIFunctionFactory.Create(
            static (string query) => $"github:{query}",
            new AIFunctionFactoryOptions
            {
                Name = "github_search_repositories",
                Description = "Search GitHub repositories by user query."
            }));

    options.AddStdioServer(
        sourceId: "filesystem",
        command: "npx",
        arguments: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]);
});

await using var serviceProvider = services.BuildServiceProvider();
var gateway = serviceProvider.GetRequiredService<IMcpGateway>();

var search = await gateway.SearchAsync("find github repositories");
var invoke = await gateway.InvokeAsync(new McpGatewayInvokeRequest(
    ToolId: search.Matches[0].ToolId,
    Query: "managedcode"));

Important defaults:

  • search is Auto by default
  • Auto uses embeddings when available and lexical fallback otherwise
  • the default result size is 5
  • the maximum result size is 15
  • the index is built lazily on first list, search, or invoke

Basic Registration

Register local tools during startup:

services.AddMcpGateway(options =>
{
    options.AddTool(
        "local",
        AIFunctionFactory.Create(
            static (string query) => $"weather:{query}",
            new AIFunctionFactoryOptions
            {
                Name = "weather_search_forecast",
                Description = "Search weather forecast and temperature information by city name."
            }));
});

If you need to add tools later, use IMcpGatewayRegistry:

await using var serviceProvider = services.BuildServiceProvider();

var registry = serviceProvider.GetRequiredService<IMcpGatewayRegistry>();
var gateway = serviceProvider.GetRequiredService<IMcpGateway>();

registry.AddTool(
    "runtime",
    AIFunctionFactory.Create(
        static (string query) => $"status:{query}",
        new AIFunctionFactoryOptions
        {
            Name = "project_status_lookup",
            Description = "Look up project status by identifier or short title."
        }));

var tools = await gateway.ListToolsAsync();

Registry updates automatically invalidate the catalog. The next list, search, or invoke rebuilds the index.

Register MCP Sources

ManagedCode.MCPGateway supports:

  • local AITool / AIFunction
  • HTTP MCP servers
  • stdio MCP servers
  • existing McpClient instances
  • deferred McpClient factories

Examples:

services.AddMcpGateway(options =>
{
    options.AddHttpServer(
        sourceId: "docs",
        endpoint: new Uri("https://example.com/mcp"));

    options.AddStdioServer(
        sourceId: "filesystem",
        command: "npx",
        arguments: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]);
});

Or through the runtime registry:

var registry = serviceProvider.GetRequiredService<IMcpGatewayRegistry>();

registry.AddMcpClient(
    sourceId: "issues",
    client: existingClient,
    disposeClient: false);

registry.AddMcpClientFactory(
    sourceId: "work-items",
    clientFactory: static async cancellationToken =>
        await CreateWorkItemClientAsync(cancellationToken));

Search And Invoke

The normal flow is:

  1. search
  2. choose a match
  3. invoke by ToolId
var search = await gateway.SearchAsync("find github repositories");

var invoke = await gateway.InvokeAsync(new McpGatewayInvokeRequest(
    ToolId: search.Matches[0].ToolId,
    Query: "managedcode"));

If the host already knows the stable tool name, invocation can target ToolName and SourceId instead:

var invoke = await gateway.InvokeAsync(new McpGatewayInvokeRequest(
    ToolName: "github_search_repositories",
    SourceId: "local",
    Query: "managedcode"));

Use SourceId when the same tool name can exist in more than one source.

Context-Aware Search And Invoke

You can pass UI or workflow context into search and invocation:

var search = await gateway.SearchAsync(new McpGatewaySearchRequest(
    Query: "search",
    ContextSummary: "User is on the GitHub repository settings page",
    Context: new Dictionary<string, object?>
    {
        ["page"] = "settings",
        ["domain"] = "github"
    },
    MaxResults: 3));

var invoke = await gateway.InvokeAsync(new McpGatewayInvokeRequest(
    ToolId: search.Matches[0].ToolId,
    Query: "managedcode",
    ContextSummary: "User wants repository administration actions",
    Context: new Dictionary<string, object?>
    {
        ["page"] = "settings",
        ["domain"] = "github"
    }));

This context is used for ranking, and MCP invocations also receive it through MCP meta.

Meta-Tools

The gateway can expose itself as two reusable tools:

  • gateway_tools_search
  • gateway_tool_invoke

From the gateway:

var tools = gateway.CreateMetaTools();

From DI:

var toolSet = serviceProvider.GetRequiredService<McpGatewayToolSet>();
var tools = toolSet.CreateTools();

You can also attach those tools to existing chat options or tool lists:

var toolSet = serviceProvider.GetRequiredService<McpGatewayToolSet>();

var options = new ChatOptions
{
    AllowMultipleToolCalls = false
}.AddMcpGatewayTools(toolSet);

var tools = toolSet.AddTools(existingTools);

Why Auto-Discovery

Large tool catalogs should not be pushed directly into every model turn.

The recommended flow is:

  1. expose only gateway_tools_search and gateway_tool_invoke
  2. let the model search the gateway catalog
  3. project only the latest matching tools as direct proxy tools
  4. replace that discovered set when a new search result arrives

This keeps prompts smaller and tool choice cleaner while still using the full gateway catalog behind the scenes.

For normal chat loops, use the staged wrapper:

using ManagedCode.MCPGateway;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;

await using var serviceProvider = services.BuildServiceProvider();

var innerChatClient = serviceProvider.GetRequiredService<IChatClient>();
using var chatClient = innerChatClient.UseMcpGatewayAutoDiscovery(
    serviceProvider,
    options =>
    {
        options.MaxDiscoveredTools = 2;
    });

var response = await chatClient.GetResponseAsync(
    [new ChatMessage(ChatRole.User, "Find the github search tool and run it.")],
    new ChatOptions
    {
        AllowMultipleToolCalls = false
    });

What this does:

  • the first turn only exposes the two gateway meta-tools
  • after a search result, the latest matches are exposed as direct proxy tools
  • a later search replaces the previous discovered set

If you already have a search result and want to materialize those proxy tools yourself, use:

var toolSet = serviceProvider.GetRequiredService<McpGatewayToolSet>();
var discoveredTools = toolSet.CreateDiscoveredTools(search.Matches, maxTools: 3);

The same chat wrapper works with Microsoft Agent Framework hosts:

using ManagedCode.MCPGateway;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

await using var serviceProvider = services.BuildServiceProvider();

var innerChatClient = serviceProvider.GetRequiredService<IChatClient>();
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
using var chatClient = innerChatClient.UseMcpGatewayAutoDiscovery(
    serviceProvider,
    options =>
    {
        options.MaxDiscoveredTools = 2;
    });

var agent = new ChatClientAgent(
    chatClient,
    instructions: "Search the gateway catalog before invoking tools.",
    name: "workspace-agent",
    tools: [],
    loggerFactory: loggerFactory,
    services: serviceProvider);

var response = await agent.RunAsync(
    "Find the github search tool and run it.",
    session: null,
    options: new ChatClientAgentRunOptions(new ChatOptions
    {
        AllowMultipleToolCalls = false
    }),
    cancellationToken: default);

ManagedCode.MCPGateway itself stays generic. The Agent Framework dependency remains in the host project.

Optional Warmup

The gateway works without explicit initialization, but you can warm the index eagerly when you want startup validation or a pre-built cache.

Manual warmup:

await using var serviceProvider = services.BuildServiceProvider();

var build = await serviceProvider.InitializeMcpGatewayAsync();

Hosted warmup:

services.AddMcpGateway(options =>
{
    options.AddTool(
        "local",
        AIFunctionFactory.Create(
            static (string query) => $"github:{query}",
            new AIFunctionFactoryOptions
            {
                Name = "github_search_repositories",
                Description = "Search GitHub repositories by user query."
            }));
});

services.AddMcpGatewayIndexWarmup();

Optional Embeddings

If the container has IEmbeddingGenerator<string, Embedding<float>>, the gateway can use vector ranking.

Preferred registration:

var services = new ServiceCollection();

services.AddKeyedSingleton<IEmbeddingGenerator<string, Embedding<float>>, MyEmbeddingGenerator>(
    McpGatewayServiceKeys.EmbeddingGenerator);

services.AddMcpGateway(options =>
{
    options.AddTool(
        "local",
        AIFunctionFactory.Create(
            static (string query) => $"github:{query}",
            new AIFunctionFactoryOptions
            {
                Name = "github_search_repositories",
                Description = "Search GitHub repositories by user query."
            }));
});

If no embedding generator is registered, the same gateway still works and falls back to lexical search automatically.

Optional Query Normalization

If you want multilingual or noisy queries normalized before ranking, register a keyed IChatClient:

var services = new ServiceCollection();

services.AddKeyedSingleton<IChatClient>(
    McpGatewayServiceKeys.SearchQueryChatClient,
    mySearchRewriteChatClient);

services.AddMcpGateway(options =>
{
    options.SearchStrategy = McpGatewaySearchStrategy.Auto;
    options.SearchQueryNormalization = McpGatewaySearchQueryNormalization.TranslateToEnglishWhenAvailable;
});

If the keyed chat client is missing or normalization fails, search continues normally.

Optional Tool Embedding Stores

For process-local caching, use the built-in IMemoryCache-backed store:

services.AddKeyedSingleton<IEmbeddingGenerator<string, Embedding<float>>, MyEmbeddingGenerator>(
    McpGatewayServiceKeys.EmbeddingGenerator);
services.AddMcpGatewayInMemoryToolEmbeddingStore();

This built-in store reuses the application's shared IMemoryCache and only caches embeddings inside the current process. It is useful for local reuse, but it is not durable and does not synchronize across replicas.

.NET also provides IDistributedCache for out-of-process cache storage and HybridCache for a combined local + distributed cache model. ManagedCode.MCPGateway does not hardcode either dependency into the gateway runtime. If you need shared cache state across instances, implement IMcpGatewayToolEmbeddingStore over the cache technology your host already uses.

For multi-instance or durable caching, register your own IMcpGatewayToolEmbeddingStore implementation:

services.AddKeyedSingleton<IEmbeddingGenerator<string, Embedding<float>>, MyEmbeddingGenerator>(
    McpGatewayServiceKeys.EmbeddingGenerator);
services.AddSingleton<IMcpGatewayToolEmbeddingStore, MyToolEmbeddingStore>();

Search Modes

McpGatewaySearchStrategy.Auto is the default and usually the right choice:

  • use vector ranking when embeddings are available
  • fall back to lexical ranking when they are not

You can also force a mode:

services.AddMcpGateway(options =>
{
    options.SearchStrategy = McpGatewaySearchStrategy.Tokenizer;
});

Or:

services.AddMcpGateway(options =>
{
    options.SearchStrategy = McpGatewaySearchStrategy.Embeddings;
});

McpGatewaySearchResult.RankingMode reports:

  • vector
  • lexical
  • browse
  • empty

Deeper Docs

Use these when you need design details rather than package onboarding:

Local Development

dotnet restore ManagedCode.MCPGateway.slnx
dotnet build ManagedCode.MCPGateway.slnx -c Release --no-restore
dotnet test --solution ManagedCode.MCPGateway.slnx -c Release --no-build

Analyzer pass:

dotnet build ManagedCode.MCPGateway.slnx -c Release --no-restore -p:RunAnalyzers=true
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.3.1 105 3/8/2026
0.3.0 68 3/8/2026
0.2.0 70 3/8/2026
0.1.1 69 3/6/2026
0.1.0 68 3/6/2026