unslothai/unsloth · warning · HTTPException

Local commands aren't enabled on this server. To allow them,

Error message

Local commands aren't enabled on this server. To allow them, set UNSLOTH_STUDIO_ALLOW_STDIO_MCP=1 and restart Unsloth, or use an http:// or https:// URL instead.

What it means

400 raised when the value is not an http(s) URL (scheme check failed) and it contains whitespace — the one-way signal that it's a local command — but stdio MCP is disabled on this server. stdio_mcp_enabled() gates local commands behind UNSLOTH_STUDIO_ALLOW_STDIO_MCP (auto-on for loopback binds, off for 0.0.0.0/Colab), because a stdio MCP server executes a process as the backend user, bypassing the sandbox.

Source

Thrown at studio/backend/routes/mcp_servers.py:103

                status_code = 400,
                detail = "Enter an http(s):// URL, or a local command whose "
                "first token is an executable (not a URL).",
            )
        return trimmed
    parsed = urlparse(trimmed)
    if parsed.scheme not in ("http", "https"):
        if _looks_like_command(trimmed):
            detail = (
                "Local commands aren't enabled on this server. To allow them, "
                "set UNSLOTH_STUDIO_ALLOW_STDIO_MCP=1 and restart Unsloth, or use "
                "an http:// or https:// URL instead."
            )
        else:
            detail = (
                "MCP server address must start with http:// or https:// "
                "(for example https://example.com/mcp)."
            )
        raise HTTPException(status_code = 400, detail = detail)
    if not parsed.netloc:
        raise HTTPException(status_code = 400, detail = "url is missing a host")
    return trimmed


def _normalize_headers(headers: dict[str, str] | None) -> dict[str, str] | None:
    """Trim header names, drop empties, coerce values to str; None if empty."""
    if not headers:
        return None
    out: dict[str, str] = {}
    for raw_key, value in headers.items():
        key = str(raw_key).strip()
        if key:
            out[key] = str(value)
    return out or None


def _row_to_response(row: dict) -> McpServerResponse:

View on GitHub (pinned to 203007d190)

Solutions

  1. Use an http(s):// MCP server URL instead — commands are intentionally blocked on network-exposed hosts.
  2. If this is your own machine and you accept local code execution, set UNSLOTH_STUDIO_ALLOW_STDIO_MCP=1 in the backend environment and restart Unsloth.
  3. Check the bind/host policy: on loopback the flag defaults to 1, so a 403/400 here usually means a network bind or an explicit --disable-tools.

Example fix

# before (backend bound to 0.0.0.0)
client.post('/api/mcp-servers/', json={'url': 'npx -y @modelcontextprotocol/server-memory'})
# 400: local commands aren't enabled

# after
client.post('/api/mcp-servers/', json={'url': 'https://mcp.example.com/sse'})
# or: start backend with UNSLOTH_STUDIO_ALLOW_STDIO_MCP=1 on your own machine
Defensive patterns

Strategy: validation

Validate before calling

const isUrl = /^https?:\/\//i.test(url.trim());
const looksLikeCommand = /\s/.test(url.trim());
if (!isUrl && looksLikeCommand && !stdioEnabled) throw new Error('Local commands disabled — use an http(s) URL');

Type guard

function acceptableMcpUrl(url, stdioEnabled) { const v = url.trim(); return /^https?:\/\//i.test(v) || (stdioEnabled && !v.split(/\s+/)[0].includes('://')); }

Prevention

When it happens

Trigger: Creating/updating an MCP server with a command like 'npx -y @modelcontextprotocol/server-filesystem /tmp' while the backend is bound to 0.0.0.0, running in Colab, or with UNSLOTH_STUDIO_ALLOW_STDIO_MCP=0/unset.

Common situations: Deploying Unsloth Studio on a LAN/remote host (where the loopback auto-default doesn't apply) and re-using an MCP command config that worked locally; or hard-disabling tools with --disable-tools.

Related errors


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/a5d376ffb16e23fc. Report an issue: GitHub.