screenpipe/screenpipe · error

unauthorized

Error message

unauthorized

What it means

The screenpipe MCP HTTP server gates every non-health endpoint behind an API-key check (`isAuthorized(req, config.apiKey)`). Requests failing that check get HTTP 401 with `{"error":"unauthorized"}`. Only the health endpoint is exempt.

Source

Thrown at packages/screenpipe-mcp/src/http-server.ts:349

    );

    if (req.method === "OPTIONS") {
      res.writeHead(204);
      res.end();
      return;
    }

    // Health check is unauthenticated — monitors / load balancers need it.
    // It only reveals session count, no user data.
    if (req.url === "/health") {
      res.writeHead(200, { "Content-Type": "application/json" });
      res.end(JSON.stringify({ status: "ok", sessions: sessions.size }));
      return;
    }

    // Auth gate for everything else.
    if (!isAuthorized(req, config.apiKey)) {
      res.writeHead(401, { "Content-Type": "application/json" });
      res.end(JSON.stringify({ error: "unauthorized" }));
      return;
    }

    // MCP endpoint
    if (req.url === "/mcp" || req.url?.startsWith("/mcp?")) {
      const sessionId = req.headers["mcp-session-id"] as string | undefined;
      let session = sessionId ? sessions.get(sessionId) : undefined;

      if (!session) {
        const server = createMcpServer(fetchAPI);
        const transport = new StreamableHTTPServerTransport({
          sessionIdGenerator: () => crypto.randomUUID(),
          onsessioninitialized: (newSessionId) => {
            sessions.set(newSessionId, { server, transport });
          },
          onsessionclosed: (closedSessionId) => {
            sessions.delete(closedSessionId);

View on GitHub (pinned to 4ebf712990)

Solutions

  1. Configure the MCP client with the same API key the server was started with (header `Authorization: Bearer <key>`).
  2. Check how the server was launched and what config.apiKey is; restart it with a known key if lost.
  3. Verify no intermediary (reverse proxy, shell script) strips or rewrites the Authorization header.
  4. Use the unauthenticated health endpoint to confirm connectivity separately from auth.

Example fix

// before: client with no auth
fetch("http://localhost:3030/mcp", { method: "POST", body });

// after
fetch("http://localhost:3030/mcp", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.SCREENPIPE_API_KEY}` },
  body,
});
Defensive patterns

Strategy: validation

Validate before calling

if (!config.apiKey) throw new Error("MCP HTTP server requires an apiKey in config");
// client side:
if (!clientApiKey) throw new Error("set Authorization: Bearer <key> for /mcp calls");

Try / catch

const res = await fetch(base + "/mcp", { headers: { Authorization: `Bearer ${key}` }, ... });
if (res.status === 401) {
  const { error } = await res.json();
  throw new Error(`MCP server rejected key (${error}); realign client and server apiKey`);
}

Prevention

When it happens

Trigger: Any request to the MCP HTTP server (e.g. POST /mcp) without the expected API key: missing Authorization header, wrong key, or the client configured without the key that the server was started with (config.apiKey).

Common situations: MCP clients configured before a key was added to the server config; key rotated on the server but not the client; multiple MCP server instances with different keys; proxies stripping the Authorization header.

Understand the failure class

Related errors


AI-assisted analysis of screenpipe/screenpipe@4ebf712990 (2026-09-01). Data as JSON: /api/errors/b2dd88761eabffdb. Report an issue: GitHub.