MCP Runtime - Garume/Manifold GitHub Wiki
The Manifold.Mcp package provides the runtime infrastructure for exposing Manifold operations as Model Context Protocol (MCP) tools. It defines the invocation interfaces, JSON argument binding utilities, tool discovery metadata models, a zero-allocation result type, and a high-performance response writer. This package depends on the core Manifold contracts and the official ModelContextProtocol SDK (v1.1.0), and serves as the target for generated code emitted by the source generator.
For information on how the generator produces the MCP artifacts (GeneratedMcpInvoker, GeneratedMcpTools, GeneratedMcpCatalog), see Source Generator — Manifold.Generators. For sample host applications that wire up the MCP runtime, see Samples — MCP Hosts (Stdio and HTTP).
The Manifold.Mcp package consists of five source files, each addressing a distinct concern in the MCP runtime pipeline.
| File | Purpose |
|---|---|
McpInvoker.cs |
Invoker interfaces and McpBinding JSON parsing helpers |
McpBinding.cs |
Service resolution helpers (partial class continuation) |
McpToolCatalogModels.cs |
Tool discovery metadata record structs |
FastMcpInvocationResult.cs |
Zero-allocation result type with union-based value overlay |
McpTextContentResponseWriter.cs |
High-performance MCP response JSON writer |
Sources: src/Manifold.Mcp/Manifold.Mcp.csproj:19-24
flowchart TD
A[Manifold Core] --> B[Manifold.Mcp]
C[ModelContextProtocol SDK v1.1.0] --> B
B --> D[Source Generator]
D --> E[GeneratedMcpInvoker]
D --> F[GeneratedMcpTools]
D --> G[GeneratedMcpCatalog]
E --> H[MCP Host Application]
F --> H
G --> H
Manifold.Mcp defines a three-tier invocation interface hierarchy, each offering a different trade-off between flexibility and performance. The source generator emits a single GeneratedMcpInvoker class that implements all three interfaces.
Sources: src/Manifold.Mcp/McpInvoker.cs:7-35
The standard invocation interface. It accepts a tool name, optional JSON arguments, an optional service provider, and a cancellation token. It returns OperationInvocationResult, which boxes the result value.
public interface IMcpToolInvoker
{
public bool TryInvoke(
string toolName,
JsonElement? arguments,
IServiceProvider? services,
CancellationToken cancellationToken,
out ValueTask<OperationInvocationResult> invocation);
}Sources: src/Manifold.Mcp/McpInvoker.cs:7-15
The fast asynchronous invocation interface. It returns FastMcpInvocationResult instead of OperationInvocationResult, avoiding boxing for scalar return types such as int, bool, long, double, decimal, Guid, and DateTimeOffset.
public interface IFastMcpToolInvoker
{
public bool TryInvokeFast(
string toolName,
JsonElement? arguments,
IServiceProvider? services,
CancellationToken cancellationToken,
out ValueTask<FastMcpInvocationResult> invocation);
}Sources: src/Manifold.Mcp/McpInvoker.cs:17-25
The synchronous fast-path interface. It eliminates ValueTask overhead entirely for operations that are synchronous. The generator only emits dispatch entries for this interface when the operation method does not return a Task or ValueTask.
public interface IFastSyncMcpToolInvoker
{
public bool TryInvokeFastSync(
string toolName,
JsonElement? arguments,
IServiceProvider? services,
CancellationToken cancellationToken,
out FastMcpInvocationResult invocation);
}Sources: src/Manifold.Mcp/McpInvoker.cs:27-35
| Interface | Return Type | Async | Boxing | Use Case |
|---|---|---|---|---|
IMcpToolInvoker |
ValueTask<OperationInvocationResult> |
Yes | Yes | General-purpose invocation |
IFastMcpToolInvoker |
ValueTask<FastMcpInvocationResult> |
Yes | No | Async operations with scalar results |
IFastSyncMcpToolInvoker |
FastMcpInvocationResult (out) |
No | No | Synchronous operations only |
All three interfaces use a TryInvoke* pattern: they return bool to indicate whether the tool name was recognized, and output the result through an out parameter. If the tool is not found, the method returns false and sets the out parameter to default.
flowchart TD
A[MCP Tool Call] --> B{Tool Name Lookup}
B -->|Not Found| C[Return false]
B -->|Found| D{Operation Type}
D -->|Sync| E[IFastSyncMcpToolInvoker]
D -->|Async| F{Result Type}
F -->|Scalar| G[IFastMcpToolInvoker]
F -->|Object| H[IMcpToolInvoker]
E --> I[FastMcpInvocationResult]
G --> I
H --> J[OperationInvocationResult]
The McpBinding static partial class provides utility methods for extracting and converting MCP tool arguments from JsonElement values. These methods are called directly by the generated invoker code.
Sources: src/Manifold.Mcp/McpInvoker.cs:37-270, src/Manifold.Mcp/McpBinding.cs:1-31
The binding layer first validates that the incoming arguments form a JSON object, then extracts individual properties by name.
// Validate arguments are a JSON object
McpBinding.TryGetObject(JsonElement? arguments, out JsonElement value)
// Extract a required property (throws if missing)
McpBinding.GetRequiredProperty(JsonElement? arguments, string name)
McpBinding.GetRequiredProperty(JsonElement? arguments, ReadOnlySpan<byte> utf8Name, string displayName)
// Extract an optional property
McpBinding.TryGetProperty(JsonElement? arguments, string name, out JsonElement value)
McpBinding.TryGetProperty(JsonElement? arguments, ReadOnlySpan<byte> utf8Name, out JsonElement value)TryGetObject returns false for null, Undefined, or Null JSON values, and throws ArgumentException if the value exists but is not an object.
Sources: src/Manifold.Mcp/McpInvoker.cs:39-105
Each supported scalar type has a dedicated parser method with consistent error reporting through the displayName parameter:
| Method | Target Type | JSON ValueKind |
|---|---|---|
ParseString |
string |
String |
ParseBoolean |
bool |
True / False
|
ParseInt32 |
int |
Number (via TryGetInt32) |
ParseInt64 |
long |
Number (via TryGetInt64) |
ParseDouble |
double |
Number (via TryGetDouble) |
ParseDecimal |
decimal |
Number (via TryGetDecimal) |
ParseGuid |
Guid |
String (via Guid.TryParse) |
ParseUri |
Uri |
String (via Uri.TryCreate) |
ParseDateTimeOffset |
DateTimeOffset |
String (via DateTimeOffset.TryParse) |
Sources: src/Manifold.Mcp/McpInvoker.cs:107-214
The ConvertValue method provides a general-purpose conversion path that handles Nullable<T>, enums (case-insensitive string or raw text parsing), all scalar types above, and falls back to JsonSerializer.Deserialize for complex types.
public static object? ConvertValue(Type targetType, JsonElement value, string displayName)Sources: src/Manifold.Mcp/McpInvoker.cs:216-269
flowchart TD
A[ConvertValue] --> B{Is string?}
B -->|Yes| C[ParseString]
B -->|No| D{Is Nullable?}
D -->|Yes, null| E[Return null]
D -->|Yes, value| F[Unwrap and recurse]
D -->|No| G{Is Enum?}
G -->|Yes| H[Enum.TryParse]
G -->|No| I{Scalar type?}
I -->|Yes| J[Type-specific parser]
I -->|No| K[JsonSerializer.Deserialize]
The partial class continuation in McpBinding.cs provides service resolution helpers used by the generated invoker to inject [FromServices] parameters and resolve IOperation<TRequest, TResult> instances:
public static TService GetRequiredServiceOrThrow<TService>(IServiceProvider? services)
public static TService GetRequiredService<TService>(IServiceProvider? services)
public static object GetRequiredService(IServiceProvider? services, Type serviceType)Sources: src/Manifold.Mcp/McpBinding.cs:5-30
Two readonly record structs describe MCP tools and their parameters for catalog and discovery purposes:
public readonly record struct McpParameterDescriptor(
string Name,
Type ParameterType,
bool Required,
string? Description = null);
public readonly record struct McpToolDescriptor(
string Name,
string? Description,
McpParameterDescriptor[] Parameters);These are populated at compile time by the generator into the GeneratedMcpCatalog static class, which provides IReadOnlyList<McpToolDescriptor> access via Tools, span-based enumeration via AsSpan(), and dictionary-based lookup via TryFind(string).
Sources: src/Manifold.Mcp/McpToolCatalogModels.cs:1-12
classDiagram
class McpToolDescriptor {
<<record struct>>
+string Name
+string? Description
+McpParameterDescriptor[] Parameters
}
class McpParameterDescriptor {
<<record struct>>
+string Name
+Type ParameterType
+bool Required
+string? Description
}
class GeneratedMcpCatalog {
<<generated>>
+IReadOnlyList~McpToolDescriptor~ Tools
+ReadOnlySpan~McpToolDescriptor~ AsSpan()
+bool TryFind(string, out McpToolDescriptor)
}
McpToolDescriptor --> McpParameterDescriptor : contains
GeneratedMcpCatalog --> McpToolDescriptor : stores
FastMcpInvocationResult is a readonly struct that carries operation results without boxing scalar values. It uses an explicit memory layout union (FastMcpInvocationValue) to overlay all supported scalar types in the same 16-byte region. For a broader discussion of the performance design, see Performance and Benchmarks.
Sources: src/Manifold.Mcp/FastMcpInvocationResult.cs:1-119
The FastMcpInvocationKind enum identifies which value slot is active:
| Kind | Value | Storage | .NET Type |
|---|---|---|---|
None |
0 | — | — |
Text |
1 |
reference (object) |
string |
Boolean |
2 |
value union |
bool |
Number |
3 |
value union |
int |
LargeNumber |
4 |
value union |
long |
RealNumber |
5 |
value union |
double |
PreciseNumber |
6 |
value union |
decimal |
Identifier |
7 |
value union |
Guid |
Timestamp |
8 |
value union |
DateTimeOffset |
Structured |
9 |
reference (object) |
arbitrary type |
Sources: src/Manifold.Mcp/FastMcpInvocationResult.cs:121-133
The FastMcpInvocationValue internal struct uses [StructLayout(LayoutKind.Explicit, Size = 16)] to overlay all scalar types at field offset 0:
[StructLayout(LayoutKind.Explicit, Size = 16)]
internal readonly struct FastMcpInvocationValue
{
[FieldOffset(0)] private readonly bool boolean;
[FieldOffset(0)] private readonly int number;
[FieldOffset(0)] private readonly long largeNumber;
[FieldOffset(0)] private readonly double realNumber;
[FieldOffset(0)] private readonly decimal preciseNumber;
[FieldOffset(0)] private readonly Guid identifier;
[FieldOffset(0)] private readonly DateTimeOffset timestamp;
}Sources: src/Manifold.Mcp/FastMcpInvocationResult.cs:135-157
Each result kind has a static factory method marked with [MethodImpl(MethodImplOptions.AggressiveInlining)]:
FastMcpInvocationResult.FromText(string? text)
FastMcpInvocationResult.FromBoolean(bool value)
FastMcpInvocationResult.FromNumber(int value)
FastMcpInvocationResult.FromLargeNumber(long value)
FastMcpInvocationResult.FromRealNumber(double value)
FastMcpInvocationResult.FromPreciseNumber(decimal value)
FastMcpInvocationResult.FromIdentifier(Guid value)
FastMcpInvocationResult.FromTimestamp(DateTimeOffset value)
FastMcpInvocationResult.FromStructured(object? value, Type resultType)FromText returns FastMcpInvocationResult.None for null input. FromStructured stores the object reference and its runtime type for later JSON serialization.
Sources: src/Manifold.Mcp/FastMcpInvocationResult.cs:48-118
The McpTextContentResponseWriter static class serializes a FastMcpInvocationResult into the MCP CallToolResponse JSON format using IBufferWriter<byte> for zero-allocation output. For details on the result formatting pipeline, see Result Types and Formatting.
Sources: src/Manifold.Mcp/McpTextContentResponseWriter.cs:1-131
The writer produces JSON of the form:
{"isError":false,"content":[{"type":"text","text":"<value>"}]}The prefix and suffix are stored as UTF-8 byte literals:
private static ReadOnlySpan<byte> ResponsePrefix =>
"{\"isError\":false,\"content\":[{\"type\":\"text\",\"text\":\""u8;
private static ReadOnlySpan<byte> ResponseSuffix => "\"}]}"u8;Sources: src/Manifold.Mcp/McpTextContentResponseWriter.cs:11-12
The WriteCallToolResponse method dispatches on FastMcpInvocationKind with optimized fast paths for the most common result types:
flowchart TD
A[WriteCallToolResponse] --> B{Invocation Kind}
B -->|None| C[WriteEmpty]
B -->|Boolean| D[WriteBoolean]
B -->|Number int| E[WriteInt32]
B -->|LargeNumber long| F[WriteInt64]
B -->|Other| G[WriteSlow]
C --> H[Prefix + Suffix]
D --> I[Prefix + true/false + Suffix]
E --> J[Prefix + Utf8Formatter + Suffix]
F --> J
G --> K[Utf8JsonWriter full envelope]
Fast paths (None, Boolean, Number, LargeNumber) write pre-computed UTF-8 byte spans directly to the buffer. Integer formatting uses Utf8Formatter.TryFormat with buffer sizes of 11 bytes for int and 20 bytes for long.
Slow path (WriteSlow) handles Text, RealNumber, PreciseNumber, Identifier, Timestamp, and Structured kinds through Utf8JsonWriter, which correctly handles JSON string escaping. The GetTextContent method converts each kind to its string representation — using invariant culture for numbers, ISO 8601 ("O") for timestamps, and JsonSerializer.Serialize for structured values.
Sources: src/Manifold.Mcp/McpTextContentResponseWriter.cs:14-131
The source generator conditionally emits three MCP source files when the Manifold.Mcp package is referenced. The generator checks for the presence of key types in the compilation before emitting each file:
| Generated File | Condition Types | Emitted Class |
|---|---|---|
GeneratedMcpTools.g.cs |
McpBinding, McpServerToolTypeAttribute, McpServerBuilderExtensions
|
GeneratedMcpTools |
GeneratedMcpCatalog.g.cs |
McpToolDescriptor, McpParameterDescriptor
|
GeneratedMcpCatalog |
GeneratedMcpInvoker.g.cs |
IMcpToolInvoker, IFastMcpToolInvoker, IFastSyncMcpToolInvoker, FastMcpInvocationResult
|
GeneratedMcpInvoker |
Sources: src/Manifold.Generators/OperationDescriptorGenerator.cs:661-687
The GeneratedMcpTools class is annotated with [McpServerToolType] from the ModelContextProtocol SDK. It exposes one method per MCP-visible operation, each decorated with [McpServerTool] and [Description] attributes. The GeneratedMcpServiceCollectionExtensions class provides the AddGeneratedMcpServer() extension method that wires the tools into the MCP SDK pipeline:
public static IMcpServerBuilder AddGeneratedMcpServer(
this IServiceCollection services)
{
ArgumentNullException.ThrowIfNull(services);
IMcpServerBuilder builder =
McpServerServiceCollectionExtensions.AddMcpServer(services, static _ => { });
return McpServerBuilderExtensions.WithTools<GeneratedMcpTools>(builder);
}Sources: src/Manifold.Generators/OperationDescriptorGenerator.cs:1003-1041
The GeneratedMcpInvoker class implements all three invoker interfaces. For each MCP-visible operation, the generator emits:
-
Invoke{Name}Async— standard path returningValueTask<OperationInvocationResult> -
Invoke{Name}FastAsync— fast path returningValueTask<FastMcpInvocationResult> -
Invoke{Name}FastSync— synchronous fast path (only for non-async operations)
Each method uses switch-based dispatch on the tool name string. Parameter binding is delegated to the McpBinding static methods.
Sources: src/Manifold.Generators/OperationDescriptorGenerator.cs:1091-1167
The GeneratedMcpCatalog static class stores a pre-built array of McpToolDescriptor entries and a Dictionary<string, McpToolDescriptor> for O(1) lookup. It exposes:
-
Tools—IReadOnlyList<McpToolDescriptor>of all registered MCP tools -
AsSpan()— zero-copy span access to the tool array -
TryFind(string toolName, out McpToolDescriptor)— dictionary-based name lookup
Sources: src/Manifold.Generators/OperationDescriptorGenerator.cs:1044-1088
MCP host applications use the generated AddGeneratedMcpServer() extension method to register the MCP server and then select a transport. Two transports are demonstrated in the sample hosts:
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddTransient<WeatherPreviewOperation>();
builder.Services.AddGeneratedMcpServer()
.WithStdioServerTransport();
IHost host = builder.Build();
await host.RunAsync();Sources: samples/Manifold.Samples.McpStdioHost/Program.cs:1-12
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddTransient<WeatherPreviewOperation>();
builder.Services.AddGeneratedMcpServer()
.WithHttpTransport();
WebApplication app = builder.Build();
app.MapMcp("/mcp");
await app.RunAsync();Sources: samples/Manifold.Samples.McpHttpHost/Program.cs:1-19
sequenceDiagram
participant Client as MCP Client
participant Host as MCP Host
participant Tools as GeneratedMcpTools
participant Invoker as GeneratedMcpInvoker
participant Binding as McpBinding
participant Op as Operation Method
Client->>Host: CallToolRequest (tool name + JSON args)
Host->>Tools: ExecuteToolAsync
Tools->>Invoker: TryInvokeFast
Invoker->>Binding: Parse JSON arguments
Binding-->>Invoker: Typed parameters
Invoker->>Op: Invoke operation
Op-->>Invoker: Result value
Invoker-->>Tools: FastMcpInvocationResult
Tools-->>Host: CallToolResponse JSON
Host-->>Client: MCP response
The MCP runtime components are tested in the Manifold.Mcp.Tests project with three test classes:
| Test Class | Coverage |
|---|---|
GeneratedMcpInvokerTests |
All three invoker interfaces, scalar boxing avoidance, optional parameters, unknown tool handling |
GeneratedMcpToolsTests |
Tool type attributes, method invocation, parameter descriptions, AddGeneratedMcpServer registration |
GeneratedMcpCatalogTests |
Tool count, TryFind metadata, AsSpan enumeration |
The tests verify that synchronous operations (e.g., sample_hello) are available through TryInvokeFastSync, while asynchronous operations (e.g., math.sum) correctly return false for the sync interface:
Assert.True(invoker.TryInvokeFastSync("sample_hello", helloArguments,
serviceProvider, cancellationToken, out FastMcpInvocationResult helloInvocation));
Assert.False(invoker.TryInvokeFastSync("math.sum", sumArguments,
serviceProvider, CancellationToken.None, out FastMcpInvocationResult sumInvocation));Sources: tests/Manifold.Mcp.Tests/GeneratedMcpInvokerTests.cs:64-82, tests/Manifold.Mcp.Tests/GeneratedMcpToolsTests.cs:10-86, tests/Manifold.Mcp.Tests/GeneratedMcpCatalogTests.cs:1-45
- Architecture Overview — High-level system architecture and the four-package design
- Core Contracts — Manifold Package — Foundational interfaces and types that Manifold.Mcp depends on
-
Attributes and Operation Definition —
[McpTool],[McpName],[McpOnly]and other attributes - Source Generator — Manifold.Generators — How the generator emits MCP artifacts
- CLI Runtime — Manifold.Cli — The parallel CLI surface for comparison
- Samples — MCP Hosts (Stdio and HTTP) — Walkthroughs of MCP host applications
- Parameter Binding and Type Conversion — JSON argument parsing details for MCP surface
- Performance and Benchmarks — Zero-allocation fast-path design and benchmark results
- Result Types and Formatting — FastMcpInvocationResult and McpTextContentResponseWriter details
-
Dependency Injection and Service Resolution —
[FromServices]and service provider integration - Testing Strategy — Test organization and coverage approach