{"record":{"id":"4b1b03a563a3562a","repo":"screenpipe/screenpipe","slug":"404-not-found","errorCode":null,"errorMessage":"404 not found","messagePattern":"404 not found","errorType":"http","errorClass":null,"httpStatus":404,"severity":"warning","filePath":"crates/screenpipe-core/assets/acp/screenpipe-tools.mjs","lineNumber":1384,"sourceCode":"}\n\n// Stateless Streamable-HTTP: each POST /mcp carries one JSON-RPC request (or a\n// batch) and gets a single JSON response. None of these tools stream, so no SSE\n// channel is opened. Bound to loopback with an Origin check per the MCP spec's\n// DNS-rebinding guidance; no session id (stateless), which every ACP harness we\n// target tolerates.\nconst LOOPBACK_ORIGIN = /^https?:\\/\\/(127\\.0\\.0\\.1|localhost|\\[::1\\])(:\\d+)?$/i;\n\nfunction startHttpServer(port) {\n  const server = createServer((req, res) => {\n    const url = req.url || \"/\";\n    if (req.method === \"GET\" && url.startsWith(\"/health\")) {\n      res.writeHead(200, { \"Content-Type\": \"application/json\" });\n      res.end(JSON.stringify({ ok: true, server: SERVER_INFO }));\n      return;\n    }\n    if (!url.startsWith(\"/mcp\")) {\n      res.writeHead(404).end();\n      return;\n    }\n    const origin = req.headers[\"origin\"];\n    if (origin && !LOOPBACK_ORIGIN.test(origin)) {\n      res.writeHead(403).end();\n      return;\n    }\n    if (req.method !== \"POST\") {\n      res.writeHead(405, { Allow: \"POST\" }).end();\n      return;\n    }\n    let body = \"\";\n    req.setEncoding(\"utf-8\");\n    req.on(\"data\", (chunk) => {\n      body += chunk;\n      if (body.length > 4_000_000) req.destroy();\n    });\n    req.on(\"end\", async () => {","sourceCodeStart":1366,"sourceCodeEnd":1402,"githubUrl":"https://github.com/screenpipe/screenpipe/blob/4ebf712990fee17eeaf904dacf749b6e96ac9bf3/crates/screenpipe-core/assets/acp/screenpipe-tools.mjs#L1366-L1402","documentation":"The ACP screenpipe-tools MCP server only accepts requests whose URL starts with /mcp (plus /health). Anything else gets a bare HTTP 404 with an empty body. This is a path/router mismatch, not an MCP protocol error — the request never reached the MCP handler.","triggerScenarios":"POSTing to a path like / or /tools or /api/mcp; misconfigured MCP client baseUrl that includes/excludes a path prefix incorrectly; requesting /mcp/ with tools that build URLs differently; hitting the wrong port where another route layout is expected.","commonSituations":"MCP client configured with server URL http://127.0.0.1:PORT/mcp/ extra-slash variants or a legacy /jsonrpc path; dev proxy stripping or adding prefixes; client pointed at /health by mistake.","solutions":["Point the MCP client at the exact path /mcp on the server port","Verify with GET /health first — it returns 200 {ok:true,server:SERVER_INFO} to confirm you reached the right server","Remove any path prefixes added by reverse proxies or client config","Check SERVER_INFO/health output to confirm the expected endpoint layout"],"exampleFix":"// before\nconst server = new MCPClient({ url: 'http://127.0.0.1:9000/api/mcp' });\n// after\nconst server = new MCPClient({ url: 'http://127.0.0.1:9000/mcp' });","handlingStrategy":"validation","validationCode":"function assertMcpUrl(u) {\n  const { pathname } = new URL(u);\n  if (pathname !== '/mcp') throw new Error(`MCP server expects path /mcp, got ${pathname}`);\n  return u;\n}\n// preflight\nawait fetch(new URL('/health', base)).then(r => r.json()); // must return {ok:true,...}","typeGuard":"function isMcpBase(u) {\n  try { return new URL(u).pathname === '/mcp'; } catch { return false; }\n}","tryCatchPattern":"const res = await fetch(mcpUrl, init);\nif (res.status === 404) {\n  throw new Error(`MCP endpoint 404 — use /mcp (health check: GET /health)`);\n}","preventionTips":["Configure MCP clients with the exact base path /mcp, no trailing extras","Run GET /health as a preflight to confirm the right server/port","Avoid proxy rewrites that alter the /mcp prefix","Store the MCP URL in config, not scattered literals"],"tags":["http-404","mcp","route-not-found"],"backgroundTag":"route-not-found","analyzedSha":"4ebf712990fee17eeaf904dacf749b6e96ac9bf3","analyzedAt":"2026-09-01T23:33:43.065Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T06:17:21.866Z"}