Sample Operations Reference - Garume/Manifold GitHub Wiki

Sample Operations Reference

The Manifold.Samples.Operations project and the Manifold.Generators.Tests.Samples namespace contain shared operation definitions that demonstrate the full range of Manifold's authoring patterns. These samples serve as a practical pattern catalog for both the static-method and class-based operation styles, illustrating parameter binding (positional arguments, named options, service injection, cancellation tokens), surface visibility control ([CliOnly], [McpOnly]), surface-specific naming, aliases, and hidden operations.

This page covers all sample operation files across the repository and cross-references the attribute definitions documented in Attributes and Operation Definition, the binding pipeline described in Parameter Binding and Type Conversion, and the result handling covered in Result Types and Formatting. The sample operations are consumed by the host applications documented in Sample — CLI Host and Samples — MCP Hosts (Stdio and HTTP).

Authoring Styles Overview

Manifold supports two distinct operation authoring styles. Both styles produce identical OperationDescriptor records via the source generator and are dispatched through the same CLI and MCP pipelines.

flowchart TD
    A[Operation Definition] --> B{Authoring Style}
    B -->|Static Method| C[Static class with<br/>attributed method]
    B -->|Class-Based| D[Class implementing<br/>IOperation&lt;TRequest, TResult&gt;]
    C --> E[Parameters on<br/>method signature]
    D --> F[Parameters on<br/>Request class properties]
    E --> G[Source Generator]
    F --> G
    G --> H[OperationDescriptor]
    H --> I[CLI Surface]
    H --> J[MCP Surface]
Loading

Static-Method Style

In the static-method style, an operation is a static method decorated with [Operation], [CliCommand], and/or [McpTool] attributes. Parameters are declared directly on the method signature and annotated with [Argument], [Option], [FromServices], or left as CancellationToken.

[Operation("math.add", Description = "Add two integers.", Summary = "Returns the sum of x and y.")]
[CliCommand("math", "add")]
[McpTool("math_add")]
public static int Add(
    [Argument(0, Name = "x", Description = "Left operand")] int x,
    [Argument(1, Name = "y", Description = "Right operand")] int y)
{
    return x + y;
}

Sources: samples/Manifold.Samples.Operations/SampleOperations.cs:5–13

Key characteristics of the static-method style:

  • Method return type can be T, Task<T>, or ValueTask<T>
  • Parameters are bound directly from method parameters
  • The containing class must be static and partial (for source generation)
  • Supports [FromServices] for dependency injection and CancellationToken for cancellation

Class-Based Style

In the class-based style, an operation is a class that implements IOperation<TRequest, TResult>. The [Operation], [CliCommand], and [McpTool] attributes are placed on the class itself. Parameters are defined as properties on a nested Request class.

[Operation("weather.preview", Description = "Return a pretend weather summary.")]
[CliCommand("weather", "preview")]
[McpTool("weather_preview")]
public sealed class WeatherPreviewOperation : IOperation<WeatherPreviewOperation.Request, string>
{
    public ValueTask<string> ExecuteAsync(Request request, OperationContext context)
    {
        int days = request.Days <= 0 ? 1 : request.Days;
        string city = string.IsNullOrWhiteSpace(request.City) ? "unknown" : request.City.Trim();
        string summary = $"Forecast for {city}: mild for the next {days} day(s). Surface={context.Surface}.";
        return ValueTask.FromResult(summary);
    }

    public sealed class Request
    {
        [Option("city", Description = "Target city")]
        public string City { get; init; } = string.Empty;

        [Option("days", Description = "Number of forecast days")]
        public int Days { get; init; } = 3;
    }
}

Sources: samples/Manifold.Samples.Operations/SampleOperations.cs:16–40

Key characteristics of the class-based style:

  • Always returns ValueTask<TResult> via the ExecuteAsync method
  • Receives OperationContext directly, providing access to Surface, Services, and CancellationToken
  • Parameters are defined as { get; init; } properties on the nested Request class
  • The Request properties use the same [Option], [Argument], [Alias], [CliName], and [McpName] attributes

