{"record":{"id":"62fbaf222d433c13","repo":"unslothai/unsloth","slug":"url-must-not-be-empty","errorCode":null,"errorMessage":"url must not be empty","messagePattern":"url must not be empty","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"studio/backend/routes/mcp_servers.py","lineNumber":65,"sourceCode":"router = APIRouter()\n\n# Only a UI session may define a local command; API keys keep http(s) MCP.\n# Annotated, not a Depends default: these routes are also called directly by the\n# tests, where a Depends object is truthy and would read as \"API key\".\nViaApiKey = Annotated[bool, Depends(authenticated_via_api_key)]\n\n\ndef _looks_like_command(value: str) -> bool:\n    \"\"\"Whitespace is a one-way signal: a URL can't hold an unencoded space, so\n    a value with whitespace is definitely a command. No whitespace proves\n    nothing (a lone token may be a single-arg command or a scheme-less URL).\"\"\"\n    return any(ch.isspace() for ch in value)\n\n\ndef _validate_url(url: str) -> str:\n    trimmed = (url or \"\").strip()\n    if not trimmed:\n        raise HTTPException(status_code = 400, detail = \"url must not be empty\")\n    # When stdio is enabled, a non-HTTP value is a local command (reuses this\n    # field so stdio servers ride existing CRUD/storage).\n    if stdio_mcp_enabled() and is_stdio(trimmed):\n        try:\n            parts = parse_stdio_command(trimmed)\n        except ValueError as exc:\n            raise log_and_http_error(\n                exc,\n                400,\n                \"Invalid command. Check quoting and try again.\",\n                event = \"mcp_servers.invalid_command\",\n                log = logger,\n            )\n        if not parts or not parts[0].strip():\n            raise HTTPException(status_code = 400, detail = \"command must not be empty\")\n        if \"://\" in parts[0]:\n            # A URL-scheme first token is a mistyped URL, not a command. Reject\n            # cleanly instead of exec-ing it (mirrors the frontend check).","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/routes/mcp_servers.py#L47-L83","documentation":"400 from _validate_url when the MCP server 'url' field is empty or only whitespace after stripping. The field is overloaded: it holds either an http(s) URL or (when stdio is enabled) a local command, but it may not be blank in either mode.","triggerScenarios":"POST /api/mcp-servers/ (create) or PUT /{server_id} with url set to '', '   ', or null coerced to empty; also PUT that includes 'url' in the payload with an empty value.","commonSituations":"Frontend form submitted before the user typed an address, JSON built with a default empty string, or an update payload that accidentally includes url:'' alongside other fields.","solutions":["Provide a non-blank url: an http(s):// URL or, when stdio is enabled, a local command string.","Trim input client-side and disable submit while the field is empty.","For updates, omit the 'url' key entirely instead of sending an empty string when you don't intend to change it."],"exampleFix":"// before\nawait api.post('/api/mcp-servers/', {display_name: 'fs', url: ''});\n\n// after\nconst url = urlInput.trim();\nif (!url) throw new Error('URL is required');\nawait api.post('/api/mcp-servers/', {display_name: 'fs', url});","handlingStrategy":"validation","validationCode":"const url = (payload.url ?? '').trim();\nif (!url) throw new Error('MCP url is required');","typeGuard":"function hasNonEmptyUrl(p) { return typeof p.url === 'string' && p.url.trim().length > 0; }","tryCatchPattern":null,"preventionTips":["Trim and require a non-empty url in the form before enabling submit.","Omit 'url' from PUT bodies when not changing it."],"tags":["mcp","validation","http-400","url"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}