Skip to content

McpSource.parseConfig

Java · package io.github.muthuishere:toolnexus · SPEC §2 · McpSource.java

public static Map<String, Object> parseConfig(Object input)

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

Three input shapes are accepted: a path to an mcp.json, a raw JSON String, and an already-parsed Map<String, Object>. A String that is not an existing regular file is treated as raw JSON, so the same parameter covers both.

  • 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 — then hand the resulting Map straight back to load.
  • Accept config from somewhere other than a file — a database row, an env var, an HTTP response — without writing it to disk first.

The returned map is not a defensive copy. When a wrapper key is present you get the caller’s own nested map back, exactly as the JS and Go references do; the bare-map form returns a shallow copy so your input is left untouched. toolnexus itself only ever reads this map — if you intend to mutate the result, copy it first.

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

import io.github.muthuishere.toolnexus.*;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
public class Example {
public static void main(String[] args) throws Exception {
// TOOLNEXUS_REPO is set by the docs test runner; in your own code just use a path.
String repo = System.getenv().getOrDefault("TOOLNEXUS_REPO", ".");
String fixture = Path.of(repo, "examples", "mcp.json").toString();
Map<String, Object> config = McpSource.parseConfig(fixture);
// The `mcpServers` wrapper is unwrapped — you get the servers directly.
List<String> names = config.keySet().stream().sorted().toList();
if (!names.equals(List.of("everything", "example-remote"))) throw new AssertionError(names);
// Parsing does NOT filter: the disabled remote server is still here.
@SuppressWarnings("unchecked")
Map<String, Object> remote = (Map<String, Object>) config.get("example-remote");
if (!Boolean.FALSE.equals(remote.get("enabled"))) throw new AssertionError(remote);
System.out.println("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.

import io.github.muthuishere.toolnexus.*;
import java.util.List;
import java.util.Map;
public class Example {
public static void main(String[] args) throws Exception {
for (String raw : List.of(
"{\"mcpServers\":{\"a\":{\"type\":\"local\",\"command\":[\"echo\",\"hi\"]}}}",
"{\"servers\":{\"a\":{\"type\":\"local\",\"command\":[\"echo\",\"hi\"]}}}",
"{\"mcp\":{\"a\":{\"type\":\"local\",\"command\":[\"echo\",\"hi\"]}}}")) {
Map<String, Object> config = McpSource.parseConfig(raw);
if (config.size() != 1 || !config.containsKey("a")) throw new AssertionError(config);
}
// A bare map of servers works too — and the sibling toolnexus keys
// (builtins/agents/a2a/mcpServer) are stripped, never read as servers.
Map<String, Object> bare = McpSource.parseConfig(Map.of(
"a", Map.of("type", "local", "command", List.of("echo", "hi")),
"builtins", Map.of("bash", true)));
if (!bare.keySet().equals(java.util.Set.of("a"))) throw new AssertionError(bare.keySet());
System.out.println("ok: 3 spellings + bare map -> 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.

import io.github.muthuishere.toolnexus.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class Example {
@SuppressWarnings("unchecked")
static List<String> problems(String raw) {
Map<String, Object> config = McpSource.parseConfig(raw);
List<String> problems = new ArrayList<>();
for (Map.Entry<String, Object> e : config.entrySet()) {
Map<String, Object> cfg = (Map<String, Object>) e.getValue();
// Disabled either way round: `enabled: false` or `disabled: true`.
if (Boolean.TRUE.equals(cfg.get("disabled"))) continue;
if (Boolean.FALSE.equals(cfg.get("enabled"))) continue;
if ("remote".equals(cfg.get("type"))) {
Object url = cfg.get("url");
if (!(url instanceof String s) || s.isEmpty())
problems.add(e.getKey() + ": remote server without a url");
} else {
Object command = cfg.get("command");
if (!(command instanceof List<?> l) || l.isEmpty())
problems.add(e.getKey() + ": local server without a command");
}
}
problems.sort(String::compareTo);
return problems;
}
public static void main(String[] args) throws Exception {
List<String> good = problems("""
{"mcpServers":{
"ok_local":{"type":"local","command":["npx","server"]},
"ok_remote":{"type":"remote","url":"https://example.com/mcp"},
"off":{"type":"local","command":[],"enabled":false}}}
""");
if (!good.isEmpty()) throw new AssertionError(good);
List<String> bad = problems("""
{"mcpServers":{
"broken_remote":{"type":"remote"},
"broken_local":{"type":"local","command":[]}}}
""");
if (bad.size() != 2) throw new AssertionError(bad);
// Malformed JSON is a hard failure, not a silently empty config.
boolean threw = false;
try {
McpSource.parseConfig("{\"mcpServers\": {");
} catch (RuntimeException e) {
threw = e.getMessage().startsWith("Invalid JSON");
}
if (!threw) throw new AssertionError("expected an Invalid JSON failure");
System.out.println("ok: problems=" + bad.size() + ", malformed rejected");
}
}
Input Behaviour
"./mcp.json" A path — read from disk and parsed.
A raw JSON String Parsed directly (any String that is not an existing file).
A parsed Map<String, Object> Normalised only.

Wrapped under mcpServers, servers or mcp — all three are unwrapped, in that order of precedence. A bare map of servers is accepted, with builtins / agents / a2a / mcpServer stripped.