Skip to content

McpSource.ParseConfig

C# · package Toolnexus · SPEC §2 · McpSource.cs

public static Dictionary<string, object?> ParseConfig(object input)

Reads MCP server configuration and normalises it into a flat dictionary of server name → config. It connects to nothing — no child processes, no HTTP. That is the whole point: it is the cheap check you can run before paying for LoadAsync.

  • Validate at startup or in a test, so a typo in mcp.json fails immediately rather than halfway through connecting to five servers.
  • Inspect or modify config before loading — filter servers by environment, inject a header, disable one in CI.
  • Accept config from somewhere other than a file — a database, an env var, an API response.

It is synchronous — unlike almost everything else on McpSource, there is no Async suffix, because there is no I/O beyond an optional file read.

This is examples/mcp.json, the fixture every port is tested against.

using Toolnexus;
// TOOLNEXUS_REPO is set by the docs test runner; in your own code just use a path.
var repo = Environment.GetEnvironmentVariable("TOOLNEXUS_REPO") ?? ".";
var fixture = Path.Combine(repo, "examples", "mcp.json");
var config = McpSource.ParseConfig(fixture);
// The `mcpServers` wrapper is unwrapped — you get the servers directly.
var names = config.Keys.OrderBy(k => k).ToList();
if (names.Count != 2 || names[0] != "everything" || names[1] != "example-remote")
throw new Exception($"unexpected servers: {string.Join(",", names)}");
// Disabled servers are still returned — parsing does not filter.
Console.WriteLine($"ok: {string.Join(", ", names)}");

2. Wrapper spellings and a raw JSON string

Section titled “2. Wrapper spellings and a raw JSON string”

mcpServers, servers and mcp all mean the same thing, and a raw JSON string is accepted too.

using Toolnexus;
foreach (var raw in new[]
{
"""{"mcpServers":{"a":{"type":"local","command":["echo","hi"]}}}""",
"""{"servers":{"a":{"type":"local","command":["echo","hi"]}}}""",
"""{"mcp":{"a":{"type":"local","command":["echo","hi"]}}}""",
})
{
var config = McpSource.ParseConfig(raw);
if (config.Count != 1) throw new Exception($"expected one server, got {config.Count}");
if (!config.ContainsKey("a")) throw new Exception("expected server 'a'");
}
Console.WriteLine("ok: 3 spellings -> a");

3. Validate before loading, and fail loudly

Section titled “3. Validate before loading, and fail loudly”

The pattern this method exists for — check the config, then decide whether to connect.

using Toolnexus;
(List<string> Enabled, List<string> Problems) Validate(string raw)
{
var config = McpSource.ParseConfig(raw);
var enabled = new List<string>();
var problems = new List<string>();
foreach (var pair in config)
{
// Values come back as nested Dictionary<string, object?>, NOT JsonElement.
var cfg = (IDictionary<string, object?>)pair.Value!;
object? Get(string k) => cfg.TryGetValue(k, out var v) ? v : null;
// Disabled either way round: `enabled: false` or `disabled: true`.
if (Get("disabled") is bool db && db) continue;
if (Get("enabled") is bool en && !en) continue;
enabled.Add(pair.Key);
var type = Get("type") as string;
if (type == "remote")
{
if (Get("url") is not string u || string.IsNullOrEmpty(u))
problems.Add($"{pair.Key}: remote server without a url");
}
else
{
var command = Get("command") as System.Collections.IEnumerable;
var count = command?.Cast<object?>().Count() ?? 0;
if (count == 0) problems.Add($"{pair.Key}: local server without a command");
}
}
enabled.Sort();
problems.Sort();
return (enabled, problems);
}
var good = Validate("""
{"mcpServers":{
"ok_local":{"type":"local","command":["npx","server"]},
"ok_remote":{"type":"remote","url":"https://example.com/mcp"},
"off":{"type":"local","command":["x"],"enabled":false}}}
""");
if (good.Enabled.Count != 2 || good.Problems.Count != 0)
throw new Exception($"unexpected: {string.Join(",", good.Enabled)} / {good.Problems.Count}");
var bad = Validate("""
{"mcpServers":{
"broken_remote":{"type":"remote"},
"broken_local":{"type":"local","command":[]}}}
""");
if (bad.Problems.Count != 2) throw new Exception($"expected 2 problems, got {bad.Problems.Count}");
Console.WriteLine($"ok: {string.Join(",", good.Enabled)} | problems: {bad.Problems.Count}");
Input Behaviour
"./mcp.json" A path — read from disk and parsed.
A raw JSON string Parsed directly.
A parsed dictionary Normalised.

Wrapped under mcpServers, servers or mcp — all three are unwrapped.