Manifold

mcp
SUMMARY

Operation-first .NET foundation for generating fast CLI and MCP surfaces from a single definition.

README.md

Manifold

Manifold

日本語版 README

Manifold is a .NET foundation for defining an operation once and exposing it through both CLI and MCP surfaces.

The model is simple:

  • Write one operation by hand
  • Let the source generator emit descriptors and invokers
  • Wire those generated artifacts into your CLI and/or MCP host

Manifold does not own your transport, hosting model, or product-specific runtime. It focuses on operation definition, binding, metadata, and fast dispatch.

If you want a runnable starting point, the repository includes sample hosts under samples/.

What's Included

Package Purpose
Manifold Core contracts, descriptors, attributes, and binding primitives
Manifold.Cli CLI runtime helpers and generated invocation
Manifold.Generators Source generator that emits descriptors and invokers
Manifold.Mcp MCP metadata and invocation helpers

Core Concepts

An operation is the single source of truth.

From that definition, Manifold.Generators emits:

  • GeneratedOperationRegistry
  • GeneratedCliInvoker
  • GeneratedMcpCatalog
  • GeneratedMcpInvoker

You compose those generated types into your own application.

Manifold supports two authoring styles:

  1. Static method operations
  2. Class-based operations implementing IOperation<TRequest, TResult>

Install

Most consumers should not install all four packages.

Start with the core package and the generator, then add only the surfaces you actually use.

Typical combinations:

Scenario Packages
Define operations only Manifold, Manifold.Generators
CLI app Manifold, Manifold.Generators, Manifold.Cli
MCP host Manifold, Manifold.Generators, Manifold.Mcp
Both CLI and MCP Manifold, Manifold.Generators, Manifold.Cli, Manifold.Mcp

CLI host:

<ItemGroup>
  <PackageReference Include="Manifold" Version="1.0.0" />
  <PackageReference Include="Manifold.Generators" Version="1.0.0" PrivateAssets="all" />
  <PackageReference Include="Manifold.Cli" Version="1.0.0" />
</ItemGroup>

MCP host:

<ItemGroup>
  <PackageReference Include="Manifold" Version="1.0.0" />
  <PackageReference Include="Manifold.Generators" Version="1.0.0" PrivateAssets="all" />
  <PackageReference Include="Manifold.Mcp" Version="1.0.0" />
</ItemGroup>

If you need both surfaces, combine the two examples.

Authoring Operations

Static Method Example

Static method operations work well when you want the simplest possible definition.

using Manifold;

public static class MathOperations
{
    [Operation("math.add", Summary = "Adds two integers.")]
    [CliCommand("math", "add")]
    [McpTool("math_add")]
    public static int Add(
        [Argument(0, Name = "x")] int x,
        [Argument(1, Name = "y")] int y)
    {
        return x + y;
    }
}

Class-Based Example

Class-based operations are useful when you need a dedicated request type, richer modeling, or DI-managed construction.

using Manifold;

[Operation("math.add", Summary = "Adds two integers.")]
[CliCommand("math", "add")]
[McpTool("math_add")]
public sealed class AddOperation : IOperation<AddOperation.Request, int>
{
    public ValueTask<int> ExecuteAsync(Request request, OperationContext context)
        => ValueTask.FromResult(request.X + request.Y);

    public sealed class Request
    {
        [Argument(0, Name = "x")]
        [McpName("x")]
        public int X { get; init; }

        [Argument(1, Name = "y")]
        [McpName("y")]
        public int Y { get; init; }
    }
}

For class-based operations, register the operation type in DI before using the generated invokers.

using Microsoft.Extensions.DependencyInjection;

ServiceCollection services = new();
services.AddTransient<AddOperation>();
ServiceProvider serviceProvider = services.BuildServiceProvider();

Static method operations do not require DI registration unless they explicitly request services.

Attribute Model

The main attributes are:

