> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coffeechats.me/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP error codes

> Every code the Coffee Chats connector publishes, where it arrives, what causes it, what to do about it, and which HTTP status carries it.

This page lists every error code the connector publishes. Each row says where the code arrives, what caused it and the action to take. A code outside this list is not something Coffee Chats sends. [Handle errors and retries](/mcp/guides/handle-errors.md) is the procedure that uses this table.

## Where these codes arrive, and under which HTTP status

Every code on this page arrives inside a normal, successful protocol response. The HTTP status is 200 in all of them, and so is the status on a read tool's failure text. An agent that branches on HTTP status alone will never see any of these.

These are the HTTP statuses the protocol address answers with. Only the first carries a code from this page; the rest mean the call never reached a tool.

| Status | Cause | What to do |
| - | - | - |
| 200 | The protocol answered. Every tool result and every tool-level failure, including all the codes below, arrives this way. | Read the result. |
| 204 | The answer to a browser's preflight request, which carries no body. | Nothing. Your agent host handles this. |
| 400 | The body of the call was not valid JSON. | Fix the request the agent host is sending. Do not repeat it unchanged. |
| 401 | The call carried no access credential, or one the connector would not accept. | Renew the credential, or start authorization again. |
| 403 | The call carried a browser `Origin` header the connector does not allow. | Call from a server or from an allowed origin. Repeating the call will not help. |
| 405 | The call used an HTTP method other than `GET`, `POST`, `DELETE` or `OPTIONS`. | Fix the method. |
| 413 | The body of the call was larger than 2 megabytes. | Send less. A spreadsheet import is the call that can come close. |
| 415 | A `POST` did not declare a JSON content type. | Send `Content-Type: application/json`. |
| 429 | A rate limit refused the call. | Wait, then call again. The headers below say how long. |
| 500 | Something failed inside the connector while answering. | Treat the call's outcome as unknown, read the value back, and report the problem if it repeats. |
| 503 | The rate limiter's own store was unavailable, so the connector refused rather than let the limit go unenforced. | Wait and call again. Nothing was written. |

Any status outside this table is not one the connector produces on purpose. Treat it as undefined, report it, and do not retry blindly.

A 429 answer carries these headers: `RateLimit-Policy`, `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` and `Retry-After`. Wait the number of seconds in `Retry-After` before calling again. The [limits](/mcp/limits.md) page states what each limiter counts.

## Codes in a failing result

These arrive inside the `error` object of a write result whose `status` is `failed`. In every one of them, nothing was written. [The result object](/mcp/writes.md) describes the object they sit in.

| Code | Cause | Action |
| - | - | - |
| `VERSION_CONFLICT` | The value changed after the agent read it, or the same idempotency key was sent again with different input. On `actions.cancel` it means the review request was no longer withdrawable. | Read the value again and make the change from what is there now, with a new key. After a refused withdrawal, poll the review request instead. |
| `ACTION_DENIED` | The student declined the review request, or the connection was no longer allowed to make the change when Coffee Chats came to apply it. | Stop. Do not call again. Tell the student the change was refused. |
| `ACTION_EXPIRED` | The change ran out of time before it started, or the review request expired before the student decided. | Ask once more with a new key, and only if the student still wants it. |
| `NOT_APPLIED` | Coffee Chats can prove it stopped before writing anything. On an approved `booking_archive_event_type` review request it also means the archive would have left the student with no active event type. | Make the change again with a new key. After a refused archive, read the student's event types again and do not propose the archive until another one is active. |
| `SERVICE_UNAVAILABLE` | Coffee Chats could not do the work just now. | Try again later with a new key. |

## Codes in a cancelled or uncertain result

| Code | Where it arrives | Cause | Action |
| - | - | - | - |
| `OPERATION_CANCELLED` | A result whose `status` is `cancelled` | On a review request, the connection was disconnected before the change started, so nothing was written. On `actions.cancel` it is the success answer: the review request is withdrawn. | After a disconnect, stop and ask the student to authorize again. After a withdrawal, tell the student the review request is gone and nothing was changed. |
| `OUTCOME_UNKNOWN` | A result whose `status` is `uncertain` | Coffee Chats cannot prove whether the change was written, and will not guess. | Read the value back and treat what is stored as the answer. Do not send the change again under a new key until you have read what is stored. [Settle an uncertain result](/mcp/guides/settle-an-uncertain-result.md) has the read for every write. |

