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-After header — 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:

python
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:

  1. Re-fetch with get_assessment_files to pick up the new state.
  2. Merge your local edits against the new file set.
  3. Retry update_assessment_files with 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 localhost instead of litmushiring.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.