Attribute Applies To Purpose
[Operation("operation.id")] Method, class Declares the canonical operation id. Supports Summary, Description, and Hidden.
[CliCommand("group", "verb")] Method, class Declares the CLI command path, for example math add.
[McpTool("tool_name")] Method, class Declares the MCP tool name, for example math_add.
[CliOnly] Method, class Exposes the operation only on the CLI surface.
[McpOnly] Method, class Exposes the operation only on the MCP surface.
[ResultFormatter(typeof(...))] Method, class Overrides default CLI text rendering with a custom formatter.
[Argument(position)] Parameter, request property Binds a positional CLI argument. Supports Name, Description, and Required.
[Option("name")] Parameter, request property Binds a named CLI option. Supports Description and Required.
[Alias(...)] Method, class, parameter, request property Adds aliases for commands, options, arguments, or names.
[CliName("...")] Method, class, parameter, request property Overrides the CLI-facing name only.
[McpName("...")] Method, class, parameter, request property Overrides the MCP-facing name only.
[FromServices] Parameter Resolves the value from DI instead of user input.

Common patterns:

  • Use [Operation] together with at least one surface attribute such as [CliCommand] or [McpTool]
  • Use [Argument] for ordered CLI inputs and [Option] for named CLI inputs
  • Use [CliName] and [McpName] when the same conceptual field should appear under different names on each surface
  • Use [CliOnly] or [McpOnly] when an operation should not be shared across both surfaces
  • Use [FromServices] for runtime services such as clocks, repositories, or application state

Examples:

  • Rename an option for CLI only with [CliName("person")]
  • Rename an MCP argument with [McpName("targetName")]
  • Hide an internal operation from generated surfaces with [Operation("internal.sync", Hidden = true)]
  • Expose to only one surface with [CliOnly] or [McpOnly]

CLI Usage

At runtime, compose the generated registry and invoker into a CliApplication.

using Manifold.Cli;
using Manifold.Generated;
using Microsoft.Extensions.DependencyInjection;

ServiceCollection services = new();
services.AddTransient<AddOperation>();
ServiceProvider serviceProvider = services.BuildServiceProvider();

CliApplication cli = new(
    GeneratedOperationRegistry.Operations,
    new GeneratedCliInvoker(),
    serviceProvider);

StringWriter output = new();
StringWriter error = new();

int exitCode = await cli.ExecuteAsync(
    ["math", "add", "2", "3"],
    output,
    error,
    CancellationToken.None);

Notes:

  • CliApplication handles usage text and command dispatch
  • GeneratedCliInvoker is the generated binding layer
  • Fast sync and async paths are selected automatically when available

MCP Usage

Manifold does not ship an MCP transport host. Instead, it provides:

  • Generated tool metadata via GeneratedMcpCatalog
  • Generated execution via GeneratedMcpInvoker
  • MCP argument parsing and helper APIs in Manifold.Mcp

A minimal local invocation looks like this:

using System.Text.Json;
using Manifold.Generated;
using Manifold.Mcp;
using Microsoft.Extensions.DependencyInjection;

ServiceCollection services = new();
services.AddTransient<AddOperation>();
ServiceProvider serviceProvider = services.BuildServiceProvider();

JsonElement args = JsonSerializer.Deserialize<JsonElement>(
    "{\"x\":2,\"y\":3}");

GeneratedMcpInvoker invoker = new();

if (invoker.TryInvokeFast(
        "math_add",
        args,
        serviceProvider,
        CancellationToken.None,
        out ValueTask<FastMcpInvocationResult> invocation))
{
    FastMcpInvocationResult result = await invocation;
    Console.WriteLine(result.Number);
}

Metadata discovery:

using Manifold.Generated;

foreach (var tool in GeneratedMcpCatalog.Tools)
{
    Console.WriteLine($"{tool.Name}: {tool.Description}");
}

MCP Transports and Samples

The primary MCP transports are:

  • stdio
  • Streamable HTTP

Manifold is intentionally transport-agnostic, so the repository includes sample hosts rather than baking transport hosting into the core packages.

Run them like this:

dotnet run --project .\samples\Manifold.Samples.McpStdioHost\Manifold.Samples.McpStdioHost.csproj
dotnet run --project .\samples\Manifold.Samples.McpHttpHost\Manifold.Samples.McpHttpHost.csproj

The HTTP sample listens on http://127.0.0.1:38474/mcp.

Note: the HTTP sample uses ModelContextProtocol.AspNetCore, which is currently a preview package. The stable core MCP package is ModelContextProtocol.

CLI Sample Host

