ManagedCode.MCPGateway started as a single gateway concept, but the package now has three distinct concerns:
- runtime search and invocation for local
AIToolinstances and remote MCP tools - mutable catalog registration for local tools, stdio/HTTP MCP servers, and deferred
McpClientfactories - index lifecycle management for lazy builds, hosted warmup, and rebuilds after registry mutations
Recent changes also made index construction cancellation-aware and single-flight, so startup warmup, shutdown, and concurrent callers do not keep issuing duplicate MCP loads or continue rebuilding after a canceled operation should stop.
The repository needs an explicit record for these boundaries so the public package surface, internal runtime structure, and README examples stay aligned.
ManagedCode.MCPGateway will keep a thin public runtime facade, a separate DI-managed registry mutation surface, and an internal runtime orchestrator with lazy, cancellation-aware single-flight index builds plus optional eager warmup integration.
flowchart LR
Host["Host application"] --> DI["AddMcpGateway(...)"]
DI --> Gateway["IMcpGateway / McpGateway"]
DI --> Registry["IMcpGatewayRegistry / McpGatewayRegistry"]
DI --> ToolSet["McpGatewayToolSet"]
DI --> Warmup["AddMcpGatewayIndexWarmup()"]
Gateway --> Runtime["McpGatewayRuntime"]
Registry --> Snapshot["Catalog snapshots"]
Runtime --> Snapshot
Warmup --> Runtime
Runtime --> Search["Search / invoke / index build"]
Pros:
- fewer types to explain
- direct mutation calls on the same service
Cons:
- violates single responsibility for runtime versus mutation
- makes DI usage less explicit
- encourages
McpGatewayto become a god object again
Pros:
- very explicit startup workflow
- easy to reason about in small demos
Cons:
- forces boilerplate on every consumer
- easy to forget in real hosts
- contradicts the package goal of working lazily by default
Pros:
- straightforward first implementation
- familiar concurrency model
Cons:
- obscures cancellation and shutdown behavior
- harder to scale under concurrent search/build callers
- already caused readability and lifecycle issues in this repository
Positive:
- public DI wiring is explicit:
IMcpGatewayfor runtime work,IMcpGatewayRegistryfor catalog mutation,McpGatewayToolSetfor meta-tools - hosts get lazy behavior by default and optional eager warmup through
InitializeMcpGatewayAsync()orAddMcpGatewayIndexWarmup() - cancellation now propagates into source loading, embedding generation, and embedding-store I/O during index builds
- runtime rebuilds after registry mutations remain automatic without forcing every host into startup code
Trade-offs:
- there are more internal collaborator types to document
- lazy behavior means startup failures may surface on first use unless the host opts into eager warmup
- single-flight lifecycle code is more subtle than a naive sequential implementation
Mitigations:
- keep
McpGatewaythin and document the boundaries indocs/Architecture/Overview.md - keep README examples for both lazy default usage and eager warmup
- cover cancellation, retry-after-cancel, and concurrent build behavior with tests
IMcpGatewayMUST remain the public runtime facade for build, list, search, invoke, and meta-tool creation.IMcpGatewayRegistryMUST remain the public mutation surface for adding tools and MCP sources after container build.AddMcpGateway(...)MUST registerIMcpGateway,IMcpGatewayRegistry, andMcpGatewayToolSet.- Index builds MUST be lazy by default and MUST rebuild automatically after registry mutations invalidate the snapshot.
- Hosted warmup MUST stay optional and MUST use the same runtime/index path as normal gateway operations.
- Cancellation of
BuildIndexAsync(...)MUST propagate into underlying source loading and embedding work.
Rollout:
- Keep the separated facade/registry/runtime structure in
src/ManagedCode.MCPGateway/. - Keep README startup guidance aligned with lazy default plus optional eager warmup.
- Keep tests covering concurrent builds, cancellation, and post-mutation rebuild behavior.
Rollback:
- Revert the runtime/registry split only if the package intentionally changes back to a single mutable gateway facade.
- Remove warmup helpers only if startup prewarming is intentionally dropped as a supported scenario.
dotnet restore ManagedCode.MCPGateway.slnxdotnet build ManagedCode.MCPGateway.slnx -c Release --no-restoredotnet build ManagedCode.MCPGateway.slnx -c Release --no-restore -p:RunAnalyzers=truedotnet test --solution ManagedCode.MCPGateway.slnx -c Release --no-buildroslynator analyze src/ManagedCode.MCPGateway/ManagedCode.MCPGateway.csproj -p Configuration=Release --severity-level warningroslynator analyze tests/ManagedCode.MCPGateway.Tests/ManagedCode.MCPGateway.Tests.csproj -p Configuration=Release --severity-level warningcloc --include-lang=C# src tests
- Keep
McpGatewayas a thin public facade overMcpGatewayRuntime. - Keep
McpGatewayRegistryas the DI-managed mutation surface and snapshot source. - Keep
McpGatewayRuntimeresponsible for lazy single-flight index builds and search/invocation orchestration. - Expose eager warmup through service-provider and hosted-service extensions instead of forcing manual
BuildIndexAsync()in every host. - Keep cancellation and concurrency regression coverage in the search/build test suite.
- Product: hosts can choose lazy startup or eager warmup without changing the public runtime API.
- Dev: runtime and mutation responsibilities are intentionally separate and must stay that way.
- QA: warmup, cancellation, and rebuild-after-mutation scenarios are first-class verification targets.
- DevOps: startup behavior is configurable; eager warmup is the fail-fast option for production hosts.