Searchable MCP and AITool gateway for .NET, built on Microsoft.Extensions.AI with embedding-based tool discovery, lexical fallback, and unified execution
ManagedCode.MCPGateway
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
McpClientinstances - 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
AIFunctiontools and MCP tools - optional meta-tools you can hand back to another model as normal
AIToolinstances
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
AIFunctiontools can receive auto-mappedquery,contextSummary, andcontextarguments 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_searchgateway_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
IMcpGatewayToolEmbeddingStorefor 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
McpClientinstances - deferred
McpClientfactories
Local Development
dotnet restore ManagedCode.MCPGateway.slnx
dotnet build ManagedCode.MCPGateway.slnx -c Release
dotnet test --solution ManagedCode.MCPGateway.slnx -c Release