@t4dhg/mcp-factorial 2.0.0 → 3.1.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.
package/dist/index.js CHANGED
@@ -2,15 +2,18 @@
2
2
  /**
3
3
  * MCP Server for FactorialHR
4
4
  *
5
- * Provides access to employee and organizational data from FactorialHR
6
- * through the Model Context Protocol for use with Claude Code and other MCP clients.
5
+ * Provides comprehensive access to FactorialHR data through the Model Context Protocol
6
+ * for use with Claude Code and other MCP clients.
7
7
  *
8
8
  * Features:
9
- * - 22 tools for employees, teams, locations, contracts, time off, attendance, documents, and job catalog
9
+ * - 80+ tools for employees, teams, locations, contracts, time off, attendance,
10
+ * documents, job catalog, projects, training, work areas, recruiting (ATS), and payroll
11
+ * - Full CRUD operations with safety guardrails for high-risk actions
10
12
  * - Pagination support for all list operations
11
13
  * - Caching for improved performance
12
14
  * - Retry logic with exponential backoff
13
15
  * - Runtime validation with Zod schemas
16
+ * - Audit logging for all write operations
14
17
  */
15
18
  import { loadEnv } from './config.js';
16
19
  // Load environment variables before other imports
@@ -19,26 +22,52 @@ import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mc
19
22
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
20
23
  import * as z from 'zod';
21
24
  import {
22
- // Employees
25
+ // Employees - Read
23
26
  listEmployees, getEmployee, searchEmployees,
24
- // Teams
27
+ // Employees - Write
28
+ createEmployee, updateEmployee, terminateEmployee,
29
+ // Teams - Read
25
30
  listTeams, getTeam,
26
- // Locations
31
+ // Teams - Write
32
+ createTeam, updateTeam, deleteTeam,
33
+ // Locations - Read
27
34
  listLocations, getLocation,
35
+ // Locations - Write
36
+ createLocation, updateLocation, deleteLocation,
28
37
  // Contracts
29
38
  listContracts,
30
- // Time Off
39
+ // Time Off - Read
31
40
  listLeaves, getLeave, listLeaveTypes, getLeaveType, listAllowances,
32
- // Shifts
41
+ // Time Off - Write
42
+ createLeave, updateLeave, cancelLeave, approveLeave, rejectLeave,
43
+ // Shifts - Read
33
44
  listShifts, getShift,
45
+ // Shifts - Write
46
+ createShift, updateShift, deleteShift,
34
47
  // Documents
35
48
  listFolders, getFolder, listDocuments, getDocument,
36
49
  // Job Catalog
37
- listJobRoles, getJobRole, listJobLevels, } from './api.js';
50
+ listJobRoles, getJobRole, listJobLevels,
51
+ // Projects - Read
52
+ listProjects, getProject, listProjectTasks, listProjectWorkers, listTimeRecords,
53
+ // Projects - Write
54
+ createProject, updateProject, deleteProject, createProjectTask, updateProjectTask, deleteProjectTask, assignProjectWorker, removeProjectWorker, createTimeRecord, updateTimeRecord, deleteTimeRecord,
55
+ // Training - Read
56
+ listTrainings, getTraining, listTrainingSessions, listTrainingEnrollments,
57
+ // Training - Write
58
+ createTraining, updateTraining, deleteTraining, createTrainingSession, updateTrainingSession, deleteTrainingSession, enrollInTraining, unenrollFromTraining,
59
+ // Work Areas
60
+ listWorkAreas, getWorkArea, createWorkArea, updateWorkArea, archiveWorkArea, unarchiveWorkArea,
61
+ // ATS - Read
62
+ listJobPostings, getJobPosting, listCandidates, getCandidate, listApplications, getApplication, listHiringStages,
63
+ // ATS - Write
64
+ createJobPosting, updateJobPosting, deleteJobPosting, createCandidate, updateCandidate, deleteCandidate, createApplication, updateApplication, deleteApplication, advanceApplication,
65
+ // Payroll (Read-only)
66
+ listPayrollSupplements, getPayrollSupplement, listTaxIdentifiers, getTaxIdentifier, listFamilySituations, getFamilySituation, } from './api.js';
38
67
  import { formatPaginationInfo } from './pagination.js';
39
68
  const server = new McpServer({
40
69
  name: 'factorial-hr',
41
- version: '2.0.0',
70
+ version: '3.0.0',
42
71
  });
43
72
  // ============================================================================
44
73
  // Employee Tools
@@ -163,6 +192,121 @@ server.registerTool('search_employees', {
163
192
  };
164
193
  }
165
194
  });
195
+ server.registerTool('create_employee', {
196
+ title: 'Create Employee',
197
+ description: 'Create a new employee in FactorialHR.',
198
+ inputSchema: {
199
+ first_name: z.string().min(1).max(100).describe('First name'),
200
+ last_name: z.string().min(1).max(100).describe('Last name'),
201
+ email: z.string().email().describe('Email address'),
202
+ birthday_on: z.string().optional().describe('Birthday (YYYY-MM-DD)'),
203
+ hired_on: z.string().optional().describe('Hire date (YYYY-MM-DD)'),
204
+ start_date: z.string().optional().describe('Start date (YYYY-MM-DD)'),
205
+ gender: z.enum(['male', 'female', 'other']).optional().describe('Gender'),
206
+ nationality: z.string().max(50).optional().describe('Nationality'),
207
+ manager_id: z.number().optional().describe('Manager employee ID'),
208
+ role: z.string().max(100).optional().describe('Job role/title'),
209
+ team_ids: z.array(z.number()).optional().describe('Team IDs to assign'),
210
+ location_id: z.number().optional().describe('Location ID'),
211
+ },
212
+ }, async (input) => {
213
+ try {
214
+ const employee = await createEmployee(input);
215
+ return {
216
+ content: [
217
+ {
218
+ type: 'text',
219
+ text: `Employee created successfully:\n\n${JSON.stringify(employee, null, 2)}`,
220
+ },
221
+ ],
222
+ };
223
+ }
224
+ catch (error) {
225
+ return {
226
+ content: [
227
+ {
228
+ type: 'text',
229
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
230
+ },
231
+ ],
232
+ isError: true,
233
+ };
234
+ }
235
+ });
236
+ server.registerTool('update_employee', {
237
+ title: 'Update Employee',
238
+ description: 'Update an existing employee in FactorialHR.',
239
+ inputSchema: {
240
+ id: z.number().describe('The employee ID to update'),
241
+ first_name: z.string().min(1).max(100).optional().describe('First name'),
242
+ last_name: z.string().min(1).max(100).optional().describe('Last name'),
243
+ email: z.string().email().optional().describe('Email address'),
244
+ birthday_on: z.string().optional().describe('Birthday (YYYY-MM-DD)'),
245
+ hired_on: z.string().optional().describe('Hire date (YYYY-MM-DD)'),
246
+ start_date: z.string().optional().describe('Start date (YYYY-MM-DD)'),
247
+ gender: z.enum(['male', 'female', 'other']).optional().describe('Gender'),
248
+ nationality: z.string().max(50).optional().describe('Nationality'),
249
+ manager_id: z.number().optional().describe('Manager employee ID'),
250
+ role: z.string().max(100).optional().describe('Job role/title'),
251
+ team_ids: z.array(z.number()).optional().describe('Team IDs to assign'),
252
+ location_id: z.number().optional().describe('Location ID'),
253
+ },
254
+ }, async ({ id, ...input }) => {
255
+ try {
256
+ const employee = await updateEmployee(id, input);
257
+ return {
258
+ content: [
259
+ {
260
+ type: 'text',
261
+ text: `Employee updated successfully:\n\n${JSON.stringify(employee, null, 2)}`,
262
+ },
263
+ ],
264
+ };
265
+ }
266
+ catch (error) {
267
+ return {
268
+ content: [
269
+ {
270
+ type: 'text',
271
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
272
+ },
273
+ ],
274
+ isError: true,
275
+ };
276
+ }
277
+ });
278
+ server.registerTool('terminate_employee', {
279
+ title: 'Terminate Employee',
280
+ description: 'Terminate an employee (soft delete). This is a high-risk operation that sets the termination date.',
281
+ inputSchema: {
282
+ id: z.number().describe('The employee ID to terminate'),
283
+ terminated_on: z.string().describe('Termination date (YYYY-MM-DD)'),
284
+ reason: z.string().max(500).optional().describe('Termination reason'),
285
+ },
286
+ }, async ({ id, terminated_on, reason }) => {
287
+ try {
288
+ const employee = await terminateEmployee(id, terminated_on, reason);
289
+ return {
290
+ content: [
291
+ {
292
+ type: 'text',
293
+ text: `Employee terminated successfully. Termination date: ${terminated_on}\n\n${JSON.stringify(employee, null, 2)}`,
294
+ },
295
+ ],
296
+ };
297
+ }
298
+ catch (error) {
299
+ return {
300
+ content: [
301
+ {
302
+ type: 'text',
303
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
304
+ },
305
+ ],
306
+ isError: true,
307
+ };
308
+ }
309
+ });
166
310
  // ============================================================================
167
311
  // Team Tools
168
312
  // ============================================================================
@@ -233,6 +377,103 @@ server.registerTool('get_team', {
233
377
  };
234
378
  }
235
379
  });
