HKUDS/DeepTutor · error · UserMcpError

mcp.stdio_not_allowed

mcp.stdio_not_allowed

Error message

A server you configure yourself must be a remote URL: a stdio server runs a command on the host and stays administrator-only.

What it means

Self-service (user-configured) MCP servers must be remote HTTP servers. A stdio server executes an arbitrary command on the host machine, which is an administrator-only security risk, so the loader refuses any user entry whose resolved transport is 'stdio' or that carries a command field.

Source

Thrown at deeptutor/services/mcp/user_config.py:179

        )


def _assert_self_service_allowed(
    name: str,
    cfg: MCPServerConfig,
    *,
    validate_url: bool = False,
) -> None:
    if not _SERVER_NAME_RE.match(name):
        raise UserMcpError("mcp.invalid_name", f"Invalid server name {name!r}")
    if name.startswith(_RESERVED_NAME_PREFIXES):
        raise UserMcpError(
            "mcp.name_reserved",
            f"{name!r} starts with a reserved tool-name prefix",
        )
    transport = cfg.resolved_type()
    if transport == "stdio" or cfg.command:
        raise UserMcpError(
            "mcp.stdio_not_allowed",
            "A server you configure yourself must be a remote URL: a stdio "
            "server runs a command on the host and stays administrator-only.",
        )
    if transport not in ("sse", "streamableHttp"):
        raise UserMcpError("mcp.no_transport", "Provide an http(s) URL for the server")
    if validate_url:
        ok, error = validate_mcp_url(cfg.url, strict=True)
        if not ok:
            raise UserMcpError("mcp.blocked_url", error)


def _read_raw(owner_id: str) -> MCPConfig:
    """The file as stored, *without* the connectability filtering.

    Writes must preserve entries this deployment refuses to connect (a stdio
    entry left over from a hand edit), or saving one server would silently
    delete another.

View on GitHub (pinned to 3e82f13042)

Solutions

  1. Switch the server to a remote URL transport (sse or streamableHttp) and remove the command/args/env fields
  2. If you genuinely need a stdio server, ask the administrator to add it to the admin-level MCP config instead of the user config
  3. Check that your config dataclass isn't leaving a stale 'command' attribute set (truthy) even when you intended a URL server

Example fix

# before
cfg = McpServerConfig(command="npx", args=["-y", "some-mcp-server"])
await save_user_server(user_id, "my-server", cfg)
# after
cfg = McpServerConfig(url="https://mcp.example.com/sse")
await save_user_server(user_id, "my-server", cfg)
Defensive patterns

Strategy: validation

Validate before calling

def is_user_configurable(cfg) -> bool:
    return cfg.resolved_type() not in ("stdio",) and not cfg.command and cfg.resolved_type() in ("sse", "streamableHttp")

Try / catch

try:
    await save_user_server(user_id, name, cfg)
except UserMcpError as e:
    if e.code == "mcp.stdio_not_allowed":
        # fall back to prompting the user for a hosted URL
        cfg = prompt_for_remote_url()
    else:
        raise

Prevention

When it happens

Trigger: Passing a config to save_user_server or having an entry in the user MCP config whose resolved_type() returns 'stdio', or any config object with a non-empty cfg.command, regardless of the declared type.

Common situations: Copying an administrator-style stdio server entry (command/args/env) from docs or a shared mcp.json into the per-user config; using a config dataclass that defaults command to something non-empty.

Related errors


AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27). Data as JSON: /api/errors/1cf66140f316774f. Report an issue: GitHub.