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

# MCP connector

> Endpoints, OAuth flow, scopes and the tool catalog of the HR Easy Model Context Protocol server.

HR Easy exposes a remote **MCP** server (Streamable HTTP, stateless JSON) and
acts as its own **OAuth 2.1 authorization server**. Any MCP client that
implements the standard authorization flow — Claude, ChatGPT connectors,
Cursor, n8n — can connect a person's account. The connector is enabled per
installation with `FEATURE_MCP_CONNECTOR=true` and the public origin in
`MCP_ISSUER_URL`.

## Endpoints

| Path                                            | Purpose                                                                          |
| ----------------------------------------------- | -------------------------------------------------------------------------------- |
| `POST /api/mcp`                                 | The MCP endpoint. Bearer access token required; `GET` answers `405`.             |
| `/.well-known/oauth-protected-resource/api/mcp` | RFC 9728 protected-resource metadata (also served at the origin root).           |
| `/.well-known/oauth-authorization-server`       | RFC 8414 authorization-server metadata (also served with the `/api/mcp` suffix). |
| `POST /api/oauth/register`                      | RFC 7591 dynamic client registration — only while the installation allows it.    |
| `GET /oauth/authorize`                          | Authorization request; SSO step-up and consent screen.                           |
| `POST /api/oauth/token`                         | Authorization-code (PKCE S256) and refresh-token grants.                         |
| `POST /api/oauth/revoke`                        | RFC 7009 revocation.                                                             |

Rules a client must follow:

* PKCE with `S256` on every authorization request; `plain` is refused.
* The `resource` parameter (RFC 8707) must equal the canonical MCP URL,
  `<issuer>/api/mcp`, on both the authorization and the token request.
* Refresh tokens rotate on every use; reusing a rotated token revokes the
  whole token family.
* Dynamic registrations are public clients (`token_endpoint_auth_method:
  "none"`); an installation may instead create a confidential client by hand and
  hand its id and secret to the assistant's advanced settings.
* Every unauthenticated call to `/api/mcp` answers `401` with a
  `WWW-Authenticate: Bearer` challenge carrying the `resource_metadata` URL.

## Scopes

| Scope                | Grants                                                                                         |
| -------------------- | ---------------------------------------------------------------------------------------------- |
| `self:read`          | The connected person's own records                                                             |
| `hr:read:people`     | Employees, positions, the HR overview and the approvals queue, as far as the role may see them |
| `hr:read:recruiting` | Applications, as far as the role may see them                                                  |
| `self:write`         | Reserved for the self-service write tools (not yet served)                                     |
| `hr:write`           | Reserved for HR write tools (not yet served)                                                   |

A grant never widens: a refresh or a later consent carries at most the scopes
first approved. A person's role is re-read from the database on every call, so
a role change or deactivation takes effect on the next request.

## Limits and records

* 60 calls per minute per connection; the HR overview additionally 6 per hour.
* A daily allowance of distinct person records per connection (installation
  setting, default 1 000). Reads beyond it are refused until the next UTC day.
* Every list returns at most 50 rows; search at most 25.
* Every call writes an audit row: assistant, connection, tool, argument
  names (free text as length and hash only), the records touched, result
  count and outcome. A connection that touches 50 or more different people in
  a day is additionally recorded as an export.

## Tool catalog

The catalog below is generated from the server's tool registry
(`npm run gen:mcp-catalog`); a drift check in CI keeps it identical to what
`tools/list` returns. Each tool declares its scope, the permission the role
must hold, MCP annotations (all Phase 2 tools are read-only, idempotent and
closed-world), and JSON Schemas for its arguments and its result.

<Card title="mcp-tools.json" icon="file-code" href="/api-reference/mcp-tools.json">
  The machine-readable catalog: 18 tools with input and output schemas.
</Card>

| Tool                     | Scope                | Permission              | Returns                                                            |
| ------------------------ | -------------------- | ----------------------- | ------------------------------------------------------------------ |
| `whoami`                 | `self:read`          | `mcp:connect`           | The acting user, role, granted scopes and available tools          |
| `get_my_summary`         | `self:read`          | `mcp:connect`           | Leave balance, current week, next payout, to-dos                   |
| `get_my_leave_balances`  | `self:read`          | `mcp:connect`           | Balances per leave type for a leave year                           |
| `list_my_leave_requests` | `self:read`          | `mcp:connect`           | Own requests; reason only with `include_free_text`                 |
| `list_my_time_entries`   | `self:read`          | `mcp:connect`           | Entries in a day range (≤ 400 days) and their sum                  |
| `get_my_sick_leave`      | `self:read`          | `mcp:connect`           | Entries and totals for a calendar year; never the reason           |
| `list_my_payslips`       | `self:read`          | `mcp:connect`           | Period, payment date and a portal link; no amounts, no PDF         |
| `get_public_holidays`    | `self:read`          | `mcp:connect`           | Public (and school) holidays for the caller's canton               |
| `get_closure_periods`    | `self:read`          | `mcp:connect`           | Company closure days that apply to the caller                      |
| `search_people`          | `hr:read:people`     | `employees:view`        | Active employees by name within the caller's scope (≤ 25)          |
| `list_employees`         | `hr:read:people`     | `employees:view`        | Employees within scope, active by default                          |
| `get_employee`           | `hr:read:people`     | `employees:view`        | Master data without sensitive fields or salary                     |
| `list_positions`         | `hr:read:people`     | `positions:view`        | Positions within scope; no salary bands                            |
| `get_position`           | `hr:read:people`     | `positions:view`        | One position with capped ad text                                   |
| `get_hr_overview`        | `hr:read:people`     | `ai-chat:use` (HR+)     | Installation aggregates; counts below five withheld                |
| `list_pending_approvals` | `hr:read:people`     | `staff-absence:approve` | Submitted absences and expense reports awaiting the caller         |
| `list_applications`      | `hr:read:recruiting` | `applications:view`     | Applications within scope; no AI scores or notes                   |
| `get_application`        | `hr:read:recruiting` | `applications:view`     | One application; cover letter and shared notes only with free text |

Every HR-scope tool description ends with the same sentence: *never use this to
rank, score or compare people against each other*. HR Easy does not support
rankings; see the product's [design principles](/concepts/design-philosophy).