380
+ server.registerTool('create_team', {
381
+ title: 'Create Team',
382
+ description: 'Create a new team in the organization.',
383
+ inputSchema: {
384
+ name: z.string().min(1).max(100).describe('Team name'),
385
+ description: z.string().max(500).optional().describe('Team description'),
386
+ lead_ids: z.array(z.number()).optional().describe('Team lead employee IDs'),
387
+ employee_ids: z.array(z.number()).optional().describe('Team member employee IDs'),
388
+ },
389
+ }, async (input) => {
390
+ try {
391
+ const team = await createTeam(input);
392
+ return {
393
+ content: [
394
+ {
395
+ type: 'text',
396
+ text: `Team created successfully:\n\n${JSON.stringify(team, null, 2)}`,
397
+ },
398
+ ],
399
+ };
400
+ }
401
+ catch (error) {
402
+ return {
403
+ content: [
404
+ {
405
+ type: 'text',
406
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
407
+ },
408
+ ],
409
+ isError: true,
410
+ };
411
+ }
412
+ });
413
+ server.registerTool('update_team', {
414
+ title: 'Update Team',
415
+ description: 'Update an existing team.',
416
+ inputSchema: {
417
+ id: z.number().describe('The team ID to update'),
418
+ name: z.string().min(1).max(100).optional().describe('Team name'),
419
+ description: z.string().max(500).optional().describe('Team description'),
420
+ lead_ids: z.array(z.number()).optional().describe('Team lead employee IDs'),
421
+ employee_ids: z.array(z.number()).optional().describe('Team member employee IDs'),
422
+ },
423
+ }, async ({ id, ...input }) => {
424
+ try {
425
+ const team = await updateTeam(id, input);
426
+ return {
427
+ content: [
428
+ {
429
+ type: 'text',
430
+ text: `Team updated successfully:\n\n${JSON.stringify(team, null, 2)}`,
431
+ },
432
+ ],
433
+ };
434
+ }
435
+ catch (error) {
436
+ return {
437
+ content: [
438
+ {
439
+ type: 'text',
440
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
441
+ },
442
+ ],
443
+ isError: true,
444
+ };
445
+ }
446
+ });
447
+ server.registerTool('delete_team', {
448
+ title: 'Delete Team',
449
+ description: 'Delete a team. This is a high-risk operation.',
450
+ inputSchema: {
451
+ id: z.number().describe('The team ID to delete'),
452
+ },
453
+ }, async ({ id }) => {
454
+ try {
455
+ await deleteTeam(id);
456
+ return {
457
+ content: [
458
+ {
459
+ type: 'text',
460
+ text: `Team ${id} deleted successfully.`,
461
+ },
462
+ ],
463
+ };
464
+ }
465
+ catch (error) {
466
+ return {
467
+ content: [
468
+ {
469
+ type: 'text',
470
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
471
+ },
472
+ ],
473
+ isError: true,
474
+ };
475
+ }
476
+ });
236
477
  // ============================================================================
237
478
  // Location Tools
238
479
  // ============================================================================
@@ -303,6 +544,111 @@ server.registerTool('get_location', {
303
544
  };
304
545
  }
305
546
  });
547
+ server.registerTool('create_location', {
548
+ title: 'Create Location',
549
+ description: 'Create a new company location.',
550
+ inputSchema: {
551
+ name: z.string().min(1).max(100).describe('Location name'),
552
+ country: z.string().max(50).optional().describe('Country'),
553
+ state: z.string().max(50).optional().describe('State/Province'),
554
+ city: z.string().max(50).optional().describe('City'),
555
+ address_line_1: z.string().max(200).optional().describe('Address line 1'),
556
+ address_line_2: z.string().max(200).optional().describe('Address line 2'),
557
+ postal_code: z.string().max(20).optional().describe('Postal/ZIP code'),
558
+ phone_number: z.string().max(30).optional().describe('Phone number'),
559
+ },
560
+ }, async (input) => {
561
+ try {
562
+ const location = await createLocation(input);
563
+ return {
564
+ content: [
565
+ {
566
+ type: 'text',
567
+ text: `Location created successfully:\n\n${JSON.stringify(location, null, 2)}`,
568
+ },
569
+ ],
570
+ };
571
+ }
572
+ catch (error) {
573
+ return {
574
+ content: [
575
+ {
576
+ type: 'text',
577
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
578
+ },
579
+ ],
580
+ isError: true,
581
+ };
582
+ }
583
+ });
584
+ server.registerTool('update_location', {
585
+ title: 'Update Location',
586
+ description: 'Update an existing location.',
587
+ inputSchema: {
588
+ id: z.number().describe('The location ID to update'),
589
+ name: z.string().min(1).max(100).optional().describe('Location name'),
590
+ country: z.string().max(50).optional().describe('Country'),
591
+ state: z.string().max(50).optional().describe('State/Province'),
592
+ city: z.string().max(50).optional().describe('City'),
593
+ address_line_1: z.string().max(200).optional().describe('Address line 1'),
594
+ address_line_2: z.string().max(200).optional().describe('Address line 2'),
595
+ postal_code: z.string().max(20).optional().describe('Postal/ZIP code'),
596
+ phone_number: z.string().max(30).optional().describe('Phone number'),
597
+ },
598
+ }, async ({ id, ...input }) => {
599
+ try {
600
+ const location = await updateLocation(id, input);
601
+ return {
602
+ content: [
603
+ {
604
+ type: 'text',
605
+ text: `Location updated successfully:\n\n${JSON.stringify(location, null, 2)}`,
606
+ },
607
+ ],
608
+ };
609
+ }
610
+ catch (error) {
611
+ return {
612
+ content: [
613
+ {
614
+ type: 'text',
615
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
616
+ },
617
+ ],
618
+ isError: true,
619
+ };
620
+ }
621
+ });
622
+ server.registerTool('delete_location', {
623
+ title: 'Delete Location',
624
+ description: 'Delete a location. This is a high-risk operation.',
625
+ inputSchema: {
626
+ id: z.number().describe('The location ID to delete'),
627
+ },
628
+ }, async ({ id }) => {
629
+ try {
630
+ await deleteLocation(id);
631
+ return {
632
+ content: [
633
+ {
634
+ type: 'text',
635
+ text: `Location ${id} deleted successfully.`,
636
+ },
637
+ ],
638
+ };
639
+ }
640
+ catch (error) {
641
+ return {
642
+ content: [
643
+ {
644
+ type: 'text',
645
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
646
+ },
647
+ ],
648
+ isError: true,
649
+ };
650
+ }
651
+ });
306
652
  // ============================================================================
307
653
  // Contract Tools
308
654
  // ============================================================================
@@ -529,41 +875,25 @@ server.registerTool('list_allowances', {
529
875
  };
530
876
  }
531
877
  });
