MCP FactorialHR
MCP serverDev toolsFull CRUD MCP server for FactorialHR: employees, teams, time off, projects, ATS, payroll.
Unavailable. This server has no hosted endpoint yet, so Ahel can't serve it.
Add to setup to save this item as a reference. Ahel cannot run it, and signing in will not install it.
Getting started
- Save this item in Your setup as a reference.
- Read the source or reference documentation for its setup requirements. Saving it here does not connect it to your AI.
- Check this page for availability before trying to install it through Ahel.
From the project's README
As published by t4dhg/mcp-factorial in README.md.
The definitive Model Context Protocol server for FactorialHR
A comprehensive Model Context Protocol (MCP) server that provides AI assistants like Claude with full access to FactorialHR. Manage employees, teams, time off, projects, training, recruiting, and more - all with built-in safety guardrails.
Why This MCP Server?
- Context-Optimized: 14 hierarchical tools (117 operations) with 88% less context usage than individual tools
- Full CRUD Operations: Create, read, update, and delete across all major entities
- Safety Guardrails: High-risk operations require explicit confirmation
- Audit Logging: All write operations are logged with timestamps and context for debugging
- Enterprise Ready: Built for companies who need AI integration with proper controls
Features
Hierarchical Tool Discovery (v8.0.0+)
The MCP server uses a hierarchical tool structure for optimal context usage. Instead of 124 individual tools, you get 14 category-based tools with an action parameter.
| Tool | Description | Actions |
|---|---|---|
factorial_discover | Discover available categories | - |
factorial_employees | Employee management | list, get, search, create, update, terminate |
factorial_teams | Team management | list, get, create, update, delete |
factorial_locations | Location management | list, get, create, update, delete |
factorial_contracts | Contract/salary data | list, get_with_employee, by_job_role, by_job_level |
factorial_time_off | Leave management | 10 actions |
factorial_attendance | Shifts and registro horario | 14 actions incl. clock_in, audit, log_range |
factorial_documents | Document management | 8 actions (downloads require OAuth2 - see below) |
factorial_job_catalog | Job roles/levels | list_roles, get_role, list_levels |
factorial_projects | Project management | 16 actions for projects, tasks, workers, time |
factorial_training | Training management | 12 actions for trainings, sessions, enrollments |
factorial_work_areas | Work area management | list, get, create, update, archive, unarchive |
factorial_ats | Applicant tracking | 17 actions for recruiting |
factorial_payroll | Payroll data (read-only) | 6 actions |
Example Usage:
// List all employees
factorial_employees({ action: 'list', page: 1, limit: 50 });
// Get a specific employee
factorial_employees({ action: 'get', id: 123 });
// Search employees
factorial_employees({ action: 'search', query: 'john' });
// Create a leave request
factorial_time_off({
action: 'create',
employee_id: 123,
leave_type_id: 1,
start_on: '2026-02-01',
finish_on: '2026-02-05',
});
// Discover available actions for a category
factorial_discover({ category: 'employees' });
124 Operations Across 14 Categories
| Category | Operations |
|---|---|
| Employees | list, get, search, create, update, terminate |
| Teams | list, get, create, update, delete |
| Locations | list, get, create, update, delete |
| Time Off | list_leaves, get_leave, list_types, get_type, list_allowances, create, update, cancel, approve, reject |
| Attendance | list, get, create, update, delete, clock_in, clock_out, status, gaps, audit, log_range, log_days, list_edit_requests, create_edit_request |
| Projects | 16 operations for projects, tasks, workers, time records |
| Training | 12 operations for trainings, sessions, enrollments |
| Work Areas | list, get, create, update, archive, unarchive |
| ATS | 17 operations for job postings, candidates, applications, hiring stages |
| Payroll | list/get supplements, tax identifiers, family situations (read-only) |
| Documents | 8 operations for folders, documents, and downloads (⚠️ downloads require OAuth2) |
| Job Catalog | list_roles, get_role, list_levels (read-only) |
| Contracts | list, get_with_employee, by_job_role, by_job_level (read-only) |
Attendance and Registro Horario
Factorial asks employees to record their working hours day by day. factorial_attendance lets Claude do that, for one day or for a whole month, and for any employee the API key can see.
Times are HH:MM in the company's local time, exactly as Factorial shows them; Factorial applies them in the company zone and the server never converts between zones. Records written by this server carry source: "api", so they are distinguishable from live clocks in Factorial's own activity log.
A write's declared working time (date, clock_in, clock_out) is independent of its entry metadata: Factorial stamps created_at, updated_at and in_source/out_source with when and how the record was entered, and those cannot be set or changed through the API. Every create, update, clock_in, clock_out, and every bulk preview and result, states the declared working time and says this once.
| Action | What it does |
|---|---|
status | Whether the employee is clocked in and since when. Always prints the configured identity. |
clock_in, clock_out | Live clocking at the current time, or, given date and time (HH:MM company local), a declared moment for someone who forgot to clock. A declared moment in the future is refused before any request is sent. A declared clock_out is refused if nothing is open, or if the moment precedes the open shift's start. |
gaps | Workdays in a date range where the contract expects more hours than were tracked. Weekends, bank holidays and full-day leave are excluded. |
audit | One row per calendar day in a range: day type, expected and tracked hours, leave cover, the shifts on record and a status (complete, missing, short, over, weekend, bank_holiday, on_leave, half_day_leave, no_contract_data, future). missing is nothing on record; short is some hours tracked but under expected by more than the tolerance, shown with its delta. A signed-off date is marked separately and is closed for writing; create_edit_request is how it gets corrected. The header gives expected (bank holidays and leave at full contract minutes) alongside workday expected (what tracked hours should actually meet). statuses restricts the summary to a given set. format is summary (default, only the days needing attention), table (every day) or json (the ledger). The starting point for reconciling what was clocked against what should have been. |
log_range | Apply a daily pattern (segments) to every workable day in a range. Skips weekends, bank holidays, days the contract expects 0 minutes, approved leave, future dates, signed-off dates, exclude_dates, and any segment overlapping an existing shift. |
log_days | Write an explicit list of days with their segments, for migrating from another platform. Only refuses future dates, approved leave, signed-off dates and overlaps, so a Saturday someone worked can be written. |
list, get, create, update, delete | Individual shift records. list needs start_on and end_on (or ids, or updated_at); Factorial ignores paging on this endpoint, so paging is client-side. fields: "compact" on list returns a smaller payload (date, clock_in, clock_out, minutes, in_source); get returns the complete record. |
list_edit_requests, create_edit_request | Factorial's route for correcting a signed-off day, since hours cannot be written onto a reviewed date directly. Filing a request notifies whoever approves timesheets, so create_edit_request is previewed and token-gated even for the configured identity. |
// Find the missing days first
factorial_attendance({ action: 'gaps', start_on: '2026-03-01', end_on: '2026-03-31' });
// Preview a month of split days; nothing is written yet
factorial_attendance({
action: 'log_range',
start_on: '2026-03-01',
end_on: '2026-03-31',
segments: [
{ clock_in: '09:00', clock_out: '14:00' },
{ clock_in: '15:00', clock_out: '18:00' },
],
});
// -> "Plan for <name> (<id>) ... 20 days to write, 40 shift records, 160h ... confirmation_token: <token>"
// Same call plus the token writes it
factorial_attendance({ action: 'log_range', /* same arguments */ confirmation_token: '<token>' });
// Afterwards, reconcile the month
factorial_attendance({ action: 'audit', start_on: '2026-03-01', end_on: '2026-03-31' });
"Today" for the future-date rule is the date in the zone of the machine running the server, so run it in the company's zone or accept that the boundary day may be off by one. Bank holidays come from the company's own calendar in Factorial (worked_times.day_type), so no holiday list is needed. Approved leave is read from timeoff/leaves; pass skip_leave: false when the source system is right and Factorial's leave record is stale. Half-day leave days are left out of log_range and named in the preview; write the worked half with log_days.
Nobody clocks in at exactly 09:00 every day, and a month of identical entries is the one pattern a real registro never shows. Pass jitter_minutes (5 to 10 is sensible) to log_range or log_days and each written time varies by up to that many minutes from your pattern, within the day; segments never cross each other. Pass variation_minutes for a different kind of variation: it shifts a whole day by one deterministic offset so the start time drifts from day to day, which jitter_minutes cannot produce because it only varies segments within a day. Both are derived from the employee, the date and (for jitter) the segment, so the preview lists the exact times that will be written, the confirmation token binds to them, and a retry recognises its own earlier records. The records still carry source: "api"; these options make reconstructed hours realistic, they do not disguise where they came from.
Every gaps, audit and bulk-write preview starts with a Data read line: how many days of the window have contract data, and how many leave and shift records were read. Reads follow Factorial's pagination to the end, so a window of any length is complete; a date the API returned nothing for is reported as no_contract_data and never written, rather than passed off as a day that was not workable. Those dates normally precede the start of employment. If they do not, the read was incomplete and the result should not be trusted.
Signed-off periods. Once a date's timesheet has been reviewed in Factorial, it is closed for writing; a shift write on it is refused with a 403. audit reads attendance/reviews alongside the other facts and marks such dates signed off before anything is written; log_range and log_days skip them the same way they skip a weekend or an approved leave day. The way to correct a signed-off day is create_edit_request, which files a request that whoever approves timesheets then decides on; list_edit_requests reads what has been filed. Filing a request is previewed and token-gated even for the configured identity, because it notifies a person.
A date that does not exist, such as 2026-02-30, is refused rather than silently rolled forward to the next valid date; a real leap day such as 2024-02-29 is accepted normally.
Auditing a month. Run audit for the range first. A day within tolerance_minutes (default 15) of its expected total counts as complete, so realistic clock-ins and jittered backfills do not read as shortfalls. Compare the ledger with what you know locally (your calendar, another time-tracking system, days you actually worked on a holiday), then fix the differences: log_range or log_days for missing days, delete or update for wrong records, and audit again to confirm every workday reads complete.
Five prompts wrap these workflows for the user, and a guide resource documents them for the model; see 5 MCP Prompts and factorial://guides/registro-horario. Some clients (Claude Code included) surface a prompt only as a slash command the human invokes, with no way for the model itself to read one, so each prompt's procedure is also published as a resource at factorial://prompts/<name>.
Set FACTORIAL_EMPLOYEE_ID to your own employee id so that employee_id can be omitted. Writes aimed at anyone else, and every bulk write, require a confirmation token; see Safety & Security.
6 MCP Resources
| Resource URI | Description |
|---|---|
factorial://org-chart | Complete organizational hierarchy (Markdown) |
factorial://guides/registro-horario | How to audit, fill and maintain a registro horario with this server (Markdown) |
factorial://employees/directory | Employee directory by team (Markdown) |
factorial://locations/directory | Location directory with employee counts (Markdown) |
factorial://timeoff/policies | All leave types and policies (JSON) |
factorial://teams/{team_id} | Team details with member list (JSON, templated) |
factorial://prompts/{name} | The procedure text of an attendance prompt, readable without invoking it (Markdown) |
5 MCP Prompts
Prompts are procedures the user invokes (in Claude Code they appear as /mcp__factorial__<name> slash commands). The attendance prompts pre-read the data the procedure starts from and state the exact tool calls that follow, so a small model can carry the workflow through. A prompt never writes anything itself; writes go through factorial_attendance and its confirmation gate.
Shortened here. Read the whole README on GitHub.
Signals
- GitHub stars
- 3
- Forks
- 1
- Last commit
- Sep 2026
- Weekly_downloads
- 63 weekly_downloads
Advanced
- Delivery
- mcp-factorial MCP server → your Ahel connector (mcp.ahel.ai) → your AI.
- Item type
- mcp-server
- Key
io-github-t4dhg-mcp-factorial- Source
- github.com/t4dhg/mcp-factorial
github.com/t4dhg/mcp-factorial