## Codes in a retry hint

These two never arrive in a failing result. They ride under `_meta` beside an accepted result, and nothing has failed when you see one.

| Code | Cause | Action |
| - | - | - |
| `PERMIT_BACKPRESSURE` | Too much work is already running on this connection. | Wait for the stated delay and call again with the same key. |
| `PERMIT_CONTENTION` | Another call was holding the same slot. | Wait for the stated delay and call again with the same key. |

## What the `retryable` boolean is worth

Every `error` object carries a `retryable` boolean, and every retry hint carries one too. They do not mean the same thing, so read them separately.

In a retry hint, `retryable` is always `true`. It is fixed by the contract and tells you nothing the hint's presence had not already told you.

In an `error` object, `retryable` is `false` on every failure the connector maps today, with one exception: `OUTCOME_UNKNOWN` copies the flag from the record it is reporting, so it can arrive as `true`.

> [!WARNING]
>
> Do not branch on `retryable` alone. A `true` on `OUTCOME_UNKNOWN` does not mean the change may be sent again. It means the outcome is unknown and a read has to settle it first. Use the Action column above, which is the authority.

## How long to keep retrying

The connector sets no retry ceiling and no backoff schedule. The only delay it ever states is `retryAfterMs` in a retry hint, which the contract bounds to between 1 and 5,000 milliseconds and which every write tool that issues one sets to 1,000.

Everything else is your choice. The following is guidance, not a rule the connector enforces. Wait the delay the hint gives you. Cap the number of attempts at a small number your agent can explain to a student. Stop once a result is `completed`, `failed`, `cancelled` or `uncertain`, because nothing moves out of those four. One connection may make 120 calls a minute, and a loop spends that budget on one change.

## Failures that carry no code

A read tool's failure carries no code. It arrives as a protocol error holding one line of text, and that text is one of three. This section is the reference for all three, and the other pages link here rather than state the rule again.

The first text is the fixed one, and almost every failure carries it:

```json Protocol error envelope theme={"system"}
{
  "isError": true,
  "content": [{ "type": "text", "text": "Tool call failed." }]
}
```

A write that fails before it produces a result of its own arrives this way, and so does a call to a tool whose permission was not granted. There is nothing in it to branch on. The reason is withheld on purpose, because the reason could itself describe data the connection was not allowed to see.

The second text arrives when a tool looks up one record by its id and your account has no such record. The text names the kind of record and nothing else:

```text A lookup that found no record of yours theme={"system"}
No contact with that id exists for this account. Use the matching list tool to find one.
```

Eight kinds of record answer this way, and the text names the kind the tool was asked for: `application`, `booking`, `calendar source`, `campaign`, `contact`, `event type`, `meeting` and `posting`. A lookup of anything else, including a review request by its `operationId`, answers with the fixed text instead.

The text is the same whether that id never existed or belongs to another student's record, so it says nothing about any account but yours. It carries no id and no code. An agent may act on it: stop asking for that id, and call the matching list tool to find the ids your account does have.

One of the eight kinds has no list tool. `jobs.postings.get` reads a posting from a catalogue shared with every signed-in student, so the connector lists no postings. When the text names a posting, search for it with `jobs.search` instead, or ask the student which posting to read.

The third text says why a value is withheld instead of which record is missing. A meeting whose transcript Coffee Chats does not hold answers `meetings.transcript.list` with:

```text A transcript Coffee Chats does not hold theme={"system"}
No transcript is available for this meeting. A transcript is kept only when the participants consented to recording and it has not been deleted.
```

A call refused for rate limiting carries no code from this page either. It is the 429 answer described at the top, and the [limits](/mcp/limits.md) page states what each limiter counts.

## Next steps

> **[Handle errors and retries](/mcp/guides/handle-errors.md)**
>
> The three shapes a failure arrives in, and the five things never to do.

> **[Tool reference](/mcp/tools/overview.md)**
>
> Each tool's own page lists the codes that tool can return.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.