different-ai/openwork · error · PluginArchRouteFailure

invalid_mcp_url

invalid_mcp_url

Error message

MCP server URL is invalid.

What it means

First gate in assertRemotePluginMcpUrl(): the candidate URL string cannot be parsed by the WHATWG URL constructor. Thrown as PluginArchRouteFailure(400, 'invalid_mcp_url') so callers can distinguish malformed input from policy violations.

Source

Thrown at ee/apps/den-api/src/routes/org/plugin-system/store.ts:4913

  return { config: entry.config, name: entry.name, url }
}

function configVersionOwnsImportedExternalMcpConnection(version: ConfigObjectVersionRow, connectionId: string) {
  const spec = parseConfigObjectVersionSpec(version)
  const metadata = isRecord(spec.metadata) ? spec.metadata : null
  const recordedConnectionId = readRecordString(spec, "externalMcpConnectionId")
    || (metadata ? readRecordString(metadata, "externalMcpConnectionId") : null)
  const owned = spec.externalMcpConnectionOwnedByPlugin === true
    || metadata?.externalMcpConnectionOwnedByPlugin === true
  return owned && recordedConnectionId === connectionId
}

async function assertRemotePluginMcpUrl(url: string) {
  let parsed: URL
  try {
    parsed = new URL(url)
  } catch {
    throw new PluginArchRouteFailure(400, "invalid_mcp_url", "MCP server URL is invalid.")
  }

  if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
    throw new PluginArchRouteFailure(400, "invalid_mcp_url", "MCP URLs must use HTTP or HTTPS.")
  }
  if (parsed.protocol === "http:" && !env.allowPrivateMcpUrls) {
    throw new PluginArchRouteFailure(400, "invalid_mcp_url", "Hosted MCP connections must use HTTPS.")
  }
  if (parsed.hash) {
    throw new PluginArchRouteFailure(400, "invalid_mcp_url", "MCP URLs must not contain a fragment.")
  }
  if (parsed.username || parsed.password) {
    throw new PluginArchRouteFailure(400, "invalid_mcp_url", "MCP URLs must not contain embedded credentials.")
  }

  const sensitiveParameters = new Set(["access_token", "api_key", "client_secret", "token", "refresh_token", "id_token", "code_verifier"])
  for (const parameter of parsed.searchParams.keys()) {
    if (sensitiveParameters.has(parameter.toLowerCase())) {

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Prepend the scheme: change 'mcp.example.com/sse' to 'https://mcp.example.com/sse'.
  2. Trim whitespace and remove non-ASCII/typographic characters from the stored URL.
  3. Sanity-check in a browser or with `new URL(url)` locally — if it throws, the server will reject it too.
  4. Ensure template variables were substituted before the value reached the config.

Example fix

// before
url: "mcp.example.com:443/sse"
// after
url: "https://mcp.example.com/sse"
Defensive patterns

Strategy: validation

Validate before calling

function isParseableUrl(value: string): boolean {
  try { new URL(value.trim()); return true } catch { return false }
}
if (!isParseableUrl(url)) throw new Error(`Not an absolute URL: ${url}`)

Type guard

function isAbsoluteUrl(value: unknown): value is string {
  if (typeof value !== "string") return false
  try { new URL(value); return true } catch { return false }
}

Try / catch

try {
  await configureRemoteMcp(url)
} catch (e) {
  if (isPluginArchRouteFailure(e) && e.code === "invalid_mcp_url") {
    // prompt user for a full absolute https:// URL
  }
}

Prevention

When it happens

Trigger: Configuring a remote MCP server where the `url` value is not a parseable absolute URL — e.g. 'mcp.example.com' (no scheme), 'localhost:3000/sse' (parsed as scheme localhost), empty string, or containing spaces/illegal characters.

Common situations: Users entering a bare hostname without protocol; template placeholders like '${MCP_URL}' left unsubstituted; copy-paste artifacts (trailing spaces, smart quotes); relative URLs pasted from docs.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/170c04cb4df25eb5. Report an issue: GitHub.