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
- Switch the server to a remote URL transport (sse or streamableHttp) and remove the command/args/env fields
- If you genuinely need a stdio server, ask the administrator to add it to the admin-level MCP config instead of the user config
- 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
- Never offer a command/args form in user-facing MCP UIs
- Strip command, args, and env fields before persisting user configs
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
- mcp.no_transport
- mcp.blocked_url
- Invalid math animator config: {details}
- Invalid folder path
- Access denied
AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27).
Data as JSON: /api/errors/1cf66140f316774f.
Report an issue: GitHub.