ParseMcpConfig
Go · module github.com/muthuishere/toolnexus/golang · SPEC §2 · golang/mcp.go
func ParseMcpConfig(input any) (McpConfig, error)Reads MCP server configuration and normalises it into a flat map 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 LoadMcp.
When to use it
Section titled “When to use it”- Validate at startup or in a test, so a typo in
mcp.jsonfails 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.
Why this and not just calling LoadMcp
Section titled “Why this and not just calling LoadMcp”The input is any because it accepts a path string, raw JSON bytes, an already-typed
McpConfig, or a parsed map[string]any.
Examples
Section titled “Examples”1. Parse the shared fixture from disk
Section titled “1. Parse the shared fixture from disk”This is examples/mcp.json, the fixture every port is tested against.
package main
import ( "fmt" "log" "os" "path/filepath" "sort"
toolnexus "github.com/muthuishere/toolnexus/golang")
func main() { // TOOLNEXUS_REPO is set by the docs test runner; in your own code just use a path. fixture := filepath.Join(os.Getenv("TOOLNEXUS_REPO"), "examples", "mcp.json")
config, err := toolnexus.ParseMcpConfig(fixture) if err != nil { log.Fatal(err) }
// The `mcpServers` wrapper is unwrapped — you get the servers directly. var names []string for name := range config { names = append(names, name) } sort.Strings(names) if len(names) != 2 || names[0] != "everything" || names[1] != "example-remote" { log.Fatalf("unexpected servers: %v", names) }
// Disabled servers are still returned — parsing does not filter. fmt.Println("ok:", names)}2. Wrapper spellings and raw bytes
Section titled “2. Wrapper spellings and raw bytes”mcpServers, servers and mcp all mean the same thing, and raw JSON bytes are accepted too.
package main
import ( "fmt" "log"
toolnexus "github.com/muthuishere/toolnexus/golang")
func main() { for _, raw := range []string{ `{"mcpServers":{"a":{"type":"local","command":["echo","hi"]}}}`, `{"servers":{"a":{"type":"local","command":["echo","hi"]}}}`, `{"mcp":{"a":{"type":"local","command":["echo","hi"]}}}`, } { config, err := toolnexus.ParseMcpConfig([]byte(raw)) if err != nil { log.Fatal(err) } if len(config) != 1 { log.Fatalf("expected one server, got %d", len(config)) } if _, ok := config["a"]; !ok { log.Fatalf("expected server 'a' in %v", config) } }
fmt.Println("ok: 3 spellings -> a")}3. Validate before loading, and surface the error
Section titled “3. Validate before loading, and surface the error”The pattern this function exists for — check the config, then decide whether to connect.
package main
import ( "fmt" "log" "sort"
toolnexus "github.com/muthuishere/toolnexus/golang")
func validate(raw string) (enabled []string, problems []string, err error) { config, err := toolnexus.ParseMcpConfig([]byte(raw)) if err != nil { return nil, nil, err }
for name, cfg := range config { // Disabled either way round. BOTH flags are *bool, so nil means "not set" // — dereference only after a nil check. if (cfg.Disabled != nil && *cfg.Disabled) || (cfg.Enabled != nil && !*cfg.Enabled) { continue } enabled = append(enabled, name)
if cfg.Type == "remote" { if cfg.URL == "" { problems = append(problems, name+": remote server without a url") } } else if len(cfg.Command) == 0 { problems = append(problems, name+": local server without a command") } } sort.Strings(enabled) sort.Strings(problems) return enabled, problems, nil}
func main() { enabled, problems, err := 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 err != nil { log.Fatal(err) } if len(enabled) != 2 || len(problems) != 0 { log.Fatalf("unexpected: %v %v", enabled, problems) }
_, badProblems, err := validate(`{"mcpServers":{ "broken_remote":{"type":"remote"}, "broken_local":{"type":"local","command":[]}}}`) if err != nil { log.Fatal(err) } if len(badProblems) != 2 { log.Fatalf("expected 2 problems, got %v", badProblems) }
// Malformed JSON is an error, not a panic and not a partial config. if _, err := toolnexus.ParseMcpConfig([]byte(`{not json`)); err == nil { log.Fatal("expected a parse error") }
fmt.Println("ok:", enabled, "| problems:", len(badProblems))}Accepted input
Section titled “Accepted input”| Input | Behaviour |
|---|---|
"./mcp.json" (string) |
Read from disk and unmarshalled. Error if missing or malformed. |
[]byte(...) |
Raw JSON, unmarshalled directly. |
McpConfig |
Returned as-is. |
map[string]any |
Normalised. |
Wrapped under mcpServers, servers or mcp — all three are unwrapped.
See also
Section titled “See also”LoadMcp— parse and connectListMcpTools— what each server would exposeCreateToolkit— takesMcpConfigin exactly these forms