McpSource.LoadAsync
C# · package Toolnexus · SPEC §2 · McpSource.cs
public static Task<McpSource> LoadAsync(object input, Func<Request, Task<Answer>>? waitFor = null, CancellationToken cancellationToken = default)Same method as McpSource.LoadAsync — this page scopes to the
cancellationToken parameter and its relationship to each server’s own timeout config value
(§2 Gap 3).
Two bounds are in play at once:
- Each server’s own
timeout(config, milliseconds) bounds that server’s connect + list. A server that blows its own timeout is markedStatus[name] == "failed"— the overall load continues. - The caller’s
cancellationTokenis linked into every server’s wait. Cancel it (or let a deadline built from it expire) and the whole load aborts: any clients that did connect are disposed, andLoadAsyncthrowsOperationCanceledException— not a per-server failure.
When to use it
Section titled “When to use it”Pass a real CancellationToken whenever LoadAsync runs inside a request, a job with its own
deadline, or anything that can be told “stop now” — a shutdown signal, a client disconnect, a
user-cancelled operation. Without it, a hung server is still bounded by its own timeout, but you
have no way to cut the whole load short from outside.
Why this and not the alternative
Section titled “Why this and not the alternative”Examples
Section titled “Examples”1. Passing the token explicitly
Section titled “1. Passing the token explicitly”using Toolnexus;
var config = new Dictionary<string, object?>{ ["mcpServers"] = new Dictionary<string, object?> { ["docs_demo"] = new Dictionary<string, object?> { ["type"] = "local", ["command"] = new[] { "echo", "hi" }, ["enabled"] = false, }, },};
// CancellationToken.None means "no external deadline" — the same as omitting the parameter.await using var mcp = await McpSource.LoadAsync(config, waitFor: null, cancellationToken: CancellationToken.None);
if (mcp.Status["docs_demo"] != "disabled") throw new Exception(mcp.Status["docs_demo"]);Console.WriteLine($"ok: {mcp.Status["docs_demo"]}");2. A per-server timeout bounds only that server
Section titled “2. A per-server timeout bounds only that server”using Toolnexus;
var config = new Dictionary<string, object?>{ ["mcpServers"] = new Dictionary<string, object?> { ["hung"] = new Dictionary<string, object?> { ["type"] = "local", // Spawns fine but never speaks MCP on stdout — the handshake just hangs. ["command"] = new[] { "sleep", "30" }, ["timeout"] = 300, // ms — bounds THIS server only, no network involved. }, },};
var sw = System.Diagnostics.Stopwatch.StartNew();await using var mcp = await McpSource.LoadAsync(config);sw.Stop();
if (mcp.Status["hung"] != "failed") throw new Exception($"status: {mcp.Status["hung"]}");// Bounded by the server's own 300ms timeout, not left hanging for the caller's lifetime.if (sw.ElapsedMilliseconds > 15_000) throw new Exception($"took too long: {sw.ElapsedMilliseconds}ms");
Console.WriteLine($"ok: {mcp.Status["hung"]} in {sw.ElapsedMilliseconds}ms");3. Parent cancellation aborts the whole load
Section titled “3. Parent cancellation aborts the whole load”using Toolnexus;
var config = new Dictionary<string, object?>{ ["mcpServers"] = new Dictionary<string, object?> { ["docs_demo"] = new Dictionary<string, object?> { ["type"] = "local", ["command"] = new[] { "sleep", "30" }, ["timeout"] = 30_000, // longer than the parent deadline below — the parent wins }, },};
using var cts = new CancellationTokenSource();cts.Cancel(); // already cancelled — simulates a caller-imposed deadline that already expired
var threw = false;try{ await McpSource.LoadAsync(config, cancellationToken: cts.Token);}catch (OperationCanceledException){ // Parent cancellation aborts the WHOLE build, unlike a per-server timeout which only // marks that one server "failed" and lets the rest continue. threw = true;}
if (!threw) throw new Exception("expected OperationCanceledException");Console.WriteLine("ok: parent cancellation aborted the whole load");Parameters
Section titled “Parameters”| Parameter | Type | What it is |
|---|---|---|
cancellationToken |
CancellationToken |
Linked into every server’s connect+list wait. Cancel/expire ⇒ the whole load throws OperationCanceledException. |
server timeout (config) |
milliseconds | Bounds only that server; a timeout marks it "failed" and the load continues. |
See also
Section titled “See also”McpSource.LoadAsync— Read an mcp.json, connect every local stdio and remote streamable-HTTP server, expose each server tool as a Tool.McpSource.ListMcpToolsAsync— List what each configured server would expose, plus per-server status, without wiring it into a toolkit.McpSource.ParseConfig— Parse and validate config without connecting — the fast fail for a malformed or misspelled server block.McpSource.ElicitationToRequest— Map an MCP server’s elicitation request onto the §10 suspension contract, and map the answer back.