Common Litmus MCP failure modes and how to fix each one.
Troubleshooting
If you hit something that isn't covered here, email founders@litmus.build with the tool you called, the arguments, and the response — we'll dig in.
401 Unauthorized
Common causes, in order of likelihood:
Missing trailing slash on the URL.
Use https://litmushiring.com/mcp/ — the trailing / matters. Hitting
/mcp triggers a 308 redirect, and most HTTP clients drop the
Authorization header across that redirect. The 401 you see is from the
second request — the one without your bearer token — not from a bad key.
This is the single most common setup mistake. If your key worked yesterday and stopped today, double-check the URL hasn't lost its trailing slash.
OAuth token expired. Clerk-issued tokens follow your org's session length. A previously-working OAuth client that suddenly 401s usually just needs a fresh sign-in — most clients trigger this automatically on the next call.
Not an admin (OAuth only).
OAuth access is admin-only. A non-admin member who completes Clerk sign-in
still 401s against /mcp/. Either promote the user from your dashboard or
hand them an API key.
Key was revoked. Revoked API keys stop working on the next request. Check Settings → API Keys in your dashboard — if the key isn't listed, it was deleted; mint a new one and roll your client.
Header is malformed.
The header must be exactly Authorization: Bearer <token> with one space
between Bearer and the token.
429 Too Many Requests
Litmus rate-limits the MCP surface per-org. The default is 60 requests per
minute, refilled continuously, so short bursts up to the full limit are
fine. All API keys on an org share one bucket — and artifact streaming
URLs (zipUrl, videoUrl from get_submission)
count against the same bucket, so switching endpoints won't get you more
headroom.
When you exceed the limit, the response is:
- HTTP status
429 - JSON body
{"error": "rate_limited", "error_description": "MCP rate limit exceeded"} Retry-Afterheader — integer seconds until one more request will succeed.
The fix is to read Retry-After, sleep, and retry. For artifact downloads
(plain HTTP), that looks like:
import asyncio, httpx
async def get_with_backoff(client, url, headers, *, max_retries=5):
for attempt in range(max_retries):
r = await client.get(url, headers=headers)
if r.status_code != 429:
r.raise_for_status()
return r
if attempt == max_retries - 1:
r.raise_for_status()
await asyncio.sleep(int(r.headers.get("Retry-After", "1")))The same principle applies to MCP tool calls — your client will surface the
429 from the transport layer; catch it, sleep Retry-After seconds, retry.
If you're consistently hitting the limit on a real workload (nightly exports, large pipeline backfills), email founders@litmus.build and we can raise your org's ceiling. Changes take effect within about a minute.
"Not found" on a tool that should exist
Litmus returns "not found" for any resource that doesn't belong to your org — we never leak cross-tenant existence. So the same error covers two cases:
- It really doesn't exist — wrong ID, typo, deleted record. Confirm the
ID by listing first (
list_submissions,list_candidates). - It belongs to a different org — you're hitting Litmus with the wrong API key. Each key is bound to exactly one org; if your team has multiple orgs, make sure you're using the right one.
Version conflict on update_assessment_files
When you supply expected_version and someone else updated the assessment
since your last get_assessment_files, the write is refused with an error
that includes the current version. The fix:
- Re-fetch with
get_assessment_filesto pick up the new state. - Merge your local edits against the new file set.
- Retry
update_assessment_fileswith the new version.
This is intentional — silently overwriting a concurrent edit would lose
work. If you genuinely want last-write-wins (e.g. a one-shot scripted
deploy with no other writers), omit expected_version entirely.
Artifact URLs return 404
zipUrl and videoUrl from get_submission 404 when:
- The candidate didn't record a walkthrough video, or
- The submission predates 2026 and is still stored on the legacy filesystem path without a zip blob, or
- The bearer token doesn't match the submission's org (same "no cross-tenant existence" rule as above).
get_submission returns null for either URL when it knows the artifact
isn't available, so a null here is expected — only an explicit 404 against
a non-null URL points at a real problem.
The MCP client hangs on initialize()
Streamable HTTP clients hold the connection open for the whole session. If the call hangs forever, either:
- A network proxy or firewall is buffering the response — try from an uncorporate-firewalled network to confirm.
- The URL is wrong (no trailing slash again, or pointing at
localhostinstead oflitmushiring.com). The connection still opens but no initialize handshake comes back.
Still stuck
Email founders@litmus.build with:
- The tool name and arguments you called.
- The full error message and any stack trace.
- The first few characters of your API key (e.g.
litmus_sk_abc123…) so we can find the request in our logs without you sharing the secret.
