mukunda1729's picture
Initial: MCP client config validator
3573b21 verified
"""MCP config validator — paste an MCP client config, get back issues.
Validates against the common shape used by Claude Desktop, Cursor, Cline,
Windsurf, and Zed. Doesn't try to BE all of those — just a quick sanity check.
"""
import json
import gradio as gr
REQUIRED_TOP = {"mcpServers"}
SERVER_REQUIRED = {"command"}
SERVER_OPTIONAL = {"args", "env", "cwd", "transport", "url"}
def validate(config_text: str):
if not config_text.strip():
return "_Paste a config to validate._"
try:
cfg = json.loads(config_text)
except json.JSONDecodeError as e:
return f"❌ **Invalid JSON:** {e.msg} at line {e.lineno}, col {e.colno}"
issues = []
suggestions = []
if not isinstance(cfg, dict):
return "❌ Config must be a JSON object at the top level."
if "mcpServers" not in cfg:
issues.append("Missing top-level `mcpServers` key.")
return _format(issues, suggestions, cfg)
servers = cfg["mcpServers"]
if not isinstance(servers, dict):
issues.append("`mcpServers` must be an object mapping server name → server config.")
return _format(issues, suggestions, cfg)
if not servers:
issues.append("`mcpServers` is empty — no servers will be loaded.")
for name, scfg in servers.items():
if not isinstance(scfg, dict):
issues.append(f"Server `{name}` is not an object.")
continue
# Either command-based (stdio) or url-based (sse/http) — must have one
if "command" not in scfg and "url" not in scfg:
issues.append(f"Server `{name}` has neither `command` (stdio) nor `url` (sse/http).")
if "command" in scfg and "url" in scfg:
suggestions.append(f"Server `{name}` has both `command` and `url`. Most clients use one.")
if "args" in scfg and not isinstance(scfg["args"], list):
issues.append(f"Server `{name}`: `args` must be a list of strings.")
if "env" in scfg and not isinstance(scfg["env"], dict):
issues.append(f"Server `{name}`: `env` must be an object of string→string.")
unknown = set(scfg.keys()) - SERVER_REQUIRED - SERVER_OPTIONAL
if unknown:
suggestions.append(f"Server `{name}` has unknown keys: {sorted(unknown)} (might be client-specific).")
return _format(issues, suggestions, cfg)
def _format(issues, suggestions, cfg):
n_servers = len(cfg.get("mcpServers", {})) if isinstance(cfg.get("mcpServers"), dict) else 0
rows = []
if not issues:
rows.append(f"✅ **Looks good** — {n_servers} server(s) configured.")
else:
rows.append(f"❌ **{len(issues)} issue(s):**")
for i in issues:
rows.append(f"- {i}")
if suggestions:
rows.append("")
rows.append("💡 **Suggestions:**")
for s in suggestions:
rows.append(f"- {s}")
return "\n".join(rows)
EXAMPLES = [
['{"mcpServers": {"filesystem": {"command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me"]}}}'],
['{"mcpServers": {"agentfit": {"command": "npx", "args": ["-y", "@mukundakatta/agentfit-mcp"]}}}'],
['{"mcpServers": {"broken": {"args": ["only-args"]}}}'],
['{"mcpServers": {}}'],
['{}'],
['not even json'],
]
with gr.Blocks(title="MCP Config Validator", theme=gr.themes.Soft()) as demo:
gr.Markdown(
"""
# MCP Config Validator
Paste your MCP client config (Claude Desktop / Cursor / Cline / Windsurf / Zed style) and get a quick sanity check.
Doesn't replace your client's own validator — just catches the common mistakes (missing `command`, wrong types, empty servers map) before you restart the client.
Sample configs from [`mcp-config-examples`](https://huggingface.co/datasets/mukunda1729/mcp-config-examples).
"""
)
with gr.Row():
with gr.Column():
txt = gr.Code(
value=json.dumps({
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me"],
}
}
}, indent=2),
language="json",
label="MCP config",
lines=14,
)
btn = gr.Button("Validate", variant="primary")
out = gr.Markdown()
btn.click(validate, inputs=txt, outputs=out)
gr.Examples(examples=EXAMPLES, inputs=txt)
gr.Markdown(
"""
---
Part of [The Agent Reliability Stack](https://mukundakatta.github.io/agent-stack/) · MIT licensed
"""
)
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860)