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

# MCP troubleshooting

> 401s, missing tools, refused writes, views that render as text — and what each actually means.

## 401 on every call, no tools listed

The bearer token is missing, malformed, expired or revoked.

Every `401` from the MCP endpoint carries a `WWW-Authenticate` header naming the discovery URL:

```http theme={null}
WWW-Authenticate: Bearer resource_metadata="https://app.ontime.hosai.app/.well-known/oauth-protected-resource"
```

<Note>
  **For an OAuth client, the initial 401 is not an error.** It is how the connector discovers that OnTime speaks OAuth at all. A well-behaved client follows the header, registers itself and starts the authorize flow automatically.
</Note>

| Credential  | Fix                                                                                                   |
| ----------- | ----------------------------------------------------------------------------------------------------- |
| **API key** | Re-issue from **API & webhooks** and update your client. Keys are shown once and cannot be retrieved. |
| **OAuth**   | Remove and re-add the connector to re-authorize.                                                      |

## A tool I expect is missing from tools/list

<Warning>
  **Not a bug — a permission.** Listing and calling share the exact same predicate, so a tool that is invisible is one you could not have called anyway. There is no hidden surface.
</Warning>

Call `ontime_whoami` and check three things in its response:

<Steps>
  <Step title="Your kind">
    A **service** credential — an API key not bound to an employee — cannot see any tool requiring an employee identity. That is every self-service tool and everything scope-checked.

    Fix: issue a key bound to an employee.
  </Step>

  <Step title="Your permissions">
    Compare against the [tool catalog](/ai-mcp/tool-catalog). If the tool needs `payroll:write` and you do not have it, it will not appear.

    Fix: bind the key to an employee whose role carries the permission, or adjust that role.
  </Step>

  <Step title="Whether you are read-only">
    A read-only credential has every mutating tool hidden. For OAuth, that means you authorized `ontime:read`.

    Fix: re-authorize with `ontime:readwrite` — but note it is intersected with your own role, so it will not grant what your role lacks.
  </Step>
</Steps>

## A write is refused with a business-rule message

The tool returns the domain's own error verbatim, in the form `"<code>" (HTTP <status>): <reason>`. This is informative, not generic. Read it.

| You see           | Meaning                                                                                            | Do                                                       |
| ----------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| `bad_stage`       | A payroll stage out of order — calculating before inputs are complete, disbursing before approval. | Read `ontime_payroll_overview` and advance in order.     |
| `not_pending`     | Already decided, often by someone else moments ago.                                                | Treat as done. Do not retry.                             |
| `day_frozen`      | The date is in a frozen payroll month.                                                             | The whole approval rolled back. Take it up with payroll. |
| `cycle_locked`    | The cycle is disbursed; overrides are refused.                                                     | Handle it as a next-month adjustment.                    |
| `type_in_use`     | A leave type with balances or requests cannot be deleted.                                          | Set its quota to zero instead.                           |
| `not_signed`      | A Form 16 batch cannot be distributed before signing.                                              | Sign it first.                                           |
| `duplicate_punch` | Same direction within 60 seconds.                                                                  | The first punch registered.                              |

<Warning>
  **Retrying unchanged input fails identically.** These are state conflicts, not transient errors. Re-read state with the matching read tool, fix the input, then retry.
</Warning>

## A write is refused with a scope error

```text theme={null}
forbidden: … outside your approval scope
```

You — or the employee your credential is bound to — are not the subject's **direct manager** and do not hold `hr:approve`.

<Warning>
  **This is never a transitive check.** Being the manager's manager does not grant it. Skip-level authority is expressed as `hr:approve`, which HR and Admin carry.
</Warning>

Not retryable. Either the subject is genuinely outside your scope, or the credential should be bound to someone else.

## I get JSON or text instead of an interactive view

Two possible causes:

<AccordionGroup>
  <Accordion title="Your host does not support interactive MCP views" icon="file-text">
    Much the most likely. **Text is the designed fallback**, not an error — the data is identical. claude.ai and ChatGPT both render these as text today.
  </Accordion>

  <Accordion title="The view's assets are missing from the deployment" icon="triangle-alert">
    A server-side build step. Nothing you can fix from the client — report it to whoever operates your OnTime instance.
  </Accordion>
</AccordionGroup>

## The agent filed something against the wrong person

<Warning>
  Almost certainly the **self-service versus on-behalf** confusion. Most domains are **two separate tools**, not one tool with an optional employee parameter:

  * `ontime_leave_request` files for **you**.
  * `ontime_leave_request_for` files for **someone else**.

  An agent that reaches for the first while intending the second files perfectly valid leave against the wrong person. See [Quickstart for agents](/ai-mcp/quickstart-for-agents).
</Warning>

The Claude Code plugin's `ontime-operations` skill exists largely to prevent this. If you are building your own agent, encode the distinction explicitly.

## Punches or approvals seem to have no effect

<AccordionGroup>
  <Accordion title="The day has not closed" icon="calendar">
    Today's attendance is provisional. `Absent` in particular is never written to a day still in progress.
  </Accordion>

  <Accordion title="The punch is pending" icon="clock">
    An out-of-geofence punch is recorded but waits on a manager's decision.
  </Accordion>

  <Accordion title="A terminal punch is parked" icon="fingerprint">
    An unmapped terminal user ID parks punches rather than dropping them. They replay when the mapping is created.
  </Accordion>

  <Accordion title="Notifications are disabled" icon="bell">
    The organization's notification preferences gate whether a decision raises a notification at all. The decision still took effect.
  </Accordion>
</AccordionGroup>

## Still stuck

Have the agent call `ontime_whoami` and share the result. Identity, kind, permissions and read-only status explain the large majority of MCP problems.