There is also a minimal runnable CLI host:

dotnet run --project .\samples\Manifold.Samples.CliHost\Manifold.Samples.CliHost.csproj -- math add 2 3
dotnet run --project .\samples\Manifold.Samples.CliHost\Manifold.Samples.CliHost.csproj -- weather preview --city Tokyo --days 3

Dependency Injection and Services

There are two service access patterns.

Method-Based Operations

Use [FromServices] on a parameter.

[Operation("clock.now")]
[CliCommand("clock", "now")]
public static DateTimeOffset Now(
    [FromServices] IClock clock)
{
    return clock.UtcNow;
}

Class-Based Operations

Use constructor injection, or request services through OperationContext.

public sealed class GreetingOperation(IGreetingService greetings)
    : IOperation<GreetingOperation.Request, string>
{
    public ValueTask<string> ExecuteAsync(Request request, OperationContext context)
        => ValueTask.FromResult(greetings.Format(request.Name));

    public sealed class Request
    {
        [Option("name")]
        public string Name { get; init; } = string.Empty;
    }
}

Result Formatting

To provide custom CLI text output while keeping structured results for JSON or MCP, implement IResultFormatter<TResult>.

using Manifold;

public sealed class WeatherFormatter : IResultFormatter<WeatherResult>
{
    public string? FormatText(WeatherResult result, OperationContext context)
        => $"{result.City}:{result.TemperatureC}";
}

Then attach it:

[ResultFormatter(typeof(WeatherFormatter))]

Generated Types

The generator emits these public entry points under Manifold.Generated:

  • GeneratedOperationRegistry
  • GeneratedCliInvoker
  • GeneratedMcpCatalog
  • GeneratedMcpInvoker

These are the standard integration surface for consumers.

Performance

Manifold includes dedicated BenchmarkDotNet suites under benchmarks/.

Benchmark notes and comparison tables are in benchmarks/README.md.

Comparison set:

  • CLI
    • Manifold.Cli
    • ConsoleAppFramework
    • System.CommandLine
  • MCP
    • Manifold.Mcp
    • official ModelContextProtocol
    • McpToolkit
    • mcpdotnet

Current snapshot:

CLI benchmark chart

CLI:

Scenario Manifold ConsoleAppFramework System.CommandLine
Positional command 22.61 ns / 0 B 26.57 ns / 0 B 1730.82 ns / 4688 B
Option-heavy command 28.89 ns / 0 B 24.76 ns / 0 B 2110.84 ns / 5632 B

MCP (roundtrip-shaped):

MCP benchmark chart

Scenario Manifold ModelContextProtocol McpToolkit McpDotNet
tools/list response 756.3 ns / 0 B 754.6 ns / 0 B 635.4 ns / 0 B 816.2 ns / 0 B
tools/call response 47.16 ns / 0 B 68.56 ns / 0 B 146.34 ns / 96 B 93.20 ns / 256 B

Notes:

  • CLI numbers measure the parser + dispatch hot path
  • MCP numbers above reflect in-memory response construction, not transport benchmarks
  • The raw ModelContextProtocol invocation microbenchmark is near ZeroMeasurement; the roundtrip-shaped table is the more meaningful comparison

For methodology and full reports, see benchmarks/README.md.

Build

From the repository root:

./build/restore.ps1
./build/build.ps1 -NoRestore
./build/test.ps1 -NoBuild
./build/quality.ps1
./build/pack.ps1

./build/pack.ps1 writes .nupkg and .snupkg files to .artifacts/packages/.

Repository Status

  • Windows-first scripts and CI
  • MIT licensed
  • CI verifies build, test, formatting, architecture checks, and package creation

OSS Housekeeping

Repository Layout

src/
  Manifold
  Manifold.Cli
  Manifold.Generators
  Manifold.Mcp
tests/
  Manifold.Tests
  Manifold.Cli.Tests
  Manifold.Generators.Tests
  Manifold.Mcp.Tests
benchmarks/
  Manifold.Benchmarks
  Manifold.Mcp.Benchmarks
samples/
  Manifold.Samples.Operations
  Manifold.Samples.CliHost
  Manifold.Samples.McpStdioHost
  Manifold.Samples.McpHttpHost

Reviews (0)

No results found