Common Litmus MCP failure modes and how to fix each one.

Troubleshooting

If you hit something that isn't covered here, email founders@litmushiring.com 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.

Member of multiple Clerk orgs (OAuth only). If your Clerk user belongs to more than one org and you connect via the legacy /mcp/ URL (no org segment), the OAuth verifier can't pick which org you mean and refuses with 401. Switch to the org-scoped URL — https://litmushiring.com/mcp/<your-org-id>/ — copy the exact value from Settings → API Keys in your dashboard. Add the connector with:

bash
claude mcp add --transport http litmus https://litmushiring.com/mcp/<your-org-id>/

Same fix applies to Cursor's mcp.json and any other client.

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@litmushiring.com 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@litmushiring.com 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.