An HR system with no user interface

Nobody logs in
to book a Friday off.

Leave, attendance, people and policy — the whole of it behind one MCP endpoint. Your team asks their assistant. The assistant does the arithmetic, checks the policy, and writes the record.

asks“How much annual leave have I got, and can I take the 14th to the 18th?”
readsexecute({ operation: 'leave.preview', params: { policy_id: 'AL', from_date: '2026-09-14', to_date: '2026-09-18' } })
getsdays: 4 · calendar_days: 5 // the 16th is a public holiday
available: 11 → would_leave: 7 · blockers: []
writesexecute({ operation: 'leave.request', params: { … } })
// sent to their manager, breakdown stored, nothing out of the balance until it is approved
2tools
9tables
216operations
0screens to learn
The shape of it

An organisation, its teams, and the people in them.

Most HR software is a filing cabinet with a person scattered across nine drawers. This has one record for a person — and everything, every day worked, every day off, every balance movement, every promotion, hangs off it and reads back in one call. Teams nest above them, so “everyone in Engineering” means Platform and Storage too.

teams

A team, department or division — and teams sit inside teams.

  • Ask for a team and get everything under it, in one recursive query
  • Headcount counted twice: in the team, and beneath it
  • A person's department follows their team's name automatically

employees

The person, and the account they sign in with. There is no second user table to keep in step.

  • Their own working week, when it differs
  • Tags — india, night-shift, senior — are how policies find their people
  • Pay and personal details live here and are redacted from everyone else

leave_policies

One row per kind of leave. The rules are a JSON document, because every company means something slightly different by them.

  • Accrual, carry-forward, caps, notice, half-days, documents
  • applies_to selects by tag, type, department or location
  • Retire one without losing the history attached to it

leave_ledger

Every movement in a balance, signed: opening, accrual, taken, adjustment, lapse, reversal.

  • A balance is the sum of these — never a stored number that can drift
  • A mistake is corrected with a reversal, not an edit
  • “Why is my balance 4.5?” is one call away

attendance

One employee, one day, one row — with the punches kept inside it.

  • Grows with days worked, not with taps on a reader
  • Overlapping readings are counted once
  • Approved leave writes itself onto the sheet

holidays

The days the company is closed, scoped the same way policies are.

  • A holiday inside a week off costs nobody a day
  • Restricted and floating days are marked as optional

history

Hired, confirmed, moved, promoted, paid differently, warned, reviewed, left.

  • Append-only in practice — this is the employment record
  • Written automatically by every operation that changes something
  • Pages by cursor, so a long career reads as fast as a short one
What it actually does

The arithmetic nobody wants to do twice.

It works out what leave costs

Friday to Monday is two days, not four. A public holiday in the middle is free. Half a day is half a day. The day-by-day breakdown is stored on the request, so an argument about “that was only three days” is settled by reading it rather than by re-deriving it.

It refuses with everything at once

Not enough notice, no balance left, dates already booked, longer than the policy allows — you get all of it in one answer, each with the number behind it. And when a human has decided it is fine anyway, the refusal names the flag that overrides it.

It keeps the balance and the sheet in step

Approving leave moves the ledger and marks the attendance days in one transaction. Cancelling posts a reversal and releases the days. A balance and a timesheet that disagree is the oldest bug in HR software; here it is not reachable.

It reconciles rather than accumulates

Accrual looks at what each person should have been given by today, compares it with what their ledger already holds, and posts the difference. Run it nightly, run it twice, run it after a month of downtime — nobody gets double-credited.

It knows who may see what

An employee sees their own records. A manager sees their team’s. HR and admin see the organisation. Pay and personal details are redacted from everyone else — and it is the storage engine that enforces it, not each operation remembering to.

It writes the record as it goes

A department change is a transfer with a date on it. A salary revision is an event with the old figure and the new one. An attendance correction keeps what it corrected. Nothing has to be remembered into the record afterwards, because it was never not in it.

The whole API

Two tools. One of them is a verb.

An agent that has to learn 216 endpoints learns none of them. It learns two, and asks the server about the rest at the moment it needs to know.

discover — learn the API

Tables, fields, allowed values, the filter language, every operation with its exact signature and a runnable example. Nine scopes, no guessing.

discover({ scope: 'operation',
           operation: 'leave.request' })

execute — do everything

All CRUD, the whole leave loop, attendance, reports, onboarding, exits, access, mail. dry_run runs it and rolls back. idempotency_key makes a retry safe.

execute({ operation: 'employee.profile',
          params: { employee_id: '[email protected]' } })

Anywhere a person is wanted, a work email address or an employee code works as well as an id — because “approve Priya’s leave” is how the request actually arrives.

Being straight about it

What it deliberately is not.

A short list is worth more than a long one that quietly includes everything.

Not a payroll

Compensation is kept as a fact about a person, and every revision is an event in their record. There are no pay runs, no payslips and no statutory calculation — those are jurisdiction-shaped, and pretending otherwise would be worse than not doing it.

Not a REST API

/mcp is the only door into the data. The web pages read through the same engine, with the same fences. There is no second surface to secure and no second set of rules to drift.

Not an app to live in

There is a records browser for the times you want to look at everything at once, and a page for managing access. Day to day, nobody opens either.

When you do want to look

A records browser, and nothing else.

Every table, every filter, sorting, search, a date range, the columns you choose, inline editing, and CSV out. The counts on each filter are real — they say how many rows you would get if you picked it, given everything else already selected.

Filter by what is there

Statuses, departments, locations, policies, people and tags come down as options with counts behind them, so you never filter your way to an empty table by accident.

Ids read as names

Every reference on the page is looked up and shown as the person or policy it stands for — one small query per table, not one per row.

Chat, docked beside it

The assistant sits next to the records rather than replacing them, pointed at this same server.

Underneath

Boring where it should be.

PostgreSQL, and only PostgreSQL

Real date and timestamptz columns, jsonb with GIN indexes on tags, and partial indexes that match the exact ordering a timeline is read in. The schema is generated from one file; there is no migration directory to keep in step.

Mail through Plinth

Invitations, approval requests, decisions and password resets. If it is not configured, the operation says so and hands back the link rather than failing silently.

Models through konsole

Policy questions answered from your actual policy records, year summaries built from real events, drafts you read before they are sent. With no key set, those operations say so and nothing else changes.

OAuth 2.1, or a key

MCP clients register themselves and authorise with PKCE. Or mint an API key from your account page. Someone who leaves loses both, immediately.

Multi-tenant by construction

Every row carries its organisation, and the filter is applied in the one path all data access goes through — so a query that forgets it is impossible rather than merely unlikely.

Recoverable

Deletes are soft and reversible, with the records that belong to a person going and coming back with them. Every row carries a version, so two edits racing cannot silently overwrite each other.

Point something at it

One line, and your assistant runs HR.

claude mcp add --transport http hr https://hrms.cleartrust.cc/mcp

Or add https://hrms.cleartrust.cc/mcp as an MCP server in any client that supports them — it will walk you through signing in.

How to connect Read the API Sign in