Complete Sample Operations Catalog

The following table summarizes every sample operation defined across the repository.

Operation ID Style Declaring Type Return Type Visibility CLI Command MCP Tool Name Hidden
math.add (samples) Static method SampleOperations int Both math add math_add No
weather.preview Class-based WeatherPreviewOperation string Both weather preview weather_preview No
sample.hello Static method SampleOperations (tests) string Both sample hello sample_hello Yes
math.add (tests) Static method SampleOperations (tests) int CliOnly math add No
weather.get Static method SampleOperations (tests) string McpOnly weather_fetch No
sample.class-hello Class-based SampleClassHelloOperation string Both sample class-hello sample_class_hello No

Sources: samples/Manifold.Samples.Operations/SampleOperations.cs:1–41, tests/Manifold.Generators.Tests/Samples/SampleOperations.cs:1–40, tests/Manifold.Generators.Tests/Samples/SampleClassOperations.cs:1–24

Parameter Binding Patterns

The sample operations collectively demonstrate all four ParameterSource types defined in the ParameterSource enum: Option, Argument, Service, and CancellationToken.

flowchart TD
    A[Parameter Sources] --> B[Argument]
    A --> C[Option]
    A --> D[Service]
    A --> E[CancellationToken]
    B --> B1["[Argument(position)]<br/>Positional, CLI only"]
    C --> C1["[Option(name)]<br/>Named, both surfaces"]
    D --> D1["[FromServices]<br/>DI-injected"]
    E --> E1["CancellationToken<br/>Auto-detected by type"]
Loading

Sources: src/Manifold/DescriptorModels.cs:18–24

Positional Arguments

Positional arguments are bound using [Argument(position)] and are primarily used on the CLI surface.

[Argument(0, Name = "x", Description = "Left operand")] int x,
[Argument(1, Name = "y", Description = "Right operand")] int y

The generated ParameterDescriptor captures the position, name, description, and required status:

Property Value (for x) Value (for y)
Name "x" "y"
ParameterType typeof(int) typeof(int)
Source ParameterSource.Argument ParameterSource.Argument
Required true true
Position 0 1
Description "Left operand" "Right operand"

Sources: src/Manifold/ParameterAttributes.cs:15–27, tests/Manifold.Generators.Tests/GeneratedOperationRegistryTests.cs:53–70

Named Options

Named options use [Option(name)] and are supported on both CLI and MCP surfaces.

[Option("city", Description = "Target city")]
public string City { get; init; } = string.Empty;

[Option("days", Description = "Number of forecast days")]
public int Days { get; init; } = 3;

Options can be marked as optional with Required = false:

[Option("city", Description = "Target city", Required = false)] string? city = null

Sources: src/Manifold/ParameterAttributes.cs:3–13, tests/Manifold.Generators.Tests/Samples/SampleOperations.cs:36

Service Injection

The [FromServices] attribute marks a parameter for dependency injection. The parameter is resolved from the IServiceProvider at invocation time and is never exposed to end users.

[FromServices] IServiceProvider services

The resulting ParameterDescriptor has Source = ParameterSource.Service and Required = false.

Sources: src/Manifold/ParameterAttributes.cs:91–92, tests/Manifold.Generators.Tests/GeneratedOperationRegistryTests.cs:72–78

CancellationToken

Parameters of type CancellationToken are automatically detected by the source generator without any attribute. They are populated from the OperationContext.CancellationToken at runtime.

CancellationToken cancellationToken = default

The resulting ParameterDescriptor has Source = ParameterSource.CancellationToken and Required = false.

Sources: tests/Manifold.Generators.Tests/GeneratedOperationRegistryTests.cs:79–86

Surface Visibility Control

