{"record":{"id":"af2d045c4c5268aa","repo":"HKUDS/DeepTutor","slug":"exc-af2d04","errorCode":null,"errorMessage":"{exc}","messagePattern":"\\{exc\\}","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"deeptutor/api/routers/mcp_settings.py","lineNumber":73,"sourceCode":"\n\n@router.get(\"\")\nasync def get_mcp_settings() -> dict[str, Any]:\n    config = load_mcp_config()\n    manager = get_mcp_manager()\n    await manager.ensure_started()\n    return {\n        \"servers\": {name: cfg.model_dump(mode=\"json\") for name, cfg in config.servers.items()},\n        \"status\": manager.status(),\n    }\n\n\n@router.put(\"\")\nasync def update_mcp_settings(payload: MCPSettingsPayload) -> dict[str, Any]:\n    try:\n        config = MCPConfig(servers=payload.servers)\n    except (ValidationError, ValueError) as exc:\n        raise HTTPException(status_code=400, detail=str(exc))\n    _validate_servers(config)\n    save_mcp_config(config)\n    manager = get_mcp_manager()\n    await manager.reload()\n    return {\"status\": manager.status()}\n\n\n@router.put(\"/servers/{name}\")\nasync def upsert_mcp_server(name: str, cfg: MCPServerConfig) -> dict[str, Any]:\n    \"\"\"Upsert one server, leaving every other entry byte-identical.\n\n    The whole-map ``PUT`` above cannot express \"change this one\": a client has to\n    send back everything it read, so it silently drops any field it does not\n    model (a hand-written ``disabled_tools`` blocklist) and overwrites whatever\n    a second administrator saved in between.\n    \"\"\"\n    config = load_mcp_config()\n    servers = dict(config.servers)","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/HKUDS/DeepTutor/blob/3e82f130422a813cdd73c10b21a44e9325f5821a/deeptutor/api/routers/mcp_settings.py#L55-L91","documentation":"Raised by PUT '' (update_mcp_settings) when constructing MCPConfig(servers=payload.servers) raises a Pydantic ValidationError or ValueError — i.e. the submitted server configuration fails model-level validation (wrong field types, unknown/invalid enum values, malformed structure) before semantic checks run. Returns 400 with the exception's message.","triggerScenarios":"PUT the MCP settings endpoint with a payload whose servers values violate MCPServerConfig field types, e.g. args as a string instead of a list, env as a list instead of a dict, or numeric values where strings are required.","commonSituations":"Hand-writing the settings JSON from memory instead of copying the schema; a frontend form serializing types incorrectly; API docs out of date with the current Pydantic model after an upgrade changed field types.","solutions":["Read the detail message — Pydantic errors name the exact field and expected type","Fix the offending field type per the MCPServerConfig schema (args: list[str], env: dict, etc.)","Validate the payload against the documented schema (or fetch GET the current settings and mirror its shape) before PUTting","After upgrading deeptutor, re-check the settings schema for renamed/retyped fields"],"exampleFix":"// before\n{\"servers\": {\"fs\": {\"command\": \"npx\", \"args\": \"-y server-fs /tmp\"}}}\n\n// after\n{\"servers\": {\"fs\": {\"command\": \"npx\", \"args\": [\"-y\", \"server-fs\", \"/tmp\"]}}}","handlingStrategy":"validation","validationCode":"const res = await api.get('/mcp');\nconst validShape = res.servers; // mirror this structure\n// validate types before PUT\nfor (const cfg of Object.values(newServers)) {\n  if (cfg.args && !Array.isArray(cfg.args)) throw new Error('args must be an array');\n  if (cfg.env && typeof cfg.env !== 'object') throw new Error('env must be an object');\n}","typeGuard":"function isMCPServerConfig(v: unknown): v is MCPServerConfig {\n  const c = v as Record<string, unknown>;\n  return typeof c === 'object' && c !== null &&\n    (c.command === undefined || typeof c.command === 'string') &&\n    (c.args === undefined || Array.isArray(c.args)) &&\n    (c.env === undefined || typeof c.env === 'object');\n}","tryCatchPattern":"try {\n  await api.put('/mcp', { servers });\n} catch (e) {\n  if (e.status === 400) {\n    // e.detail contains the Pydantic message naming the bad field — fix and retry\n  }\n}","preventionTips":["Derive the payload from GET /mcp output rather than hand-writing it","Run client-side type checks mirroring the Pydantic schema","Re-check the schema after deeptutor upgrades"],"tags":["mcp","pydantic","validation","config","bad-request"],"backgroundTag":"pydantic-validation-error","analyzedSha":"3e82f130422a813cdd73c10b21a44e9325f5821a","analyzedAt":"2026-08-27T06:57:25.364Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}