{"id":46658956,"url":"https://github.com/managedcode/mcpgateway","last_synced_at":"2026-05-27T19:00:57.171Z","repository":{"id":342565393,"uuid":"1174342266","full_name":"managedcode/MCPGateway","owner":"managedcode","description":"Searchable MCP and AITool gateway for .NET, built on Microsoft.Extensions.AI with embedding-based tool discovery, lexical fallback, and unified execution","archived":false,"fork":false,"pushed_at":"2026-04-23T07:59:53.000Z","size":707,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-23T09:36:26.007Z","etag":null,"topics":["ai-extensions","ai-tools","csharp","dotnet","embeddings","mcp","mcp-gateway","mcp-search","microsoft-extensions-ai","model-context-protocol","search","semantic-search","tool-calling"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/managedcode.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-06T10:32:14.000Z","updated_at":"2026-04-23T07:57:37.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/managedcode/MCPGateway","commit_stats":null,"previous_names":["managedcode/mcpgateway"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/managedcode/MCPGateway","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/managedcode%2FMCPGateway","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/managedcode%2FMCPGateway/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/managedcode%2FMCPGateway/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/managedcode%2FMCPGateway/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/managedcode","download_url":"https://codeload.github.com/managedcode/MCPGateway/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/managedcode%2FMCPGateway/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33579668,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-27T02:00:06.184Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-extensions","ai-tools","csharp","dotnet","embeddings","mcp","mcp-gateway","mcp-search","microsoft-extensions-ai","model-context-protocol","search","semantic-search","tool-calling"],"created_at":"2026-03-08T09:03:58.888Z","updated_at":"2026-05-27T19:00:57.156Z","avatar_url":"https://github.com/managedcode.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ManagedCode.MCPGateway\n\n[![CI](https://github.com/managedcode/MCPGateway/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/managedcode/MCPGateway/actions/workflows/ci.yml)\n[![Release](https://github.com/managedcode/MCPGateway/actions/workflows/release.yml/badge.svg?branch=main)](https://github.com/managedcode/MCPGateway/actions/workflows/release.yml)\n[![CodeQL](https://github.com/managedcode/MCPGateway/actions/workflows/codeql.yml/badge.svg?branch=main)](https://github.com/managedcode/MCPGateway/actions/workflows/codeql.yml)\n[![NuGet](https://img.shields.io/nuget/v/ManagedCode.MCPGateway.svg)](https://www.nuget.org/packages/ManagedCode.MCPGateway)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\n`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`.\n\nIt is built on:\n\n- `Microsoft.Extensions.AI`\n- the official `ModelContextProtocol` .NET SDK\n\n`ManagedCode.MCPGateway` treats the official [`modelcontextprotocol/csharp-sdk`](https://github.com/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.\n\n## Install\n\n```bash\ndotnet add package ManagedCode.MCPGateway\n```\n\n## What You Get\n\n- one gateway for local `AITool` instances and MCP tools\n- one prompt catalog for source-aware MCP prompts plus gateway-owned custom and composite prompts\n- one resource catalog for MCP resources and resource templates aggregated across registered MCP sources\n- 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\n- one search API with default schema-aware Markdown-LD SPARQL graph ranking, opt-in vector ranking, and vector-first `Auto`\n- one graph search API for schema/profile inspection, schema-aware SPARQL search, explicit allowlisted federation, graph evidence, and graph export\n- one category-first routing API for advanced tool discovery flows\n- one invocation API for both local tools and MCP tools\n- additive catalog registration through `IMcpGatewayRegistry`\n- full in-memory catalog control through `IMcpGatewayCatalogRuntime`\n- prompt inspection and rendering through `IMcpGatewayPromptCatalog`\n- resource inspection and reading through `IMcpGatewayResourceCatalog`\n- DI-owned factory creation for isolated custom gateway instances\n- reusable gateway meta-tools for chat loops\n- optional warmup, caching, query normalization, and embedding reuse\n- BenchmarkDotNet performance and allocation benchmarks for search, indexing, and meta-tools\n\nAfter `services.AddMcpGateway(...)`, the container exposes:\n\n- `IMcpGateway`\n- `IMcpGatewayRegistry`\n- `IMcpGatewayCatalogRuntime`\n- `IMcpGatewayGraphSearch`\n- `IMcpGatewayPromptCatalog`\n- `IMcpGatewayResourceCatalog`\n- `IMcpGatewayFactory`\n- `McpGatewayToolSet`\n\n## Quickstart\n\n```csharp\nusing ManagedCode.MCPGateway;\nusing ManagedCode.MCPGateway.Abstractions;\nusing Microsoft.Extensions.AI;\nusing Microsoft.Extensions.DependencyInjection;\n\nvar services = new ServiceCollection();\n\nservices.AddMcpGateway(options =\u003e\n{\n    options.AddTool(\n        \"local\",\n        AIFunctionFactory.Create(\n            static (string query) =\u003e $\"github:{query}\",\n            new AIFunctionFactoryOptions\n            {\n                Name = \"github_search_repositories\",\n                Description = \"Search GitHub repositories by user query.\"\n            }));\n\n    options.AddStdioServer(\n        sourceId: \"filesystem\",\n        command: \"npx\",\n        arguments: [\"-y\", \"@modelcontextprotocol/server-filesystem\", \"/tmp\"]);\n});\n\nawait using var serviceProvider = services.BuildServiceProvider();\nvar gateway = serviceProvider.GetRequiredService\u003cIMcpGateway\u003e();\n\nvar search = await gateway.SearchAsync(\"find github repositories\");\nvar route = await gateway.RouteToolsAsync(new McpGatewayToolRouteRequest(\n    Query: \"find github repositories\",\n    MaxCategories: 3,\n    MaxToolsPerCategory: 2));\nvar invoke = await gateway.InvokeAsync(new McpGatewayInvokeRequest(\n    ToolId: search.Matches[0].ToolId,\n    Query: \"managedcode\"));\n```\n\nDefault behavior:\n\n- `SearchStrategy = Graph`\n- `MarkdownLdGraphSource = GeneratedToolGraph`\n- `MarkdownLdGraphSearchMode = Hybrid`\n- `SearchQueryNormalization = TranslateToEnglishWhenAvailable`\n- `DefaultSearchLimit = 5`\n- `MaxSearchResults = 50`\n- `MaxDescriptorLength = 16384`\n- the index is built lazily on first list, search, or invoke\n\n`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.\n\n`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.\n\n## Register Tools And Sources\n\nRegister local tools during startup:\n\n```csharp\nservices.AddMcpGateway(options =\u003e\n{\n    options.AddTool(\n        \"local\",\n        AIFunctionFactory.Create(\n            static (string query) =\u003e $\"weather:{query}\",\n            new AIFunctionFactoryOptions\n            {\n                Name = \"weather_search_forecast\",\n                Description = \"Search weather forecast and temperature information by city name.\"\n            }));\n});\n```\n\nRegister MCP sources during startup:\n\n```csharp\nservices.AddMcpGateway(options =\u003e\n{\n    options.AddHttpServer(\n        sourceId: \"docs\",\n        endpoint: new Uri(\"https://example.com/mcp\"));\n\n    options.AddStdioServer(\n        sourceId: \"filesystem\",\n        command: \"npx\",\n        arguments: [\"-y\", \"@modelcontextprotocol/server-filesystem\", \"/tmp\"]);\n});\n```\n\n`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.\nUse the overload with `HttpTransportMode` only when a legacy endpoint requires `AutoDetect` or `Sse`.\nUse `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.\n\nYou can also register:\n\n- existing `McpClient` instances through `AddMcpClient(...)`\n- deferred `McpClient` factories through `AddMcpClientFactory(...)`\n\nIf you need to add tools or sources after the container is built, use `IMcpGatewayRegistry`:\n\n```csharp\nawait using var serviceProvider = services.BuildServiceProvider();\n\nvar registry = serviceProvider.GetRequiredService\u003cIMcpGatewayRegistry\u003e();\nvar gateway = serviceProvider.GetRequiredService\u003cIMcpGateway\u003e();\n\nregistry.AddTool(\n    \"runtime\",\n    AIFunctionFactory.Create(\n        static (string query) =\u003e $\"status:{query}\",\n        new AIFunctionFactoryOptions\n        {\n            Name = \"project_status_lookup\",\n            Description = \"Look up project status by identifier or short title.\"\n        }));\n\nvar tools = await gateway.ListToolsAsync();\n```\n\nRegistry updates invalidate the catalog. The next list, search, or invoke rebuilds the index automatically.\n\n## List And Render MCP Prompts\n\nIf registered MCP sources expose prompts, resolve `IMcpGatewayPromptCatalog` from DI and use it as the aggregated MCP prompt surface:\n\n```csharp\nvar promptCatalog = serviceProvider.GetRequiredService\u003cIMcpGatewayPromptCatalog\u003e();\n\nvar prompts = await promptCatalog.ListPromptsAsync();\nvar prompt = await promptCatalog.GetPromptAsync(new McpGatewayPromptRequest(\n    SourceId: prompts[0].SourceId,\n    PromptName: prompts[0].PromptName,\n    Arguments: new Dictionary\u003cstring, object?\u003e\n    {\n        [\"repository\"] = \"ManagedCode.MCPGateway\"\n    }));\n```\n\nPrompt 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.\n\n## Create Gateway-Owned Prompts\n\nYou can register custom prompts directly on the gateway and compose them from multiple upstream prompt sources:\n\n```csharp\nservices.AddMcpGateway(options =\u003e\n{\n    options.AddMcpClient(\"repo\", repositoryClient, disposeClient: false);\n    options.AddMcpClient(\"ops\", operationsClient, disposeClient: false);\n\n    options.AddPrompt(\n        new McpGatewayPrompt(\"release_review_bundle\", async (context, cancellationToken) =\u003e\n        {\n            var repositoryPrompt = await context.GetPromptAsync(\n                \"repo\",\n                \"repository_triage_system_prompt\",\n                new Dictionary\u003cstring, object?\u003e\n                {\n                    [\"repository\"] = context.Arguments[\"repository\"],\n                    [\"locale\"] = context.Arguments[\"locale\"]\n                },\n                cancellationToken);\n\n            var deploymentPrompt = await context.GetPromptAsync(\n                \"ops\",\n                \"deployment_review_system_prompt\",\n                new Dictionary\u003cstring, object?\u003e\n                {\n                    [\"environment\"] = context.Arguments[\"environment\"]\n                },\n                cancellationToken);\n\n            return new GetPromptResult\n            {\n                Description = \"Release review bundle prompt.\",\n                Messages =\n                [\n                    new PromptMessage\n                    {\n                        Role = Role.User,\n                        Content = new TextContentBlock\n                        {\n                            Text = \"Combine repository and deployment guidance into one review plan.\"\n                        }\n                    },\n                    ..repositoryPrompt!.Messages,\n                    ..deploymentPrompt!.Messages\n                ]\n            };\n        })\n        {\n            DisplayName = \"Release review bundle\",\n            Description = \"Combines repository and deployment review guidance into one prompt.\",\n            Arguments =\n            [\n                new McpGatewayPromptArgumentDescriptor(\"repository\", \"Repository\", \"Repository name.\", true),\n                new McpGatewayPromptArgumentDescriptor(\"environment\", \"Environment\", \"Deployment environment.\", true),\n                new McpGatewayPromptArgumentDescriptor(\"locale\", \"Locale\", \"Preferred locale.\", false)\n            ],\n            CompleteAsync = static (context, cancellationToken) =\u003e\n            {\n                cancellationToken.ThrowIfCancellationRequested();\n\n                var values = context.ArgumentName == \"repository\"\n                    ? new[] { \"ManagedCode/MCPGateway\", \"ManagedCode/AIBase\" }\n                    : Array.Empty\u003cstring\u003e();\n\n                var matches = values\n                    .Where(value =\u003e value.StartsWith(context.ArgumentValue, StringComparison.OrdinalIgnoreCase))\n                    .ToList();\n\n                return ValueTask.FromResult\u003cCompleteResult?\u003e(new CompleteResult\n                {\n                    Completion = new Completion\n                    {\n                        Values = matches,\n                        Total = matches.Count,\n                        HasMore = false\n                    }\n                });\n            }\n        });\n});\n```\n\nUse gateway-owned prompts when you need:\n\n- one prompt that combines several upstream prompts\n- a modified or opinionated overlay on top of an upstream prompt\n- custom prompt argument completion values exposed through downstream MCP `completion/complete`\n\n## List And Read MCP Resources\n\nIf registered MCP sources expose resources, resolve `IMcpGatewayResourceCatalog` from DI and use it as the aggregated MCP resource surface:\n\n```csharp\nvar resourceCatalog = serviceProvider.GetRequiredService\u003cIMcpGatewayResourceCatalog\u003e();\n\nvar resources = await resourceCatalog.ListResourcesAsync();\nvar templates = await resourceCatalog.ListResourceTemplatesAsync();\nvar issue = await resourceCatalog.ReadResourceAsync(new McpGatewayResourceRequest(\n    SourceId: templates[0].SourceId,\n    ResourceUri: \"docs://issues/42\"));\n```\n\nResource 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(...)`.\n\n## Export The Gateway As An MCP Server\n\nIf you want one downstream MCP endpoint over the aggregated gateway catalog, register an MCP server and add the gateway export:\n\n```csharp\nvar services = new ServiceCollection();\nservices.AddLogging();\nservices.AddMcpGateway(options =\u003e\n{\n    options.AddMcpClient(\"docs\", docsClient, disposeClient: false);\n    options.AddMcpClient(\"ops\", opsClient, disposeClient: false);\n});\n\nservices.AddMcpServer()\n    .WithStdioServerTransport()\n    .WithMcpGatewayCatalog();\n```\n\n`WithMcpGatewayCatalog()` exports:\n\n- aggregated tools through MCP `tools/list` and `tools/call`\n- aggregated prompts through MCP `prompts/list` and `prompts/get`\n- aggregated resources through MCP `resources/list`, `resources/templates/list`, and `resources/read`\n- prompt and resource completions through MCP `completion/complete`\n- forwarded `notifications/prompts/list_changed` when upstream or gateway-owned prompts change\n- resource subscriptions through MCP `resources/subscribe` and `resources/unsubscribe`, including forwarded `notifications/resources/updated`\n- logging level changes through MCP `logging/setLevel`\n- task-backed tool execution through MCP `tools/call` with `task` metadata plus MCP `tasks/list`, `tasks/get`, `tasks/result`, and `tasks/cancel`\n- forwarded `notifications/tasks/status` for exported gateway tasks\n\nExported MCP tool and prompt names are source-qualified gateway ids such as `docs:search_repository`, `ops:deployment_review_system_prompt`, or `local:release_review_bundle`, so multiple upstream servers and gateway-owned prompts can be combined without name collisions. 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 the corresponding upstream task flow. Local gateway tools are exported as optional task-capable tools and are executed through the gateway-owned task store.\n\nThe exported task store uses the official SDK `InMemoryMcpTaskStore` with MCPGateway-owned bounded defaults: task TTL 30 minutes, maximum task TTL 2 hours, cleanup every minute, maximum 10,000 tasks globally, and maximum 1,000 tasks per downstream session. Hosts can override those limits through `McpGatewayOptions.McpTaskStore`, or replace `McpServerOptions.TaskStore` with a durable production store when tasks must survive process restarts:\n\n```csharp\nservices.AddMcpGateway(options =\u003e\n{\n    options.McpTaskStore.TaskTimeToLive = TimeSpan.FromHours(1);\n    options.McpTaskStore.MaximumTaskTimeToLive = TimeSpan.FromHours(4);\n    options.McpTaskStore.MaximumTasks = 50_000;\n    options.McpTaskStore.MaximumTasksPerSession = 5_000;\n});\n```\n\n`WithMcpGatewayCatalog()` does not replace the official SDK HTTP session manager with a custom session store. When used with `ModelContextProtocol.AspNetCore` Streamable HTTP transport, the gateway composes the SDK `HttpServerTransportOptions.RunSessionHandler` lifecycle so gateway-owned per-session prompt notification, resource subscription, and active task binding state is removed when the SDK session ends. Hosts should still use the official transport options for session policy such as `IdleTimeout`, `MaxIdleSessionCount`, `EventStreamStore`, and `SessionMigrationHandler`.\n\nIf the downstream MCP host cannot use the default singleton `IMcpGateway`, `IMcpGatewayPromptCatalog`, and `IMcpGatewayResourceCatalog` registrations directly, register a custom `IMcpGatewayServerBindingResolver`. The resolver can create or select a request-specific or session-specific gateway instance and return it through `McpGatewayServerBinding`, while `WithMcpGatewayCatalog()` continues to own the exported MCP handlers, prompt/resource notifications, subscriptions, and task flow:\n\n```csharp\nservices.AddMcpGateway();\n\nservices.AddSingleton\u003cIMcpGatewayServerBindingResolver\u003e(serviceProvider =\u003e\n    new RouteScopedGatewayBindingResolver(\n        serviceProvider.GetRequiredService\u003cIMcpGatewayFactory\u003e()));\n\nservices.AddMcpServer()\n    .WithHttpTransport()\n    .WithMcpGatewayCatalog();\n```\n\nUse the default resolver when one singleton aggregated gateway is enough. Use a custom binding resolver when the exported MCP endpoint needs to select a different gateway instance per downstream route, tenant, or authenticated session.\n\nIf you need to fully reconfigure the in-memory runtime catalog, use `IMcpGatewayCatalogRuntime` instead of internal reflection:\n\n```csharp\nvar catalogRuntime = serviceProvider.GetRequiredService\u003cIMcpGatewayCatalogRuntime\u003e();\nvar replacement = new McpGatewayOptions()\n    .AddTool(\n        \"runtime\",\n        AIFunctionFactory.Create(\n            static (string query) =\u003e $\"status:{query}\",\n            new AIFunctionFactoryOptions\n            {\n                Name = \"project_status_lookup\",\n                Description = \"Look up project status by identifier or short title.\"\n            }));\n\nawait catalogRuntime.ReconfigureAsync(replacement);\n```\n\n## Factory-Created Custom Gateways\n\nIf you want an isolated custom gateway instance beyond the default singleton gateway, resolve `IMcpGatewayFactory` from DI and create a custom gateway from there:\n\n```csharp\nvar services = new ServiceCollection();\nservices.AddLogging();\nservices.AddMcpGateway();\n\nawait using var serviceProvider = services.BuildServiceProvider();\nvar factory = serviceProvider.GetRequiredService\u003cIMcpGatewayFactory\u003e();\n\nawait using var gatewayHost = factory.Create(options =\u003e\n{\n    options.AddTool(\n        \"local\",\n        AIFunctionFactory.Create(\n            static (string query) =\u003e $\"github:{query}\",\n            new AIFunctionFactoryOptions\n            {\n                Name = \"github_search_repositories\",\n                Description = \"Search GitHub repositories by user query.\"\n            }));\n});\n\nvar search = await gatewayHost.Gateway.SearchAsync(\"find github repositories\");\nvar prompts = await gatewayHost.PromptCatalog.ListPromptsAsync();\n```\n\nUse the package surfaces like this:\n\n- `IMcpGateway`: build, list, search, route, invoke\n- `IMcpGatewayRegistry`: additive tool and source registration\n- `IMcpGatewayCatalogRuntime`: full in-memory catalog clear or reconfiguration\n- `IMcpGatewayGraphSearch`: schema/profile inspection, schema-aware SPARQL graph search, explicit federation, and graph export\n- `IMcpGatewayPromptCatalog`: list and render aggregated upstream plus gateway-owned prompts\n- `IMcpGatewayResourceCatalog`: list direct resources, list resource templates, and read concrete resource URIs\n- `IMcpGatewayFactory`: create isolated custom gateway instances\n- `McpGatewayToolSet`: reusable meta-tools\n\n## Search And Invoke\n\nSearch first, then invoke by `ToolId`:\n\n```csharp\nvar search = await gateway.SearchAsync(\"find github repositories\");\n\nvar invoke = await gateway.InvokeAsync(new McpGatewayInvokeRequest(\n    ToolId: search.Matches[0].ToolId,\n    Query: \"managedcode\"));\n```\n\nIf the caller already knows the exact tool identity, invocation can use `ToolName` plus `SourceId`:\n\n```csharp\nvar invoke = await gateway.InvokeAsync(new McpGatewayInvokeRequest(\n    ToolName: \"github_search_repositories\",\n    SourceId: \"local\",\n    Query: \"managedcode\"));\n```\n\nFor contextual search and invocation, pass `ContextSummary` and `Context`:\n\n```csharp\nvar search = await gateway.SearchAsync(new McpGatewaySearchRequest(\n    Query: \"search\",\n    ContextSummary: \"User is on the GitHub repository settings page\",\n    Context: new Dictionary\u003cstring, object?\u003e\n    {\n        [\"page\"] = \"settings\",\n        [\"domain\"] = \"github\"\n    },\n    MaxResults: 3));\n\nvar invoke = await gateway.InvokeAsync(new McpGatewayInvokeRequest(\n    ToolId: search.Matches[0].ToolId,\n    Query: \"managedcode\",\n    ContextSummary: \"User wants repository administration actions\",\n    Context: new Dictionary\u003cstring, object?\u003e\n    {\n        [\"page\"] = \"settings\",\n        [\"domain\"] = \"github\"\n    }));\n```\n\n`McpGatewaySearchResult` returns:\n\n- `Matches` for the primary result set\n- `RelatedMatches` for bounded graph-related expansion\n- `NextStepMatches` for bounded graph next-step expansion\n- `Diagnostics` for fallback, normalization, and retrieval signals\n- `RankingMode` with `graph`, `vector`, `hybrid`, `browse`, or `empty`\n- `UsedSchemaSearch` and `UsedSchemaFallback` for the Markdown-LD schema/SPARQL path\n- `FocusedGraphNodeCount` and `FocusedGraphEdgeCount` for focused graph scope\n\n`McpGatewayInvokeResult` returns:\n\n- `IsSuccess`\n- `ToolId`\n- `SourceId`\n- `ToolName`\n- `Output`\n- `Error`\n\n## Search Hints\n\nIf a tool should be easier to find through multilingual aliases, stable domain keywords, category-first routing, or execution-aware discovery, register explicit search hints:\n\n```csharp\nservices.AddMcpGateway(options =\u003e\n{\n    options.AddTool(\n        AIFunctionFactory.Create(\n            static () =\u003e \"ok\",\n            new AIFunctionFactoryOptions\n            {\n                Name = \"notification_activity_search\",\n                Description = \"List notification inbox alerts, unread activity, mentions, and message updates.\"\n            }),\n        new McpGatewayToolSearchHints(\n            Aliases:\n            [\n                \"сповіщення\",\n                \"нотифікації\",\n                \"уведомления\"\n            ],\n            Keywords:\n            [\n                \"alerts\",\n                \"inbox\",\n                \"mentions\"\n            ],\n            Categories: [\"communications\"],\n            Tags: [\"notifications\", \"activity-feed\"],\n            DataSources: [\"inbox-api\"],\n            UsageExamples:\n            [\n                new McpGatewayToolExample(\n                    \"show unread alerts\",\n                    \"{\\\"count\\\":3}\",\n                    \"Check whether the user has pending notifications.\"),\n                new McpGatewayToolExample(\n                    \"summarize mentions from the last release thread\",\n                    \"{\\\"mentions\\\":[\\\"alice\\\",\\\"bob\\\"]}\",\n                    \"Triage release-thread mentions before replying.\"),\n                new McpGatewayToolExample(\n                    \"list urgent notifications for the mobile workspace\",\n                    \"{\\\"alerts\\\":[{\\\"severity\\\":\\\"high\\\"}]}\",\n                    \"Filter alerts for a specific workspace and urgency level.\")\n            ],\n            ReadOnly: true,\n            Idempotent: true,\n            CostTier: McpGatewayToolCostTier.Low,\n            LatencyTier: McpGatewayToolLatencyTier.Low));\n});\n```\n\nHints are included in descriptors, Markdown-LD graph documents, vector documents, exported MCP metadata, and lexical boosts. They are the preferred way to improve multilingual discovery and category-aware routing without hardcoded ranking exceptions.\n\nIf a tool should stay out of default discovery until a host explicitly opts into it, set `EnabledByDefault: false`. Standard `SearchAsync(...)` and `RouteToolsAsync(...)` hide those tools unless the request sets `IncludeDisabledTools = true`.\n\n## Search Strategies\n\n### Graph\n\n`Graph` is the default and does not require embeddings:\n\n```csharp\nservices.AddMcpGateway(options =\u003e\n{\n    options.SearchStrategy = McpGatewaySearchStrategy.Graph;\n});\n```\n\nUse it when you want deterministic Markdown-LD graph retrieval with related and next-step expansion. The default graph mode is schema-aware `Hybrid`: it asks `ManagedCode.MarkdownLd.Kb` to generate and execute schema-scoped SPARQL against the tool graph, then merges gateway-ranked graph candidate results as supporting evidence. If schema search finds no mapped gateway tools, hybrid mode enables fuzzy token matching in that candidate fallback so typo-heavy queries such as `trak shipmnt` can still map to shipment-tracking tools without embeddings. Large catalogs use a bounded candidate-backed schema path instead of an unbounded full-graph SPARQL pass.\n\n```csharp\nservices.AddMcpGateway(options =\u003e\n{\n    options.SearchStrategy = McpGatewaySearchStrategy.Graph;\n    options.UseHybridMarkdownLdGraphSearch();\n});\n```\n\nIf a host wants to force only the schema-aware SPARQL path or explicitly use the older lower-level token-distance graph path:\n\n```csharp\nservices.AddMcpGateway(options =\u003e\n{\n    options.UseSchemaAwareMarkdownLdGraphSearch();\n});\n\nservices.AddMcpGateway(options =\u003e\n{\n    options.UseTokenDistanceMarkdownLdGraphSearch();\n});\n```\n\n### Embeddings\n\n`Embeddings` uses vector ranking first:\n\n```csharp\nservices.AddMcpGateway(options =\u003e\n{\n    options.SearchStrategy = McpGatewaySearchStrategy.Embeddings;\n});\n```\n\nUse it when the host provides an embedding generator and wants purely embedding-first ranking.\n\n### Auto\n\n`Auto` runs vector ranking first and then uses the Markdown-LD graph as a bounded supplement:\n\n```csharp\nservices.AddMcpGateway(options =\u003e\n{\n    options.SearchStrategy = McpGatewaySearchStrategy.Auto;\n});\n```\n\nUse it when you want semantic ranking without losing graph-based related and next-step expansion. For larger catalogs, `Auto` avoids unbounded graph supplementation after a usable vector primary result; graph fallback still runs when vector ranking is unavailable or unusable.\n\nIf vector ranking is unavailable or unusable, `Auto` falls back to graph ranking and reports diagnostics.\n\n## Schema-Aware SPARQL Graph Search\n\nUse `IMcpGatewayGraphSearch` when the caller needs graph schema/profile inspection, graph evidence, generated SPARQL, focused graph counts, federation metadata, or graph exports:\n\n```csharp\nvar graphSearch = serviceProvider.GetRequiredService\u003cIMcpGatewayGraphSearch\u003e();\n\nvar schema = await graphSearch.DescribeGraphSchemaAsync();\nConsole.WriteLine(schema.Prefixes[\"schema\"]);\nConsole.WriteLine(schema.GraphNodeCount);\n\nvar graphResult = await graphSearch.SearchGraphAsync(\n    new McpGatewayGraphSearchRequest(\"severity filter\")\n    {\n        MaxResults = 3\n    });\n\nConsole.WriteLine(graphResult.GeneratedSparql);\nConsole.WriteLine(graphResult.Matches[0].ToolMatch?.ToolId);\n```\n\n`McpGatewayGraphSchemaResult` returns:\n\n- graph availability, node count, edge count, and graph source\n- search strategy, graph search mode, default limits, and max result settings\n- schema prefixes, text predicates, relationship predicates, expansion predicates, and type filters\n- configured federated service endpoints\n- diagnostics when the profile cannot be validated against the current graph\n\n`McpGatewayGraphSearchResult` returns:\n\n- `Matches`, `RelatedMatches`, and `NextStepMatches`\n- `GeneratedSparql` and `GeneratedExpansionSparql`\n- graph evidence with predicate ids, matched text, source context, and optional service endpoint\n- mapped gateway `ToolMatch` values when a graph node maps back to a registered tool\n- focused graph node and edge counts\n\nFederated graph search is explicit. Configure allowed SPARQL service endpoints first:\n\n```csharp\nservices.AddMcpGateway(options =\u003e\n{\n    options.AddMarkdownLdFederatedServiceEndpoint(\n        new Uri(\"https://knowledge.example.com/sparql\"));\n\n    options.MarkdownLdFederatedSparqlQueryTimeout = TimeSpan.FromSeconds(30);\n});\n```\n\nThen request federation:\n\n```csharp\nvar federatedResult = await graphSearch.SearchGraphAsync(\n    new McpGatewayGraphSearchRequest(\"story detail lookup\")\n    {\n        UseFederation = true,\n        IncludeLocalGatewayGraph = true,\n        ServiceEndpoints = [\"https://knowledge.example.com/sparql\"]\n    });\n```\n\nThe gateway never discovers remote SPARQL endpoints on its own. It uses the configured allowlist, can bind the local gateway graph as a federated service, and reports diagnostics when a requested endpoint is invalid or blocked.\nFederated SPARQL execution defaults to `McpGatewayOptions.DefaultMarkdownLdFederatedSparqlQueryTimeout`, which is 30 seconds. Set `MarkdownLdFederatedSparqlQueryTimeout` to a larger `TimeSpan` for slower trusted endpoints, or set it to `null` when the host owns the deadline entirely through cancellation tokens or infrastructure policy.\n\n## Graph Sources\n\nBy default the gateway generates Markdown-LD tool documents from the current catalog during index build:\n\n```csharp\nservices.AddMcpGateway(options =\u003e\n{\n    options.SearchStrategy = McpGatewaySearchStrategy.Graph;\n    options.UseGeneratedMarkdownLdGraph();\n});\n```\n\nYou can also point the runtime at a previously written graph bundle, a Markdown-LD file, or a directory:\n\n```csharp\nservices.AddMcpGateway(options =\u003e\n{\n    options.SearchStrategy = McpGatewaySearchStrategy.Graph;\n    options.UseMarkdownLdGraphFile(\"artifacts/mcp-tools.graph.json\");\n});\n```\n\nTo author a bundle through the package:\n\n```csharp\nawait using var serviceProvider = services.BuildServiceProvider();\nvar gateway = serviceProvider.GetRequiredService\u003cIMcpGateway\u003e();\nvar descriptors = await gateway.ListToolsAsync();\nvar documents = McpGatewayMarkdownLdGraphFile.CreateDocuments(descriptors);\n\nawait McpGatewayMarkdownLdGraphFile.WriteAsync(\n    \"artifacts/mcp-tools.graph.json\",\n    documents);\n```\n\nTo export the generated tool graph for RDF tooling, graph visualization, or preprocessing handoff:\n\n```csharp\nvar export = await McpGatewayMarkdownLdGraphFile.ExportAsync(documents);\n\nawait File.WriteAllTextAsync(\"artifacts/mcp-tools.graph.jsonld\", export.JsonLd);\nawait File.WriteAllTextAsync(\"artifacts/mcp-tools.graph.ttl\", export.Turtle);\nawait File.WriteAllTextAsync(\"artifacts/mcp-tools.graph.mmd\", export.MermaidFlowchart);\nawait File.WriteAllTextAsync(\"artifacts/mcp-tools.graph.dot\", export.DotGraph);\n```\n\nTo export the currently indexed runtime graph:\n\n```csharp\nvar graphSearch = serviceProvider.GetRequiredService\u003cIMcpGatewayGraphSearch\u003e();\nvar export = await graphSearch.ExportMarkdownLdGraphAsync();\n```\n\nFor full control over the graph input, provide the Markdown-LD documents directly:\n\n```csharp\nservices.AddMcpGateway(options =\u003e\n{\n    options.SearchStrategy = McpGatewaySearchStrategy.Graph;\n    options.UseMarkdownLdGraphDocuments(descriptors =\u003e\n    {\n        var documents = McpGatewayMarkdownLdGraphFile.CreateDocuments(descriptors).ToList();\n        var githubIndex = documents.FindIndex(document =\u003e\n            document.Path.Contains(\"github_search_repositories\", StringComparison.Ordinal));\n\n        documents[githubIndex] = documents[githubIndex] with\n        {\n            Content = string.Concat(\n                documents[githubIndex].Content,\n                \"\\n\\nrelease approvals merge trains\")\n        };\n\n        return (IReadOnlyList\u003cMcpGatewayMarkdownLdGraphDocument\u003e)documents;\n    });\n});\n```\n\n## Optional Services\n\n### Embedding Generator\n\nVector search is optional. If the host registers `IEmbeddingGenerator\u003cstring, Embedding\u003cfloat\u003e\u003e`, the gateway can use embeddings for `Embeddings` or `Auto`.\n\nRecommended registration:\n\n```csharp\nservices.AddKeyedSingleton\u003cIEmbeddingGenerator\u003cstring, Embedding\u003cfloat\u003e\u003e, MyEmbeddingGenerator\u003e(\n    McpGatewayServiceKeys.EmbeddingGenerator);\n\nservices.AddMcpGateway(options =\u003e\n{\n    options.SearchStrategy = McpGatewaySearchStrategy.Auto;\n});\n```\n\nThe gateway also accepts an unkeyed embedding generator, but the keyed registration is the package-specific path.\n\n### Query Normalization\n\nIf the host registers a keyed `IChatClient`, the gateway can normalize multilingual or noisy queries to concise English before ranking:\n\n```csharp\nservices.AddKeyedSingleton\u003cIChatClient\u003e(\n    McpGatewayServiceKeys.SearchQueryChatClient,\n    mySearchRewriteChatClient);\n\nservices.AddMcpGateway(options =\u003e\n{\n    options.SearchQueryNormalization =\n        McpGatewaySearchQueryNormalization.TranslateToEnglishWhenAvailable;\n});\n```\n\nIf the keyed chat client is missing or normalization fails, search continues normally.\n\n### Runtime Search Cache\n\n`AddMcpGateway(...)` uses a no-op `IMcpGatewaySearchCache` by default.\n\nTo enable process-local reuse for normalized queries, query embeddings, and exact repeated search results:\n\n```csharp\nservices.AddMcpGatewayInMemorySearchCache();\nservices.AddMcpGateway();\n```\n\n`McpGatewayInMemorySearchCache` is an adapter over a host-owned `IMemoryCache`; it does not create or dispose a private cache. The DI helper provisions `IMemoryCache`, and direct construction must pass an explicit cache from the host or test container. Entries include short TTLs and cache sizes so host-owned `MemoryCacheOptions.SizeLimit` policies can bound memory. If the host needs a different cache technology or policy, register its own `IMcpGatewaySearchCache`.\n\n### Tool Embedding Store\n\nFor process-local embedding reuse:\n\n```csharp\nservices.AddKeyedSingleton\u003cIEmbeddingGenerator\u003cstring, Embedding\u003cfloat\u003e\u003e, MyEmbeddingGenerator\u003e(\n    McpGatewayServiceKeys.EmbeddingGenerator);\nservices.AddMcpGatewayInMemoryToolEmbeddingStore();\nservices.AddMcpGateway(options =\u003e\n{\n    options.SearchStrategy = McpGatewaySearchStrategy.Auto;\n});\n```\n\nIf the host needs durable or shared storage, register a custom `IMcpGatewayToolEmbeddingStore` instead:\n\n```csharp\nservices.AddKeyedSingleton\u003cIEmbeddingGenerator\u003cstring, Embedding\u003cfloat\u003e\u003e, MyEmbeddingGenerator\u003e(\n    McpGatewayServiceKeys.EmbeddingGenerator);\nservices.AddSingleton\u003cIMcpGatewayToolEmbeddingStore, MyToolEmbeddingStore\u003e();\nservices.AddMcpGateway(options =\u003e\n{\n    options.SearchStrategy = McpGatewaySearchStrategy.Auto;\n});\n```\n\n`McpGatewayInMemoryToolEmbeddingStore` is a process-local implementation of the `IMcpGatewayToolEmbeddingStore` persistence boundary. It requires a host-owned `IMemoryCache`, either through `AddMcpGatewayInMemoryToolEmbeddingStore()` or an explicit constructor argument, so production hosts keep cache ownership, size policy, and disposal centralized.\n\n## Routing, Meta-Tools, And Chat Integration\n\nFor category-first discovery:\n\n```csharp\nvar route = await gateway.RouteToolsAsync(new McpGatewayToolRouteRequest(\n    Query: \"open a bridge and prepare a failover plan for incident 42\",\n    MaxCategories: 2,\n    MaxToolsPerCategory: 2,\n    ContextSummary: \"incident already confirmed; the next step is an action tool\",\n    PreferReadOnly: false,\n    IncludeDisabledTools: true));\n```\n\n`McpGatewayToolRouteResult` returns:\n\n- `Categories` with grouped tool candidates\n- `SuggestedMatches` with the flattened recommended tools\n- `Diagnostics` from the underlying search path\n- `RankingMode` from the underlying graph/vector/hybrid search\n\nThe router prefers safe read-only tools for discovery/inspection-style requests and uses cost and latency tiers as tie-breakers when tool quality is otherwise similar.\n\nThe gateway can expose itself as three reusable tools:\n\n- `gateway_tools_search`\n- `gateway_tools_route`\n- `gateway_tool_invoke`\n\n`McpGatewayToolSet` also exposes graph-specific tools for callers that need schema/profile inspection, explicit index rebuilds, schema/SPARQL evidence, federated SPARQL search, or graph export:\n\n- `gateway_graph_schema_describe`\n- `gateway_tool_index_build`\n- `gateway_graph_schema_search`\n- `gateway_graph_federated_search`\n- `gateway_graph_export`\n\nFrom the gateway:\n\n```csharp\nvar tools = gateway.CreateMetaTools();\n```\n\nFrom DI:\n\n```csharp\nvar toolSet = serviceProvider.GetRequiredService\u003cMcpGatewayToolSet\u003e();\nvar tools = toolSet.CreateTools();\n```\n\nTo add them to `ChatOptions`:\n\n```csharp\nvar toolSet = serviceProvider.GetRequiredService\u003cMcpGatewayToolSet\u003e();\n\nvar options = new ChatOptions\n{\n    AllowMultipleToolCalls = false\n}.AddMcpGatewayTools(toolSet);\n```\n\nTo add the graph/SPARQL tools to `ChatOptions`:\n\n```csharp\nvar options = new ChatOptions()\n    .AddMcpGatewayTools(serviceProvider)\n    .AddMcpGatewayGraphTools(serviceProvider);\n```\n\nFor staged auto-discovery in a chat loop, wrap any `IChatClient`:\n\n```csharp\nvar innerChatClient = serviceProvider.GetRequiredService\u003cIChatClient\u003e();\nusing var chatClient = innerChatClient.UseMcpGatewayAutoDiscovery(\n    serviceProvider,\n    options =\u003e\n    {\n        options.MaxDiscoveredTools = 2;\n    });\n\nvar response = await chatClient.GetResponseAsync(\n    [new ChatMessage(ChatRole.User, \"Find the github search tool and run it.\")],\n    new ChatOptions\n    {\n        AllowMultipleToolCalls = false\n    });\n```\n\nThis flow starts with the three gateway meta-tools and only projects the latest matching tools as direct proxy tools after search.\n\nIf the host already has search results and only wants the discovered proxy tools:\n\n```csharp\nvar toolSet = serviceProvider.GetRequiredService\u003cMcpGatewayToolSet\u003e();\nvar discoveredTools = toolSet.CreateDiscoveredTools(search.Matches, maxTools: 3);\n```\n\n## Warmup\n\nThe gateway works with lazy indexing by default. If you want startup validation or a pre-built graph/vector index, warm it explicitly.\n\nManual warmup:\n\n```csharp\nawait using var serviceProvider = services.BuildServiceProvider();\nvar build = await serviceProvider.InitializeMcpGatewayAsync();\n```\n\nHosted warmup:\n\n```csharp\nservices.AddMcpGateway(options =\u003e\n{\n    options.AddTool(\n        \"local\",\n        AIFunctionFactory.Create(\n            static (string query) =\u003e $\"github:{query}\",\n            new AIFunctionFactoryOptions\n            {\n                Name = \"github_search_repositories\",\n                Description = \"Search GitHub repositories by user query.\"\n            }));\n});\n\nservices.AddMcpGatewayIndexWarmup();\n```\n\n## Runtime Telemetry\n\nThe gateway emits built-in .NET tracing and metrics:\n\n- `ActivitySource`: `ManagedCode.MCPGateway`\n- search activity: `ManagedCode.MCPGateway.Search`\n- build activity: `ManagedCode.MCPGateway.BuildIndex`\n- `Meter`: `ManagedCode.MCPGateway`\n\nInstruments:\n\n- `mcpgateway.search.requests`\n- `mcpgateway.search.duration`\n- `mcpgateway.search.vector.duration`\n- `mcpgateway.search.vector.tokens`\n- `mcpgateway.search.graph.duration`\n- `mcpgateway.index.builds`\n- `mcpgateway.index.build.duration`\n- `mcpgateway.index.build.vector.tokens`\n\nSearch telemetry includes configured strategy, ranking mode, graph/vector usage, cache-hit state, normalization state, result counts, focused graph counts, and vector token usage. Build telemetry includes tool counts, graph state, vectorized tool counts, duration, and vector token usage.\n\n`McpGatewayIndexBuildResult` also exposes:\n\n- `ToolCount`\n- `VectorizedToolCount`\n- `IsVectorSearchEnabled`\n- `IsGraphSearchEnabled`\n- `GraphNodeCount`\n- `GraphEdgeCount`\n- `Diagnostics`\n\n## Performance Benchmarks\n\nBenchmarkDotNet benchmarks live under `benchmarks/ManagedCode.MCPGateway.Benchmarks/` and run in `Release` with allocation statistics enabled:\n\n```bash\ndotnet run -c Release --project benchmarks/ManagedCode.MCPGateway.Benchmarks/ManagedCode.MCPGateway.Benchmarks.csproj -- --filter \"*\"\n```\n\nFocused benchmark groups:\n\n```bash\ndotnet run -c Release --project benchmarks/ManagedCode.MCPGateway.Benchmarks/ManagedCode.MCPGateway.Benchmarks.csproj -- --filter \"*Search*\"\ndotnet run -c Release --project benchmarks/ManagedCode.MCPGateway.Benchmarks/ManagedCode.MCPGateway.Benchmarks.csproj -- --filter \"*Index*\"\ndotnet run -c Release --project benchmarks/ManagedCode.MCPGateway.Benchmarks/ManagedCode.MCPGateway.Benchmarks.csproj -- --filter \"*ToolSet*\"\n```\n\nRun focused BenchmarkDotNet commands one at a time so generated benchmark build artifacts do not contend with each other.\n\nCI runs the full BenchmarkDotNet suite with `--filter \"*\"` after the build/test gate and uploads the complete benchmark reports as `benchmark-results`. The release workflow also runs the full suite before package creation and uploads `release-benchmark-results`. These benchmark jobs are intentionally not smoke tests or reduced benchmark subsets.\n\nLatest full local BenchmarkDotNet snapshot on May 4, 2026, Apple M2 Pro, .NET SDK `10.0.201`, runtime `.NET 10.0.5`. Benchmark setup, cleanup, and measured async paths run async end-to-end without sync-over-async blocking:\n\n| Scenario | Mean | Allocated |\n| --- | ---: | ---: |\n| `BuildGraphIndex` | 608.6 ms | 500.46 MB |\n| `SearchWeatherGraph` | 25.22 ms | 25.88 MB |\n| `SearchPortfolioGraph` | 24.55 ms | 26.07 MB |\n| `SearchArchiveGraph` | 122.17 ms | 108.64 MB |\n| `SearchWeatherGraphTool` | 24.40 ms | 25.92 MB |\n| `SearchArchiveGraphTool` | 111.05 ms | 108.10 MB |\n| `CreateGatewayTools` | 568.5 ns | 936 B |\n| `CreateGraphTools` | 1.094 us | 1,528 B |\n| `CreateDiscoveredTools` | 786.0 ns | 3,192 B |\n\nSee [docs/Performance/Benchmarks.md](docs/Performance/Benchmarks.md) for benchmark scope and optimization policy.\n\n## Deeper Docs\n\n- [Architecture overview](docs/Architecture/Overview.md)\n- [ADR-0001: Runtime boundaries and index lifecycle](docs/ADR/ADR-0001-runtime-boundaries-and-index-lifecycle.md)\n- [ADR-0002: Search ranking and query normalization](docs/ADR/ADR-0002-search-ranking-and-query-normalization.md)\n- [ADR-0003: Reusable chat-client and agent auto-discovery modules](docs/ADR/ADR-0003-reusable-chat-client-and-agent-tool-modules.md)\n- [ADR-0004: Process-local embedding store uses IMemoryCache](docs/ADR/ADR-0004-process-local-embedding-store-uses-imemorycache.md)\n- [ADR-0005: Markdown-LD graph search for tool retrieval](docs/ADR/ADR-0005-markdown-ld-graph-search-for-tool-retrieval.md)\n- [ADR-0006: Vector-first auto search and runtime telemetry](docs/ADR/ADR-0006-vector-first-auto-search-and-runtime-telemetry.md)\n- [ADR-0007: Vertical-slice package organization](docs/ADR/ADR-0007-vertical-slice-package-organization.md)\n- [ADR-0012: Schema-aware SPARQL graph search](docs/ADR/ADR-0012-schema-aware-sparql-graph-search.md)\n- [Feature spec: Search query normalization and ranking](docs/Features/SearchQueryNormalizationAndRanking.md)\n- [Feature spec: Auto vector-first search and performance](docs/Features/AutoVectorFirstSearchAndPerformance.md)\n- [Performance benchmarks](docs/Performance/Benchmarks.md)\n\n## Local Development\n\n```bash\ndotnet tool restore\ndotnet restore ManagedCode.MCPGateway.slnx\ndotnet build ManagedCode.MCPGateway.slnx -c Release --no-restore\ndotnet test --solution ManagedCode.MCPGateway.slnx -c Release --no-build\n```\n\nAnalyzer and formatting pass:\n\n```bash\ndotnet build ManagedCode.MCPGateway.slnx -c Release --no-restore -p:RunAnalyzers=true\ndotnet format ManagedCode.MCPGateway.slnx --verify-no-changes\ndotnet tool run roslynator analyze src/ManagedCode.MCPGateway/ManagedCode.MCPGateway.csproj tests/ManagedCode.MCPGateway.Tests/ManagedCode.MCPGateway.Tests.csproj\n```\n\nOptional opt-in `CSharpier` check:\n\n```bash\ndotnet csharpier check .\n```\n\nCoverage:\n\n```bash\ndotnet tool run coverlet tests/ManagedCode.MCPGateway.Tests/bin/Release/net10.0/ManagedCode.MCPGateway.Tests.dll --target \"./tests/ManagedCode.MCPGateway.Tests/bin/Release/net10.0/ManagedCode.MCPGateway.Tests\" --targetargs \"\" --format cobertura --output artifacts/coverage/coverage.cobertura.xml\ndotnet tool run reportgenerator -reports:\"artifacts/coverage/coverage.cobertura.xml\" -targetdir:\"artifacts/coverage-report\" -reporttypes:\"HtmlSummary;MarkdownSummaryGithub\"\n```\n\nBenchmarks:\n\n```bash\ndotnet run -c Release --project benchmarks/ManagedCode.MCPGateway.Benchmarks/ManagedCode.MCPGateway.Benchmarks.csproj -- --filter \"*\"\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmanagedcode%2Fmcpgateway","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmanagedcode%2Fmcpgateway","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmanagedcode%2Fmcpgateway/lists"}