Operations can be restricted to a single surface using [CliOnly] or [McpOnly], or exposed on both surfaces (the default).

flowchart TD
    A[Operation Visibility] --> B{Attribute Present?}
    B -->|"[CliOnly]"| C["OperationVisibility.CliOnly<br/>CLI surface only"]
    B -->|"[McpOnly]"| D["OperationVisibility.McpOnly<br/>MCP surface only"]
    B -->|Neither| E["OperationVisibility.Both<br/>Both surfaces"]
    C --> F["CliCommandPath set<br/>McpToolName = null"]
    D --> G["CliCommandPath = null<br/>McpToolName set"]
    E --> H["Both paths set"]
Loading

CliOnly Example

The test math.add operation is annotated with [CliOnly], restricting it to the CLI surface:

[Operation("math.add", Description = "Add two integers.")]
[CliOnly]
[CliCommand("math", "add")]
[Alias("sum", "calc plus")]
public static Task<int> AddAsync(...)

The generated descriptor has Visibility = OperationVisibility.CliOnly and no McpToolName. The [Alias] attribute on this operation generates two alternative CLI command paths: ["math", "sum"] and ["calc", "plus"].

Sources: tests/Manifold.Generators.Tests/Samples/SampleOperations.cs:19–29, tests/Manifold.Generators.Tests/GeneratedOperationRegistryTests.cs:44–50

McpOnly Example

The weather.get operation is annotated with [McpOnly] and uses [McpName] to override the tool name:

[Operation("weather.get")]
[McpOnly]
[McpName("weather_fetch")]
public static ValueTask<string> FetchAsync(
    [Option("city", Description = "Target city", Required = false)] string? city = null)

The generated descriptor has Visibility = OperationVisibility.McpOnly, McpToolName = "weather_fetch", and CliCommandPath = null.

Sources: tests/Manifold.Generators.Tests/Samples/SampleOperations.cs:32–39, tests/Manifold.Generators.Tests/GeneratedOperationRegistryTests.cs:125–128

Surface-Specific Naming and Aliases

Parameters can have different names on the CLI and MCP surfaces, and can define multiple aliases for CLI discovery. This pattern is demonstrated in both the static-method and class-based styles.

flowchart TD
    A["Parameter 'name'"] --> B["[Option('name')]<br/>Canonical name"]
    A --> C["[CliName('person')]<br/>CLI display name"]
    A --> D["[McpName('targetName')]<br/>MCP JSON key"]
    A --> E["[Alias('n', 'username')]<br/>CLI aliases"]
Loading

Static-Method Naming Example

[Operation("sample.hello", Description = "Say hello.", Hidden = true)]
[CliCommand("sample", "hello")]
[McpTool("sample_hello")]
public static string Hello(
    [Option("name", Description = "User name")]
    [CliName("person")]
    [McpName("targetName")]
    [Alias("n", "username")]
    string name,
    CancellationToken cancellationToken = default)

Sources: tests/Manifold.Generators.Tests/Samples/SampleOperations.cs:5–17

Class-Based Naming Example

internal sealed class Request
{
    [Option("name", Description = "User name")]
    [CliName("person")]
    [McpName("targetName")]
    [Alias("n", "username")]
    public string Name { get; init; } = string.Empty;
}

Sources: tests/Manifold.Generators.Tests/Samples/SampleClassOperations.cs:16–23

Generated Parameter Descriptor

Both styles produce identical ParameterDescriptor values, as verified by the registry tests:

Descriptor Field Value
Name "name"
CliName "person"
McpName "targetName"
Aliases ["n", "username"]
ParameterType typeof(string)
Source ParameterSource.Option

Sources: tests/Manifold.Generators.Tests/GeneratedOperationRegistryTests.cs:109–123

Hidden Operations

The Hidden property on [Operation] controls whether the operation appears in help text and tool listings while remaining fully invocable.

