"""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)