different-ai/openwork · error · RemoteMcpAppError
source_fetch_failed
source_fetch_failed
Error message
${error instanceof Error ? error.message : "The app URL could not be downloaded."} What it means
fetchRemoteMcpApp wraps the outbound guardedFetch download in a try/catch. Any failure that is not already a RemoteMcpAppError (network failure, DNS error, timeout from AbortSignal.timeout, TLS error, invalid URL behavior) is rethrown as a 502 source_fetch_failed with the underlying error message, or a generic fallback message.
Source
Thrown at ee/apps/den-api/src/remote-mcp-apps.ts:292
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes)
} catch {
throw new RemoteMcpAppError(422, "invalid_encoding", "Remote MCP Apps must be UTF-8 HTML.")
}
}
export async function fetchRemoteMcpApp(sourceUrl: string) {
const { env } = await import("./env.js")
const normalizedUrl = validateRemoteMcpAppSourceUrl(sourceUrl, env.allowPrivateMcpUrls)
const guardedFetch = env.allowPrivateMcpUrls ? createRealmSafeFetch() : createGuardedFetch()
let response: Response
try {
response = await guardedFetch(normalizedUrl, {
headers: { accept: "text/html,application/xhtml+xml;q=0.9,text/plain;q=0.5" },
signal: AbortSignal.timeout(REMOTE_MCP_APP_FETCH_TIMEOUT_MS),
})
} catch (error) {
if (error instanceof RemoteMcpAppError) throw error
throw new RemoteMcpAppError(502, "source_fetch_failed", error instanceof Error ? error.message : "The app URL could not be downloaded.")
}
if (!response.ok) {
await response.body?.cancel()
throw new RemoteMcpAppError(502, "source_fetch_failed", `The app URL returned HTTP ${response.status}.`)
}
const contentType = response.headers.get("content-type")
try {
validateRemoteMcpAppContentType(contentType)
} catch (error) {
await response.body?.cancel()
throw error
}
const html = await boundedResponseText(response)
const inspected = inspectRemoteMcpAppHtml(html)
const resolvedSourceUrl = validateRemoteMcpAppSourceUrl(response.url || normalizedUrl, env.allowPrivateMcpUrls)
return {
...inspected,
html,View on GitHub (pinned to 2b7df46e8a)
Solutions
- Check the source URL is reachable: curl it from the den-api host and fix the hostname/DNS.
- Re-register the app with the correct, publicly reachable URL.
- If the origin is legitimately slow, reduce the page size or check for network issues; the timeout is REMOTE_MCP_APP_FETCH_TIMEOUT_MS.
- For internal/private URLs, enable allowPrivateMcpUrls in the environment configuration.
- Fix TLS (valid certificate chain) on the source server.
Defensive patterns
Strategy: retry
Validate before calling
const probe = await fetch(sourceUrl, { method: "HEAD" }).catch((e) => { throw new Error(`Unreachable before import: ${e.message}`); });
if (!probe.ok) throw new Error(`Probe failed with HTTP ${probe.status}`); Try / catch
try {
await importRemoteMcpApp({ sourceUrl, ... });
} catch (e) {
if (e instanceof RemoteMcpAppError && e.code === "source_fetch_failed") {
// inspect e.message for the underlying cause; retry with backoff for transient network errors
} else throw e;
} Prevention
- curl the source URL from the den-api host before registering it.
- Use stable, public, permanently-hosted URLs (avoid expiring signed links).
- Ensure DNS and TLS are valid for the origin domain.
- Keep the app host fast; the fetch has AbortSignal.timeout(REMOTE_MCP_APP_FETCH_TIMEOUT_MS).
When it happens
Trigger: guardedFetch throws while fetching the validated sourceUrl: connection refused/reset, DNS resolution failure, TLS certificate errors, or the fetch exceeding REMOTE_MCP_APP_FETCH_TIMEOUT_MS via AbortSignal.timeout.
Common situations: Typo in the source URL hostname; source server temporarily down; private-network URL blocked (SSRF guard) or allowPrivateMcpUrls disabled; slow origin exceeding the fetch timeout; self-signed certificates.
Related errors
- Request timed out.
- Failed to fetch latest-mac.yml (${response.status} ${respons
- Timed out waiting for server health
- Connection lost
- MCP_SHUTDOWN_FAILED
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/d9dbd8549a2d004b.
Report an issue: GitHub.