This is a full-featured FactorialHR integration that gives Claude native access to your entire HR platform. It exposes 117 operations across employees, teams, time off, attendance, projects, ATS, payroll, and documents through 14 hierarchical tools that reduce context usage by 88% compared to individual endpoints. You get proper CRUD operations with safety guardrails on destructive actions like terminations and deletions, plus audit logging for compliance. It includes MCP resources for org charts and employee directories, and prompts for onboarding checklists and team reports. Reach for this when you need AI-powered HR workflows that go beyond read-only data dumps. Note that document downloads require OAuth2, while most operations work with API key authentication.
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.
The MCP server uses a hierarchical tool structure for optimal context usage. Instead of 117 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 | Shift management | list, get, create, update, delete |
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' });
| 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 |
| 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) |
| Resource URI | Description |
|---|---|
factorial://org-chart | Complete organizational hierarchy (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) |
| Prompt | Description |
|---|---|
onboard-employee | Generate personalized onboarding checklists |
analyze-org-structure | Analyze org structure (reporting lines, team sizes, distribution) |
timeoff-report | Generate time off reports by team or date range |
team-document-summary | Summarize documents across a team (certifications, payslips, etc) |
{
"mcpServers": {
"factorial": {
"command": "npx",
"args": ["-y", "@t4dhg/mcp-factorial"]
}
}
}
Create a .env file in your project root:
FACTORIAL_API_KEY=your-api-key-here
Or pass it directly in the MCP config:
{
"mcpServers": {
"factorial": {
"command": "npx",
"args": ["-y", "@t4dhg/mcp-factorial"],
"env": {
"FACTORIAL_API_KEY": "your-api-key-here"
}
}
}
}
Once configured, ask Claude things like:
You'll need a FactorialHR API key to use this MCP server. Here's how to get one:
.env file or MCP configurationImportant: API keys have full access to your FactorialHR data and never expire. Store them securely, never commit them to version control, and rotate them periodically.
Document download actions (download_payslips, download) require OAuth2 authentication. This is a Factorial API limitation - the download endpoint does not accept API key authentication.
Note: You need admin access in Factorial to create OAuth applications.
http://localhost:8080/callback (or any URL you can access)Open this URL in your browser (replace YOUR_CLIENT_ID):
https://api.factorialhr.com/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=http://localhost:8080/callback&response_type=code
?code=AUTHORIZATION_CODERun this curl command (replace placeholders):
curl -X POST 'https://api.factorialhr.com/oauth/token' \
-d 'client_id=YOUR_CLIENT_ID' \
-d 'client_secret=YOUR_CLIENT_SECRET' \
-d 'code=AUTHORIZATION_CODE' \
-d 'grant_type=authorization_code' \
-d 'redirect_uri=http://localhost:8080/callback'
You'll get a response with access_token and refresh_token. Save the refresh_token.
Add OAuth2 credentials to your MCP configuration:
{
"mcpServers": {
"factorial": {
"command": "npx",
"args": ["-y", "@t4dhg/mcp-factorial"],
"env": {
"FACTORIAL_API_KEY": "your-api-key",
"FACTORIAL_OAUTH_CLIENT_ID": "your-client-id",
"FACTORIAL_OAUTH_CLIENT_SECRET": "your-client-secret",
"FACTORIAL_OAUTH_REFRESH_TOKEN": "your-refresh-token"
}
}
}
}
Or add to your .env file:
FACTORIAL_API_KEY=your-api-key
FACTORIAL_OAUTH_CLIENT_ID=your-client-id
FACTORIAL_OAUTH_CLIENT_SECRET=your-client-secret
FACTORIAL_OAUTH_REFRESH_TOKEN=your-refresh-token
| Environment Variable | Description | Default |
|---|---|---|
FACTORIAL_API_KEY | Your FactorialHR API key | Required |
FACTORIAL_API_VERSION | API version | 2025-10-01 |
FACTORIAL_TIMEOUT_MS | Request timeout (ms) | 30000 |
FACTORIAL_MAX_RETRIES | Max retry attempts | 3 |
DEBUG | Enable debug logging | false |
FACTORIAL_OAUTH_CLIENT_ID | OAuth2 client ID (for downloads) | - |
FACTORIAL_OAUTH_CLIENT_SECRET | OAuth2 client secret (for downloads) | - |
FACTORIAL_OAUTH_REFRESH_TOKEN | OAuth2 refresh token (for downloads) | - |
The following operations are marked as high-risk and require explicit confirmation (confirm: true):
factorial_employees({ action: 'terminate' }) - Terminates an employeefactorial_teams({ action: 'delete' }) - Permanently deletes a teamfactorial_locations({ action: 'delete' }) - Permanently deletes a locationfactorial_projects({ action: 'delete' }) - Permanently deletes a projectfactorial_ats({ action: 'delete_candidate' }) - Permanently deletes a candidateSome categories are intentionally read-only for security:
Document and contract list operations return summary format by default to prevent token overflow:
Documents (factorial_documents({ action: 'list' })):
id, name, folder_id, employee_id, mime_type (5 fields)factorial_documents({ action: 'get', id: X }) for complete metadataContracts (factorial_contracts({ action: 'list' })):
id, employee_id, job_title, effective_on (4 fields)All list operations accept page and limit parameters for pagination control.
All write operations (create, update, delete, approve, reject) are logged with:
# Clone the repository
git clone https://github.com/t4dhg/mcp-factorial.git
cd mcp-factorial
# Install dependencies
npm install
# Build
npm run build
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Lint
npm run lint
# Format
npm run format
# Run locally
FACTORIAL_API_KEY=your-key npm start
# Test with MCP Inspector
npx @modelcontextprotocol/inspector
The codebase is organized into domain-based modules for maintainability:
src/
├── schemas/ # Zod schemas by domain
│ ├── employees.ts # Employee, Team, Location, Contract schemas
│ ├── time-off.ts # Leave, LeaveType, Allowance, Shift schemas
│ ├── projects.ts # Project, Task, Worker, TimeRecord schemas
│ ├── training.ts # Training, Session, Membership schemas
│ ├── ats.ts # JobPosting, Candidate, Application schemas
│ └── ...
├── api/ # API functions by domain
│ ├── employees.ts # listEmployees, getEmployee, createEmployee, etc.
│ ├── time-off.ts # listLeaves, createLeave, approveLeave, etc.
│ ├── projects.ts # listProjects, createProject, etc.
│ └── ...
├── tools/ # MCP tool registrations by domain
│ ├── employees.ts # factorial_employees tool registration
│ ├── time-off.ts # factorial_time_off tool registration
│ ├── index.ts # Server setup, discovery tool, resources, prompts
│ └── ...
├── index.ts # Entry point (re-exports from tools/)
├── api.ts # Re-exports from api/
└── schemas.ts # Re-exports from schemas/
Adding a new feature:
src/schemas/{domain}.tssrc/api/{domain}.tssrc/tools/{domain}.tssrc/schemas/index.ts, src/api/index.ts exports if needednpm test to verifyThe server implements exponential backoff for rate limits. If you're hitting limits frequently:
hired_on field: The FactorialHR API may not populate this for all employeesDocument downloads require OAuth2 authentication. This is a Factorial API limitation - the download endpoint does not accept API key authentication.
If you see an error like:
"Document download requires OAuth2 authentication"
You need to set up OAuth2 credentials. See OAuth2 Setup above.
Note: OAuth2 refresh tokens expire after 1 week. If downloads suddenly stop working, re-authorize and get a new refresh token.
The Factorial API's individual document endpoint (GET /documents/{id}) has limitations accessing employee-specific documents. This happens because:
list_documents with employee_ids filter correctly returns all employee documentsget_document by ID cannot access those same documents individuallyWorkaround: Use download_payslips action instead of download action. The download_payslips action uses the document metadata from the list operation directly, bypassing the problematic individual GET endpoint:
// This works - uses document list internally
factorial_documents({
action: 'download_payslips',
employee_id: 123,
output_dir: '/path/to/downloads',
});
Q: Does this expose salary/payroll data? A: Payroll data (supplements, tax identifiers, family situations) is available read-only. No write operations for payroll are supported.
Q: Can Claude modify data in Factorial? A: Yes! Full CRUD operations are available for employees, teams, locations, time off, projects, training, and recruiting. High-risk operations are clearly marked.
Q: How is data cached? A: Data is cached in-memory with TTLs: employees (5 min), teams (10 min), locations (15 min), contracts (3 min).
Q: What FactorialHR API version is used?
A: Version 2025-10-01 by default. Override with FACTORIAL_API_VERSION environment variable.
Q: Are write operations logged? A: Yes, all write operations are logged via the audit module for compliance and debugging.
The FactorialHR API has some design patterns that differ from typical REST APIs. This MCP server handles these automatically, but understanding them helps when debugging or extending:
| Data | Expected Location | Actual Location | Impact |
|---|---|---|---|
| Team membership | On Employee object (team_ids) | On Team object (employee_ids) | Use list_teams to find an employee's teams |
| Job role assignment | On Employee object (job_role_id) | In Contract object (job_catalog_role_id) | Use get_employee_with_contract for role info |
| Salary information | On Employee object | In Contract object (salary_amount, salary_frequency) | Use get_employee_with_contract for salary |
| Job title | On Employee object | In Contract object (job_title) | May be null if not set in Factorial |
| Endpoint | Quirk | Workaround |
|---|---|---|
GET /employees/{id} | May return 404 for valid employees | Server falls back to listing all and filtering |
GET /documents/{id} | May return 404 for employee-specific documents | Use download_payslips which bypasses this |
GET /contracts?employee_id=X | Filtering unreliable | Server fetches all and filters client-side |
| Empty results | Returns {"errors": null} instead of `{"data": []} | Server handles both formats |
| Document download URLs | Requires OAuth2 (API key does not work) | Configure OAuth2 credentials for downloads |
Some fields may be null even when you expect data:
job_title: Only populated if set in employee's contractmanager_id: Only populated if reporting structure is configuredseniority_calculation_date: Use this instead of the non-existent hired_on fieldname, mime_type, size_bytes): May be null for some documentsSalary information is available in the Contract entity, not the Employee entity:
salary_amount: number (in cents, e.g., 7000000 = €70,000)
salary_frequency: 'yearly' | 'monthly' | 'weekly' | 'daily' | 'hourly'
Use get_employee_with_contract to retrieve employee data with their latest salary information.
get_employee_with_contract instead of get_employeelist_employees_by_job_role with a job role IDlist_teams and check employee_ids arraysContributions are welcome! Please see CONTRIBUTING.md for guidelines.
MIT © Taig Mac Carthy
Built with the Model Context Protocol by Anthropic
FACTORIAL_API_KEY*secretAPI key from FactorialHR (Settings > API Keys)
FACTORIAL_API_VERSIONAPI version (default: 2025-10-01)
FACTORIAL_TIMEOUT_MSRequest timeout in milliseconds (default: 30000)
FACTORIAL_MAX_RETRIESMaximum retry attempts (default: 3)
DEBUGEnable debug logging (default: false)
io.github.mindstone/mcp-server-microsoft-teams
com.mintmcp/outlook-email
helbertparanhos/resend-email-mcp
marlinjai/email-mcp
io.github.mindstone/mcp-server-email-imap
io.github.osamahassouna/email-playbook-mcp