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
- Configure the MCP client with the same API key the server was started with (header `Authorization: Bearer <key>`).
- Check how the server was launched and what config.apiKey is; restart it with a known key if lost.
- Verify no intermediary (reverse proxy, shell script) strips or rewrites the Authorization header.
- 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
- Pass the same apiKey value to the server launch and every client config.
- Rotate keys on both sides in one deploy step.
- Smoke-test with the health endpoint, then an authed /mcp ping, before real usage.
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- sp_mcp_call failed (${res.status}): ${bodyText.slice(0, 800)
- {"error":"unauthorized: API access requires authentication.
- OAuth sign-in required for MCP server '{}'
- POST /artifacts/register returned ${res.status}
- web search unavailable: sign in to screenpipe first
AI-assisted analysis of screenpipe/screenpipe@4ebf712990 (2026-09-01).
Data as JSON: /api/errors/b2dd88761eabffdb.
Report an issue: GitHub.