Architecture Overview - Garume/Manifold GitHub Wiki
This page describes the high-level architecture of the Manifold framework, a source-generated .NET library that enables developers to define operations once and expose them through both CLI and MCP (Model Context Protocol) surfaces. The framework is organized into four NuGet packages with clearly defined responsibilities and dependency relationships, connected by a compile-time source generation pipeline that eliminates runtime reflection.
For the detailed directory layout and build tooling, see Project Structure and Tech Stack. For the foundational types that all packages share, see Core Contracts — Manifold Package.
Manifold is structured as four packages that form a layered dependency graph. The core Manifold package defines contracts with zero external dependencies. The two runtime packages (Manifold.Cli and Manifold.Mcp) each depend on the core but not on each other, enabling host applications to reference only the surface they need. The Manifold.Generators source generator operates at compile time and has no runtime project references.
flowchart TD
subgraph Compile["Compile Time"]
GEN["Manifold.Generators<br/>(Source Generator)"]
end
subgraph Runtime["Runtime Packages"]
CORE["Manifold<br/>(Core Contracts)"]
CLI["Manifold.Cli<br/>(CLI Runtime)"]
MCP["Manifold.Mcp<br/>(MCP Runtime)"]
end
subgraph Host["Host Applications"]
CLIHOST["CLI Host"]
MCPHOST["MCP Host"]
end
CLI -->|project reference| CORE
MCP -->|project reference| CORE
MCP -->|NuGet| MCPSDK["ModelContextProtocol SDK"]
GEN -.->|generates code into| CLIHOST
GEN -.->|generates code into| MCPHOST
CLIHOST --> CLI
CLIHOST --> CORE
MCPHOST --> MCP
MCPHOST --> CORE
| Package | Target | Role | Dependencies |
|---|---|---|---|
| Manifold | .NET 10.0 | Core contracts: IOperation, OperationDescriptor, OperationContext, attributes |
None |
| Manifold.Cli | .NET 10.0 | CLI parsing, dispatch, result formatting | Manifold |
| Manifold.Mcp | .NET 10.0 | MCP tool invocation, JSON argument binding, response writing | Manifold, ModelContextProtocol SDK |
| Manifold.Generators | netstandard2.0 | Roslyn incremental source generator | Roslyn APIs (analyzer reference) |
Sources: Manifold/src/Manifold/Manifold.csproj, Manifold/src/Manifold.Cli/Manifold.Cli.csproj, Manifold/src/Manifold.Mcp/Manifold.Mcp.csproj, Manifold/src/Manifold.Generators/Manifold.Generators.csproj
The Manifold package defines the shared vocabulary used by all other packages. Every operation, regardless of surface, is described by the same metadata types.
| Type | Kind | Purpose |
|---|---|---|
IOperation<TRequest, TResult> |
Interface | Contract for class-based operations with typed request/result |
OperationContext |
Class | Per-invocation context carrying surface, services, and cancellation |
OperationDescriptor |
Record | Complete metadata for a single operation |
ParameterDescriptor |
Record | Metadata for a single parameter (name, type, source, position) |
InvocationSurface |
Enum | Identifies the calling surface: Cli, Mcp, or Protocol
|
OperationVisibility |
Enum | Controls surface availability: Both, CliOnly, McpOnly
|
ParameterSource |
Enum | Identifies binding origin: Option, Argument, Service, CancellationToken
|
classDiagram
class IOperation~TRequest, TResult~ {
+ExecuteAsync(TRequest, OperationContext) ValueTask~TResult~
}
class OperationContext {
+OperationId string
+Surface InvocationSurface
+Services IServiceProvider
+CancellationToken CancellationToken
+ForCli() OperationContext
+ForMcp() OperationContext
}
class OperationDescriptor {
+OperationId string
+DeclaringType Type
+ResultType Type
+Visibility OperationVisibility
+Parameters IReadOnlyList~ParameterDescriptor~
+CliCommandPath IReadOnlyList~string~
+McpToolName string
}
class ParameterDescriptor {
+Name string
+ParameterType Type
+Source ParameterSource
+Required bool
+Position int
}
OperationDescriptor --> "*" ParameterDescriptor
IOperation~TRequest, TResult~ ..> OperationContext : receives
The OperationContext provides factory methods (ForCli, ForMcp, ForProtocol) that stamp the correct InvocationSurface value, allowing operation implementations to branch on which surface invoked them.
Sources: Manifold/src/Manifold/IOperation.cs:3-6, Manifold/src/Manifold/OperationContext.cs:3-84, Manifold/src/Manifold/DescriptorModels.cs:1-52
The Manifold.Generators package contains an incremental Roslyn source generator (OperationDescriptorGenerator) that runs at compile time. It discovers all [Operation]-attributed methods and classes, validates them, and emits up to six generated source files depending on which runtime packages the host application references.
flowchart TD
SRC["User Source Code<br/>[Operation] methods/classes"] --> ROSLYN["Roslyn Compilation"]
ROSLYN --> GEN["OperationDescriptorGenerator<br/>IIncrementalGenerator"]
GEN --> DISCOVER["Discover Candidates<br/>ForAttributeWithMetadataName"]
DISCOVER --> VALIDATE["Validate & Analyze<br/>DMCF001-DMCF005"]
VALIDATE --> COLLECT["Collect & Sort"]
COLLECT --> REG["GeneratedOperationRegistry.g.cs"]
COLLECT --> OPINV["GeneratedOperationInvoker.g.cs"]
COLLECT --> CLIINV["GeneratedCliInvoker.g.cs"]
COLLECT --> MCPTOOLS["GeneratedMcpTools.g.cs"]
COLLECT --> MCPCAT["GeneratedMcpCatalog.g.cs"]
COLLECT --> MCPINV["GeneratedMcpInvoker.g.cs"]
style REG fill:#e8f5e9
style OPINV fill:#e8f5e9
style CLIINV fill:#e8f5e9
style MCPTOOLS fill:#e8f5e9
style MCPCAT fill:#e8f5e9
style MCPINV fill:#e8f5e9
Each artifact is conditionally emitted based on whether the corresponding runtime types are available in the compilation:
| Generated File | Condition | Contents |
|---|---|---|
GeneratedOperationRegistry.g.cs |
Always | Static OperationDescriptor[] array and TryFind() lookup |
GeneratedOperationInvoker.g.cs |
IOperationInvoker exists |
Generic invoker for class-based operations |
GeneratedCliInvoker.g.cs |
ICliInvoker exists |
Implements ICliInvoker, IFastSyncCliInvoker, IFastCliInvoker
|
GeneratedMcpTools.g.cs |
MCP SDK types exist | MCP tool methods with [McpServerTool] attributes |
GeneratedMcpCatalog.g.cs |
McpToolDescriptor exists |
Static McpToolDescriptor[] catalog |
GeneratedMcpInvoker.g.cs |
MCP invoker types exist | Implements IMcpToolInvoker, IFastMcpToolInvoker, IFastSyncMcpToolInvoker
|
The conditional emission means a CLI-only host that does not reference Manifold.Mcp will never have MCP invoker code generated, and vice versa. The GeneratedOperationRegistry is always emitted because both surfaces depend on it.
Sources: Manifold/src/Manifold.Generators/OperationDescriptorGenerator.cs:9-10, Manifold/src/Manifold.Generators/OperationDescriptorGenerator.cs:63-76, Manifold/src/Manifold.Generators/OperationDescriptorGenerator.cs:620-688
The generator uses ForAttributeWithMetadataName to find all symbols decorated with [Operation]. Each candidate is classified as either a static method or a class implementing IOperation<TRequest, TResult>. The generator validates candidates at compile time and reports diagnostics for invalid configurations:
| Diagnostic | ID | Description |
|---|---|---|
| Conflicting visibility | DMCF001 | Operation marked with both [CliOnly] and [McpOnly]
|
| Conflicting binding | DMCF002 | Parameter marked with both [Option] and [Argument]
|
| Unsupported binding | DMCF003 | Parameter missing a required binding attribute |
| Missing interface | DMCF004 | Class does not implement IOperation<TRequest, TResult>
|
| Non-writable property | DMCF005 | Request property lacks a public init or set accessor |
For the full diagnostics reference, see Diagnostics and Compile-Time Validation.
Sources: Manifold/src/Manifold.Generators/OperationDescriptorGenerator.cs:27-61
Manifold's central design principle is that a single operation definition produces two independent dispatch paths: CLI and MCP. Each surface has its own invoker interface hierarchy, parameter binding strategy, and result type, but both ultimately call the same user-defined operation logic.
flowchart TD
OP["[Operation] Definition"] --> GEN["Source Generator"]
GEN --> CLINV["GeneratedCliInvoker"]
GEN --> MCPINV["GeneratedMcpInvoker"]
subgraph CLI["CLI Surface"]
CLIAPP["CliApplication"] --> PARSE["Parse Arguments"]
PARSE --> RESOLVE["Resolve Operation"]
RESOLVE --> CLINV
CLINV --> CLIBIND["CliBinding<br/>string → typed values"]
end
subgraph MCP_SURFACE["MCP Surface"]
MCPHOST["MCP Host"] --> MCPSDK["MCP SDK"]
MCPSDK --> MCPINV
MCPINV --> MCPBIND["McpBinding<br/>JSON → typed values"]
end
CLIBIND --> EXEC["Operation.ExecuteAsync()"]
MCPBIND --> EXEC
The CLI surface is driven by CliApplication, which accepts the generated OperationDescriptor list and GeneratedCliInvoker at construction. When ExecuteAsync is called, the application follows a tiered invocation strategy:
sequenceDiagram
participant User
participant App as CliApplication
participant Fast as IFastSyncCliInvoker
participant Async as IFastCliInvoker
participant Slow as ICliInvoker
User->>App: ExecuteAsync(args)
App->>App: TryExecuteArrayFastPath()
alt Fast sync path available
App->>Fast: TryInvokeFastSync(tokens)
Fast-->>App: FastCliInvocationResult
else Fast async path available
App->>Async: TryInvokeFast(tokens)
Async-->>App: ValueTask of FastCliInvocationResult
else Slow path
App->>App: ParseArguments()
App->>App: TryResolveOperation()
App->>App: ParseCommandInput()
App->>Slow: TryInvoke(operationId, options, arguments)
Slow-->>App: ValueTask of CliInvocationResult
end
App->>User: Exit code + formatted output
-
Fast sync path (
IFastSyncCliInvoker): The generated invoker receives rawstring[]tokens and performs command resolution, argument parsing, and invocation in a single synchronous call. ReturnsFastCliInvocationResult, a union struct that avoids heap allocation for common primitive types. -
Fast async path (
IFastCliInvoker): Same as above but returnsValueTask<FastCliInvocationResult>for operations that require asynchronous execution. -
Slow path (
ICliInvoker): Falls back to full argument parsing.CliApplicationparses tokens into a dictionary of named options and a list of positional arguments, resolves the target operation by matchingCliCommandPath, and delegates toICliInvoker.TryInvoke. ReturnsCliInvocationResultcontaining the boxed result object.
The fast path is attempted first for string[] input. It bypasses the token parsing and option/argument decomposition entirely, relying on the generated invoker to handle everything inline.
Sources: Manifold/src/Manifold.Cli/CliApplication.cs:62-76, Manifold/src/Manifold.Cli/CliApplication.cs:94-149, Manifold/src/Manifold.Cli/CliApplication.cs:151-195, Manifold/src/Manifold.Cli/ICliInvoker.cs:3-13, Manifold/src/Manifold.Cli/IFastCliInvoker.cs:1-19
The MCP surface integrates with the ModelContextProtocol SDK. Host applications register the generated MCP server components via AddGeneratedMcpServer(), which wires up the generated tool methods, catalog, and invoker. The MCP SDK routes incoming tool calls to the generated McpServerTool methods, which in turn delegate to the GeneratedMcpInvoker.
sequenceDiagram
participant Client as MCP Client
participant SDK as MCP SDK
participant Tools as GeneratedMcpTools
participant Invoker as GeneratedMcpInvoker
participant Binding as McpBinding
participant Op as Operation Logic
Client->>SDK: tools/call (toolName, JSON args)
SDK->>Tools: Invoke generated tool method
Tools->>Invoker: TryInvokeFastSync / TryInvokeFast / TryInvoke
Invoker->>Binding: Parse JSON arguments
Binding->>Binding: GetRequiredProperty / TryGetProperty
Binding->>Binding: ParseInt32 / ParseString / ConvertValue
Invoker->>Op: Execute operation
Op-->>Invoker: Result
Invoker-->>Tools: FastMcpInvocationResult
Tools-->>SDK: TextContent response
SDK-->>Client: Tool result
The MCP invoker follows the same three-tier pattern as the CLI invoker:
| Interface | Input | Output | Allocation |
|---|---|---|---|
IFastSyncMcpToolInvoker |
string toolName, JsonElement? arguments |
FastMcpInvocationResult |
Zero-allocation for primitives |
IFastMcpToolInvoker |
string toolName, JsonElement? arguments |
ValueTask<FastMcpInvocationResult> |
Zero-allocation for primitives |
IMcpToolInvoker |
string toolName, JsonElement? arguments |
ValueTask<OperationInvocationResult> |
Boxed result |
McpBinding provides typed parsing methods (ParseInt32, ParseString, ParseBoolean, etc.) and a general-purpose ConvertValue method that handles nullable types, enums, and complex JSON deserialization.
Sources: Manifold/src/Manifold.Mcp/McpInvoker.cs:7-35, Manifold/src/Manifold.Mcp/McpInvoker.cs:37-105
Both surfaces bind operation parameters from their respective input formats, but the binding mechanics differ:
| Aspect | CLI Surface | MCP Surface |
|---|---|---|
| Input format |
string[] command-line tokens |
JsonElement from MCP protocol |
| Options |
--name value parsed into IReadOnlyDictionary<string, string>
|
JSON object properties |
| Arguments | Positional strings in IReadOnlyList<string>
|
JSON object properties (no positional concept) |
| Type conversion | CliBinding.ConvertValue(Type, string, name) |
McpBinding.ConvertValue(Type, JsonElement, name) |
| Service injection | CliBinding.GetRequiredService<T>(IServiceProvider) |
McpBinding.GetRequiredService<T>(IServiceProvider) |
| Name resolution |
[CliName] override or parameter name |
[McpName] override or parameter name |
For the full parameter binding reference, see Parameter Binding and Type Conversion.
A CLI host wires CliApplication with the generated registry and invoker:
CliApplication application = new(
GeneratedOperationRegistry.Operations,
new GeneratedCliInvoker(),
serviceProvider,
rawOutput: Console.OpenStandardOutput(),
jsonSerializerOptions: new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
WriteIndented = true
});
return await application.ExecuteAsync(args, Console.Out, Console.Error);Sources: Manifold/samples/Manifold.Samples.CliHost/Program.cs:1-22
An MCP stdio host uses the DI extension to register all generated components:
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddTransient<WeatherPreviewOperation>();
builder.Services.AddGeneratedMcpServer()
.WithStdioServerTransport();
IHost host = builder.Build();
await host.RunAsync();Sources: Manifold/samples/Manifold.Samples.McpStdioHost/Program.cs:1-12
An MCP HTTP host uses ASP.NET Core with the HTTP transport:
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddTransient<WeatherPreviewOperation>();
builder.Services.AddGeneratedMcpServer()
.WithHttpTransport();
WebApplication app = builder.Build();
app.MapMcp("/mcp");
await app.RunAsync();Sources: Manifold/samples/Manifold.Samples.McpHttpHost/Program.cs:1-19
For detailed walkthroughs, see Sample — CLI Host and Samples — MCP Hosts (Stdio and HTTP).
The following diagram shows the complete lifecycle from operation definition through compile-time generation to runtime invocation on both surfaces:
flowchart TD
subgraph Author["Author Time"]
DEF["Define Operation<br/>[Operation] + [CliCommand] + [McpTool]"]
end
subgraph Compile["Compile Time"]
DEF --> GEN["OperationDescriptorGenerator"]
GEN --> DIAG{"Validation<br/>Passed?"}
DIAG -->|No| ERR["Compile Error<br/>DMCF001-005"]
DIAG -->|Yes| EMIT["Emit Generated Code"]
EMIT --> REG["OperationRegistry"]
EMIT --> CLINV["CliInvoker"]
EMIT --> MCPINV["McpInvoker + Tools"]
end
subgraph Runtime["Runtime"]
subgraph CLISurface["CLI Surface"]
CLIAPP["CliApplication<br/>+ OperationRegistry<br/>+ CliInvoker"]
CLIARGS["args: string[]"] --> CLIAPP
CLIAPP --> CLIFAST{"Fast Path?"}
CLIFAST -->|Yes| CLISYNC["Sync/Async Fast Invoke"]
CLIFAST -->|No| CLISLOW["Parse → Resolve → Invoke"]
end
subgraph MCPSurface["MCP Surface"]
MCPAPP["MCP Server<br/>+ McpTools<br/>+ McpInvoker"]
MCPREQ["JSON tool call"] --> MCPAPP
MCPAPP --> MCPFAST{"Fast Path?"}
MCPFAST -->|Yes| MCPSYNC["Sync/Async Fast Invoke"]
MCPFAST -->|No| MCPSLOW["Parse JSON → Invoke"]
end
CLISYNC --> EXEC["User Operation Logic"]
CLISLOW --> EXEC
MCPSYNC --> EXEC
MCPSLOW --> EXEC
EXEC --> RESULT["Operation Result"]
RESULT --> CLIFMT["CLI Formatting<br/>Text / JSON"]
RESULT --> MCPFMT["MCP Formatting<br/>TextContent"]
end
| Decision | Rationale |
|---|---|
| Source generation over reflection | Eliminates runtime reflection, enables AOT compilation, and produces compile-time errors for invalid operations |
| Conditional code emission | Generated artifacts depend on which runtime packages are referenced, preventing unused code |
| Three-tier invoker hierarchy | Fast sync → fast async → slow path provides zero-allocation hot paths while maintaining full-featured fallback |
| Frozen dictionary dispatch |
CliApplication pre-computes a FrozenDictionary of command candidates for O(1) lookup at runtime |
| Core package has zero dependencies | Ensures the contract library can be referenced without pulling in CLI or MCP runtime dependencies |
| Separate CLI and MCP naming |
[CliName]/[McpName] and [CliCommand]/[McpTool] allow each surface to use idiomatic naming conventions |
Sources: Manifold/src/Manifold.Cli/CliApplication.cs:23-24, Manifold/src/Manifold/OperationAttribute.cs:1-15
- Home — Project overview and quick-start guide
- Project Structure and Tech Stack — Directory layout and tooling
- Core Contracts — Manifold Package — Foundational types and interfaces
- Attributes and Operation Definition — All attributes for defining operations
- Source Generator — Manifold.Generators — Generator internals and emitted artifacts
- CLI Runtime — Manifold.Cli — CLI dispatch pipeline details
- MCP Runtime — Manifold.Mcp — MCP dispatch pipeline details
- Parameter Binding and Type Conversion — Binding mechanics for both surfaces
- Performance and Benchmarks — Zero-allocation fast-path design and benchmark results
- Dependency Injection and Service Resolution — Service provider integration
- Result Types and Formatting — Result handling across surfaces
- Sample — CLI Host — CLI host wiring walkthrough
- Samples — MCP Hosts (Stdio and HTTP) — MCP host wiring walkthrough