Status: Accepted
Date: 2026-04-21
Related ADRs: ADR-0001, ADR-0003, ADR-0005, ADR-0006
Before this decision, ManagedCode.MCPGateway already had strong package boundaries at the public API level:
Abstractions/Models/Configuration/Registration/Internal/
The problem is inside the implementation and test trees. The current organization is still dominated by technical buckets and one large partial runtime:
Internal/Runtime/CoreInternal/Runtime/CatalogInternal/Runtime/SearchInternal/Runtime/GraphInternal/Runtime/InvocationInternal/Runtime/Embeddingstests/.../Searchtests/.../TestSupport
That keeps feature ownership diffuse:
- one end-to-end feature is often split across
Models,Registration,Internal/Catalog, andInternal/Runtime/* McpGatewayRuntimeremains the central gravity well- search is spread across graph, vector, normalization, caching, telemetry, and test folders that do not read as one slice
Measured signals from the audit:
Internal/Runtime/Search:1112LOCInternal/Runtime/Graph:997LOCInternal/Runtime/Catalog:934LOC- package root public files:
819LOC tests/.../Search:3546LOCtests/.../TestSupport:1496LOC
Migration constraints:
- keep the package library-first and reusable
- keep public contracts separate from
Internal/implementation - prefer moving by feature ownership first; namespace and API cleanup may follow when it improves clarity and the package remains coherent
- reduce large-file and high-CRAP hotspots incrementally, not through one unsafe rewrite
The repository will adopt feature-first vertical-slice organization for non-trivial package areas while preserving the public/internal split.
Key points:
- Public contracts may stay distinct, but their owning feature must be explicit.
- Internal implementation must be grouped by feature and subfeature instead of by one shared
Runtime/*bucket. - New functionality should land in the target slice first; do not grow the old flat buckets when a feature-specific home exists.
- Existing large technical buckets must be retired incrementally through safe move batches.
- Tests must mirror the same slice ownership and stop accumulating in generic folders such as
Search/andTestSupport/.
The target package slices are now:
Gateway/Discovery/Catalog/Search/Invocation/Prompts/Hosting/
flowchart LR
Public["Public contracts<br/>Abstractions / Models / Configuration"] --> Catalog["Catalog slice"]
Public --> Search["Search slice"]
Public --> Invocation["Invocation slice"]
Public --> Prompts["Prompts slice"]
Public --> Discovery["Discovery slice"]
Catalog --> CatalogInternal["Registration / Sources / Descriptors / Indexing"]
Search --> SearchInternal["Context / Confidence / Graph / Ranking / Normalization / Vector"]
Invocation --> InvocationInternal["Execution / Result mapping"]
Prompts --> PromptsInternal["Prompt catalog"]
Discovery --> DiscoveryInternal["Tool set / Auto-discovery"]
SearchInternal --> Tests["Mirrored tests by slice"]
CatalogInternal --> Tests
InvocationInternal --> Tests
DiscoveryInternal --> Tests
Pros:
- no file moves
- current docs already reference the existing folder names
Cons:
- keeps
McpGatewayRuntimeas the implementation center of gravity - search, catalog, and discovery keep leaking across unrelated folders
- reviewers must understand multiple technical trees to change one feature
Rejected because it preserves the problem the user explicitly asked to remove.
Pros:
- strongest feature locality
- simplest read path for a single slice
Cons:
- conflicts with the repository rule that public API folders stay clearly separated from internal implementation
- makes public contract review noisier in a NuGet package
Rejected because the package still needs a clean public surface map.
Pros:
- minimal class churn
- low namespace impact
Cons:
- folders improve, but responsibilities still stay trapped in one type
- large-file and CRAP hotspots remain
- slice ownership is still implicit instead of explicit
Rejected because it is a cosmetic directory change, not a real architecture move.
Positive:
- feature ownership becomes reviewable and auditable
- new contributors can change one slice without reading the whole runtime
- tests can mirror the production feature boundaries directly
- high-risk methods gain natural extraction targets
Trade-offs:
- more folders and more collaborators
- incremental refactors require temporary compatibility seams
- docs and architecture maps must be kept current during the migration
Mitigations:
- keep namespaces stable while moving files first
- move tests before or alongside production moves
- prioritize the largest and riskiest buckets first
One maintainability exception remains after the slice move and the MCP metadata/routing expansion:
src/ManagedCode.MCPGateway/Search/Internal/Graph/McpGatewayRuntime.GraphIndexing.csis currently714code LOC, which is64lines above the repositoryfile_max_loclimit of650.
Justification:
- the file is now isolated inside the correct
Search/Graphslice instead of being hidden in a flat runtime bucket - the latest MCP improvements added category, tag, data-source, and execution-contract graph enrichment in one place, and splitting that logic safely needs a dedicated follow-up instead of a rushed late-turn cut
Required follow-up:
- extract graph markdown rendering and metadata/tag projection into dedicated collaborators inside
Search/Internal/Graph/ - keep the follow-up local to the graph slice instead of re-expanding a shared runtime bucket
- Public package identity remains
ManagedCode.MCPGateway. - Public API naming stays concise and
McpGateway-aligned. - Public contracts remain visibly separate from
Internal/implementation. - New non-trivial code must prefer a feature slice over a generic shared folder.
- Search, catalog, discovery, invocation, prompts, and hosting must each have an explicit owning slice.
- Tests must mirror feature ownership instead of growing a generic
TestSupportdump.
- Move package-root public files into
Gateway/andDiscovery/. - Split
tests/.../Searchandtests/.../TestSupportinto mirrored sub-slices. - Merge
Internal/Catalog/*andInternal/Runtime/Catalog/*into one catalog slice. - Break large runtime search and graph files into feature collaborators.
- Finish by isolating hosting-only concerns such as server export, warmup, and telemetry.
dotnet tool restoredotnet restore ManagedCode.MCPGateway.slnxdotnet build ManagedCode.MCPGateway.slnx -c Release --no-restoredotnet test --solution ManagedCode.MCPGateway.slnx -c Release --no-builddotnet 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.xmldotnet tool run roslynator analyze src/ManagedCode.MCPGateway/ManagedCode.MCPGateway.csproj tests/ManagedCode.MCPGateway.Tests/ManagedCode.MCPGateway.Tests.csprojcloc src/ManagedCode.MCPGateway tests/ManagedCode.MCPGateway.Tests --exclude-dir=bin,obj --include-lang="C#" --by-file --csv