@t4dhg/mcp-factorial 7.1.0 → 8.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/README.md +278 -55
  2. package/dist/api/ats.d.ts +78 -0
  3. package/dist/api/ats.d.ts.map +1 -0
  4. package/dist/api/ats.js +173 -0
  5. package/dist/api/ats.js.map +1 -0
  6. package/dist/api/attendance.d.ts +27 -0
  7. package/dist/api/attendance.d.ts.map +1 -0
  8. package/dist/api/attendance.js +62 -0
  9. package/dist/api/attendance.js.map +1 -0
  10. package/dist/api/contracts.d.ts +40 -0
  11. package/dist/api/contracts.d.ts.map +1 -0
  12. package/dist/api/contracts.js +146 -0
  13. package/dist/api/contracts.js.map +1 -0
  14. package/dist/api/documents.d.ts +95 -0
  15. package/dist/api/documents.d.ts.map +1 -0
  16. package/dist/api/documents.js +265 -0
  17. package/dist/api/documents.js.map +1 -0
  18. package/dist/api/employees.d.ts +35 -0
  19. package/dist/api/employees.d.ts.map +1 -0
  20. package/dist/api/employees.js +116 -0
  21. package/dist/api/employees.js.map +1 -0
  22. package/dist/api/index.d.ts +22 -0
  23. package/dist/api/index.d.ts.map +1 -0
  24. package/dist/api/index.js +36 -0
  25. package/dist/api/index.js.map +1 -0
  26. package/dist/api/job-catalog.d.ts +21 -0
  27. package/dist/api/job-catalog.d.ts.map +1 -0
  28. package/dist/api/job-catalog.js +34 -0
  29. package/dist/api/job-catalog.js.map +1 -0
  30. package/dist/api/locations.d.ts +26 -0
  31. package/dist/api/locations.d.ts.map +1 -0
  32. package/dist/api/locations.js +59 -0
  33. package/dist/api/locations.js.map +1 -0
  34. package/dist/api/payroll.d.ts +30 -0
  35. package/dist/api/payroll.d.ts.map +1 -0
  36. package/dist/api/payroll.js +68 -0
  37. package/dist/api/payroll.js.map +1 -0
  38. package/dist/api/projects.d.ts +82 -0
  39. package/dist/api/projects.d.ts.map +1 -0
  40. package/dist/api/projects.js +193 -0
  41. package/dist/api/projects.js.map +1 -0
  42. package/dist/api/shared.d.ts +12 -0
  43. package/dist/api/shared.d.ts.map +1 -0
  44. package/dist/api/shared.js +17 -0
  45. package/dist/api/shared.js.map +1 -0
  46. package/dist/api/teams.d.ts +26 -0
  47. package/dist/api/teams.d.ts.map +1 -0
  48. package/dist/api/teams.js +59 -0
  49. package/dist/api/teams.js.map +1 -0
  50. package/dist/api/time-off.d.ts +47 -0
  51. package/dist/api/time-off.d.ts.map +1 -0
  52. package/dist/api/time-off.js +112 -0
  53. package/dist/api/time-off.js.map +1 -0
  54. package/dist/api/training.d.ts +62 -0
  55. package/dist/api/training.d.ts.map +1 -0
  56. package/dist/api/training.js +147 -0
  57. package/dist/api/training.js.map +1 -0
  58. package/dist/api/work-areas.d.ts +30 -0
  59. package/dist/api/work-areas.d.ts.map +1 -0
  60. package/dist/api/work-areas.js +71 -0
  61. package/dist/api/work-areas.js.map +1 -0
  62. package/dist/api.d.ts +3 -464
  63. package/dist/api.d.ts.map +1 -1
  64. package/dist/api.js +3 -1274
  65. package/dist/api.js.map +1 -1
  66. package/dist/audit.d.ts +8 -3
  67. package/dist/audit.d.ts.map +1 -1
  68. package/dist/audit.js +8 -3
  69. package/dist/audit.js.map +1 -1
  70. package/dist/index.d.ts +6 -13
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +6 -3860
  73. package/dist/index.js.map +1 -1
  74. package/dist/oauth.d.ts +63 -0
  75. package/dist/oauth.d.ts.map +1 -0
  76. package/dist/oauth.js +143 -0
  77. package/dist/oauth.js.map +1 -0
  78. package/dist/schemas/ats.d.ts +326 -0
  79. package/dist/schemas/ats.d.ts.map +1 -0
  80. package/dist/schemas/ats.js +121 -0
  81. package/dist/schemas/ats.js.map +1 -0
  82. package/dist/schemas/documents.d.ts +78 -0
  83. package/dist/schemas/documents.d.ts.map +1 -0
  84. package/dist/schemas/documents.js +34 -0
  85. package/dist/schemas/documents.js.map +1 -0
  86. package/dist/schemas/employees.d.ts +528 -0
  87. package/dist/schemas/employees.d.ts.map +1 -0
  88. package/dist/schemas/employees.js +206 -0
  89. package/dist/schemas/employees.js.map +1 -0
  90. package/dist/schemas/index.d.ts +18 -0
  91. package/dist/schemas/index.d.ts.map +1 -0
  92. package/dist/schemas/index.js +28 -0
  93. package/dist/schemas/index.js.map +1 -0
  94. package/dist/schemas/job-catalog.d.ts +57 -0
  95. package/dist/schemas/job-catalog.d.ts.map +1 -0
  96. package/dist/schemas/job-catalog.js +27 -0
  97. package/dist/schemas/job-catalog.js.map +1 -0
  98. package/dist/schemas/payroll.d.ts +98 -0
  99. package/dist/schemas/payroll.d.ts.map +1 -0
  100. package/dist/schemas/payroll.js +43 -0
  101. package/dist/schemas/payroll.js.map +1 -0
  102. package/dist/schemas/projects.d.ts +277 -0
  103. package/dist/schemas/projects.d.ts.map +1 -0
  104. package/dist/schemas/projects.js +114 -0
  105. package/dist/schemas/projects.js.map +1 -0
  106. package/dist/schemas/shared.d.ts +42 -0
  107. package/dist/schemas/shared.d.ts.map +1 -0
  108. package/dist/schemas/shared.js +65 -0
  109. package/dist/schemas/shared.js.map +1 -0
  110. package/dist/schemas/time-off.d.ts +286 -0
  111. package/dist/schemas/time-off.d.ts.map +1 -0
  112. package/dist/schemas/time-off.js +111 -0
  113. package/dist/schemas/time-off.js.map +1 -0
  114. package/dist/schemas/training.d.ts +223 -0
  115. package/dist/schemas/training.d.ts.map +1 -0
  116. package/dist/schemas/training.js +88 -0
  117. package/dist/schemas/training.js.map +1 -0
  118. package/dist/schemas/work-areas.d.ts +71 -0
  119. package/dist/schemas/work-areas.d.ts.map +1 -0
  120. package/dist/schemas/work-areas.js +30 -0
  121. package/dist/schemas/work-areas.js.map +1 -0
  122. package/dist/schemas.d.ts +3 -1932
  123. package/dist/schemas.d.ts.map +1 -1
  124. package/dist/schemas.js +3 -785
  125. package/dist/schemas.js.map +1 -1
  126. package/dist/tool-utils.d.ts +0 -14
  127. package/dist/tool-utils.d.ts.map +1 -1
  128. package/dist/tool-utils.js +5 -18
  129. package/dist/tool-utils.js.map +1 -1
  130. package/dist/tools/ats.d.ts +6 -0
  131. package/dist/tools/ats.d.ts.map +1 -0
  132. package/dist/tools/ats.js +206 -0
  133. package/dist/tools/ats.js.map +1 -0
  134. package/dist/tools/attendance.d.ts +6 -0
  135. package/dist/tools/attendance.d.ts.map +1 -0
  136. package/dist/tools/attendance.js +86 -0
  137. package/dist/tools/attendance.js.map +1 -0
  138. package/dist/tools/contracts.d.ts +6 -0
  139. package/dist/tools/contracts.d.ts.map +1 -0
  140. package/dist/tools/contracts.js +63 -0
  141. package/dist/tools/contracts.js.map +1 -0
  142. package/dist/tools/documents.d.ts +6 -0
  143. package/dist/tools/documents.d.ts.map +1 -0
  144. package/dist/tools/documents.js +135 -0
  145. package/dist/tools/documents.js.map +1 -0
  146. package/dist/tools/employees.d.ts +6 -0
  147. package/dist/tools/employees.d.ts.map +1 -0
  148. package/dist/tools/employees.js +125 -0
  149. package/dist/tools/employees.js.map +1 -0
  150. package/dist/tools/index.d.ts +17 -0
  151. package/dist/tools/index.d.ts.map +1 -0
  152. package/dist/tools/index.js +221 -0
  153. package/dist/tools/index.js.map +1 -0
  154. package/dist/tools/job-catalog.d.ts +6 -0
  155. package/dist/tools/job-catalog.d.ts.map +1 -0
  156. package/dist/tools/job-catalog.js +39 -0
  157. package/dist/tools/job-catalog.js.map +1 -0
  158. package/dist/tools/locations.d.ts +6 -0
  159. package/dist/tools/locations.d.ts.map +1 -0
  160. package/dist/tools/locations.js +93 -0
  161. package/dist/tools/locations.js.map +1 -0
  162. package/dist/tools/payroll.d.ts +6 -0
  163. package/dist/tools/payroll.d.ts.map +1 -0
  164. package/dist/tools/payroll.js +76 -0
  165. package/dist/tools/payroll.js.map +1 -0
  166. package/dist/tools/projects.d.ts +6 -0
  167. package/dist/tools/projects.d.ts.map +1 -0
  168. package/dist/tools/projects.js +203 -0
  169. package/dist/tools/projects.js.map +1 -0
  170. package/dist/tools/shared.d.ts +84 -0
  171. package/dist/tools/shared.d.ts.map +1 -0
  172. package/dist/tools/shared.js +164 -0
  173. package/dist/tools/shared.js.map +1 -0
  174. package/dist/tools/teams.d.ts +6 -0
  175. package/dist/tools/teams.d.ts.map +1 -0
  176. package/dist/tools/teams.js +81 -0
  177. package/dist/tools/teams.js.map +1 -0
  178. package/dist/tools/time-off.d.ts +6 -0
  179. package/dist/tools/time-off.d.ts.map +1 -0
  180. package/dist/tools/time-off.js +144 -0
  181. package/dist/tools/time-off.js.map +1 -0
  182. package/dist/tools/training.d.ts +6 -0
  183. package/dist/tools/training.d.ts.map +1 -0
  184. package/dist/tools/training.js +160 -0
  185. package/dist/tools/training.js.map +1 -0
  186. package/dist/tools/work-areas.d.ts +6 -0
  187. package/dist/tools/work-areas.d.ts.map +1 -0
  188. package/dist/tools/work-areas.js +85 -0
  189. package/dist/tools/work-areas.js.map +1 -0
  190. package/dist/utils.d.ts +35 -0
  191. package/dist/utils.d.ts.map +1 -1
  192. package/dist/utils.js +97 -0
  193. package/dist/utils.js.map +1 -1
  194. package/dist/write-safety.d.ts +229 -2
  195. package/dist/write-safety.d.ts.map +1 -1
  196. package/dist/write-safety.js +42 -3
  197. package/dist/write-safety.js.map +1 -1
  198. package/llms.txt +2 -2
  199. package/package.json +2 -2
