Skip to main content

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

A tool I expect is missing from tools/list

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.
Call ontime_whoami and check three things in its response:
1

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.
2

Your permissions

Compare against the 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.
3

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.

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.
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.

A write is refused with a scope error

You — or the employee your credential is bound to — are not the subject’s direct manager and do not hold hr:approve.
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.
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:
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.
A server-side build step. Nothing you can fix from the client — report it to whoever operates your OnTime instance.

The agent filed something against the wrong person

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.
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

Today’s attendance is provisional. Absent in particular is never written to a day still in progress.
An out-of-geofence punch is recorded but waits on a manager’s decision.
An unmapped terminal user ID parks punches rather than dropping them. They replay when the mapping is created.
The organization’s notification preferences gate whether a decision raises a notification at all. The decision still took effect.

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.