Skip to content

managedcode/MCPGateway

Repository files navigation

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.

The package is built on:

  • Microsoft.Extensions.AI
  • the official ModelContextProtocol .NET SDK
  • in-memory descriptor indexing with optional embedding-based ranking

Install

dotnet add package ManagedCode.MCPGateway

What You Get

  • one registry for local tools, stdio MCP servers, HTTP MCP servers, or prebuilt McpClient instances
  • descriptor indexing that enriches search with tool name, description, required arguments, and input schema
  • vector search when an IEmbeddingGenerator<string, Embedding<float>> is registered
  • optional persisted tool embeddings through IMcpGatewayToolEmbeddingStore
  • lexical fallback when embeddings are unavailable
  • one invoke surface for both local AIFunction tools and MCP tools
  • optional meta-tools you can hand back to another model as normal AITool instances

Quickstart

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

var services = new ServiceCollection();

services.AddManagedCodeMcpGateway(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>();

await gateway.BuildIndexAsync();

var search = await gateway.SearchAsync("find github repositories", maxResults: 3);
var selectedTool = search.Matches[0];

var invoke = await gateway.InvokeAsync(new McpGatewayInvokeRequest(
    ToolId: selectedTool.ToolId,
            Query: "managedcode"));

AddManagedCodeMcpGateway(...) does not create or configure an embedding generator for you. Vector ranking is enabled only when the same DI container also has an IEmbeddingGenerator<string, Embedding<float>>. The gateway first tries the keyed registration McpGatewayServiceKeys.EmbeddingGenerator and falls back to any regular registration. Otherwise it stays fully functional and uses lexical ranking.

Context-Aware Search And Invoke

When the current turn has extra UI, workflow, or chat context, pass it through the request models:

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"
    }));

The gateway uses this request context in two ways:

  • search combines the query, context summary, and context values into one effective search input for embeddings or lexical fallback
  • MCP invocation sends the request context in MCP meta
  • local AIFunction tools can receive auto-mapped query, contextSummary, and context arguments when those parameters are required

Meta-Tools

You can expose the gateway itself as two reusable AITool instances:

var tools = gateway.CreateMetaTools();

By default this creates:

  • gateway_tools_search
  • gateway_tool_invoke

These tools are useful when another model should first search the gateway catalog and then invoke the selected tool.

Search Behavior

ManagedCode.MCPGateway builds one descriptor document per tool from:

  • tool name
  • display name
  • description
  • required arguments
  • input schema summaries

If an embedding generator is registered, the gateway vectorizes those descriptor documents and uses cosine similarity plus a small lexical boost. It first tries the keyed registration McpGatewayServiceKeys.EmbeddingGenerator and then falls back to any regular IEmbeddingGenerator<string, Embedding<float>>. If no embedding generator is present, it falls back to lexical ranking without disabling execution.

The embedding generator is resolved per gateway operation, so singleton, scoped, and transient DI registrations all work with index builds and search.

Optional Embeddings

Register any provider-specific implementation of IEmbeddingGenerator<string, Embedding<float>> in the same DI container before building the service provider.

Preferred registration for the gateway:

var services = new ServiceCollection();

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

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

Fallback when your app already exposes a regular embedding generator:

var services = new ServiceCollection();

services.AddSingleton<IEmbeddingGenerator<string, Embedding<float>>, MyEmbeddingGenerator>();

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

The keyed registration is the preferred one, so you can dedicate a specific embedder to the gateway without affecting other app services.

Persistent Tool Embeddings

For process-local caching, the package already includes McpGatewayInMemoryToolEmbeddingStore:

var services = new ServiceCollection();

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

services.AddManagedCodeMcpGateway(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 you want to keep descriptor embeddings in a database or another persistent store, register your own IMcpGatewayToolEmbeddingStore implementation instead:

var services = new ServiceCollection();

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

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

During BuildIndexAsync() the gateway:

  • computes a descriptor-document hash per tool
  • asks IMcpGatewayToolEmbeddingStore for matching stored vectors
  • generates embeddings only for tools that are missing in the store
  • upserts the newly generated vectors back into the store

This avoids recalculating tool embeddings on every rebuild while still refreshing them automatically when the descriptor document changes. Stored vectors are scoped to both the descriptor hash and the resolved embedding-generator fingerprint, so changing the provider or model automatically forces regeneration. Query embeddings are still generated at search time from the registered IEmbeddingGenerator<string, Embedding<float>>.

Supported Sources

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

Local Development

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

About

Searchable MCP and AITool gateway for .NET, built on Microsoft.Extensions.AI with embedding-based tool discovery, lexical fallback, and unified execution

Topics

Resources

License

Stars

Watchers

Forks

Contributors

Languages