Skip to content

C# SDK differences from spec #15

Description

@BobDickinson

In implementing the TypeScript SDK and using the C# SDK as a model, I found a few potential issues with the C# SDK.

mode value

SEP specifies:

  /**
   * Execution mode (default: "enforce")
   * - "enforce": Normal blocking / transforming behavior
   * - "audit": Non-blocking operation
   *   - For validators: logs violations without blocking execution
   *   - For mutators: computes transformations without applying them (shadow mutations)
   */
  mode?: "enforce" | "audit";

Whereas the C# SDK uses:

[JsonConverter(typeof(JsonStringEnumConverter<InterceptorMode>))]
public enum InterceptorMode
{
    /// <summary>
    /// Normal blocking / transforming behavior. Validators block on error severity;
    /// mutators apply their payload changes.
    /// </summary>
    [JsonStringEnumMemberName("active")]
    Active,

    /// <summary>
    /// Non-blocking operation. Validators log violations without blocking execution;
    /// mutators compute transformations without applying them (shadow mutations).
    /// </summary>
    [JsonStringEnumMemberName("audit")]
    Audit,
}

Our application already had an active/inactive setting which is a client setting indicating that the interceptor should not be run (or advertised) at all. I strongly prefer the enforce language from the SEP as it more accurately describes the difference from the audit mode (I would argue that an audit mode interceptor is still "active").

The TypeScript SDK uses enforce (per the SEP) and is therefore currently incompatible with the C# SDK.

priorityHint not fully supported

The C# SDK does not support the object form of priorityHint per the spec:

  /**
   * Priority hint for mutation interceptor ordering (lower executes first).
   * 
   * Can be specified as:
   * - A single number: applies to both request and response phases
   * - An object: specify different priorities per phase
   * 
   * Range: 32-bit signed integer (-2,147,483,648 to 2,147,483,647)
   * Default: 0 if omitted
   * 
   * Tie-breaker: Interceptors with equal priorityHint are ordered alphabetically by name.
   * For validation interceptors, this field is ignored.
   * 
   * Examples:
   *   priorityHint: -1000                           // Same priority for both phases
   *   priorityHint: { request: -1000 }              // Request: -1000, Response: 0 (default)
   *   priorityHint: { response: 1000 }              // Request: 0 (default), Response: 1000
   *   priorityHint: { request: -1000, response: 1000 }  // Different per phase
   */
  priorityHint?: number | {
    request?: number;
    response?: number;
  };

This makes it incompatible with interceptors that use that form (interceptors using the TypeScript SDK can use this form).

Chain orchestrator only supports one MCP server

The SEP is clear that the orchestration pattern should support combining interceptors from multiple MCP servers:

#### Chain Execution

Chain execution is a **convenience utility**, provided by SDKs to enforce the execution model defined above. Its purpose is to execute interceptors — potentially hosted across N MCP servers — in a way that enforces the trust-boundary-aware ordering, type-specific execution semantics, and failure behaviors.

> Important: The execution model defined above MUST be followed by all implementations, whether invoking interceptors individually via `interceptor/invoke` or using this convenience utility.

The orchestration pattern is as follows:

1. **Discover:** Call `interceptors/list` on one or more MCP servers to collect all registered interceptors.
2. **Merge & Sort:** Combine all discovered interceptors into a single chain, sorted by `priorityHint` (ascending, with alphabetical tie-breaking by interceptor name).
3. **Order by Trust Boundary:** Apply the trust-boundary-aware execution model — mutations before validations when sending, validations before mutations when receiving.
4. **Execute:** Call `interceptor/invoke` on the appropriate MCP server for each interceptor. Mutations MUST be invoked sequentially (each receiving the output of the previous). Validations MAY be invoked in parallel.
5. **Aggregate:** Collect all results, assembling the final payload and validation summary.

SDK libraries are expected to provide reference implementations of this orchestration logic so that individual applications do not need to implement it from scratch.

The C# SDK only supports a single host at present.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions