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)Parses input (a path, a raw JSON string, or a parsed config dictionary — same acceptance rules as
ParseConfig), then connects to every enabled server
concurrently: stdio for local, streamable-HTTP for remote. Each listed server tool becomes a
uniform ITool with Source == "mcp". The result is an IAsyncDisposable
McpSource carrying .Tools and .Status (server → "connected" | "disabled" | "failed").
A bad server never sinks the whole call — its failure is isolated to Status[name] == "failed" and
every other server still loads. This page covers input and per-server status; the waitFor and
cancellationToken parameters are covered on
the ctx-aware page.
When to use it
Section titled “When to use it”At startup, once you have a real (or user-supplied) mcp.json and want live, callable tools —
not just the config. This is the “connect for real” half of the pair;
ListMcpToolsAsync is the “look, don’t touch” half.
Why this and not the alternative
Section titled “Why this and not the alternative”Unlike ParseConfig, this method does real I/O — it spawns child
processes and opens HTTP connections — so it is async and returns a resource you must dispose
(await using).
Examples
Section titled “Examples”1. A disabled server never connects
Section titled “1. A disabled server never connects”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, }, },};
await using var mcp = await McpSource.LoadAsync(config);
if (mcp.Status["docs_demo"] != "disabled") throw new Exception($"status: {mcp.Status["docs_demo"]}");if (mcp.Tools.Count != 0) throw new Exception("expected no tools from a disabled server");
Console.WriteLine($"ok: {mcp.Status["docs_demo"]}");2. A server that can’t spawn is isolated, not fatal
Section titled “2. A server that can’t spawn is isolated, not fatal”using Toolnexus;
var config = new Dictionary<string, object?>{ ["mcpServers"] = new Dictionary<string, object?> { ["broken"] = new Dictionary<string, object?> { ["type"] = "local", // No such binary — spawning fails immediately. No network involved. ["command"] = new[] { "toolnexus-docs-nonexistent-binary-xyz" }, ["timeout"] = 2_000, }, },};
await using var mcp = await McpSource.LoadAsync(config);
// LoadAsync itself never throws for a bad server — the failure lands in Status.if (mcp.Status["broken"] != "failed") throw new Exception($"status: {mcp.Status["broken"]}");if (mcp.Tools.Count != 0) throw new Exception("expected no tools from a failed server");
Console.WriteLine($"ok: {mcp.Status["broken"]}");3. The realistic flow: parse from disk, then gate, then load
Section titled “3. The realistic flow: parse from disk, then gate, then load”using Toolnexus;
var repo = Environment.GetEnvironmentVariable("TOOLNEXUS_REPO") ?? ".";var fixture = Path.Combine(repo, "examples", "mcp.json");
// Parse first (cheap, no I/O beyond the read) — same pattern as ParseConfig's "validate before// loading" example. This doc forces every server off so it never dials out in CI, but the shape// (parse -> gate -> LoadAsync) is exactly what a real startup path runs.var config = McpSource.ParseConfig(fixture);foreach (var (_, value) in config) ((IDictionary<string, object?>)value!)["enabled"] = false;
await using var mcp = await McpSource.LoadAsync(config);
var statuses = mcp.Status.OrderBy(kv => kv.Key).Select(kv => $"{kv.Key}={kv.Value}").ToList();if (mcp.Status.Values.Any(s => s != "disabled")) throw new Exception($"unexpected: {string.Join(",", statuses)}");
Console.WriteLine($"ok: {string.Join(", ", statuses)}");Parameters
Section titled “Parameters”| Parameter | Type | What it is |
|---|---|---|
input |
object |
Path, raw JSON string, or parsed config — same as ParseConfig. |
waitFor |
Func<Request, Task<Answer>>? |
§10 resolver for MCP elicitation. null ⇒ elicitation not advertised. |
cancellationToken |
CancellationToken |
Covered on the ctx-aware page. |
See also
Section titled “See also”McpSource.LoadAsync— The ctx-aware load: bound connection time and cancel a slow or hung server without leaking a child process.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.