odysseus-dev/odysseus · error · HTTPException

url is required for SSE transport

Error message

url is required for SSE transport

What it means

The admin MCP server registration endpoint rejects transport="sse" configurations that omit url. SSE servers connect to a remote HTTP endpoint that streams events, so the URL is the connection target and is mandatory before any connect attempt.

Source

Thrown at routes/mcp/mcp_routes.py:180

        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 config
        parsed_oauth_config = None
        if oauth_config:

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Include url="https://host.example/sse" as a form field when transport="sse".
  2. Verify the field is literally named 'url' and sent as form data, not JSON.
  3. If the server is local-stdio, use transport="stdio" with a command instead.

Example fix

# before
data = {"transport": "sse"}

# after
data = {"transport": "sse", "url": "https://mcp.example.com/sse"}
Defensive patterns

Strategy: validation

Validate before calling

def sse_config_valid(data: dict) -> bool:
    return data.get("transport") != "sse" or bool(data.get("url"))

Try / catch

Treat as a client bug: catch the 400, log the payload, fix the url field — no retry.

Prevention

When it happens

Trigger: POST with transport="sse" and url empty/omitted; typo'd form field name (e.g. "endpoint" instead of "url"); JSON body sent to the Form()-based endpoint so url arrives as None.

Common situations: Switching a server entry from stdio to SSE and leaving the command field but no url; form-binding bug that drops the url input; copy-pasting a config where the field is named uri/endpoint.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/9e5e1e5fe9a0aab0. Report an issue: GitHub.