532
- // ============================================================================
533
- // Attendance / Shift Tools
534
- // ============================================================================
535
- server.registerTool('list_shifts', {
536
- title: 'List Shifts',
537
- description: 'Get employee attendance shifts. Filter by employee or date range. Read-only access.',
878
+ server.registerTool('create_leave', {
879
+ title: 'Create Leave Request',
880
+ description: 'Create a new time off/leave request for an employee.',
538
881
  inputSchema: {
539
- employee_id: z.number().optional().describe('Filter by employee ID'),
540
- clock_in_gte: z
541
- .string()
542
- .optional()
543
- .describe('Filter shifts clocking in after this time (ISO 8601)'),
544
- clock_in_lte: z
545
- .string()
546
- .optional()
547
- .describe('Filter shifts clocking in before this time (ISO 8601)'),
548
- page: z.number().optional().default(1).describe('Page number'),
549
- limit: z.number().optional().default(100).describe('Items per page (max: 100)'),
882
+ employee_id: z.number().describe('Employee ID'),
883
+ leave_type_id: z.number().describe('Leave type ID'),
884
+ start_on: z.string().describe('Start date (YYYY-MM-DD)'),
885
+ finish_on: z.string().describe('End date (YYYY-MM-DD)'),
886
+ half_day: z.enum(['all_day', 'start', 'finish']).optional().describe('Half day option'),
887
+ description: z.string().max(500).optional().describe('Description/reason'),
550
888
  },
551
- }, async ({ employee_id, clock_in_gte, clock_in_lte, page, limit }) => {
889
+ }, async (input) => {
552
890
  try {
553
- const result = await listShifts({ employee_id, clock_in_gte, clock_in_lte, page, limit });
554
- const summary = result.data.map(s => ({
555
- id: s.id,
556
- employee_id: s.employee_id,
557
- clock_in: s.clock_in,
558
- clock_out: s.clock_out,
559
- worked_hours: s.worked_hours,
560
- break_minutes: s.break_minutes,
561
- }));
891
+ const leave = await createLeave(input);
562
892
  return {
563
893
  content: [
564
894
  {
565
895
  type: 'text',
566
- text: `Found ${result.data.length} shifts (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(summary, null, 2)}`,
896
+ text: `Leave request created successfully:\n\n${JSON.stringify(leave, null, 2)}`,
567
897
  },
568
898
  ],
569
899
  };
@@ -580,20 +910,25 @@ server.registerTool('list_shifts', {
580
910
  };
581
911
  }
582
912
  });
583
- server.registerTool('get_shift', {
584
- title: 'Get Shift Details',
585
- description: 'Get detailed information about a specific shift.',
913
+ server.registerTool('update_leave', {
914
+ title: 'Update Leave Request',
915
+ description: 'Update an existing leave request.',
586
916
  inputSchema: {
587
- id: z.number().describe('The shift ID'),
917
+ id: z.number().describe('The leave ID to update'),
918
+ leave_type_id: z.number().optional().describe('Leave type ID'),
919
+ start_on: z.string().optional().describe('Start date (YYYY-MM-DD)'),
920
+ finish_on: z.string().optional().describe('End date (YYYY-MM-DD)'),
921
+ half_day: z.enum(['all_day', 'start', 'finish']).optional().describe('Half day option'),
922
+ description: z.string().max(500).optional().describe('Description/reason'),
588
923
  },
589
- }, async ({ id }) => {
924
+ }, async ({ id, ...input }) => {
590
925
  try {
591
- const shift = await getShift(id);
926
+ const leave = await updateLeave(id, input);
592
927
  return {
593
928
  content: [
594
929
  {
595
930
  type: 'text',
596
- text: JSON.stringify(shift, null, 2),
931
+ text: `Leave request updated successfully:\n\n${JSON.stringify(leave, null, 2)}`,
597
932
  },
598
933
  ],
599
934
  };
@@ -610,16 +945,289 @@ server.registerTool('get_shift', {
610
945
  };
611
946
  }
612
947
  });
613
- // ============================================================================
614
- // Document Tools (Read-Only)
615
- // ============================================================================
616
- server.registerTool('list_folders', {
617
- title: 'List Folders',
618
- description: 'Get all document folders. Read-only access.',
619
- inputSchema: {},
620
- }, async () => {
621
- try {
622
- const folders = await listFolders();
948
+ server.registerTool('cancel_leave', {
949
+ title: 'Cancel Leave Request',
950
+ description: 'Cancel a leave request.',
951
+ inputSchema: {
952
+ id: z.number().describe('The leave ID to cancel'),
953
+ },
954
+ }, async ({ id }) => {
955
+ try {
956
+ await cancelLeave(id);
957
+ return {
958
+ content: [
959
+ {
960
+ type: 'text',
961
+ text: `Leave request ${id} cancelled successfully.`,
962
+ },
963
+ ],
964
+ };
965
+ }
966
+ catch (error) {
967
+ return {
968
+ content: [
969
+ {
970
+ type: 'text',
971
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
972
+ },
973
+ ],
974
+ isError: true,
975
+ };
976
+ }
977
+ });
978
+ server.registerTool('approve_leave', {
979
+ title: 'Approve Leave Request',
980
+ description: 'Approve a pending leave request.',
981
+ inputSchema: {
982
+ id: z.number().describe('The leave ID to approve'),
983
+ reason: z.string().max(500).optional().describe('Approval comment'),
984
+ },
985
+ }, async ({ id, reason }) => {
986
+ try {
987
+ const leave = await approveLeave(id, reason ? { reason } : undefined);
988
+ return {
989
+ content: [
990
+ {
991
+ type: 'text',
992
+ text: `Leave request approved successfully:\n\n${JSON.stringify(leave, null, 2)}`,
993
+ },
994
+ ],
995
+ };
996
+ }
997
+ catch (error) {
998
+ return {
999
+ content: [
1000
+ {
1001
+ type: 'text',
1002
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1003
+ },
1004
+ ],
1005
+ isError: true,
1006
+ };
1007
+ }
1008
+ });
1009
+ server.registerTool('reject_leave', {
1010
+ title: 'Reject Leave Request',
1011
+ description: 'Reject a pending leave request.',
1012
+ inputSchema: {
1013
+ id: z.number().describe('The leave ID to reject'),
1014
+ reason: z.string().max(500).optional().describe('Rejection reason'),
1015
+ },
1016
+ }, async ({ id, reason }) => {
1017
+ try {
1018
+ const leave = await rejectLeave(id, reason ? { reason } : undefined);
1019
+ return {
1020
+ content: [
1021
+ {
1022
+ type: 'text',
1023
+ text: `Leave request rejected:\n\n${JSON.stringify(leave, null, 2)}`,
1024
+ },
1025
+ ],
1026
+ };
1027
+ }
1028
+ catch (error) {
1029
+ return {
1030
+ content: [
1031
+ {
1032
+ type: 'text',
1033
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1034
+ },
1035
+ ],
1036
+ isError: true,
1037
+ };
1038
+ }
1039
+ });
1040
+ // ============================================================================
1041
+ // Attendance / Shift Tools
1042
+ // ============================================================================
1043
+ server.registerTool('list_shifts', {
1044
+ title: 'List Shifts',
1045
+ description: 'Get employee attendance shifts. Filter by employee or date range. Read-only access.',
1046
+ inputSchema: {
1047
+ employee_id: z.number().optional().describe('Filter by employee ID'),
1048
+ clock_in_gte: z
1049
+ .string()
1050
+ .optional()
1051
+ .describe('Filter shifts clocking in after this time (ISO 8601)'),
1052
+ clock_in_lte: z
1053
+ .string()
1054
+ .optional()
1055
+ .describe('Filter shifts clocking in before this time (ISO 8601)'),
1056
+ page: z.number().optional().default(1).describe('Page number'),
1057
+ limit: z.number().optional().default(100).describe('Items per page (max: 100)'),
1058
+ },
1059
+ }, async ({ employee_id, clock_in_gte, clock_in_lte, page, limit }) => {
1060
+ try {
1061
+ const result = await listShifts({ employee_id, clock_in_gte, clock_in_lte, page, limit });
1062
+ const summary = result.data.map(s => ({
1063
+ id: s.id,
1064
+ employee_id: s.employee_id,
1065
+ clock_in: s.clock_in,
1066
+ clock_out: s.clock_out,
1067
+ worked_hours: s.worked_hours,
1068
+ break_minutes: s.break_minutes,
1069
+ }));
1070
+ return {
1071
+ content: [
1072
+ {
1073
+ type: 'text',
1074
+ text: `Found ${result.data.length} shifts (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(summary, null, 2)}`,
1075
+ },
1076
+ ],
1077
+ };
1078
+ }
1079
+ catch (error) {
1080
+ return {
1081
+ content: [
1082
+ {
1083
+ type: 'text',
1084
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1085
+ },
1086
+ ],
1087
+ isError: true,
1088
+ };
1089
+ }
1090
+ });
1091
+ server.registerTool('get_shift', {
1092
+ title: 'Get Shift Details',
1093
+ description: 'Get detailed information about a specific shift.',
1094
+ inputSchema: {
1095
+ id: z.number().describe('The shift ID'),
1096
+ },
1097
+ }, async ({ id }) => {
1098
+ try {
1099
+ const shift = await getShift(id);
1100
+ return {
1101
+ content: [
1102
+ {
1103
+ type: 'text',
1104
+ text: JSON.stringify(shift, null, 2),
1105
+ },
1106
+ ],
1107
+ };
1108
+ }
1109
+ catch (error) {
1110
+ return {
1111
+ content: [
1112
+ {
1113
+ type: 'text',
1114
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1115
+ },
1116
+ ],
1117
+ isError: true,
1118
+ };
1119
+ }
1120
+ });
1121
+ server.registerTool('create_shift', {
1122
+ title: 'Create Shift',
1123
+ description: 'Create a new attendance shift (clock in/out record).',
1124
+ inputSchema: {
1125
+ employee_id: z.number().describe('Employee ID'),
1126
+ clock_in: z.string().describe('Clock in time (ISO 8601)'),
1127
+ clock_out: z.string().optional().describe('Clock out time (ISO 8601)'),
1128
+ break_minutes: z.number().min(0).max(480).optional().describe('Break duration in minutes'),
1129
+ location: z.string().max(200).optional().describe('Work location'),
1130
+ notes: z.string().max(500).optional().describe('Notes'),
1131
+ },
1132
+ }, async (input) => {
1133
+ try {
1134
+ const shift = await createShift(input);
1135
+ return {
1136
+ content: [
1137
+ {
1138
+ type: 'text',
1139
+ text: `Shift created successfully:\n\n${JSON.stringify(shift, null, 2)}`,
1140
+ },
1141
+ ],
1142
+ };
1143
+ }
1144
+ catch (error) {
1145
+ return {
1146
+ content: [
1147
+ {
1148
+ type: 'text',
1149
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1150
+ },
1151
+ ],
1152
+ isError: true,
1153
+ };
1154
+ }
1155
+ });
1156
+ server.registerTool('update_shift', {
1157
+ title: 'Update Shift',
1158
+ description: 'Update an existing shift.',
1159
+ inputSchema: {
1160
+ id: z.number().describe('The shift ID to update'),
1161
+ clock_in: z.string().optional().describe('Clock in time (ISO 8601)'),
1162
+ clock_out: z.string().optional().describe('Clock out time (ISO 8601)'),
1163
+ break_minutes: z.number().min(0).max(480).optional().describe('Break duration in minutes'),
1164
+ location: z.string().max(200).optional().describe('Work location'),
1165
+ notes: z.string().max(500).optional().describe('Notes'),
1166
+ },
1167
+ }, async ({ id, ...input }) => {
1168
+ try {
1169
+ const shift = await updateShift(id, input);
1170
+ return {
1171
+ content: [
1172
+ {
1173
+ type: 'text',
1174
+ text: `Shift updated successfully:\n\n${JSON.stringify(shift, null, 2)}`,
1175
+ },
1176
+ ],
1177
+ };
1178
+ }
1179
+ catch (error) {
1180
+ return {
1181
+ content: [
1182
+ {
1183
+ type: 'text',
1184
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1185
+ },
1186
+ ],
1187
+ isError: true,
1188
+ };
1189
+ }
1190
+ });
1191
+ server.registerTool('delete_shift', {
1192
+ title: 'Delete Shift',
1193
+ description: 'Delete a shift record.',
1194
+ inputSchema: {
1195
+ id: z.number().describe('The shift ID to delete'),
1196
+ },
1197
+ }, async ({ id }) => {
1198
+ try {
1199
+ await deleteShift(id);
1200
+ return {
1201
+ content: [
1202
+ {
1203
+ type: 'text',
1204
+ text: `Shift ${id} deleted successfully.`,
1205
+ },
1206
+ ],
1207
+ };
1208
+ }
1209
+ catch (error) {
1210
+ return {
1211
+ content: [
1212
+ {
1213
+ type: 'text',
1214
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1215
+ },
1216
+ ],
1217
+ isError: true,
1218
+ };
1219
+ }
1220
+ });
1221
+ // ============================================================================
1222
+ // Document Tools (Read-Only)
1223
+ // ============================================================================
1224
+ server.registerTool('list_folders', {
1225
+ title: 'List Folders',
1226
+ description: 'Get all document folders. Read-only access.',
1227
+ inputSchema: {},
1228
+ }, async () => {
1229
+ try {
1230
+ const folders = await listFolders();
623
1231
  return {
624
1232
  content: [
625
1233
  {
@@ -830,6 +1438,1656 @@ server.registerTool('list_job_levels', {
830
1438
  }
831
1439
  });
832
1440
  // ============================================================================
1441
+ // Project Tools
1442
+ // ============================================================================
1443
+ server.registerTool('list_projects', {
1444
+ title: 'List Projects',
1445
+ description: 'Get all projects. Supports pagination.',
1446
+ inputSchema: {
1447
+ page: z.number().optional().default(1).describe('Page number'),
1448
+ limit: z.number().optional().default(100).describe('Items per page'),
1449
+ },
1450
+ }, async ({ page, limit }) => {
1451
+ try {
1452
+ const result = await listProjects({ page, limit });
1453
+ return {
1454
+ content: [
1455
+ {
1456
+ type: 'text',
1457
+ text: `Found ${result.data.length} projects (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(result.data, null, 2)}`,
1458
+ },
1459
+ ],
1460
+ };
1461
+ }
1462
+ catch (error) {
1463
+ return {
1464
+ content: [
1465
+ {
1466
+ type: 'text',
1467
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1468
+ },
1469
+ ],
1470
+ isError: true,
1471
+ };
1472
+ }
1473
+ });
1474
+ server.registerTool('get_project', {
1475
+ title: 'Get Project',
1476
+ description: 'Get detailed information about a specific project.',
1477
+ inputSchema: {
1478
+ id: z.number().describe('The project ID'),
1479
+ },
1480
+ }, async ({ id }) => {
1481
+ try {
1482
+ const project = await getProject(id);
1483
+ return {
1484
+ content: [{ type: 'text', text: JSON.stringify(project, null, 2) }],
1485
+ };
1486
+ }
1487
+ catch (error) {
1488
+ return {
1489
+ content: [
1490
+ {
1491
+ type: 'text',
1492
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1493
+ },
1494
+ ],
1495
+ isError: true,
1496
+ };
1497
+ }
1498
+ });
1499
+ server.registerTool('create_project', {
1500
+ title: 'Create Project',
1501
+ description: 'Create a new project.',
1502
+ inputSchema: {
1503
+ name: z.string().min(1).max(100).describe('Project name'),
1504
+ code: z.string().max(20).optional().describe('Project code'),
1505
+ description: z.string().max(500).optional().describe('Description'),
1506
+ employees_assignment: z.enum(['manual', 'company']).optional().describe('Assignment mode'),
1507
+ },
1508
+ }, async (input) => {
1509
+ try {
1510
+ const project = await createProject(input);
1511
+ return {
1512
+ content: [
1513
+ { type: 'text', text: `Project created:\n\n${JSON.stringify(project, null, 2)}` },
1514
+ ],
1515
+ };
1516
+ }
1517
+ catch (error) {
1518
+ return {
1519
+ content: [
1520
+ {
1521
+ type: 'text',
1522
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1523
+ },
1524
+ ],
1525
+ isError: true,
1526
+ };
1527
+ }
1528
+ });
1529
+ server.registerTool('update_project', {
1530
+ title: 'Update Project',
1531
+ description: 'Update an existing project.',
1532
+ inputSchema: {
1533
+ id: z.number().describe('The project ID'),
1534
+ name: z.string().min(1).max(100).optional().describe('Project name'),
1535
+ code: z.string().max(20).optional().describe('Project code'),
1536
+ description: z.string().max(500).optional().describe('Description'),
1537
+ status: z.enum(['active', 'inactive', 'archived']).optional().describe('Status'),
1538
+ },
1539
+ }, async ({ id, ...input }) => {
1540
+ try {
1541
+ const project = await updateProject(id, input);
1542
+ return {
1543
+ content: [
1544
+ { type: 'text', text: `Project updated:\n\n${JSON.stringify(project, null, 2)}` },
1545
+ ],
1546
+ };
1547
+ }
1548
+ catch (error) {
1549
+ return {
1550
+ content: [
1551
+ {
1552
+ type: 'text',
1553
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1554
+ },
1555
+ ],
1556
+ isError: true,
1557
+ };
1558
+ }
1559
+ });
1560
+ server.registerTool('delete_project', {
1561
+ title: 'Delete Project',
1562
+ description: 'Delete a project. This is a high-risk operation.',
1563
+ inputSchema: {
1564
+ id: z.number().describe('The project ID to delete'),
1565
+ },
1566
+ }, async ({ id }) => {
1567
+ try {
1568
+ await deleteProject(id);
1569
+ return {
1570
+ content: [{ type: 'text', text: `Project ${id} deleted successfully.` }],
1571
+ };
1572
+ }
1573
+ catch (error) {
1574
+ return {
1575
+ content: [
1576
+ {
1577
+ type: 'text',
1578
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1579
+ },
1580
+ ],
1581
+ isError: true,
1582
+ };
1583
+ }
1584
+ });
1585
+ server.registerTool('list_project_tasks', {
1586
+ title: 'List Project Tasks',
1587
+ description: 'Get tasks for a project.',
1588
+ inputSchema: {
1589
+ project_id: z.number().optional().describe('Filter by project ID'),
1590
+ page: z.number().optional().default(1).describe('Page number'),
1591
+ limit: z.number().optional().default(100).describe('Items per page'),
1592
+ },
1593
+ }, async ({ project_id, page, limit }) => {
1594
+ try {
1595
+ const result = await listProjectTasks(project_id, { page, limit });
1596
+ return {
1597
+ content: [
1598
+ {
1599
+ type: 'text',
1600
+ text: `Found ${result.data.length} tasks (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(result.data, null, 2)}`,
1601
+ },
1602
+ ],
1603
+ };
1604
+ }
1605
+ catch (error) {
1606
+ return {
1607
+ content: [
1608
+ {
1609
+ type: 'text',
1610
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1611
+ },
1612
+ ],
1613
+ isError: true,
1614
+ };
1615
+ }
1616
+ });
1617
+ server.registerTool('create_project_task', {
1618
+ title: 'Create Project Task',
1619
+ description: 'Create a new task for a project.',
1620
+ inputSchema: {
1621
+ name: z.string().min(1).max(100).describe('Task name'),
1622
+ project_id: z.number().describe('Project ID'),
1623
+ description: z.string().max(500).optional().describe('Description'),
1624
+ due_on: z.string().optional().describe('Due date (YYYY-MM-DD)'),
1625
+ },
1626
+ }, async (input) => {
1627
+ try {
1628
+ const task = await createProjectTask(input);
1629
+ return {
1630
+ content: [{ type: 'text', text: `Task created:\n\n${JSON.stringify(task, null, 2)}` }],
1631
+ };
1632
+ }
1633
+ catch (error) {
1634
+ return {
1635
+ content: [
1636
+ {
1637
+ type: 'text',
1638
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1639
+ },
1640
+ ],
1641
+ isError: true,
1642
+ };
1643
+ }
1644
+ });
1645
+ server.registerTool('update_project_task', {
1646
+ title: 'Update Project Task',
1647
+ description: 'Update a project task.',
1648
+ inputSchema: {
1649
+ id: z.number().describe('The task ID'),
1650
+ name: z.string().min(1).max(100).optional().describe('Task name'),
1651
+ description: z.string().max(500).optional().describe('Description'),
1652
+ due_on: z.string().optional().describe('Due date (YYYY-MM-DD)'),
1653
+ completed: z.boolean().optional().describe('Mark as completed'),
1654
+ },
1655
+ }, async ({ id, ...input }) => {
1656
+ try {
1657
+ const task = await updateProjectTask(id, input);
1658
+ return {
1659
+ content: [{ type: 'text', text: `Task updated:\n\n${JSON.stringify(task, null, 2)}` }],
1660
+ };
1661
+ }
1662
+ catch (error) {
1663
+ return {
1664
+ content: [
1665
+ {
1666
+ type: 'text',
1667
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1668
+ },
1669
+ ],
1670
+ isError: true,
1671
+ };
1672
+ }
1673
+ });
1674
+ server.registerTool('delete_project_task', {
1675
+ title: 'Delete Project Task',
1676
+ description: 'Delete a project task.',
1677
+ inputSchema: {
1678
+ id: z.number().describe('The task ID to delete'),
1679
+ },
1680
+ }, async ({ id }) => {
1681
+ try {
1682
+ await deleteProjectTask(id);
1683
+ return {
1684
+ content: [{ type: 'text', text: `Task ${id} deleted successfully.` }],
1685
+ };
1686
+ }
1687
+ catch (error) {
1688
+ return {
1689
+ content: [
1690
+ {
1691
+ type: 'text',
1692
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1693
+ },
1694
+ ],
1695
+ isError: true,
1696
+ };
1697
+ }
1698
+ });
1699
+ server.registerTool('list_project_workers', {
1700
+ title: 'List Project Workers',
1701
+ description: 'Get workers assigned to projects.',
1702
+ inputSchema: {
1703
+ project_id: z.number().optional().describe('Filter by project ID'),
1704
+ page: z.number().optional().default(1).describe('Page number'),
1705
+ limit: z.number().optional().default(100).describe('Items per page'),
1706
+ },
1707
+ }, async ({ project_id, page, limit }) => {
1708
+ try {
1709
+ const result = await listProjectWorkers(project_id, { page, limit });
1710
+ return {
1711
+ content: [
1712
+ {
1713
+ type: 'text',
1714
+ text: `Found ${result.data.length} project workers (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(result.data, null, 2)}`,
1715
+ },
1716
+ ],
1717
+ };
1718
+ }
1719
+ catch (error) {
1720
+ return {
1721
+ content: [
1722
+ {
1723
+ type: 'text',
1724
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1725
+ },
1726
+ ],
1727
+ isError: true,
1728
+ };
1729
+ }
1730
+ });
1731
+ server.registerTool('assign_project_worker', {
1732
+ title: 'Assign Project Worker',
1733
+ description: 'Assign an employee to a project.',
1734
+ inputSchema: {
1735
+ project_id: z.number().describe('Project ID'),
1736
+ employee_id: z.number().describe('Employee ID'),
1737
+ },
1738
+ }, async (input) => {
1739
+ try {
1740
+ const worker = await assignProjectWorker(input);
1741
+ return {
1742
+ content: [{ type: 'text', text: `Worker assigned:\n\n${JSON.stringify(worker, null, 2)}` }],
1743
+ };
1744
+ }
1745
+ catch (error) {
1746
+ return {
1747
+ content: [
1748
+ {
1749
+ type: 'text',
1750
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1751
+ },
1752
+ ],
1753
+ isError: true,
1754
+ };
1755
+ }
1756
+ });
1757
+ server.registerTool('remove_project_worker', {
1758
+ title: 'Remove Project Worker',
1759
+ description: 'Remove an employee from a project.',
1760
+ inputSchema: {
1761
+ id: z.number().describe('The project worker ID to remove'),
1762
+ },
1763
+ }, async ({ id }) => {
1764
+ try {
1765
+ await removeProjectWorker(id);
1766
+ return {
1767
+ content: [{ type: 'text', text: `Project worker ${id} removed successfully.` }],
1768
+ };
1769
+ }
1770
+ catch (error) {
1771
+ return {
1772
+ content: [
1773
+ {
1774
+ type: 'text',
1775
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1776
+ },
1777
+ ],
1778
+ isError: true,
1779
+ };
1780
+ }
1781
+ });
1782
+ server.registerTool('list_time_records', {
1783
+ title: 'List Time Records',
1784
+ description: 'Get time records for project workers.',
1785
+ inputSchema: {
1786
+ project_worker_id: z.number().optional().describe('Filter by project worker ID'),
1787
+ page: z.number().optional().default(1).describe('Page number'),
1788
+ limit: z.number().optional().default(100).describe('Items per page'),
1789
+ },
1790
+ }, async ({ project_worker_id, page, limit }) => {
1791
+ try {
1792
+ const result = await listTimeRecords(project_worker_id, { page, limit });
1793
+ return {
1794
+ content: [
1795
+ {
1796
+ type: 'text',
1797
+ text: `Found ${result.data.length} time records (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(result.data, null, 2)}`,
1798
+ },
1799
+ ],
1800
+ };
1801
+ }
1802
+ catch (error) {
1803
+ return {
1804
+ content: [
1805
+ {
1806
+ type: 'text',
1807
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1808
+ },
1809
+ ],
1810
+ isError: true,
1811
+ };
1812
+ }
1813
+ });
1814
+ server.registerTool('create_time_record', {
1815
+ title: 'Create Time Record',
1816
+ description: 'Log time for a project worker.',
1817
+ inputSchema: {
1818
+ project_worker_id: z.number().describe('Project worker ID'),
1819
+ date: z.string().describe('Date (YYYY-MM-DD)'),
1820
+ minutes: z.number().min(1).max(1440).describe('Minutes worked'),
1821
+ description: z.string().max(500).optional().describe('Description'),
1822
+ },
1823
+ }, async (input) => {
1824
+ try {
1825
+ const record = await createTimeRecord(input);
1826
+ return {
1827
+ content: [
1828
+ { type: 'text', text: `Time record created:\n\n${JSON.stringify(record, null, 2)}` },
1829
+ ],
1830
+ };
1831
+ }
1832
+ catch (error) {
1833
+ return {
1834
+ content: [
1835
+ {
1836
+ type: 'text',
1837
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1838
+ },
1839
+ ],
1840
+ isError: true,
1841
+ };
1842
+ }
1843
+ });
1844
+ server.registerTool('update_time_record', {
1845
+ title: 'Update Time Record',
1846
+ description: 'Update a time record.',
1847
+ inputSchema: {
1848
+ id: z.number().describe('Time record ID'),
1849
+ date: z.string().optional().describe('Date (YYYY-MM-DD)'),
1850
+ minutes: z.number().min(1).max(1440).optional().describe('Minutes worked'),
1851
+ description: z.string().max(500).optional().describe('Description'),
1852
+ },
1853
+ }, async ({ id, ...input }) => {
1854
+ try {
1855
+ const record = await updateTimeRecord(id, input);
1856
+ return {
1857
+ content: [
1858
+ { type: 'text', text: `Time record updated:\n\n${JSON.stringify(record, null, 2)}` },
1859
+ ],
1860
+ };
1861
+ }
1862
+ catch (error) {
1863
+ return {
1864
+ content: [
1865
+ {
1866
+ type: 'text',
1867
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1868
+ },
1869
+ ],
1870
+ isError: true,
1871
+ };
1872
+ }
1873
+ });
1874
+ server.registerTool('delete_time_record', {
1875
+ title: 'Delete Time Record',
1876
+ description: 'Delete a time record.',
1877
+ inputSchema: {
1878
+ id: z.number().describe('Time record ID to delete'),
1879
+ },
1880
+ }, async ({ id }) => {
1881
+ try {
1882
+ await deleteTimeRecord(id);
1883
+ return {
1884
+ content: [{ type: 'text', text: `Time record ${id} deleted successfully.` }],
1885
+ };
1886
+ }
1887
+ catch (error) {
1888
+ return {
1889
+ content: [
1890
+ {
1891
+ type: 'text',
1892
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1893
+ },
1894
+ ],
1895
+ isError: true,
1896
+ };
1897
+ }
1898
+ });
1899
+ // ============================================================================
1900
+ // Training Tools
1901
+ // ============================================================================
1902
+ server.registerTool('list_trainings', {
1903
+ title: 'List Trainings',
1904
+ description: 'Get all training programs.',
1905
+ inputSchema: {
1906
+ page: z.number().optional().default(1).describe('Page number'),
1907
+ limit: z.number().optional().default(100).describe('Items per page'),
1908
+ },
1909
+ }, async ({ page, limit }) => {
1910
+ try {
1911
+ const result = await listTrainings({ page, limit });
1912
+ return {
1913
+ content: [
1914
+ {
1915
+ type: 'text',
1916
+ text: `Found ${result.data.length} trainings (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(result.data, null, 2)}`,
1917
+ },
1918
+ ],
1919
+ };
1920
+ }
1921
+ catch (error) {
1922
+ return {
1923
+ content: [
1924
+ {
1925
+ type: 'text',
1926
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1927
+ },
1928
+ ],
1929
+ isError: true,
1930
+ };
1931
+ }
1932
+ });
1933
+ server.registerTool('get_training', {
1934
+ title: 'Get Training',
1935
+ description: 'Get details about a training program.',
1936
+ inputSchema: {
1937
+ id: z.number().describe('The training ID'),
1938
+ },
1939
+ }, async ({ id }) => {
1940
+ try {
1941
+ const training = await getTraining(id);
1942
+ return {
1943
+ content: [{ type: 'text', text: JSON.stringify(training, null, 2) }],
1944
+ };
1945
+ }
1946
+ catch (error) {
1947
+ return {
1948
+ content: [
1949
+ {
1950
+ type: 'text',
1951
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1952
+ },
1953
+ ],
1954
+ isError: true,
1955
+ };
1956
+ }
1957
+ });
1958
+ server.registerTool('create_training', {
1959
+ title: 'Create Training',
1960
+ description: 'Create a new training program.',
1961
+ inputSchema: {
1962
+ name: z.string().min(1).max(100).describe('Training name'),
1963
+ description: z.string().max(1000).optional().describe('Description'),
1964
+ category_id: z.number().optional().describe('Category ID'),
1965
+ subsidized: z.boolean().optional().describe('Is subsidized'),
1966
+ },
1967
+ }, async (input) => {
1968
+ try {
1969
+ const training = await createTraining(input);
1970
+ return {
1971
+ content: [
1972
+ { type: 'text', text: `Training created:\n\n${JSON.stringify(training, null, 2)}` },
1973
+ ],
1974
+ };
1975
+ }
1976
+ catch (error) {
1977
+ return {
1978
+ content: [
1979
+ {
1980
+ type: 'text',
1981
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
1982
+ },
1983
+ ],
1984
+ isError: true,
1985
+ };
1986
+ }
1987
+ });
1988
+ server.registerTool('update_training', {
1989
+ title: 'Update Training',
1990
+ description: 'Update a training program.',
1991
+ inputSchema: {
1992
+ id: z.number().describe('The training ID'),
1993
+ name: z.string().min(1).max(100).optional().describe('Training name'),
1994
+ description: z.string().max(1000).optional().describe('Description'),
1995
+ category_id: z.number().optional().describe('Category ID'),
1996
+ subsidized: z.boolean().optional().describe('Is subsidized'),
1997
+ },
1998
+ }, async ({ id, ...input }) => {
1999
+ try {
2000
+ const training = await updateTraining(id, input);
2001
+ return {
2002
+ content: [
2003
+ { type: 'text', text: `Training updated:\n\n${JSON.stringify(training, null, 2)}` },
2004
+ ],
2005
+ };
2006
+ }
2007
+ catch (error) {
2008
+ return {
2009
+ content: [
2010
+ {
2011
+ type: 'text',
2012
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2013
+ },
2014
+ ],
2015
+ isError: true,
2016
+ };
2017
+ }
2018
+ });
2019
+ server.registerTool('delete_training', {
2020
+ title: 'Delete Training',
2021
+ description: 'Delete a training program.',
2022
+ inputSchema: {
2023
+ id: z.number().describe('The training ID to delete'),
2024
+ },
2025
+ }, async ({ id }) => {
2026
+ try {
2027
+ await deleteTraining(id);
2028
+ return {
2029
+ content: [{ type: 'text', text: `Training ${id} deleted successfully.` }],
2030
+ };
2031
+ }
2032
+ catch (error) {
2033
+ return {
2034
+ content: [
2035
+ {
2036
+ type: 'text',
2037
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2038
+ },
2039
+ ],
2040
+ isError: true,
2041
+ };
2042
+ }
2043
+ });
2044
+ server.registerTool('list_training_sessions', {
2045
+ title: 'List Training Sessions',
2046
+ description: 'Get sessions for a training program.',
2047
+ inputSchema: {
2048
+ training_id: z.number().optional().describe('Filter by training ID'),
2049
+ page: z.number().optional().default(1).describe('Page number'),
2050
+ limit: z.number().optional().default(100).describe('Items per page'),
2051
+ },
2052
+ }, async ({ training_id, page, limit }) => {
2053
+ try {
2054
+ const result = await listTrainingSessions(training_id, { page, limit });
2055
+ return {
2056
+ content: [
2057
+ {
2058
+ type: 'text',
2059
+ text: `Found ${result.data.length} sessions (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(result.data, null, 2)}`,
2060
+ },
2061
+ ],
2062
+ };
2063
+ }
2064
+ catch (error) {
2065
+ return {
2066
+ content: [
2067
+ {
2068
+ type: 'text',
2069
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2070
+ },
2071
+ ],
2072
+ isError: true,
2073
+ };
2074
+ }
2075
+ });
2076
+ server.registerTool('create_training_session', {
2077
+ title: 'Create Training Session',
2078
+ description: 'Create a session for a training program.',
2079
+ inputSchema: {
2080
+ training_id: z.number().describe('Training ID'),
2081
+ name: z.string().max(100).optional().describe('Session name'),
2082
+ start_date: z.string().optional().describe('Start date (YYYY-MM-DD)'),
2083
+ end_date: z.string().optional().describe('End date (YYYY-MM-DD)'),
2084
+ location: z.string().max(200).optional().describe('Location'),
2085
+ max_attendees: z.number().optional().describe('Max attendees'),
2086
+ },
2087
+ }, async (input) => {
2088
+ try {
2089
+ const session = await createTrainingSession(input);
2090
+ return {
2091
+ content: [
2092
+ { type: 'text', text: `Session created:\n\n${JSON.stringify(session, null, 2)}` },
2093
+ ],
2094
+ };
2095
+ }
2096
+ catch (error) {
2097
+ return {
2098
+ content: [
2099
+ {
2100
+ type: 'text',
2101
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2102
+ },
2103
+ ],
2104
+ isError: true,
2105
+ };
2106
+ }
2107
+ });
2108
+ server.registerTool('update_training_session', {
2109
+ title: 'Update Training Session',
2110
+ description: 'Update a training session.',
2111
+ inputSchema: {
2112
+ id: z.number().describe('Session ID'),
2113
+ name: z.string().max(100).optional().describe('Session name'),
2114
+ start_date: z.string().optional().describe('Start date (YYYY-MM-DD)'),
2115
+ end_date: z.string().optional().describe('End date (YYYY-MM-DD)'),
2116
+ location: z.string().max(200).optional().describe('Location'),
2117
+ max_attendees: z.number().optional().describe('Max attendees'),
2118
+ },
2119
+ }, async ({ id, ...input }) => {
2120
+ try {
2121
+ const session = await updateTrainingSession(id, input);
2122
+ return {
2123
+ content: [
2124
+ { type: 'text', text: `Session updated:\n\n${JSON.stringify(session, null, 2)}` },
2125
+ ],
2126
+ };
2127
+ }
2128
+ catch (error) {
2129
+ return {
2130
+ content: [
2131
+ {
2132
+ type: 'text',
2133
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2134
+ },
2135
+ ],
2136
+ isError: true,
2137
+ };
2138
+ }
2139
+ });
2140
+ server.registerTool('delete_training_session', {
2141
+ title: 'Delete Training Session',
2142
+ description: 'Delete a training session.',
2143
+ inputSchema: {
2144
+ id: z.number().describe('Session ID to delete'),
2145
+ },
2146
+ }, async ({ id }) => {
2147
+ try {
2148
+ await deleteTrainingSession(id);
2149
+ return {
2150
+ content: [{ type: 'text', text: `Session ${id} deleted successfully.` }],
2151
+ };
2152
+ }
2153
+ catch (error) {
2154
+ return {
2155
+ content: [
2156
+ {
2157
+ type: 'text',
2158
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2159
+ },
2160
+ ],
2161
+ isError: true,
2162
+ };
2163
+ }
2164
+ });
2165
+ server.registerTool('list_training_enrollments', {
2166
+ title: 'List Training Enrollments',
2167
+ description: 'Get enrollments for a training program.',
2168
+ inputSchema: {
2169
+ training_id: z.number().optional().describe('Filter by training ID'),
2170
+ page: z.number().optional().default(1).describe('Page number'),
2171
+ limit: z.number().optional().default(100).describe('Items per page'),
2172
+ },
2173
+ }, async ({ training_id, page, limit }) => {
2174
+ try {
2175
+ const result = await listTrainingEnrollments(training_id, { page, limit });
2176
+ return {
2177
+ content: [
2178
+ {
2179
+ type: 'text',
2180
+ text: `Found ${result.data.length} enrollments (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(result.data, null, 2)}`,
2181
+ },
2182
+ ],
2183
+ };
2184
+ }
2185
+ catch (error) {
2186
+ return {
2187
+ content: [
2188
+ {
2189
+ type: 'text',
2190
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2191
+ },
2192
+ ],
2193
+ isError: true,
2194
+ };
2195
+ }
2196
+ });
2197
+ server.registerTool('enroll_in_training', {
2198
+ title: 'Enroll in Training',
2199
+ description: 'Enroll an employee in a training program.',
2200
+ inputSchema: {
2201
+ training_id: z.number().describe('Training ID'),
2202
+ employee_id: z.number().describe('Employee ID'),
2203
+ session_id: z.number().optional().describe('Session ID'),
2204
+ },
2205
+ }, async (input) => {
2206
+ try {
2207
+ const enrollment = await enrollInTraining(input);
2208
+ return {
2209
+ content: [{ type: 'text', text: `Enrolled:\n\n${JSON.stringify(enrollment, null, 2)}` }],
2210
+ };
2211
+ }
2212
+ catch (error) {
2213
+ return {
2214
+ content: [
2215
+ {
2216
+ type: 'text',
2217
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2218
+ },
2219
+ ],
2220
+ isError: true,
2221
+ };
2222
+ }
2223
+ });
2224
+ server.registerTool('unenroll_from_training', {
2225
+ title: 'Unenroll from Training',
2226
+ description: 'Remove an enrollment from a training.',
2227
+ inputSchema: {
2228
+ id: z.number().describe('Enrollment ID to remove'),
2229
+ },
2230
+ }, async ({ id }) => {
2231
+ try {
2232
+ await unenrollFromTraining(id);
2233
+ return {
2234
+ content: [{ type: 'text', text: `Enrollment ${id} removed successfully.` }],
2235
+ };
2236
+ }
2237
+ catch (error) {
2238
+ return {
2239
+ content: [
2240
+ {
2241
+ type: 'text',
2242
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2243
+ },
2244
+ ],
2245
+ isError: true,
2246
+ };
2247
+ }
2248
+ });
2249
+ // ============================================================================
2250
+ // Work Area Tools
2251
+ // ============================================================================
2252
+ server.registerTool('list_work_areas', {
2253
+ title: 'List Work Areas',
2254
+ description: 'Get all work areas within locations.',
2255
+ inputSchema: {
2256
+ page: z.number().optional().default(1).describe('Page number'),
2257
+ limit: z.number().optional().default(100).describe('Items per page'),
2258
+ },
2259
+ }, async ({ page, limit }) => {
2260
+ try {
2261
+ const result = await listWorkAreas({ page, limit });
2262
+ return {
2263
+ content: [
2264
+ {
2265
+ type: 'text',
2266
+ text: `Found ${result.data.length} work areas (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(result.data, null, 2)}`,
2267
+ },
2268
+ ],
2269
+ };
2270
+ }
2271
+ catch (error) {
2272
+ return {
2273
+ content: [
2274
+ {
2275
+ type: 'text',
2276
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2277
+ },
2278
+ ],
2279
+ isError: true,
2280
+ };
2281
+ }
2282
+ });
2283
+ server.registerTool('get_work_area', {
2284
+ title: 'Get Work Area',
2285
+ description: 'Get details about a work area.',
2286
+ inputSchema: {
2287
+ id: z.number().describe('The work area ID'),
2288
+ },
2289
+ }, async ({ id }) => {
2290
+ try {
2291
+ const workArea = await getWorkArea(id);
2292
+ return {
2293
+ content: [{ type: 'text', text: JSON.stringify(workArea, null, 2) }],
2294
+ };
2295
+ }
2296
+ catch (error) {
2297
+ return {
2298
+ content: [
2299
+ {
2300
+ type: 'text',
2301
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2302
+ },
2303
+ ],
2304
+ isError: true,
2305
+ };
2306
+ }
2307
+ });
2308
+ server.registerTool('create_work_area', {
2309
+ title: 'Create Work Area',
2310
+ description: 'Create a new work area within a location.',
2311
+ inputSchema: {
2312
+ name: z.string().min(1).max(100).describe('Work area name'),
2313
+ description: z.string().max(500).optional().describe('Description'),
2314
+ location_id: z.number().optional().describe('Location ID'),
2315
+ },
2316
+ }, async (input) => {
2317
+ try {
2318
+ const workArea = await createWorkArea(input);
2319
+ return {
2320
+ content: [
2321
+ { type: 'text', text: `Work area created:\n\n${JSON.stringify(workArea, null, 2)}` },
2322
+ ],
2323
+ };
2324
+ }
2325
+ catch (error) {
2326
+ return {
2327
+ content: [
2328
+ {
2329
+ type: 'text',
2330
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2331
+ },
2332
+ ],
2333
+ isError: true,
2334
+ };
2335
+ }
2336
+ });
2337
+ server.registerTool('update_work_area', {
2338
+ title: 'Update Work Area',
2339
+ description: 'Update a work area.',
2340
+ inputSchema: {
2341
+ id: z.number().describe('Work area ID'),
2342
+ name: z.string().min(1).max(100).optional().describe('Work area name'),
2343
+ description: z.string().max(500).optional().describe('Description'),
2344
+ location_id: z.number().optional().describe('Location ID'),
2345
+ },
2346
+ }, async ({ id, ...input }) => {
2347
+ try {
2348
+ const workArea = await updateWorkArea(id, input);
2349
+ return {
2350
+ content: [
2351
+ { type: 'text', text: `Work area updated:\n\n${JSON.stringify(workArea, null, 2)}` },
2352
+ ],
2353
+ };
2354
+ }
2355
+ catch (error) {
2356
+ return {
2357
+ content: [
2358
+ {
2359
+ type: 'text',
2360
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2361
+ },
2362
+ ],
2363
+ isError: true,
2364
+ };
2365
+ }
2366
+ });
2367
+ server.registerTool('archive_work_area', {
2368
+ title: 'Archive Work Area',
2369
+ description: 'Archive a work area.',
2370
+ inputSchema: {
2371
+ id: z.number().describe('Work area ID to archive'),
2372
+ },
2373
+ }, async ({ id }) => {
2374
+ try {
2375
+ const workArea = await archiveWorkArea(id);
2376
+ return {
2377
+ content: [
2378
+ { type: 'text', text: `Work area archived:\n\n${JSON.stringify(workArea, null, 2)}` },
2379
+ ],
2380
+ };
2381
+ }
2382
+ catch (error) {
2383
+ return {
2384
+ content: [
2385
+ {
2386
+ type: 'text',
2387
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2388
+ },
2389
+ ],
2390
+ isError: true,
2391
+ };
2392
+ }
2393
+ });
2394
+ server.registerTool('unarchive_work_area', {
2395
+ title: 'Unarchive Work Area',
2396
+ description: 'Unarchive a work area.',
2397
+ inputSchema: {
2398
+ id: z.number().describe('Work area ID to unarchive'),
2399
+ },
2400
+ }, async ({ id }) => {
2401
+ try {
2402
+ const workArea = await unarchiveWorkArea(id);
2403
+ return {
2404
+ content: [
2405
+ { type: 'text', text: `Work area unarchived:\n\n${JSON.stringify(workArea, null, 2)}` },
2406
+ ],
2407
+ };
2408
+ }
2409
+ catch (error) {
2410
+ return {
2411
+ content: [
2412
+ {
2413
+ type: 'text',
2414
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2415
+ },
2416
+ ],
2417
+ isError: true,
2418
+ };
2419
+ }
2420
+ });
2421
+ // ============================================================================
2422
+ // ATS (Recruiting) Tools
2423
+ // ============================================================================
2424
+ server.registerTool('list_job_postings', {
2425
+ title: 'List Job Postings',
2426
+ description: 'Get all job postings for recruiting.',
2427
+ inputSchema: {
2428
+ page: z.number().optional().default(1).describe('Page number'),
2429
+ limit: z.number().optional().default(100).describe('Items per page'),
2430
+ },
2431
+ }, async ({ page, limit }) => {
2432
+ try {
2433
+ const result = await listJobPostings({ page, limit });
2434
+ return {
2435
+ content: [
2436
+ {
2437
+ type: 'text',
2438
+ text: `Found ${result.data.length} job postings (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(result.data, null, 2)}`,
2439
+ },
2440
+ ],
2441
+ };
2442
+ }
2443
+ catch (error) {
2444
+ return {
2445
+ content: [
2446
+ {
2447
+ type: 'text',
2448
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2449
+ },
2450
+ ],
2451
+ isError: true,
2452
+ };
2453
+ }
2454
+ });
2455
+ server.registerTool('get_job_posting', {
2456
+ title: 'Get Job Posting',
2457
+ description: 'Get details about a job posting.',
2458
+ inputSchema: {
2459
+ id: z.number().describe('The job posting ID'),
2460
+ },
2461
+ }, async ({ id }) => {
2462
+ try {
2463
+ const posting = await getJobPosting(id);
2464
+ return {
2465
+ content: [{ type: 'text', text: JSON.stringify(posting, null, 2) }],
2466
+ };
2467
+ }
2468
+ catch (error) {
2469
+ return {
2470
+ content: [
2471
+ {
2472
+ type: 'text',
2473
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2474
+ },
2475
+ ],
2476
+ isError: true,
2477
+ };
2478
+ }
2479
+ });
2480
+ server.registerTool('create_job_posting', {
2481
+ title: 'Create Job Posting',
2482
+ description: 'Create a new job posting.',
2483
+ inputSchema: {
2484
+ title: z.string().min(1).max(200).describe('Job title'),
2485
+ description: z.string().max(5000).optional().describe('Job description'),
2486
+ department: z.string().max(100).optional().describe('Department'),
2487
+ location_id: z.number().optional().describe('Location ID'),
2488
+ team_id: z.number().optional().describe('Team ID'),
2489
+ employment_type: z.string().max(50).optional().describe('Employment type'),
2490
+ remote_status: z.string().max(50).optional().describe('Remote status'),
2491
+ },
2492
+ }, async (input) => {
2493
+ try {
2494
+ const posting = await createJobPosting(input);
2495
+ return {
2496
+ content: [
2497
+ { type: 'text', text: `Job posting created:\n\n${JSON.stringify(posting, null, 2)}` },
2498
+ ],
2499
+ };
2500
+ }
2501
+ catch (error) {
2502
+ return {
2503
+ content: [
2504
+ {
2505
+ type: 'text',
2506
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2507
+ },
2508
+ ],
2509
+ isError: true,
2510
+ };
2511
+ }
2512
+ });
2513
+ server.registerTool('update_job_posting', {
2514
+ title: 'Update Job Posting',
2515
+ description: 'Update a job posting.',
2516
+ inputSchema: {
2517
+ id: z.number().describe('Job posting ID'),
2518
+ title: z.string().min(1).max(200).optional().describe('Job title'),
2519
+ description: z.string().max(5000).optional().describe('Job description'),
2520
+ department: z.string().max(100).optional().describe('Department'),
2521
+ location_id: z.number().optional().describe('Location ID'),
2522
+ team_id: z.number().optional().describe('Team ID'),
2523
+ status: z.enum(['draft', 'published', 'closed', 'archived']).optional().describe('Status'),
2524
+ },
2525
+ }, async ({ id, ...input }) => {
2526
+ try {
2527
+ const posting = await updateJobPosting(id, input);
2528
+ return {
2529
+ content: [
2530
+ { type: 'text', text: `Job posting updated:\n\n${JSON.stringify(posting, null, 2)}` },
2531
+ ],
2532
+ };
2533
+ }
2534
+ catch (error) {
2535
+ return {
2536
+ content: [
2537
+ {
2538
+ type: 'text',
2539
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2540
+ },
2541
+ ],
2542
+ isError: true,
2543
+ };
2544
+ }
2545
+ });
2546
+ server.registerTool('delete_job_posting', {
2547
+ title: 'Delete Job Posting',
2548
+ description: 'Delete a job posting.',
2549
+ inputSchema: {
2550
+ id: z.number().describe('Job posting ID to delete'),
2551
+ },
2552
+ }, async ({ id }) => {
2553
+ try {
2554
+ await deleteJobPosting(id);
2555
+ return {
2556
+ content: [{ type: 'text', text: `Job posting ${id} deleted successfully.` }],
2557
+ };
2558
+ }
2559
+ catch (error) {
2560
+ return {
2561
+ content: [
2562
+ {
2563
+ type: 'text',
2564
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2565
+ },
2566
+ ],
2567
+ isError: true,
2568
+ };
2569
+ }
2570
+ });
2571
+ server.registerTool('list_candidates', {
2572
+ title: 'List Candidates',
2573
+ description: 'Get all candidates.',
2574
+ inputSchema: {
2575
+ page: z.number().optional().default(1).describe('Page number'),
2576
+ limit: z.number().optional().default(100).describe('Items per page'),
2577
+ },
2578
+ }, async ({ page, limit }) => {
2579
+ try {
2580
+ const result = await listCandidates({ page, limit });
2581
+ return {
2582
+ content: [
2583
+ {
2584
+ type: 'text',
2585
+ text: `Found ${result.data.length} candidates (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(result.data, null, 2)}`,
2586
+ },
2587
+ ],
2588
+ };
2589
+ }
2590
+ catch (error) {
2591
+ return {
2592
+ content: [
2593
+ {
2594
+ type: 'text',
2595
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2596
+ },
2597
+ ],
2598
+ isError: true,
2599
+ };
2600
+ }
2601
+ });
2602
+ server.registerTool('get_candidate', {
2603
+ title: 'Get Candidate',
2604
+ description: 'Get details about a candidate.',
2605
+ inputSchema: {
2606
+ id: z.number().describe('The candidate ID'),
2607
+ },
2608
+ }, async ({ id }) => {
2609
+ try {
2610
+ const candidate = await getCandidate(id);
2611
+ return {
2612
+ content: [{ type: 'text', text: JSON.stringify(candidate, null, 2) }],
2613
+ };
2614
+ }
2615
+ catch (error) {
2616
+ return {
2617
+ content: [
2618
+ {
2619
+ type: 'text',
2620
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2621
+ },
2622
+ ],
2623
+ isError: true,
2624
+ };
2625
+ }
2626
+ });
2627
+ server.registerTool('create_candidate', {
2628
+ title: 'Create Candidate',
2629
+ description: 'Create a new candidate.',
2630
+ inputSchema: {
2631
+ first_name: z.string().min(1).max(100).describe('First name'),
2632
+ last_name: z.string().min(1).max(100).describe('Last name'),
2633
+ email: z.string().email().optional().describe('Email'),
2634
+ phone: z.string().max(30).optional().describe('Phone'),
2635
+ source: z.string().max(100).optional().describe('Source'),
2636
+ linkedin_url: z.string().optional().describe('LinkedIn URL'),
2637
+ },
2638
+ }, async (input) => {
2639
+ try {
2640
+ const candidate = await createCandidate(input);
2641
+ return {
2642
+ content: [
2643
+ { type: 'text', text: `Candidate created:\n\n${JSON.stringify(candidate, null, 2)}` },
2644
+ ],
2645
+ };
2646
+ }
2647
+ catch (error) {
2648
+ return {
2649
+ content: [
2650
+ {
2651
+ type: 'text',
2652
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2653
+ },
2654
+ ],
2655
+ isError: true,
2656
+ };
2657
+ }
2658
+ });
2659
+ server.registerTool('update_candidate', {
2660
+ title: 'Update Candidate',
2661
+ description: 'Update a candidate.',
2662
+ inputSchema: {
2663
+ id: z.number().describe('Candidate ID'),
2664
+ first_name: z.string().min(1).max(100).optional().describe('First name'),
2665
+ last_name: z.string().min(1).max(100).optional().describe('Last name'),
2666
+ email: z.string().email().optional().describe('Email'),
2667
+ phone: z.string().max(30).optional().describe('Phone'),
2668
+ source: z.string().max(100).optional().describe('Source'),
2669
+ linkedin_url: z.string().optional().describe('LinkedIn URL'),
2670
+ },
2671
+ }, async ({ id, ...input }) => {
2672
+ try {
2673
+ const candidate = await updateCandidate(id, input);
2674
+ return {
2675
+ content: [
2676
+ { type: 'text', text: `Candidate updated:\n\n${JSON.stringify(candidate, null, 2)}` },
2677
+ ],
2678
+ };
2679
+ }
2680
+ catch (error) {
2681
+ return {
2682
+ content: [
2683
+ {
2684
+ type: 'text',
2685
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2686
+ },
2687
+ ],
2688
+ isError: true,
2689
+ };
2690
+ }
2691
+ });
2692
+ server.registerTool('delete_candidate', {
2693
+ title: 'Delete Candidate',
2694
+ description: 'Delete a candidate.',
2695
+ inputSchema: {
2696
+ id: z.number().describe('Candidate ID to delete'),
2697
+ },
2698
+ }, async ({ id }) => {
2699
+ try {
2700
+ await deleteCandidate(id);
2701
+ return {
2702
+ content: [{ type: 'text', text: `Candidate ${id} deleted successfully.` }],
2703
+ };
2704
+ }
2705
+ catch (error) {
2706
+ return {
2707
+ content: [
2708
+ {
2709
+ type: 'text',
2710
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2711
+ },
2712
+ ],
2713
+ isError: true,
2714
+ };
2715
+ }
2716
+ });
2717
+ server.registerTool('list_applications', {
2718
+ title: 'List Applications',
2719
+ description: 'Get job applications.',
2720
+ inputSchema: {
2721
+ job_posting_id: z.number().optional().describe('Filter by job posting ID'),
2722
+ page: z.number().optional().default(1).describe('Page number'),
2723
+ limit: z.number().optional().default(100).describe('Items per page'),
2724
+ },
2725
+ }, async ({ job_posting_id, page, limit }) => {
2726
+ try {
2727
+ const result = await listApplications(job_posting_id, { page, limit });
2728
+ return {
2729
+ content: [
2730
+ {
2731
+ type: 'text',
2732
+ text: `Found ${result.data.length} applications (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(result.data, null, 2)}`,
2733
+ },
2734
+ ],
2735
+ };
2736
+ }
2737
+ catch (error) {
2738
+ return {
2739
+ content: [
2740
+ {
2741
+ type: 'text',
2742
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2743
+ },
2744
+ ],
2745
+ isError: true,
2746
+ };
2747
+ }
2748
+ });
2749
+ server.registerTool('get_application', {
2750
+ title: 'Get Application',
2751
+ description: 'Get details about an application.',
2752
+ inputSchema: {
2753
+ id: z.number().describe('The application ID'),
2754
+ },
2755
+ }, async ({ id }) => {
2756
+ try {
2757
+ const application = await getApplication(id);
2758
+ return {
2759
+ content: [{ type: 'text', text: JSON.stringify(application, null, 2) }],
2760
+ };
2761
+ }
2762
+ catch (error) {
2763
+ return {
2764
+ content: [
2765
+ {
2766
+ type: 'text',
2767
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2768
+ },
2769
+ ],
2770
+ isError: true,
2771
+ };
2772
+ }
2773
+ });
2774
+ server.registerTool('create_application', {
2775
+ title: 'Create Application',
2776
+ description: 'Create a job application for a candidate.',
2777
+ inputSchema: {
2778
+ job_posting_id: z.number().describe('Job posting ID'),
2779
+ candidate_id: z.number().describe('Candidate ID'),
2780
+ notes: z.string().max(2000).optional().describe('Notes'),
2781
+ },
2782
+ }, async (input) => {
2783
+ try {
2784
+ const application = await createApplication(input);
2785
+ return {
2786
+ content: [
2787
+ { type: 'text', text: `Application created:\n\n${JSON.stringify(application, null, 2)}` },
2788
+ ],
2789
+ };
2790
+ }
2791
+ catch (error) {
2792
+ return {
2793
+ content: [
2794
+ {
2795
+ type: 'text',
2796
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2797
+ },
2798
+ ],
2799
+ isError: true,
2800
+ };
2801
+ }
2802
+ });
2803
+ server.registerTool('update_application', {
2804
+ title: 'Update Application',
2805
+ description: 'Update a job application.',
2806
+ inputSchema: {
2807
+ id: z.number().describe('Application ID'),
2808
+ hiring_stage_id: z.number().optional().describe('Hiring stage ID'),
2809
+ rating: z.number().min(0).max(5).optional().describe('Rating (0-5)'),
2810
+ notes: z.string().max(2000).optional().describe('Notes'),
2811
+ },
2812
+ }, async ({ id, ...input }) => {
2813
+ try {
2814
+ const application = await updateApplication(id, input);
2815
+ return {
2816
+ content: [
2817
+ { type: 'text', text: `Application updated:\n\n${JSON.stringify(application, null, 2)}` },
2818
+ ],
2819
+ };
2820
+ }
2821
+ catch (error) {
2822
+ return {
2823
+ content: [
2824
+ {
2825
+ type: 'text',
2826
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2827
+ },
2828
+ ],
2829
+ isError: true,
2830
+ };
2831
+ }
2832
+ });
2833
+ server.registerTool('delete_application', {
2834
+ title: 'Delete Application',
2835
+ description: 'Delete a job application.',
2836
+ inputSchema: {
2837
+ id: z.number().describe('Application ID to delete'),
2838
+ },
2839
+ }, async ({ id }) => {
2840
+ try {
2841
+ await deleteApplication(id);
2842
+ return {
2843
+ content: [{ type: 'text', text: `Application ${id} deleted successfully.` }],
2844
+ };
2845
+ }
2846
+ catch (error) {
2847
+ return {
2848
+ content: [
2849
+ {
2850
+ type: 'text',
2851
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2852
+ },
2853
+ ],
2854
+ isError: true,
2855
+ };
2856
+ }
2857
+ });
2858
+ server.registerTool('advance_application', {
2859
+ title: 'Advance Application',
2860
+ description: 'Move an application to the next hiring stage.',
2861
+ inputSchema: {
2862
+ id: z.number().describe('Application ID'),
2863
+ },
2864
+ }, async ({ id }) => {
2865
+ try {
2866
+ const application = await advanceApplication(id);
2867
+ return {
2868
+ content: [
2869
+ {
2870
+ type: 'text',
2871
+ text: `Application advanced:\n\n${JSON.stringify(application, null, 2)}`,
2872
+ },
2873
+ ],
2874
+ };
2875
+ }
2876
+ catch (error) {
2877
+ return {
2878
+ content: [
2879
+ {
2880
+ type: 'text',
2881
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2882
+ },
2883
+ ],
2884
+ isError: true,
2885
+ };
2886
+ }
2887
+ });
2888
+ server.registerTool('list_hiring_stages', {
2889
+ title: 'List Hiring Stages',
2890
+ description: 'Get all hiring stages for the recruiting workflow.',
2891
+ inputSchema: {},
2892
+ }, async () => {
2893
+ try {
2894
+ const stages = await listHiringStages();
2895
+ return {
2896
+ content: [
2897
+ {
2898
+ type: 'text',
2899
+ text: `Found ${stages.length} hiring stages:\n\n${JSON.stringify(stages, null, 2)}`,
2900
+ },
2901
+ ],
2902
+ };
2903
+ }
2904
+ catch (error) {
2905
+ return {
2906
+ content: [
2907
+ {
2908
+ type: 'text',
2909
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2910
+ },
2911
+ ],
2912
+ isError: true,
2913
+ };
2914
+ }
2915
+ });
2916
+ // ============================================================================
2917
+ // Payroll Tools (Read-Only)
2918
+ // ============================================================================
2919
+ server.registerTool('list_payroll_supplements', {
2920
+ title: 'List Payroll Supplements',
2921
+ description: 'Get payroll supplements (bonuses, allowances, etc.). Read-only.',
2922
+ inputSchema: {
2923
+ employee_id: z.number().optional().describe('Filter by employee ID'),
2924
+ page: z.number().optional().default(1).describe('Page number'),
2925
+ limit: z.number().optional().default(100).describe('Items per page'),
2926
+ },
2927
+ }, async ({ employee_id, page, limit }) => {
2928
+ try {
2929
+ const result = await listPayrollSupplements(employee_id, { page, limit });
2930
+ return {
2931
+ content: [
2932
+ {
2933
+ type: 'text',
2934
+ text: `Found ${result.data.length} supplements (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(result.data, null, 2)}`,
2935
+ },
2936
+ ],
2937
+ };
2938
+ }
2939
+ catch (error) {
2940
+ return {
2941
+ content: [
2942
+ {
2943
+ type: 'text',
2944
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2945
+ },
2946
+ ],
2947
+ isError: true,
2948
+ };
2949
+ }
2950
+ });
2951
+ server.registerTool('get_payroll_supplement', {
2952
+ title: 'Get Payroll Supplement',
2953
+ description: 'Get details about a payroll supplement. Read-only.',
2954
+ inputSchema: {
2955
+ id: z.number().describe('The supplement ID'),
2956
+ },
2957
+ }, async ({ id }) => {
2958
+ try {
2959
+ const supplement = await getPayrollSupplement(id);
2960
+ return {
2961
+ content: [{ type: 'text', text: JSON.stringify(supplement, null, 2) }],
2962
+ };
2963
+ }
2964
+ catch (error) {
2965
+ return {
2966
+ content: [
2967
+ {
2968
+ type: 'text',
2969
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
2970
+ },
2971
+ ],
2972
+ isError: true,
2973
+ };
2974
+ }
2975
+ });
2976
+ server.registerTool('list_tax_identifiers', {
2977
+ title: 'List Tax Identifiers',
2978
+ description: 'Get employee tax identifiers. Read-only.',
2979
+ inputSchema: {
2980
+ employee_id: z.number().optional().describe('Filter by employee ID'),
2981
+ page: z.number().optional().default(1).describe('Page number'),
2982
+ limit: z.number().optional().default(100).describe('Items per page'),
2983
+ },
2984
+ }, async ({ employee_id, page, limit }) => {
2985
+ try {
2986
+ const result = await listTaxIdentifiers(employee_id, { page, limit });
2987
+ return {
2988
+ content: [
2989
+ {
2990
+ type: 'text',
2991
+ text: `Found ${result.data.length} tax identifiers (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(result.data, null, 2)}`,
2992
+ },
2993
+ ],
2994
+ };
2995
+ }
2996
+ catch (error) {
2997
+ return {
2998
+ content: [
2999
+ {
3000
+ type: 'text',
3001
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
3002
+ },
3003
+ ],
3004
+ isError: true,
3005
+ };
3006
+ }
3007
+ });
3008
+ server.registerTool('get_tax_identifier', {
3009
+ title: 'Get Tax Identifier',
3010
+ description: 'Get details about a tax identifier. Read-only.',
3011
+ inputSchema: {
3012
+ id: z.number().describe('The tax identifier ID'),
3013
+ },
3014
+ }, async ({ id }) => {
3015
+ try {
3016
+ const identifier = await getTaxIdentifier(id);
3017
+ return {
3018
+ content: [{ type: 'text', text: JSON.stringify(identifier, null, 2) }],
3019
+ };
3020
+ }
3021
+ catch (error) {
3022
+ return {
3023
+ content: [
3024
+ {
3025
+ type: 'text',
3026
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
3027
+ },
3028
+ ],
3029
+ isError: true,
3030
+ };
3031
+ }
3032
+ });
3033
+ server.registerTool('list_family_situations', {
3034
+ title: 'List Family Situations',
3035
+ description: 'Get employee family situations. Read-only.',
3036
+ inputSchema: {
3037
+ employee_id: z.number().optional().describe('Filter by employee ID'),
3038
+ page: z.number().optional().default(1).describe('Page number'),
3039
+ limit: z.number().optional().default(100).describe('Items per page'),
3040
+ },
3041
+ }, async ({ employee_id, page, limit }) => {
3042
+ try {
3043
+ const result = await listFamilySituations(employee_id, { page, limit });
3044
+ return {
3045
+ content: [
3046
+ {
3047
+ type: 'text',
3048
+ text: `Found ${result.data.length} family situations (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(result.data, null, 2)}`,
3049
+ },
3050
+ ],
3051
+ };
3052
+ }
3053
+ catch (error) {
3054
+ return {
3055
+ content: [
3056
+ {
3057
+ type: 'text',
3058
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
3059
+ },
3060
+ ],
3061
+ isError: true,
3062
+ };
3063
+ }
3064
+ });
3065
+ server.registerTool('get_family_situation', {
3066
+ title: 'Get Family Situation',
3067
+ description: 'Get details about an employee family situation. Read-only.',
3068
+ inputSchema: {
3069
+ id: z.number().describe('The family situation ID'),
3070
+ },
3071
+ }, async ({ id }) => {
3072
+ try {
3073
+ const situation = await getFamilySituation(id);
3074
+ return {
3075
+ content: [{ type: 'text', text: JSON.stringify(situation, null, 2) }],
3076
+ };
3077
+ }
3078
+ catch (error) {
3079
+ return {
3080
+ content: [
3081
+ {
3082
+ type: 'text',
3083
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
3084
+ },
3085
+ ],
3086
+ isError: true,
3087
+ };
3088
+ }
3089
+ });
3090
+ // ============================================================================
833
3091
  // MCP Resources
834
3092
  // ============================================================================
835
3093
  /**