MCP Runtime - Garume/Manifold GitHub Wiki

MCP Runtime — Manifold.Mcp

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).

Package Structure

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
Loading

Invocation Interfaces

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

IMcpToolInvoker

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

IFastMcpToolInvoker

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

IFastSyncMcpToolInvoker

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 Dispatch Comparison

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]
Loading

McpBinding — JSON Argument Parsing

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

JSON Property Access

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

Type-Specific Parsers

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

Universal Type Converter

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]
Loading

Service Resolution

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

McpToolCatalogModels — Tool Discovery Metadata

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
Loading

FastMcpInvocationResult

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

Result Kind Enum

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

Union Value Layout

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

Factory Methods

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

McpTextContentResponseWriter

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

Response Format

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

Fast and Slow Paths

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]
Loading

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

Generated Code Integration

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

GeneratedMcpTools

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

GeneratedMcpInvoker

The GeneratedMcpInvoker class implements all three invoker interfaces. For each MCP-visible operation, the generator emits:

  • Invoke{Name}Async — standard path returning ValueTask<OperationInvocationResult>
  • Invoke{Name}FastAsync — fast path returning ValueTask<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

GeneratedMcpCatalog

The GeneratedMcpCatalog static class stores a pre-built array of McpToolDescriptor entries and a Dictionary<string, McpToolDescriptor> for O(1) lookup. It exposes:

  • ToolsIReadOnlyList<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

Host Integration

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:

Stdio Transport

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

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: 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
Loading

Testing

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

Related Pages

⚠️ **GitHub.com Fallback** ⚠️