[Operation("sample.hello", Description = "Say hello.", Summary = "Returns a greeting.", Hidden = true)]

The generated OperationDescriptor has Hidden = true, which the CLI help generator and MCP tool catalog can use to suppress the operation from discovery listings.

Sources: src/Manifold/OperationAttribute.cs:14, tests/Manifold.Generators.Tests/GeneratedOperationRegistryTests.cs:32

OperationContext Usage

Class-based operations receive an OperationContext instance directly in the ExecuteAsync method. This provides access to the current invocation surface, service provider, and cancellation token.

public ValueTask<string> ExecuteAsync(Request request, OperationContext context)
{
    string summary = $"Forecast for {city}: mild for the next {days} day(s). Surface={context.Surface}.";
    return ValueTask.FromResult(summary);
}

The OperationContext class provides:

Member Type Description
OperationId string? The operation identifier
Surface InvocationSurface Current surface (Cli, Mcp, Protocol, Unknown)
Services IServiceProvider? Dependency injection container
CancellationToken CancellationToken Cancellation signal
GetService<T>() T? Typed service resolution
GetRequiredService<T>() T Required typed service resolution (throws on missing)

Sources: src/Manifold/OperationContext.cs:3–84, samples/Manifold.Samples.Operations/SampleOperations.cs:21–29

Project Structure and Dependencies

The Manifold.Samples.Operations project is a shared library referenced by all three host samples. It depends on Manifold (core contracts), Manifold.Cli, Manifold.Mcp, and Manifold.Generators (as a source generator).

flowchart TD
    A[Manifold.Samples.Operations] -->|ProjectReference| B[Manifold]
    A -->|ProjectReference| C[Manifold.Cli]
    A -->|ProjectReference| D[Manifold.Mcp]
    A -->|Analyzer| E[Manifold.Generators]
    F[Manifold.Samples.CliHost] -->|References| A
    G[Manifold.Samples.McpStdioHost] -->|References| A
    H[Manifold.Samples.McpHttpHost] -->|References| A
    E -->|Generates at compile time| I[GeneratedOperationRegistry<br/>GeneratedCliInvoker<br/>GeneratedMcpCatalog<br/>GeneratedMcpInvoker]
Loading

The generator reference uses OutputItemType="Analyzer" and ReferenceOutputAssembly="false", meaning it runs at compile time without becoming a runtime dependency.

Sources: samples/Manifold.Samples.Operations/Manifold.Samples.Operations.csproj:1–14

Pattern Summary

The following diagram shows the complete decision tree for defining an operation using the patterns demonstrated in the samples.

flowchart TD
    Start[Define Operation] --> Style{Choose style}
    Style -->|Simple, stateless| SM[Static Method]
    Style -->|Stateful, needs context| CB["Class : IOperation"]

    SM --> SMAttr["Add [Operation],<br/>[CliCommand], [McpTool]"]
    CB --> CBAttr["Add attributes to class"]

    SMAttr --> Params{Define parameters}
    CBAttr --> ReqClass[Define Request class<br/>with properties]

    Params --> Arg["[Argument(n)]<br/>positional"]
    Params --> Opt["[Option(name)]<br/>named"]
    Params --> Svc["[FromServices]<br/>injected"]
    Params --> CT["CancellationToken<br/>auto-detected"]

    ReqClass --> PropAttr["Annotate properties<br/>with [Option]/[Argument]"]

    Arg --> Naming
    Opt --> Naming
    PropAttr --> Naming

    Naming{Surface-specific naming?}
    Naming -->|Yes| Names["[CliName], [McpName],<br/>[Alias]"]
    Naming -->|No| Vis

    Names --> Vis{Restrict visibility?}
    Vis -->|CLI only| CliO["[CliOnly]"]
    Vis -->|MCP only| McpO["[McpOnly]"]
    Vis -->|Both| Done[Done]
    CliO --> Done
    McpO --> Done
Loading

Related Pages

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