AgentStack
MCP verified MIT Self-run

MCPGateway

mcp-managedcode-mcpgateway · by managedcode

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

No reviews yet
0 installs
9 views
0.0% view→install

Install

$ agentstack add mcp-managedcode-mcpgateway

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of MCPGateway? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

ManagedCode.MCPGateway

[](https://github.com/managedcode/MCPGateway/actions/workflows/ci.yml) [](https://github.com/managedcode/MCPGateway/actions/workflows/release.yml) [](https://github.com/managedcode/MCPGateway/actions/workflows/codeql.yml) [](https://www.nuget.org/packages/ManagedCode.MCPGateway) [](LICENSE)

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

It is built on:

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

ManagedCode.MCPGateway treats the official modelcontextprotocol/csharp-sdk as its MCP protocol baseline. The package builds on top of that SDK rather than replacing it with a narrower custom protocol layer, and the shipped gateway surface now includes aggregated MCP tools, gateway-owned and upstream MCP prompts, and MCP resources plus downstream MCP export support for completion, prompt list-change notifications, resource subscriptions, logging/setLevel, and task-backed MCP tool execution.

Install

dotnet add package ManagedCode.MCPGateway

What You Get

  • one gateway for local AITool instances and MCP tools
  • one prompt catalog for source-aware MCP prompts plus gateway-owned custom and composite prompts
  • one resource catalog for MCP resources and resource templates aggregated across registered MCP sources
  • one downstream MCP server export path over the aggregated tool, prompt, and resource catalogs with stable MCP protocol parity for completions, prompt list-change notifications, resource subscriptions, logging level changes, and task-backed tool execution
  • one search API with default schema-aware Markdown-LD SPARQL graph ranking, opt-in vector ranking, and vector-first Auto
  • one graph search API for schema/profile inspection, schema-aware SPARQL search, explicit allowlisted federation, graph evidence, and graph export
  • one category-first routing API for advanced tool discovery flows
  • one invocation API for both local tools and MCP tools
  • additive catalog registration through IMcpGatewayRegistry
  • full in-memory catalog control through IMcpGatewayCatalogRuntime
  • prompt inspection and rendering through IMcpGatewayPromptCatalog
  • resource inspection and reading through IMcpGatewayResourceCatalog
  • DI-owned factory creation for isolated custom gateway instances
  • reusable gateway meta-tools for chat loops
  • optional warmup, caching, query normalization, and embedding reuse
  • BenchmarkDotNet performance and allocation benchmarks for search, indexing, and meta-tools

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

  • IMcpGateway
  • IMcpGatewayRegistry
  • IMcpGatewayCatalogRuntime
  • IMcpGatewayGraphSearch
  • IMcpGatewayPromptCatalog
  • IMcpGatewayResourceCatalog
  • IMcpGatewayFactory
  • McpGatewayToolSet

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();

var search = await gateway.SearchAsync("find github repositories");
var route = await gateway.RouteToolsAsync(new McpGatewayToolRouteRequest(
    Query: "find github repositories",
    MaxCategories: 3,
    MaxToolsPerCategory: 2));
var invoke = await gateway.InvokeAsync(new McpGatewayInvokeRequest(
    ToolId: search.Matches[0].ToolId,
    Query: "managedcode"));

Default behavior:

  • SearchStrategy = Graph
  • MarkdownLdGraphSource = GeneratedToolGraph
  • MarkdownLdGraphSearchMode = Hybrid
  • SearchQueryNormalization = TranslateToEnglishWhenAvailable
  • DefaultSearchLimit = 5
  • MaxSearchResults = 50
  • MaxDescriptorLength = 16384
  • the index is built lazily on first list, search, or invoke

Hybrid means the Markdown-LD graph path runs schema-aware SPARQL search first, then uses the gateway-built ranked graph candidate path as supporting evidence and fuzzy fallback for noisy queries. It is not a tokenizer-only search path.

DefaultSearchLimit is the normal top-N result size when a caller does not ask for a count. MaxSearchResults is only a hard cap for caller-requested result sizes so LLM-facing tool discovery cannot accidentally flood context. MaxDescriptorLength bounds the generated descriptor text used by search and Markdown-LD graph indexing; hosts with very large tool schemas can raise it explicitly.

Register Tools And Sources

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

Register MCP sources during startup:

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

AddHttpServer(...) uses the official MCP C# SDK Streamable HTTP transport for modern remote MCP endpoints and keeps the source registered as an HTTP MCP source in gateway descriptors and downstream export metadata. Use the overload with HttpTransportMode only when a legacy endpoint requires AutoDetect or Sse. Use McpGatewayHttpServerOptions when a host needs the SDK HTTP transport knobs such as additional headers, connection timeout, known session id, session ownership, OAuth options, or SSE reconnection settings. The package does not set a transport timeout by default; hosts can pass one explicitly or own deadline policy through cancellation tokens and hosting infrastructure.

You can also register:

  • existing McpClient instances through AddMcpClient(...)
  • deferred McpClient factories through AddMcpClientFactory(...)

If you need to add tools or sources after the container is built, use IMcpGatewayRegistry:

await using var serviceProvider = services.BuildServiceProvider();

var registry = serviceProvider.GetRequiredService();
var gateway = serviceProvider.GetRequiredService();

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 invalidate the catalog. The next list, search, or invoke rebuilds the index automatically.

List And Render MCP Prompts

If registered MCP sources expose prompts, resolve IMcpGatewayPromptCatalog from DI and use it as the aggregated MCP prompt surface:

var promptCatalog = serviceProvider.GetRequiredService();

var prompts = await promptCatalog.ListPromptsAsync();
var prompt = await promptCatalog.GetPromptAsync(new McpGatewayPromptRequest(
    SourceId: prompts[0].SourceId,
    PromptName: prompts[0].PromptName,
    Arguments: new Dictionary
    {
        ["repository"] = "ManagedCode.MCPGateway"
    }));

Prompt retrieval is source-aware on purpose. Different MCP servers may expose prompts with the same name, so callers should use the SourceId plus PromptName pair returned by ListPromptsAsync(). Identically named prompts are not merged implicitly by the gateway. If you want one higher-level prompt that combines or modifies multiple upstream prompts, register an explicit gateway-owned prompt instead.

Create Gateway-Owned Prompts

You can register custom prompts directly on the gateway and compose them from multiple upstream prompt sources:

services.AddMcpGateway(options =>
{
    options.AddMcpClient("repo", repositoryClient, disposeClient: false);
    options.AddMcpClient("ops", operationsClient, disposeClient: false);

    options.AddPrompt(
        new McpGatewayPrompt("release_review_bundle", async (context, cancellationToken) =>
        {
            var repositoryPrompt = await context.GetPromptAsync(
                "repo",
                "repository_triage_system_prompt",
                new Dictionary
                {
                    ["repository"] = context.Arguments["repository"],
                    ["locale"] = context.Arguments["locale"]
                },
                cancellationToken);

            var deploymentPrompt = await context.GetPromptAsync(
                "ops",
                "deployment_review_system_prompt",
                new Dictionary
                {
                    ["environment"] = context.Arguments["environment"]
                },
                cancellationToken);

            return new GetPromptResult
            {
                Description = "Release review bundle prompt.",
                Messages =
                [
                    new PromptMessage
                    {
                        Role = Role.User,
                        Content = new TextContentBlock
                        {
                            Text = "Combine repository and deployment guidance into one review plan."
                        }
                    },
                    ..repositoryPrompt!.Messages,
                    ..deploymentPrompt!.Messages
                ]
            };
        })
        {
            DisplayName = "Release review bundle",
            Description = "Combines repository and deployment review guidance into one prompt.",
            Arguments =
            [
                new McpGatewayPromptArgumentDescriptor("repository", "Repository", "Repository name.", true),
                new McpGatewayPromptArgumentDescriptor("environment", "Environment", "Deployment environment.", true),
                new McpGatewayPromptArgumentDescriptor("locale", "Locale", "Preferred locale.", false)
            ],
            CompleteAsync = static (context, cancellationToken) =>
            {
                cancellationToken.ThrowIfCancellationRequested();

                var values = context.ArgumentName == "repository"
                    ? new[] { "ManagedCode/MCPGateway", "ManagedCode/AIBase" }
                    : Array.Empty();

                var matches = values
                    .Where(value => value.StartsWith(context.ArgumentValue, StringComparison.OrdinalIgnoreCase))
                    .ToList();

                return ValueTask.FromResult(new CompleteResult
                {
                    Completion = new Completion
                    {
                        Values = matches,
                        Total = matches.Count,
                        HasMore = false
                    }
                });
            }
        });
});

Use gateway-owned prompts when you need:

  • one prompt that combines several upstream prompts
  • a modified or opinionated overlay on top of an upstream prompt
  • custom prompt argument completion values exposed through downstream MCP completion/complete

List And Read MCP Resources

If registered MCP sources expose resources, resolve IMcpGatewayResourceCatalog from DI and use it as the aggregated MCP resource surface:

var resourceCatalog = serviceProvider.GetRequiredService();

var resources = await resourceCatalog.ListResourcesAsync();
var templates = await resourceCatalog.ListResourceTemplatesAsync();
var issue = await resourceCatalog.ReadResourceAsync(new McpGatewayResourceRequest(
    SourceId: templates[0].SourceId,
    ResourceUri: "docs://issues/42"));

Resource reads are source-aware on purpose. Different MCP servers may expose the same URI scheme or URI shape, so callers should use the SourceId returned by ListResourcesAsync() or ListResourceTemplatesAsync(). For templated resources, expand the template to a concrete resource URI before calling ReadResourceAsync(...).

Export The Gateway As An MCP Server

If you want one downstream MCP endpoint over the aggregated gateway catalog, register an MCP server and add the gateway export:

var services = new ServiceCollection();
services.AddLogging();
services.AddMcpGateway(options =>
{
    options.AddMcpClient("docs", docsClient, disposeClient: false);
    options.AddMcpClient("ops", opsClient, disposeClient: false);
});

services.AddMcpServer()
    .WithStdioServerTransport()
    .WithMcpGatewayCatalog();

WithMcpGatewayCatalog() exports:

  • aggregated tools through MCP tools/list and tools/call
  • aggregated prompts through MCP prompts/list and prompts/get
  • aggregated resources through MCP resources/list, resources/templates/list, and resources/read
  • prompt and resource completions through MCP completion/complete
  • forwarded notifications/prompts/list_changed when upstream or gateway-owned prompts change
  • resource subscriptions through MCP resources/subscribe and resources/unsubscribe, including forwarded notifications/resources/updated
  • logging level changes through MCP logging/setLevel
  • task-backed tool execution through MCP tools/call with task metadata plus MCP tasks/list, tasks/get, tasks/result, and tasks/cancel
  • forwarded notifications/tasks/status for exported gateway tasks

Exported MCP tool names are canonical lowercase MCP-safe gateway ids. A unique upstream tool such as notion-fetch is exported as notion-fetch; when multiple sources expose the same normalized tool name, the gateway uses a source-qualified safe id such as docs_search_repository or ops_search_repository. Hash suffixes are added only when length or collision handling requires them. Names that would exceed 64 characters keep a readable normalized prefix and receive a deterministic hash suffix instead of being blindly truncated. McpGatewayToolDescriptor.ToolId, downstream MCP Tool.Name, and downstream tools/call names are the same value, and gateway-owned name resolution is case-insensitive so callers can vary casing without creating a different tool identity. Prompt names and exported resource names use the same lowercase MCP-safe source qualification, for example ops_deployment_review_system_prompt or local_release_review_bundle, while raw upstream SourceId, prompt name, tool name, and resource URI remain available as routing metadata and _meta values. Exported MCP resource URIs and URI templates are rewritten into gateway-owned opaque URIs so downstream resources/read calls route back to the correct upstream source even when multiple servers expose overlapping URI spaces. The same source-aware rewrite is also used for completion/complete, forwarded prompt list changes, and forwarded resource update notifications, so downstream clients always talk in terms of gateway-owned prompt names and resource URIs while the gateway proxies the corresponding upstream MCP operations. When an upstream MCP tool already advertises task support, the gateway preserves that contract on the exported tool and proxies th

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.