Advanced
Cancellation-aware streaming
StreamingTool.define collects an IAsyncEnumerable<Content> into the MCP tool
result. In 2.0 both the handler and the enumerator receive the protocol request
token. Long-running producers should observe it when acquiring and yielding
items.
open System.Collections.Generic
open FsMcp.Core
open FsMcp.Server
let streamTool =
StreamingTool.define "count" "Count to N" (fun args cancellationToken ->
let count =
args
|> Map.tryFind "count"
|> Option.map (fun value -> value.GetInt32())
|> Option.defaultValue 5
{ new IAsyncEnumerable<Content> with
member _.GetAsyncEnumerator(requestToken) =
let linked =
System.Threading.CancellationTokenSource.CreateLinkedTokenSource(
cancellationToken,
requestToken)
let mutable current = 0
{ new IAsyncEnumerator<Content> with
member _.Current = Content.text $"Count: {current}"
member _.MoveNextAsync() =
linked.Token.ThrowIfCancellationRequested()
current <- current + 1
ValueTask<bool>(current <= count)
member _.DisposeAsync() =
linked.Dispose()
ValueTask() } })
|> unwrapResult
StreamingTool.defineTyped<'T> provides the same cancellation contract with a
strictly deserialized F# record input. JSON strings are not coerced into numeric
or Boolean record fields.
Runtime extension points
FsMcp 2.0 composes with the official SDK instead of advertising disconnected parallel middleware and notification runtimes:
- use SDK request filters for protocol request interception;
- use caller-owned dependency injection for services and request-scoped SDK primitives;
- use ASP.NET Core middleware, authentication, and authorization around
MapMcpfor Streamable HTTP; - use the SDK request-scoped sampling primitive when a server must ask the connected client to sample.
The legacy FsMcp middleware, DynamicServer, contextual notification, and
SamplingTool.define entry points are obsolete and fail closed when their old
behavior was never connected to the transport.
Resource subscriptions
Streamable HTTP subscriptions are stateful. Subscription identifiers and errors are opaque FsMcp values; create them through the public modules instead of depending on their representation. Notification fan-out is bounded, observes caller cancellation, and removes failed sessions. Disconnect cleanup removes the session's subscriptions.
Stateless HTTP deliberately omits the subscribe capability and rejects subscription requests. It does not claim a stateful feature that it cannot honor.
Sampling package boundary
FsMcp.Sampling continues to provide sampling domain types, request builders,
and explicit test helpers. It does not provide working server-to-client runtime
wiring in 2.0. SamplingTool.define rejects registration rather than silently
using a no-op sampling context.
TypeShape schema caching
Typed handler schemas and optional-field detection are cached per input type.
The first definition pays the reflection cost; later definitions reuse the
generated schema. F# option fields are omitted from required and represented
as nullable where appropriate.