odysseus-dev/odysseus · error · HTTPException
command is required for stdio transport
Error message
command is required for stdio transport
What it means
Admin-only POST that registers an MCP server rejects a stdio transport config with no command. A stdio server is spawned by executing a local binary, so command is the minimum required field; without it FastAPI returns 400 before any connection attempt.
Source
Thrown at routes/mcp/mcp_routes.py:178
request: Request,
name: str = Form(...),
transport: str = Form("stdio"),
command: str = Form(None),
args: str = Form("[]"),
env: str = Form("{}"),
url: str = Form(None),
oauth_file: str = Form(None),
oauth_config: str = Form(None),
):
"""Add a new MCP server config and attempt connection. Admin-only:
registering a stdio server is equivalent to executing arbitrary
binaries on the host."""
require_admin(request)
server_id = str(uuid.uuid4())[:8]
# Validate
if transport == "stdio" and not command:
raise HTTPException(400, "command is required for stdio transport")
if transport == "sse" and not url:
raise HTTPException(400, "url is required for SSE transport")
if transport == "http" and not url:
raise HTTPException(400, "url is required for HTTP transport")
# Parse JSON fields
try:
parsed_args = json.loads(args) if args else []
except json.JSONDecodeError:
parsed_args = []
try:
parsed_env = json.loads(env) if env else {}
except json.JSONDecodeError:
parsed_env = {}
if not isinstance(parsed_env, dict):
parsed_env = {}
# Parse OAuth configView on GitHub (pinned to f9235ebbf1)
Solutions
- Send command (e.g. command="npx") as a multipart/urlencoded form field together with transport="stdio".
- Confirm you are using the correct content type — the endpoint reads Form(...) fields, so a JSON body will leave command empty.
- If you actually meant a remote server, use transport="sse" or "http" with a url instead.
Example fix
# before (JSON body → Form endpoint, command lost)
requests.post(url, json={"transport": "stdio", "command": "npx"})
# after
requests.post(url, data={"transport": "stdio", "command": "npx", "args": '["-y","some-mcp"]'}) Defensive patterns
Strategy: validation
Validate before calling
def stdio_config_valid(data: dict) -> bool:
return data.get("transport") != "stdio" or bool(data.get("command")) Try / catch
Catch the 400 and surface 'command required' to the user instead of retrying; the server state is unchanged.
Prevention
- Send form data (not JSON) to Form()-based endpoints.
- Build a small client wrapper that asserts transport→required-field pairs before posting.
When it happens
Trigger: POST to the MCP servers endpoint with transport="stdio" but command omitted or empty (Form default None), e.g. when the client sends JSON fields but the endpoint expects form data so command never arrives.
Common situations: Content-type mismatch: sending JSON body to a Form()-based endpoint so command is silently None; UI dialog where the command input was left blank; migrating a config from SSE/HTTP to stdio and forgetting the command field.
Related errors
- Email account not found for selector {account!r}. Available
- url is required for SSE transport
- url is required for HTTP transport
- disabled must be a list of tool names
- Server has no OAuth config
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/83104c5c6ef0eb4c.
Report an issue: GitHub.