{"record":{"id":"92d37da9104823b5","repo":"HKUDS/Vibe-Trading","slug":"stdio-mcp-servers-do-not-accept-url-headers","errorCode":null,"errorMessage":"stdio MCP servers do not accept url/headers","messagePattern":"stdio MCP servers do not accept url/headers","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent/src/config/schema.py","lineNumber":390,"sourceCode":"\n    @model_validator(mode=\"after\")\n    def validate_transport_config(self) -> \"MCPServerConfig\":\n        \"\"\"Validate transport-specific MCP server configuration.\n\n        Returns:\n            The validated MCP server config instance.\n\n        Raises:\n            ValueError: If required fields are missing for the resolved\n                transport or conflicting fields are provided.\n        \"\"\"\n        transport = self.resolved_transport()\n\n        if transport == \"stdio\":\n            if not self.command.strip():\n                raise ValueError(\"stdio MCP servers require a command\")\n            if self.url.strip() or self.headers:\n                raise ValueError(\"stdio MCP servers do not accept url/headers\")\n            if self.auth is not None:\n                raise ValueError(\"stdio MCP servers do not accept auth (OAuth is HTTP-only)\")\n            return self\n\n        if not self.url.strip():\n            raise ValueError(f\"{transport} MCP servers require a url\")\n        if self.command.strip() or self.args or self.env:\n            raise ValueError(f\"{transport} MCP servers do not accept command/args/env\")\n\n        if self.auth is not None:\n            # The OAuth provider owns the runtime Authorization header; a\n            # hand-set static header alongside it is always a config error.\n            if self.headers:\n                raise ValueError(\n                    \"MCP servers using auth must not also set static headers \"\n                    \"(the OAuth provider owns the Authorization header)\"\n                )\n            # A refresh token must never traverse cleartext.","sourceCodeStart":372,"sourceCodeEnd":408,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/config/schema.py#L372-L408","documentation":"Raised by validate_transport_config when a stdio MCP server also defines a url or headers. stdio servers communicate over a child process pipe, so HTTP-style fields are contradictory and rejected to catch config mistakes early.","triggerScenarios":"An entry with command set (implying stdio) plus a url or headers field; leftover HTTP fields after converting a server from sse to stdio.","commonSituations":"Editing an existing HTTP server entry to run locally but forgetting to delete url/headers; merging configs where both halves survive.","solutions":["Remove url and headers from the stdio server entry.","If you actually want the HTTP server, set type: sse or streamableHttp and remove command/args/env."],"exampleFix":"# before\nmcp_servers:\n  mixed:\n    command: npx\n    url: https://mcp.example.com/sse\n\n# after\nmcp_servers:\n  mixed:\n    command: npx\n    args: [\"-y\", \"some-mcp-server\"]","handlingStrategy":"validation","validationCode":"STDIO_FORBIDDEN = {'url', 'headers', 'auth'}\n\ndef stdio_entry_clean(entry: dict) -> bool:\n    if entry.get('type') == 'stdio' or entry.get('command'):\n        return not any(entry.get(k) for k in STDIO_FORBIDDEN)\n    return True","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never mix stdio and HTTP fields in one entry","Diff the whole server block, not just added lines, when switching transports"],"tags":["mcp","config","stdio","validation"],"backgroundTag":"config-validation-failed","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}