package/README.md CHANGED
@@ -1,6 +1,10 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/t4dhg/mcp-factorial/main/assets/factorial-logo.svg" alt="FactorialHR" height="48">
3
+ </p>
4
+
1
5
  # MCP FactorialHR
2
6
 
3
- > **The definitive Model Context Protocol server for FactorialHR**
7
+ > **The definitive Model Context Protocol server for [FactorialHR](https://factorialhr.com)**
4
8
 
5
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
10
  [![CI](https://github.com/t4dhg/mcp-factorial/actions/workflows/ci.yml/badge.svg)](https://github.com/t4dhg/mcp-factorial/actions/workflows/ci.yml)
@@ -10,37 +14,82 @@
10
14
  [![Node.js](https://img.shields.io/badge/Node.js-18%2B-brightgreen.svg)](https://nodejs.org/)
11
15
  [![npm version](https://img.shields.io/npm/v/@t4dhg/mcp-factorial.svg)](https://www.npmjs.com/package/@t4dhg/mcp-factorial)
12
16
  [![MCP Compatible](https://img.shields.io/badge/MCP-Compatible-green.svg)](https://modelcontextprotocol.io/)
13
- [![smithery badge](https://smithery.ai/badge/@t4dhg/mcp-factorial)](https://smithery.ai/server/@t4dhg/mcp-factorial)
14
17
 
15
18
  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.
16
19
 
17
20
  ## Why This MCP Server?
18
21
 
19
- - **Comprehensive Coverage**: 85+ tools spanning employees, teams, time off, attendance, projects, training, recruiting (ATS), and payroll
22
+ - **Context-Optimized**: 14 hierarchical tools (117 operations) with 88% less context usage than individual tools
20
23
  - **Full CRUD Operations**: Create, read, update, and delete across all major entities
21
24
  - **Safety Guardrails**: High-risk operations require explicit confirmation
22
- - **Audit Logging**: All write operations are logged for compliance
25
+ - **Audit Logging**: All write operations are logged with timestamps and context for debugging
23
26
  - **Enterprise Ready**: Built for companies who need AI integration with proper controls
24
27
 
25
28
  ## Features
26
29
 
27
- ### 85+ Tools
28
-
29
- | Category | Tools | Operations |
30
- | --------------- | ----- | ----------------------------------------------------------------------- |
31
- | **Employees** | 6 | List, get, search, create, update, terminate |
32
- | **Teams** | 5 | List, get, create, update, delete |
33
- | **Locations** | 5 | List, get, create, update, delete |
34
- | **Time Off** | 10 | List leaves/types/allowances, create, update, cancel, approve, reject |
35
- | **Attendance** | 5 | List shifts, create, update, delete |
36
- | **Projects** | 17 | Full CRUD for projects, tasks, workers, time records |
37
- | **Training** | 14 | Full CRUD for trainings, sessions, enrollments |
38
- | **Work Areas** | 6 | List, get, create, update, archive, unarchive |
39
- | **ATS** | 16 | Job postings, candidates, applications, hiring stages, advance workflow |
40
- | **Payroll** | 6 | List/get supplements, tax identifiers, family situations (read-only) |
41
- | **Documents** | 5 | List/get/search folders and documents (read-only) |
42
- | **Job Catalog** | 3 | List/get job roles and levels (read-only) |
43
- | **Contracts** | 4 | Get contracts, employee with contract, by job role/level (read-only) |
30
+ ### Hierarchical Tool Discovery (v8.0.0+)
31
+
32
+ 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.
33
+
34
+ | Tool | Description | Actions |
35
+ | ----------------------- | ----------------------------- | -------------------------------------------------- |
36
+ | `factorial_discover` | Discover available categories | - |
37
+ | `factorial_employees` | Employee management | list, get, search, create, update, terminate |
38
+ | `factorial_teams` | Team management | list, get, create, update, delete |
39
+ | `factorial_locations` | Location management | list, get, create, update, delete |
40
+ | `factorial_contracts` | Contract/salary data | list, get_with_employee, by_job_role, by_job_level |
41
+ | `factorial_time_off` | Leave management | 10 actions |
42
+ | `factorial_attendance` | Shift management | list, get, create, update, delete |
43
+ | `factorial_documents` | Document management | 8 actions (downloads require OAuth2 - see below) |
44
+ | `factorial_job_catalog` | Job roles/levels | list_roles, get_role, list_levels |
45
+ | `factorial_projects` | Project management | 16 actions for projects, tasks, workers, time |
46
+ | `factorial_training` | Training management | 12 actions for trainings, sessions, enrollments |
47
+ | `factorial_work_areas` | Work area management | list, get, create, update, archive, unarchive |
48
+ | `factorial_ats` | Applicant tracking | 17 actions for recruiting |
49
+ | `factorial_payroll` | Payroll data (read-only) | 6 actions |
50
+
51
+ **Example Usage:**
52
+
53
+ ```typescript
54
+ // List all employees
55
+ factorial_employees({ action: 'list', page: 1, limit: 50 });
56
+
57
+ // Get a specific employee
58
+ factorial_employees({ action: 'get', id: 123 });
59
+
60
+ // Search employees
61
+ factorial_employees({ action: 'search', query: 'john' });
62
+
63
+ // Create a leave request
64
+ factorial_time_off({
65
+ action: 'create',
66
+ employee_id: 123,
67
+ leave_type_id: 1,
68
+ start_on: '2026-02-01',
69
+ finish_on: '2026-02-05',
70
+ });
71
+
72
+ // Discover available actions for a category
73
+ factorial_discover({ category: 'employees' });
74
+ ```
75
+
76
+ ### 117 Operations Across 14 Categories
77
+
78
+ | Category | Operations |
79
+ | --------------- | ------------------------------------------------------------------------------------------------------ |
80
+ | **Employees** | list, get, search, create, update, terminate |
81
+ | **Teams** | list, get, create, update, delete |
82
+ | **Locations** | list, get, create, update, delete |
83
+ | **Time Off** | list_leaves, get_leave, list_types, get_type, list_allowances, create, update, cancel, approve, reject |
84
+ | **Attendance** | list, get, create, update, delete |
85
+ | **Projects** | 16 operations for projects, tasks, workers, time records |
86
+ | **Training** | 12 operations for trainings, sessions, enrollments |
87
+ | **Work Areas** | list, get, create, update, archive, unarchive |
88
+ | **ATS** | 17 operations for job postings, candidates, applications, hiring stages |
89
+ | **Payroll** | list/get supplements, tax identifiers, family situations (read-only) |
90
+ | **Documents** | 8 operations for folders, documents, and downloads (⚠️ downloads require OAuth2) |
91
+ | **Job Catalog** | list_roles, get_role, list_levels (read-only) |
92
+ | **Contracts** | list, get_with_employee, by_job_role, by_job_level (read-only) |
44
93
 
45
94
  ### 5 MCP Resources
46
95
 
@@ -64,7 +113,7 @@ A comprehensive Model Context Protocol (MCP) server that provides AI assistants
64
113
  ### Architecture Features
65
114
 
66
115
  - **Safety Guardrails**: High-risk operations (terminate, delete) marked for confirmation
67
- - **Audit Logging**: All write operations logged with timestamps and context
116
+ - **Audit Logging**: All write operations logged in-process with timestamps and context
68
117
  - **Caching**: In-memory TTL-based caching (configurable by resource type)
69
118
  - **Pagination**: All list operations support pagination
70
119
  - **Retry Logic**: Exponential backoff with rate limit handling
@@ -135,6 +184,87 @@ You'll need a FactorialHR API key to use this MCP server. Here's how to get one:
135
184
 
136
185
  > **Important**: 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.
137
186
 
187
+ ## OAuth2 Setup (Required for Document Downloads)
188
+
189
+ Document download actions (`download_payslips`, `download`) **require OAuth2 authentication**. This is a Factorial API limitation - the download endpoint does not accept API key authentication.
190
+
191
+ > **Note**: You need admin access in Factorial to create OAuth applications.
192
+
193
+ ### Step 1: Create an OAuth2 Application
194
+
195
+ 1. Go to: **https://api.factorialhr.com/oauth/applications**
196
+ 2. Click **"New application"**
197
+ 3. Fill in:
198
+ - **Redirect URI**: `http://localhost:8080/callback` (or any URL you can access)
199
+ - **Confidentiality**: Yes (server application)
200
+ - **Scopes**: Select the scopes you need:
201
+ - **Required for downloads**: Documents, Employees
202
+ - **Recommended for full MCP functionality**: Contracts, Payroll, Payroll supplements, Time off, Shift management, Trainings, Recruitment, Company locations, Job catalog
203
+ 4. Save and note your **Client ID** and **Client Secret**
204
+
205
+ ### Step 2: Get Authorization Code
206
+
207
+ Open this URL in your browser (replace `YOUR_CLIENT_ID`):
208
+
209
+ ```
210
+ https://api.factorialhr.com/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=http://localhost:8080/callback&response_type=code
211
+ ```
212
+
213
+ - Log in and authorize the app
214
+ - You'll be redirected to your callback URL with `?code=AUTHORIZATION_CODE`
215
+ - Copy that code from the URL (it expires quickly, so proceed to step 3 immediately)
216
+
217
+ ### Step 3: Exchange Code for Tokens
218
+
219
+ Run this curl command (replace placeholders):
220
+
221
+ ```bash
222
+ curl -X POST 'https://api.factorialhr.com/oauth/token' \
223
+ -d 'client_id=YOUR_CLIENT_ID' \
224
+ -d 'client_secret=YOUR_CLIENT_SECRET' \
225
+ -d 'code=AUTHORIZATION_CODE' \
226
+ -d 'grant_type=authorization_code' \
227
+ -d 'redirect_uri=http://localhost:8080/callback'
228
+ ```
229
+
230
+ You'll get a response with `access_token` and `refresh_token`. Save the **refresh_token**.
231
+
232
+ ### Step 4: Configure MCP Server
233
+
234
+ Add OAuth2 credentials to your MCP configuration:
235
+
236
+ ```json
237
+ {
238
+ "mcpServers": {
239
+ "factorial": {
240
+ "command": "npx",
241
+ "args": ["-y", "@t4dhg/mcp-factorial"],
242
+ "env": {
243
+ "FACTORIAL_API_KEY": "your-api-key",
244
+ "FACTORIAL_OAUTH_CLIENT_ID": "your-client-id",
245
+ "FACTORIAL_OAUTH_CLIENT_SECRET": "your-client-secret",
246
+ "FACTORIAL_OAUTH_REFRESH_TOKEN": "your-refresh-token"
247
+ }
248
+ }
249
+ }
250
+ }
251
+ ```
252
+
253
+ Or add to your `.env` file:
254
+
255
+ ```env
256
+ FACTORIAL_API_KEY=your-api-key
257
+ FACTORIAL_OAUTH_CLIENT_ID=your-client-id
258
+ FACTORIAL_OAUTH_CLIENT_SECRET=your-client-secret
259
+ FACTORIAL_OAUTH_REFRESH_TOKEN=your-refresh-token
260
+ ```
261
+
262
+ ### Important Notes
263
+
264
+ - **Refresh tokens expire after 1 week** - you'll need to repeat steps 2-3 if it expires
265
+ - The MCP server automatically refreshes access tokens using the refresh token
266
+ - If document downloads suddenly stop working, your refresh token has likely expired
267
+
138
268
  ## Use Cases
139
269
 
140
270
  ### For Managers
@@ -162,60 +292,86 @@ You'll need a FactorialHR API key to use this MCP server. Here's how to get one:
162
292
 
163
293
  ## Configuration Options
164
294
 
165
- | Environment Variable | Description | Default |
166
- | ----------------------- | ------------------------ | ------------ |
167
- | `FACTORIAL_API_KEY` | Your FactorialHR API key | Required |
168
- | `FACTORIAL_API_VERSION` | API version | `2025-10-01` |
169
- | `FACTORIAL_TIMEOUT_MS` | Request timeout (ms) | `30000` |
170
- | `FACTORIAL_MAX_RETRIES` | Max retry attempts | `3` |
171
- | `DEBUG` | Enable debug logging | `false` |
295
+ | Environment Variable | Description | Default |
296
+ | ------------------------------- | ------------------------------------ | ------------ |
297
+ | `FACTORIAL_API_KEY` | Your FactorialHR API key | Required |
298
+ | `FACTORIAL_API_VERSION` | API version | `2025-10-01` |
299
+ | `FACTORIAL_TIMEOUT_MS` | Request timeout (ms) | `30000` |
300
+ | `FACTORIAL_MAX_RETRIES` | Max retry attempts | `3` |
301
+ | `DEBUG` | Enable debug logging | `false` |
302
+ | `FACTORIAL_OAUTH_CLIENT_ID` | OAuth2 client ID (for downloads) | - |
303
+ | `FACTORIAL_OAUTH_CLIENT_SECRET` | OAuth2 client secret (for downloads) | - |
304
+ | `FACTORIAL_OAUTH_REFRESH_TOKEN` | OAuth2 refresh token (for downloads) | - |
172
305
 
173
306
  ## Safety & Security
174
307
 
175
- ### High-Risk Operations
308
+ ### Operations That Require Confirmation
176
309
 
177
- The following operations are marked as high-risk and should be used with care:
310
+ The following operations require explicit confirmation (`confirm: true`). Called without it, the tool returns a warning describing the impact and makes no change. Risk is classified per operation in `src/write-safety.ts`; everything below is gated regardless of whether it is rated high or medium:
178
311
 
179
- - `terminate_employee` - Terminates an employee (sets termination date)
180
- - `delete_team` - Permanently deletes a team
181
- - `delete_location` - Permanently deletes a location
182
- - `delete_project` - Permanently deletes a project
183
- - `delete_candidate` - Permanently deletes a candidate
312
+ - `factorial_employees({ action: 'terminate' })` - Terminates an employee
313
+ - `factorial_teams({ action: 'delete' })` - Permanently deletes a team
314
+ - `factorial_locations({ action: 'delete' })` - Permanently deletes a location
315
+ - `factorial_projects({ action: 'delete' })` - Permanently deletes a project
316
+ - `factorial_projects({ action: 'delete_task' })` - Deletes a project task
317
+ - `factorial_projects({ action: 'delete_time' })` - Deletes a time record
318
+ - `factorial_projects({ action: 'remove_worker' })` - Deletes an employee's assignment to a project
319
+ - `factorial_attendance({ action: 'delete' })` - Deletes a shift record
320
+ - `factorial_time_off({ action: 'cancel' })` - Cancels a leave request
321
+ - `factorial_time_off({ action: 'reject' })` - Rejects a leave request
322
+ - `factorial_training({ action: 'delete' })` - Deletes a training program and its enrollments
323
+ - `factorial_training({ action: 'delete_session' })` - Deletes a training session
324
+ - `factorial_training({ action: 'unenroll' })` - Deletes an employee's training enrollment
325
+ - `factorial_ats({ action: 'delete_posting' })` - Deletes a job posting and its applications
326
+ - `factorial_ats({ action: 'delete_candidate' })` - Permanently deletes a candidate
327
+ - `factorial_ats({ action: 'delete_application' })` - Permanently deletes an application
328
+
329
+ ### Document Downloads
330
+
331
+ Document names come from Factorial metadata rather than from the caller, so downloads treat them as untrusted:
332
+
333
+ - The name is reduced to a single path segment, so a name containing path separators cannot redirect the download outside `output_dir`.
334
+ - Control characters are dropped and over-long names are shortened, keeping the extension.
335
+ - **Nothing is ever overwritten.** If the target name is already taken, the file is saved as `name (1).ext`, `name (2).ext` and so on. This protects existing files in `output_dir` and stops two documents that share a name from collapsing into one.
336
+
337
+ Downloads still write wherever you point `output_dir`, so point it at a directory meant for downloads rather than a source tree or your home directory.
184
338
 
185
339
  ### Read-Only Categories
186
340
 
187
341
  Some categories are intentionally read-only for security:
188
342
 
189
343
  - **Payroll**: Supplements, tax identifiers, family situations
190
- - **Documents**: Folder and document metadata only
344
+ - **Documents**: Folder and document metadata (download tools available for payslips and documents)
191
345
  - **Contracts**: Historical contract data
192
346
 
193
- ### Response Optimization for Employee Collections
347
+ ### Response Optimization
194
348
 
195
- Employee collection tools (`get_employee_documents`, `get_employee_contracts`) return **summary format** by default to prevent token overflow:
349
+ Document and contract list operations return **summary format** by default to prevent token overflow:
196
350
 
197
- **Documents** (`get_employee_documents`):
351
+ **Documents** (`factorial_documents({ action: 'list' })`):
198
352
 
199
- - Returns: `id`, `name`, `folder_id`, `employee_id`, `author_id`, `mime_type`, `size_bytes` (7 fields)
200
- - Default limit: 20 documents per page
201
- - For full details: Use `get_document(id)` to retrieve complete document metadata including `file_url`, timestamps, etc.
353
+ - Returns: `id`, `name`, `folder_id`, `employee_id`, `mime_type` (5 fields)
354
+ - Default limit: 100 documents per page
355
+ - For full details: Use `factorial_documents({ action: 'get', id: X })` for complete metadata
202
356
 
203
- **Contracts** (`get_employee_contracts`):
357
+ **Contracts** (`factorial_contracts({ action: 'list' })`):
204
358
 
205
359
  - Returns: `id`, `employee_id`, `job_title`, `effective_on` (4 fields)
206
- - Default limit: 20 contracts per page
207
- - For full details: Timestamps (`created_at`, `updated_at`) excluded from summary
360
+ - Default limit: 100 contracts per page
208
361
 
209
- Both tools accept `page` and `limit` parameters (max: 100) for pagination control.
362
+ All list operations accept `page` and `limit` parameters for pagination control.
210
363
 
211
364
  ### Audit Logging
212
365
 
213
- All write operations (create, update, delete, approve, reject) are logged with:
366
+ All write operations (create, update, delete, approve, reject) are recorded with:
214
367
 
215
368
  - Timestamp
216
369
  - Operation type
217
370
  - Entity type and ID
218
371
  - Changes made
372
+ - Success or failure, and duration
373
+
374
+ **Scope of this log.** The trail is held in memory in the running server process, capped at the most recent 1000 entries, and is not exposed through any tool or resource. It is lost when the process exits. Set `DEBUG=true` to have each entry written to the server's stderr, which is the only way to retain it today. It is a debugging aid, not a compliance record: if you need a durable, queryable audit trail, use FactorialHR's own activity log as the system of record.
219
375
 
220
376
  ## Development
221
377
 
@@ -249,6 +405,42 @@ FACTORIAL_API_KEY=your-key npm start
249
405
  npx @modelcontextprotocol/inspector
250
406
  ```
251
407
 
408
+ ### Project Structure
409
+
410
+ The codebase is organized into domain-based modules for maintainability:
411
+
412
+ ```
413
+ src/
414
+ ├── schemas/ # Zod schemas by domain
415
+ │ ├── employees.ts # Employee, Team, Location, Contract schemas
416
+ │ ├── time-off.ts # Leave, LeaveType, Allowance, Shift schemas
417
+ │ ├── projects.ts # Project, Task, Worker, TimeRecord schemas
418
+ │ ├── training.ts # Training, Session, Membership schemas
419
+ │ ├── ats.ts # JobPosting, Candidate, Application schemas
420
+ │ └── ...
421
+ ├── api/ # API functions by domain
422
+ │ ├── employees.ts # listEmployees, getEmployee, createEmployee, etc.
423
+ │ ├── time-off.ts # listLeaves, createLeave, approveLeave, etc.
424
+ │ ├── projects.ts # listProjects, createProject, etc.
425
+ │ └── ...
426
+ ├── tools/ # MCP tool registrations by domain
427
+ │ ├── employees.ts # factorial_employees tool registration
428
+ │ ├── time-off.ts # factorial_time_off tool registration
429
+ │ ├── index.ts # Server setup, discovery tool, resources, prompts
430
+ │ └── ...
431
+ ├── index.ts # Entry point (re-exports from tools/)
432
+ ├── api.ts # Re-exports from api/
433
+ └── schemas.ts # Re-exports from schemas/
434
+ ```
435
+
436
+ **Adding a new feature:**
437
+
438
+ 1. Add schemas to `src/schemas/{domain}.ts`
439
+ 2. Add API functions to `src/api/{domain}.ts`
440
+ 3. Add tool actions to `src/tools/{domain}.ts`
441
+ 4. Update `src/schemas/index.ts`, `src/api/index.ts` exports if needed
442
+ 5. Run `npm test` to verify
443
+
252
444
  ## Troubleshooting
253
445
 
254
446
  ### API Key Not Working
@@ -271,6 +463,36 @@ The server implements exponential backoff for rate limits. If you're hitting lim
271
463
  - **Team membership**: Some employees may not be assigned to teams
272
464
  - **Empty responses**: Check if the resource exists in your Factorial account
273
465
 
466
+ ### Document Downloads Not Working
467
+
468
+ Document downloads **require OAuth2 authentication**. This is a Factorial API limitation - the download endpoint does not accept API key authentication.
469
+
470
+ If you see an error like:
471
+
472
+ > "Document download requires OAuth2 authentication"
473
+
474
+ You need to set up OAuth2 credentials. See [OAuth2 Setup](#oauth2-setup-required-for-document-downloads) above.
475
+
476
+ **Note**: OAuth2 refresh tokens expire after 1 week. If downloads suddenly stop working, re-authorize and get a new refresh token.
477
+
478
+ ### "Document with ID X not found" Error
479
+
480
+ The Factorial API's individual document endpoint (`GET /documents/{id}`) has limitations accessing employee-specific documents. This happens because:
481
+
482
+ 1. `list_documents` with `employee_ids` filter correctly returns all employee documents
483
+ 2. `get_document` by ID cannot access those same documents individually
484
+
485
+ **Workaround**: 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:
486
+
487
+ ```typescript
488
+ // This works - uses document list internally
489
+ factorial_documents({
490
+ action: 'download_payslips',
491
+ employee_id: 123,
492
+ output_dir: '/path/to/downloads',
493
+ });
494
+ ```
495
+
274
496
  ## FAQ
275
497
 
276
498
  **Q: Does this expose salary/payroll data?**
@@ -286,7 +508,7 @@ A: Data is cached in-memory with TTLs: employees (5 min), teams (10 min), locati
286
508
  A: Version `2025-10-01` by default. Override with `FACTORIAL_API_VERSION` environment variable.
287
509
 
288
510
  **Q: Are write operations logged?**
289
- A: Yes, all write operations are logged via the audit module for compliance and debugging.
511
+ A: Yes, every write is recorded by the audit module with a timestamp, entity, changes, and outcome. The log lives in memory in the running process (last 1000 entries) and is not retrievable through the MCP interface, so treat it as a debugging aid rather than a compliance record. See [Audit Logging](#audit-logging).
290
512
 
291
513
  ## Factorial API Quirks and Limitations
292
514
 
@@ -303,12 +525,13 @@ The FactorialHR API has some design patterns that differ from typical REST APIs.
303
525
 
304
526
  ### Endpoint Quirks
305
527
 
306
- | Endpoint | Quirk | Workaround |
307
- | ------------------------------ | ---------------------------------------------------- | ---------------------------------------------- |
308
- | `GET /employees/{id}` | May return 404 for valid employees | Server falls back to listing all and filtering |
309
- | `GET /documents/{id}` | May return 404 for valid documents | Server falls back to listing all and filtering |
310
- | `GET /contracts?employee_id=X` | Filtering unreliable | Server fetches all and filters client-side |
311
- | Empty results | Returns `{"errors": null}` instead of `{"data": []}` | Server handles both formats |
528
+ | Endpoint | Quirk | Workaround |
529
+ | ------------------------------ | --------------------------------------------------- | ---------------------------------------------- |
530
+ | `GET /employees/{id}` | May return 404 for valid employees | Server falls back to listing all and filtering |
531
+ | `GET /documents/{id}` | May return 404 for employee-specific documents | Use `download_payslips` which bypasses this |
532
+ | `GET /contracts?employee_id=X` | Filtering unreliable | Server fetches all and filters client-side |
533
+ | Empty results | Returns `{"errors": null}` instead of `{"data": []} | Server handles both formats |
534
+ | Document download URLs | Requires OAuth2 (API key does not work) | Configure OAuth2 credentials for downloads |
312
535
 
313
536
  ### Field Availability
314
537
 
@@ -0,0 +1,78 @@
1
+ /**
2
+ * ATS (Applicant Tracking System) API endpoints
3
+ */
4
+ import { type PaginatedResponse, type PaginationInput } from '../pagination.js';
5
+ import type { JobPosting, Candidate, Application, HiringStage, CreateJobPostingInput, UpdateJobPostingInput, CreateCandidateInput, UpdateCandidateInput, CreateApplicationInput, UpdateApplicationInput } from '../schemas.js';
6
+ /**
7
+ * List all job postings
8
+ */
9
+ export declare function listJobPostings(options?: PaginationInput): Promise<PaginatedResponse<JobPosting>>;
10
+ /**
11
+ * Get a specific job posting by ID
12
+ */
13
+ export declare function getJobPosting(id: number): Promise<JobPosting>;
14
+ /**
15
+ * List all candidates
16
+ */
17
+ export declare function listCandidates(options?: PaginationInput): Promise<PaginatedResponse<Candidate>>;
18
+ /**
19
+ * Get a specific candidate by ID
20
+ */
21
+ export declare function getCandidate(id: number): Promise<Candidate>;
22
+ /**
23
+ * List all applications
24
+ */
25
+ export declare function listApplications(jobPostingId?: number, options?: PaginationInput): Promise<PaginatedResponse<Application>>;
26
+ /**
27
+ * Get a specific application by ID
28
+ */
29
+ export declare function getApplication(id: number): Promise<Application>;
30
+ /**
31
+ * List all hiring stages
32
+ */
33
+ export declare function listHiringStages(): Promise<HiringStage[]>;
34
+ /**
35
+ * Get a specific hiring stage by ID
36
+ */
37
+ export declare function getHiringStage(id: number): Promise<HiringStage>;
38
+ /**
39
+ * Create a job posting
40
+ */
41
+ export declare function createJobPosting(input: CreateJobPostingInput): Promise<JobPosting>;
42
+ /**
43
+ * Update a job posting
44
+ */
45
+ export declare function updateJobPosting(id: number, input: UpdateJobPostingInput): Promise<JobPosting>;
46
+ /**
47
+ * Delete a job posting
48
+ */
49
+ export declare function deleteJobPosting(id: number): Promise<void>;
50
+ /**
51
+ * Create a candidate
52
+ */
53
+ export declare function createCandidate(input: CreateCandidateInput): Promise<Candidate>;
54
+ /**
55
+ * Update a candidate
56
+ */
57
+ export declare function updateCandidate(id: number, input: UpdateCandidateInput): Promise<Candidate>;
58
+ /**
59
+ * Delete a candidate
60
+ */
61
+ export declare function deleteCandidate(id: number): Promise<void>;
62
+ /**
63
+ * Create an application
64
+ */
65
+ export declare function createApplication(input: CreateApplicationInput): Promise<Application>;
66
+ /**
67
+ * Update an application
68
+ */
69
+ export declare function updateApplication(id: number, input: UpdateApplicationInput): Promise<Application>;
70
+ /**
71
+ * Delete an application
72
+ */
73
+ export declare function deleteApplication(id: number): Promise<void>;
74
+ /**
75
+ * Advance an application to the next stage
76
+ */
77
+ export declare function advanceApplication(id: number): Promise<Application>;
78
+ //# sourceMappingURL=ats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ats.d.ts","sourceRoot":"","sources":["../../src/api/ats.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EACV,UAAU,EACV,SAAS,EACT,WAAW,EACX,WAAW,EACX,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,EACvB,MAAM,eAAe,CAAC;AASvB;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC,CAQxC;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAOnE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC,CAIvC;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAGjE;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,YAAY,CAAC,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC,CASzC;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAGrE;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAM/D;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAGrE;AAMD;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,UAAU,CAAC,CAMxF;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,UAAU,CAAC,CASrB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhE;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,SAAS,CAAC,CAIrF;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,SAAS,CAAC,CAMjG;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM/D;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,WAAW,CAAC,CAI3F;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,sBAAsB,GAC5B,OAAO,CAAC,WAAW,CAAC,CAMtB;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMjE;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAMzE"}