@t4dhg/mcp-factorial 7.0.1 → 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.
- package/README.md +321 -49
- package/dist/api/ats.d.ts +78 -0
- package/dist/api/ats.d.ts.map +1 -0
- package/dist/api/ats.js +173 -0
- package/dist/api/ats.js.map +1 -0
- package/dist/api/attendance.d.ts +27 -0
- package/dist/api/attendance.d.ts.map +1 -0
- package/dist/api/attendance.js +62 -0
- package/dist/api/attendance.js.map +1 -0
- package/dist/api/contracts.d.ts +40 -0
- package/dist/api/contracts.d.ts.map +1 -0
- package/dist/api/contracts.js +146 -0
- package/dist/api/contracts.js.map +1 -0
- package/dist/api/documents.d.ts +95 -0
- package/dist/api/documents.d.ts.map +1 -0
- package/dist/api/documents.js +265 -0
- package/dist/api/documents.js.map +1 -0
- package/dist/api/employees.d.ts +35 -0
- package/dist/api/employees.d.ts.map +1 -0
- package/dist/api/employees.js +116 -0
- package/dist/api/employees.js.map +1 -0
- package/dist/api/index.d.ts +22 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +36 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/job-catalog.d.ts +21 -0
- package/dist/api/job-catalog.d.ts.map +1 -0
- package/dist/api/job-catalog.js +34 -0
- package/dist/api/job-catalog.js.map +1 -0
- package/dist/api/locations.d.ts +26 -0
- package/dist/api/locations.d.ts.map +1 -0
- package/dist/api/locations.js +59 -0
- package/dist/api/locations.js.map +1 -0
- package/dist/api/payroll.d.ts +30 -0
- package/dist/api/payroll.d.ts.map +1 -0
- package/dist/api/payroll.js +68 -0
- package/dist/api/payroll.js.map +1 -0
- package/dist/api/projects.d.ts +82 -0
- package/dist/api/projects.d.ts.map +1 -0
- package/dist/api/projects.js +193 -0
- package/dist/api/projects.js.map +1 -0
- package/dist/api/shared.d.ts +12 -0
- package/dist/api/shared.d.ts.map +1 -0
- package/dist/api/shared.js +17 -0
- package/dist/api/shared.js.map +1 -0
- package/dist/api/teams.d.ts +26 -0
- package/dist/api/teams.d.ts.map +1 -0
- package/dist/api/teams.js +59 -0
- package/dist/api/teams.js.map +1 -0
- package/dist/api/time-off.d.ts +47 -0
- package/dist/api/time-off.d.ts.map +1 -0
- package/dist/api/time-off.js +112 -0
- package/dist/api/time-off.js.map +1 -0
- package/dist/api/training.d.ts +62 -0
- package/dist/api/training.d.ts.map +1 -0
- package/dist/api/training.js +147 -0
- package/dist/api/training.js.map +1 -0
- package/dist/api/work-areas.d.ts +30 -0
- package/dist/api/work-areas.d.ts.map +1 -0
- package/dist/api/work-areas.js +71 -0
- package/dist/api/work-areas.js.map +1 -0
- package/dist/api.d.ts +3 -434
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +3 -1154
- package/dist/api.js.map +1 -1
- package/dist/audit.d.ts +8 -3
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +8 -3
- package/dist/audit.js.map +1 -1
- package/dist/index.d.ts +6 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3701
- package/dist/index.js.map +1 -1
- package/dist/oauth.d.ts +63 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +143 -0
- package/dist/oauth.js.map +1 -0
- package/dist/schemas/ats.d.ts +326 -0
- package/dist/schemas/ats.d.ts.map +1 -0
- package/dist/schemas/ats.js +121 -0
- package/dist/schemas/ats.js.map +1 -0
- package/dist/schemas/documents.d.ts +78 -0
- package/dist/schemas/documents.d.ts.map +1 -0
- package/dist/schemas/documents.js +34 -0
- package/dist/schemas/documents.js.map +1 -0
- package/dist/schemas/employees.d.ts +528 -0
- package/dist/schemas/employees.d.ts.map +1 -0
- package/dist/schemas/employees.js +206 -0
- package/dist/schemas/employees.js.map +1 -0
- package/dist/schemas/index.d.ts +18 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +28 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/job-catalog.d.ts +57 -0
- package/dist/schemas/job-catalog.d.ts.map +1 -0
- package/dist/schemas/job-catalog.js +27 -0
- package/dist/schemas/job-catalog.js.map +1 -0
- package/dist/schemas/payroll.d.ts +98 -0
- package/dist/schemas/payroll.d.ts.map +1 -0
- package/dist/schemas/payroll.js +43 -0
- package/dist/schemas/payroll.js.map +1 -0
- package/dist/schemas/projects.d.ts +277 -0
- package/dist/schemas/projects.d.ts.map +1 -0
- package/dist/schemas/projects.js +114 -0
- package/dist/schemas/projects.js.map +1 -0
- package/dist/schemas/shared.d.ts +42 -0
- package/dist/schemas/shared.d.ts.map +1 -0
- package/dist/schemas/shared.js +65 -0
- package/dist/schemas/shared.js.map +1 -0
- package/dist/schemas/time-off.d.ts +286 -0
- package/dist/schemas/time-off.d.ts.map +1 -0
- package/dist/schemas/time-off.js +111 -0
- package/dist/schemas/time-off.js.map +1 -0
- package/dist/schemas/training.d.ts +223 -0
- package/dist/schemas/training.d.ts.map +1 -0
- package/dist/schemas/training.js +88 -0
- package/dist/schemas/training.js.map +1 -0
- package/dist/schemas/work-areas.d.ts +71 -0
- package/dist/schemas/work-areas.d.ts.map +1 -0
- package/dist/schemas/work-areas.js +30 -0
- package/dist/schemas/work-areas.js.map +1 -0
- package/dist/schemas.d.ts +4 -1879
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +4 -755
- package/dist/schemas.js.map +1 -1
- package/dist/tool-utils.d.ts +0 -14
- package/dist/tool-utils.d.ts.map +1 -1
- package/dist/tool-utils.js +5 -18
- package/dist/tool-utils.js.map +1 -1
- package/dist/tools/ats.d.ts +6 -0
- package/dist/tools/ats.d.ts.map +1 -0
- package/dist/tools/ats.js +206 -0
- package/dist/tools/ats.js.map +1 -0
- package/dist/tools/attendance.d.ts +6 -0
- package/dist/tools/attendance.d.ts.map +1 -0
- package/dist/tools/attendance.js +86 -0
- package/dist/tools/attendance.js.map +1 -0
- package/dist/tools/contracts.d.ts +6 -0
- package/dist/tools/contracts.d.ts.map +1 -0
- package/dist/tools/contracts.js +63 -0
- package/dist/tools/contracts.js.map +1 -0
- package/dist/tools/documents.d.ts +6 -0
- package/dist/tools/documents.d.ts.map +1 -0
- package/dist/tools/documents.js +135 -0
- package/dist/tools/documents.js.map +1 -0
- package/dist/tools/employees.d.ts +6 -0
- package/dist/tools/employees.d.ts.map +1 -0
- package/dist/tools/employees.js +125 -0
- package/dist/tools/employees.js.map +1 -0
- package/dist/tools/index.d.ts +17 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +221 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/job-catalog.d.ts +6 -0
- package/dist/tools/job-catalog.d.ts.map +1 -0
- package/dist/tools/job-catalog.js +39 -0
- package/dist/tools/job-catalog.js.map +1 -0
- package/dist/tools/locations.d.ts +6 -0
- package/dist/tools/locations.d.ts.map +1 -0
- package/dist/tools/locations.js +93 -0
- package/dist/tools/locations.js.map +1 -0
- package/dist/tools/payroll.d.ts +6 -0
- package/dist/tools/payroll.d.ts.map +1 -0
- package/dist/tools/payroll.js +76 -0
- package/dist/tools/payroll.js.map +1 -0
- package/dist/tools/projects.d.ts +6 -0
- package/dist/tools/projects.d.ts.map +1 -0
- package/dist/tools/projects.js +203 -0
- package/dist/tools/projects.js.map +1 -0
- package/dist/tools/shared.d.ts +84 -0
- package/dist/tools/shared.d.ts.map +1 -0
- package/dist/tools/shared.js +164 -0
- package/dist/tools/shared.js.map +1 -0
- package/dist/tools/teams.d.ts +6 -0
- package/dist/tools/teams.d.ts.map +1 -0
- package/dist/tools/teams.js +81 -0
- package/dist/tools/teams.js.map +1 -0
- package/dist/tools/time-off.d.ts +6 -0
- package/dist/tools/time-off.d.ts.map +1 -0
- package/dist/tools/time-off.js +144 -0
- package/dist/tools/time-off.js.map +1 -0
- package/dist/tools/training.d.ts +6 -0
- package/dist/tools/training.d.ts.map +1 -0
- package/dist/tools/training.js +160 -0
- package/dist/tools/training.js.map +1 -0
- package/dist/tools/work-areas.d.ts +6 -0
- package/dist/tools/work-areas.d.ts.map +1 -0
- package/dist/tools/work-areas.js +85 -0
- package/dist/tools/work-areas.js.map +1 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +35 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +97 -0
- package/dist/utils.js.map +1 -1
- package/dist/write-safety.d.ts +229 -2
- package/dist/write-safety.d.ts.map +1 -1
- package/dist/write-safety.js +42 -3
- package/dist/write-safety.js.map +1 -1
- package/llms.txt +2 -2
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,3707 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* MCP Server for FactorialHR
|
|
3
|
+
* MCP Server for FactorialHR - Hierarchical Tool Discovery
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* v8.0.0 introduces hierarchical tool discovery to reduce context usage.
|
|
6
|
+
* Instead of 117 individual tools, we now have 14 category-based tools.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* documents, job catalog, projects, training, work areas, recruiting (ATS), and payroll
|
|
11
|
-
* - Full CRUD operations with safety guardrails for high-risk actions
|
|
12
|
-
* - Pagination support for all list operations
|
|
13
|
-
* - Caching for improved performance
|
|
14
|
-
* - Retry logic with exponential backoff
|
|
15
|
-
* - Runtime validation with Zod schemas
|
|
16
|
-
* - Audit logging for all write operations
|
|
8
|
+
* This file re-exports from the modular src/tools/ directory.
|
|
9
|
+
* All tools, resources, and prompts are now organized by domain.
|
|
17
10
|
*/
|
|
18
|
-
|
|
19
|
-
// Load environment variables before other imports
|
|
20
|
-
loadEnv();
|
|
21
|
-
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
22
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
23
|
-
import * as z from 'zod';
|
|
24
|
-
import { cache } from './cache.js';
|
|
25
|
-
import {
|
|
26
|
-
// Employees - Read
|
|
27
|
-
listEmployees, getEmployee, searchEmployees,
|
|
28
|
-
// Employees - Write
|
|
29
|
-
createEmployee, updateEmployee, terminateEmployee,
|
|
30
|
-
// Teams - Read
|
|
31
|
-
listTeams, getTeam,
|
|
32
|
-
// Teams - Write
|
|
33
|
-
createTeam, updateTeam, deleteTeam,
|
|
34
|
-
// Locations - Read
|
|
35
|
-
listLocations, getLocation,
|
|
36
|
-
// Locations - Write
|
|
37
|
-
createLocation, updateLocation, deleteLocation,
|
|
38
|
-
// Contracts
|
|
39
|
-
listContracts,
|
|
40
|
-
// Time Off - Read
|
|
41
|
-
listLeaves, getLeave, listLeaveTypes, getLeaveType, listAllowances,
|
|
42
|
-
// Time Off - Write
|
|
43
|
-
createLeave, updateLeave, cancelLeave, approveLeave, rejectLeave,
|
|
44
|
-
// Shifts - Read
|
|
45
|
-
listShifts, getShift,
|
|
46
|
-
// Shifts - Write
|
|
47
|
-
createShift, updateShift, deleteShift,
|
|
48
|
-
// Documents
|
|
49
|
-
listFolders, getFolder, listDocuments, getDocument,
|
|
50
|
-
// Job Catalog
|
|
51
|
-
listJobRoles, getJobRole, listJobLevels,
|
|
52
|
-
// Projects - Read
|
|
53
|
-
listProjects, getProject, listProjectTasks, listProjectWorkers, listTimeRecords,
|
|
54
|
-
// Projects - Write
|
|
55
|
-
createProject, updateProject, deleteProject, createProjectTask, updateProjectTask, deleteProjectTask, assignProjectWorker, removeProjectWorker, createTimeRecord, updateTimeRecord, deleteTimeRecord,
|
|
56
|
-
// Training - Read
|
|
57
|
-
listTrainings, getTraining, listTrainingSessions, listTrainingEnrollments,
|
|
58
|
-
// Training - Write
|
|
59
|
-
createTraining, updateTraining, deleteTraining, createTrainingSession, updateTrainingSession, deleteTrainingSession, enrollInTraining, unenrollFromTraining,
|
|
60
|
-
// Work Areas
|
|
61
|
-
listWorkAreas, getWorkArea, createWorkArea, updateWorkArea, archiveWorkArea, unarchiveWorkArea,
|
|
62
|
-
// ATS - Read
|
|
63
|
-
listJobPostings, getJobPosting, listCandidates, getCandidate, listApplications, getApplication, listHiringStages,
|
|
64
|
-
// ATS - Write
|
|
65
|
-
createJobPosting, updateJobPosting, deleteJobPosting, createCandidate, updateCandidate, deleteCandidate, createApplication, updateApplication, deleteApplication, advanceApplication,
|
|
66
|
-
// Payroll (Read-only)
|
|
67
|
-
listPayrollSupplements, getPayrollSupplement, listTaxIdentifiers, getTaxIdentifier, listFamilySituations, getFamilySituation, } from './api.js';
|
|
68
|
-
import { formatPaginationInfo } from './pagination.js';
|
|
69
|
-
import { wrapHighRiskToolHandler, textResponse } from './tool-utils.js';
|
|
70
|
-
const server = new McpServer({
|
|
71
|
-
name: 'factorial-hr',
|
|
72
|
-
version: '3.0.0',
|
|
73
|
-
});
|
|
74
|
-
// ============================================================================
|
|
75
|
-
// Employee Tools
|
|
76
|
-
// ============================================================================
|
|
77
|
-
server.registerTool('list_employees', {
|
|
78
|
-
title: 'List Employees',
|
|
79
|
-
description: 'Get employees from FactorialHR. Can filter by team or location. Supports pagination.',
|
|
80
|
-
inputSchema: {
|
|
81
|
-
team_id: z.number().optional().describe('Filter by team ID'),
|
|
82
|
-
location_id: z.number().optional().describe('Filter by location ID'),
|
|
83
|
-
page: z.number().optional().default(1).describe('Page number (default: 1)'),
|
|
84
|
-
limit: z.number().optional().default(100).describe('Items per page (max: 100)'),
|
|
85
|
-
},
|
|
86
|
-
}, async ({ team_id, location_id, page, limit }) => {
|
|
87
|
-
try {
|
|
88
|
-
const result = await listEmployees({ team_id, location_id, page, limit });
|
|
89
|
-
const summary = result.data.map(e => ({
|
|
90
|
-
id: e.id,
|
|
91
|
-
name: e.full_name,
|
|
92
|
-
email: e.email,
|
|
93
|
-
identifier: e.identifier || null,
|
|
94
|
-
identifier_type: e.identifier_type || null,
|
|
95
|
-
location_id: e.location_id,
|
|
96
|
-
manager_id: e.manager_id,
|
|
97
|
-
active: e.active,
|
|
98
|
-
seniority_date: e.seniority_calculation_date,
|
|
99
|
-
terminated_on: e.terminated_on,
|
|
100
|
-
}));
|
|
101
|
-
return {
|
|
102
|
-
content: [
|
|
103
|
-
{
|
|
104
|
-
type: 'text',
|
|
105
|
-
text: `Found ${result.data.length} employees (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(summary, null, 2)}`,
|
|
106
|
-
},
|
|
107
|
-
],
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
catch (error) {
|
|
111
|
-
return {
|
|
112
|
-
content: [
|
|
113
|
-
{
|
|
114
|
-
type: 'text',
|
|
115
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
116
|
-
},
|
|
117
|
-
],
|
|
118
|
-
isError: true,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
server.registerTool('get_employee', {
|
|
123
|
-
title: 'Get Employee',
|
|
124
|
-
description: 'Get detailed information about a specific employee by their ID.',
|
|
125
|
-
inputSchema: {
|
|
126
|
-
id: z.number().describe('The employee ID'),
|
|
127
|
-
},
|
|
128
|
-
}, async ({ id }) => {
|
|
129
|
-
try {
|
|
130
|
-
const employee = await getEmployee(id);
|
|
131
|
-
return {
|
|
132
|
-
content: [
|
|
133
|
-
{
|
|
134
|
-
type: 'text',
|
|
135
|
-
text: `Employee details:\n\n${JSON.stringify(employee, null, 2)}`,
|
|
136
|
-
},
|
|
137
|
-
],
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
catch (error) {
|
|
141
|
-
return {
|
|
142
|
-
content: [
|
|
143
|
-
{
|
|
144
|
-
type: 'text',
|
|
145
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
146
|
-
},
|
|
147
|
-
],
|
|
148
|
-
isError: true,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
server.registerTool('search_employees', {
|
|
153
|
-
title: 'Search Employees',
|
|
154
|
-
description: 'Search for employees by name or email.',
|
|
155
|
-
inputSchema: {
|
|
156
|
-
query: z.string().describe('Search query (name or email)'),
|
|
157
|
-
},
|
|
158
|
-
}, async ({ query }) => {
|
|
159
|
-
try {
|
|
160
|
-
const employees = await searchEmployees(query);
|
|
161
|
-
if (employees.length === 0) {
|
|
162
|
-
return {
|
|
163
|
-
content: [{ type: 'text', text: `No employees found matching "${query}"` }],
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
const summary = employees.map(e => ({
|
|
167
|
-
id: e.id,
|
|
168
|
-
name: e.full_name,
|
|
169
|
-
email: e.email,
|
|
170
|
-
identifier: e.identifier,
|
|
171
|
-
}));
|
|
172
|
-
return {
|
|
173
|
-
content: [
|
|
174
|
-
{
|
|
175
|
-
type: 'text',
|
|
176
|
-
text: `Found ${employees.length} employees matching "${query}":\n\n${JSON.stringify(summary, null, 2)}`,
|
|
177
|
-
},
|
|
178
|
-
],
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
catch (error) {
|
|
182
|
-
return {
|
|
183
|
-
content: [
|
|
184
|
-
{
|
|
185
|
-
type: 'text',
|
|
186
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
187
|
-
},
|
|
188
|
-
],
|
|
189
|
-
isError: true,
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
server.registerTool('create_employee', {
|
|
194
|
-
title: 'Create Employee',
|
|
195
|
-
description: 'Create a new employee in FactorialHR.',
|
|
196
|
-
inputSchema: {
|
|
197
|
-
first_name: z.string().min(1).max(100).describe('First name'),
|
|
198
|
-
last_name: z.string().min(1).max(100).describe('Last name'),
|
|
199
|
-
email: z.string().email().describe('Email address'),
|
|
200
|
-
birthday_on: z.string().optional().describe('Birthday (YYYY-MM-DD)'),
|
|
201
|
-
hired_on: z.string().optional().describe('Hire date (YYYY-MM-DD)'),
|
|
202
|
-
start_date: z.string().optional().describe('Start date (YYYY-MM-DD)'),
|
|
203
|
-
gender: z.enum(['male', 'female', 'other']).optional().describe('Gender'),
|
|
204
|
-
nationality: z.string().max(50).optional().describe('Nationality'),
|
|
205
|
-
manager_id: z.number().optional().describe('Manager employee ID'),
|
|
206
|
-
role: z.string().max(100).optional().describe('Job role/title'),
|
|
207
|
-
team_ids: z.array(z.number()).optional().describe('Team IDs to assign'),
|
|
208
|
-
location_id: z.number().optional().describe('Location ID'),
|
|
209
|
-
},
|
|
210
|
-
}, async (input) => {
|
|
211
|
-
try {
|
|
212
|
-
const employee = await createEmployee(input);
|
|
213
|
-
return {
|
|
214
|
-
content: [
|
|
215
|
-
{
|
|
216
|
-
type: 'text',
|
|
217
|
-
text: `Employee created successfully:\n\n${JSON.stringify(employee, null, 2)}`,
|
|
218
|
-
},
|
|
219
|
-
],
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
catch (error) {
|
|
223
|
-
return {
|
|
224
|
-
content: [
|
|
225
|
-
{
|
|
226
|
-
type: 'text',
|
|
227
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
228
|
-
},
|
|
229
|
-
],
|
|
230
|
-
isError: true,
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
server.registerTool('update_employee', {
|
|
235
|
-
title: 'Update Employee',
|
|
236
|
-
description: 'Update an existing employee in FactorialHR.',
|
|
237
|
-
inputSchema: {
|
|
238
|
-
id: z.number().describe('The employee ID to update'),
|
|
239
|
-
first_name: z.string().min(1).max(100).optional().describe('First name'),
|
|
240
|
-
last_name: z.string().min(1).max(100).optional().describe('Last name'),
|
|
241
|
-
email: z.string().email().optional().describe('Email address'),
|
|
242
|
-
birthday_on: z.string().optional().describe('Birthday (YYYY-MM-DD)'),
|
|
243
|
-
hired_on: z.string().optional().describe('Hire date (YYYY-MM-DD)'),
|
|
244
|
-
start_date: z.string().optional().describe('Start date (YYYY-MM-DD)'),
|
|
245
|
-
gender: z.enum(['male', 'female', 'other']).optional().describe('Gender'),
|
|
246
|
-
nationality: z.string().max(50).optional().describe('Nationality'),
|
|
247
|
-
manager_id: z.number().optional().describe('Manager employee ID'),
|
|
248
|
-
role: z.string().max(100).optional().describe('Job role/title'),
|
|
249
|
-
team_ids: z.array(z.number()).optional().describe('Team IDs to assign'),
|
|
250
|
-
location_id: z.number().optional().describe('Location ID'),
|
|
251
|
-
},
|
|
252
|
-
}, async ({ id, ...input }) => {
|
|
253
|
-
try {
|
|
254
|
-
const employee = await updateEmployee(id, input);
|
|
255
|
-
return {
|
|
256
|
-
content: [
|
|
257
|
-
{
|
|
258
|
-
type: 'text',
|
|
259
|
-
text: `Employee updated successfully:\n\n${JSON.stringify(employee, null, 2)}`,
|
|
260
|
-
},
|
|
261
|
-
],
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
catch (error) {
|
|
265
|
-
return {
|
|
266
|
-
content: [
|
|
267
|
-
{
|
|
268
|
-
type: 'text',
|
|
269
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
270
|
-
},
|
|
271
|
-
],
|
|
272
|
-
isError: true,
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
server.registerTool('terminate_employee', {
|
|
277
|
-
title: 'Terminate Employee',
|
|
278
|
-
description: 'Terminate an employee (soft delete). This is a HIGH-RISK operation that requires confirmation.',
|
|
279
|
-
inputSchema: {
|
|
280
|
-
id: z.number().describe('The employee ID to terminate'),
|
|
281
|
-
terminated_on: z.string().describe('Termination date (YYYY-MM-DD)'),
|
|
282
|
-
reason: z.string().max(500).optional().describe('Termination reason'),
|
|
283
|
-
confirm: z.boolean().optional().describe('Set to true to confirm this high-risk operation'),
|
|
284
|
-
},
|
|
285
|
-
}, wrapHighRiskToolHandler('terminate_employee', async ({ id, terminated_on, reason }) => {
|
|
286
|
-
const employee = await terminateEmployee(id, terminated_on, reason);
|
|
287
|
-
return textResponse(`Employee terminated successfully. Termination date: ${terminated_on}\n\n${JSON.stringify(employee, null, 2)}`);
|
|
288
|
-
}));
|
|
289
|
-
// ============================================================================
|
|
290
|
-
// Team Tools
|
|
291
|
-
// ============================================================================
|
|
292
|
-
server.registerTool('list_teams', {
|
|
293
|
-
title: 'List Teams',
|
|
294
|
-
description: 'Get all teams in the organization. Supports pagination.',
|
|
295
|
-
inputSchema: {
|
|
296
|
-
page: z.number().optional().default(1).describe('Page number'),
|
|
297
|
-
limit: z.number().optional().default(100).describe('Items per page'),
|
|
298
|
-
},
|
|
299
|
-
}, async ({ page, limit }) => {
|
|
300
|
-
try {
|
|
301
|
-
const result = await listTeams({ page, limit });
|
|
302
|
-
const summary = result.data.map(t => ({
|
|
303
|
-
id: t.id,
|
|
304
|
-
name: t.name,
|
|
305
|
-
description: t.description,
|
|
306
|
-
employee_count: t.employee_ids?.length || 0,
|
|
307
|
-
}));
|
|
308
|
-
return {
|
|
309
|
-
content: [
|
|
310
|
-
{
|
|
311
|
-
type: 'text',
|
|
312
|
-
text: `Found ${result.data.length} teams (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(summary, null, 2)}`,
|
|
313
|
-
},
|
|
314
|
-
],
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
catch (error) {
|
|
318
|
-
return {
|
|
319
|
-
content: [
|
|
320
|
-
{
|
|
321
|
-
type: 'text',
|
|
322
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
323
|
-
},
|
|
324
|
-
],
|
|
325
|
-
isError: true,
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
server.registerTool('get_team', {
|
|
330
|
-
title: 'Get Team',
|
|
331
|
-
description: 'Get detailed information about a specific team by its ID.',
|
|
332
|
-
inputSchema: {
|
|
333
|
-
id: z.number().describe('The team ID'),
|
|
334
|
-
},
|
|
335
|
-
}, async ({ id }) => {
|
|
336
|
-
try {
|
|
337
|
-
const team = await getTeam(id);
|
|
338
|
-
return {
|
|
339
|
-
content: [
|
|
340
|
-
{
|
|
341
|
-
type: 'text',
|
|
342
|
-
text: JSON.stringify(team, null, 2),
|
|
343
|
-
},
|
|
344
|
-
],
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
catch (error) {
|
|
348
|
-
return {
|
|
349
|
-
content: [
|
|
350
|
-
{
|
|
351
|
-
type: 'text',
|
|
352
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
353
|
-
},
|
|
354
|
-
],
|
|
355
|
-
isError: true,
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
});
|
|
359
|
-
server.registerTool('create_team', {
|
|
360
|
-
title: 'Create Team',
|
|
361
|
-
description: 'Create a new team in the organization.',
|
|
362
|
-
inputSchema: {
|
|
363
|
-
name: z.string().min(1).max(100).describe('Team name'),
|
|
364
|
-
description: z.string().max(500).optional().describe('Team description'),
|
|
365
|
-
lead_ids: z.array(z.number()).optional().describe('Team lead employee IDs'),
|
|
366
|
-
employee_ids: z.array(z.number()).optional().describe('Team member employee IDs'),
|
|
367
|
-
},
|
|
368
|
-
}, async (input) => {
|
|
369
|
-
try {
|
|
370
|
-
const team = await createTeam(input);
|
|
371
|
-
return {
|
|
372
|
-
content: [
|
|
373
|
-
{
|
|
374
|
-
type: 'text',
|
|
375
|
-
text: `Team created successfully:\n\n${JSON.stringify(team, null, 2)}`,
|
|
376
|
-
},
|
|
377
|
-
],
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
catch (error) {
|
|
381
|
-
return {
|
|
382
|
-
content: [
|
|
383
|
-
{
|
|
384
|
-
type: 'text',
|
|
385
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
386
|
-
},
|
|
387
|
-
],
|
|
388
|
-
isError: true,
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
});
|
|
392
|
-
server.registerTool('update_team', {
|
|
393
|
-
title: 'Update Team',
|
|
394
|
-
description: 'Update an existing team.',
|
|
395
|
-
inputSchema: {
|
|
396
|
-
id: z.number().describe('The team ID to update'),
|
|
397
|
-
name: z.string().min(1).max(100).optional().describe('Team name'),
|
|
398
|
-
description: z.string().max(500).optional().describe('Team description'),
|
|
399
|
-
lead_ids: z.array(z.number()).optional().describe('Team lead employee IDs'),
|
|
400
|
-
employee_ids: z.array(z.number()).optional().describe('Team member employee IDs'),
|
|
401
|
-
},
|
|
402
|
-
}, async ({ id, ...input }) => {
|
|
403
|
-
try {
|
|
404
|
-
const team = await updateTeam(id, input);
|
|
405
|
-
return {
|
|
406
|
-
content: [
|
|
407
|
-
{
|
|
408
|
-
type: 'text',
|
|
409
|
-
text: `Team updated successfully:\n\n${JSON.stringify(team, null, 2)}`,
|
|
410
|
-
},
|
|
411
|
-
],
|
|
412
|
-
};
|
|
413
|
-
}
|
|
414
|
-
catch (error) {
|
|
415
|
-
return {
|
|
416
|
-
content: [
|
|
417
|
-
{
|
|
418
|
-
type: 'text',
|
|
419
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
420
|
-
},
|
|
421
|
-
],
|
|
422
|
-
isError: true,
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
});
|
|
426
|
-
server.registerTool('delete_team', {
|
|
427
|
-
title: 'Delete Team',
|
|
428
|
-
description: 'Delete a team. This is a HIGH-RISK operation that requires confirmation.',
|
|
429
|
-
inputSchema: {
|
|
430
|
-
id: z.number().describe('The team ID to delete'),
|
|
431
|
-
confirm: z.boolean().optional().describe('Set to true to confirm this high-risk operation'),
|
|
432
|
-
},
|
|
433
|
-
}, wrapHighRiskToolHandler('delete_team', async ({ id }) => {
|
|
434
|
-
await deleteTeam(id);
|
|
435
|
-
return textResponse(`Team ${id} deleted successfully.`);
|
|
436
|
-
}));
|
|
437
|
-
// ============================================================================
|
|
438
|
-
// Location Tools
|
|
439
|
-
// ============================================================================
|
|
440
|
-
server.registerTool('list_locations', {
|
|
441
|
-
title: 'List Locations',
|
|
442
|
-
description: 'Get all company locations. Supports pagination.',
|
|
443
|
-
inputSchema: {
|
|
444
|
-
page: z.number().optional().default(1).describe('Page number'),
|
|
445
|
-
limit: z.number().optional().default(100).describe('Items per page'),
|
|
446
|
-
},
|
|
447
|
-
}, async ({ page, limit }) => {
|
|
448
|
-
try {
|
|
449
|
-
const result = await listLocations({ page, limit });
|
|
450
|
-
const summary = result.data.map(l => ({
|
|
451
|
-
id: l.id,
|
|
452
|
-
name: l.name,
|
|
453
|
-
city: l.city,
|
|
454
|
-
country: l.country,
|
|
455
|
-
}));
|
|
456
|
-
return {
|
|
457
|
-
content: [
|
|
458
|
-
{
|
|
459
|
-
type: 'text',
|
|
460
|
-
text: `Found ${result.data.length} locations (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(summary, null, 2)}`,
|
|
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
|
-
});
|
|
477
|
-
server.registerTool('get_location', {
|
|
478
|
-
title: 'Get Location',
|
|
479
|
-
description: 'Get detailed information about a specific location by its ID.',
|
|
480
|
-
inputSchema: {
|
|
481
|
-
id: z.number().describe('The location ID'),
|
|
482
|
-
},
|
|
483
|
-
}, async ({ id }) => {
|
|
484
|
-
try {
|
|
485
|
-
const location = await getLocation(id);
|
|
486
|
-
return {
|
|
487
|
-
content: [
|
|
488
|
-
{
|
|
489
|
-
type: 'text',
|
|
490
|
-
text: JSON.stringify(location, null, 2),
|
|
491
|
-
},
|
|
492
|
-
],
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
catch (error) {
|
|
496
|
-
return {
|
|
497
|
-
content: [
|
|
498
|
-
{
|
|
499
|
-
type: 'text',
|
|
500
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
501
|
-
},
|
|
502
|
-
],
|
|
503
|
-
isError: true,
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
});
|
|
507
|
-
server.registerTool('create_location', {
|
|
508
|
-
title: 'Create Location',
|
|
509
|
-
description: 'Create a new company location.',
|
|
510
|
-
inputSchema: {
|
|
511
|
-
name: z.string().min(1).max(100).describe('Location name'),
|
|
512
|
-
country: z.string().max(50).optional().describe('Country'),
|
|
513
|
-
state: z.string().max(50).optional().describe('State/Province'),
|
|
514
|
-
city: z.string().max(50).optional().describe('City'),
|
|
515
|
-
address_line_1: z.string().max(200).optional().describe('Address line 1'),
|
|
516
|
-
address_line_2: z.string().max(200).optional().describe('Address line 2'),
|
|
517
|
-
postal_code: z.string().max(20).optional().describe('Postal/ZIP code'),
|
|
518
|
-
phone_number: z.string().max(30).optional().describe('Phone number'),
|
|
519
|
-
},
|
|
520
|
-
}, async (input) => {
|
|
521
|
-
try {
|
|
522
|
-
const location = await createLocation(input);
|
|
523
|
-
return {
|
|
524
|
-
content: [
|
|
525
|
-
{
|
|
526
|
-
type: 'text',
|
|
527
|
-
text: `Location created successfully:\n\n${JSON.stringify(location, null, 2)}`,
|
|
528
|
-
},
|
|
529
|
-
],
|
|
530
|
-
};
|
|
531
|
-
}
|
|
532
|
-
catch (error) {
|
|
533
|
-
return {
|
|
534
|
-
content: [
|
|
535
|
-
{
|
|
536
|
-
type: 'text',
|
|
537
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
538
|
-
},
|
|
539
|
-
],
|
|
540
|
-
isError: true,
|
|
541
|
-
};
|
|
542
|
-
}
|
|
543
|
-
});
|
|
544
|
-
server.registerTool('update_location', {
|
|
545
|
-
title: 'Update Location',
|
|
546
|
-
description: 'Update an existing location.',
|
|
547
|
-
inputSchema: {
|
|
548
|
-
id: z.number().describe('The location ID to update'),
|
|
549
|
-
name: z.string().min(1).max(100).optional().describe('Location name'),
|
|
550
|
-
country: z.string().max(50).optional().describe('Country'),
|
|
551
|
-
state: z.string().max(50).optional().describe('State/Province'),
|
|
552
|
-
city: z.string().max(50).optional().describe('City'),
|
|
553
|
-
address_line_1: z.string().max(200).optional().describe('Address line 1'),
|
|
554
|
-
address_line_2: z.string().max(200).optional().describe('Address line 2'),
|
|
555
|
-
postal_code: z.string().max(20).optional().describe('Postal/ZIP code'),
|
|
556
|
-
phone_number: z.string().max(30).optional().describe('Phone number'),
|
|
557
|
-
},
|
|
558
|
-
}, async ({ id, ...input }) => {
|
|
559
|
-
try {
|
|
560
|
-
const location = await updateLocation(id, input);
|
|
561
|
-
return {
|
|
562
|
-
content: [
|
|
563
|
-
{
|
|
564
|
-
type: 'text',
|
|
565
|
-
text: `Location updated successfully:\n\n${JSON.stringify(location, null, 2)}`,
|
|
566
|
-
},
|
|
567
|
-
],
|
|
568
|
-
};
|
|
569
|
-
}
|
|
570
|
-
catch (error) {
|
|
571
|
-
return {
|
|
572
|
-
content: [
|
|
573
|
-
{
|
|
574
|
-
type: 'text',
|
|
575
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
576
|
-
},
|
|
577
|
-
],
|
|
578
|
-
isError: true,
|
|
579
|
-
};
|
|
580
|
-
}
|
|
581
|
-
});
|
|
582
|
-
server.registerTool('delete_location', {
|
|
583
|
-
title: 'Delete Location',
|
|
584
|
-
description: 'Delete a location. This is a HIGH-RISK operation that requires confirmation.',
|
|
585
|
-
inputSchema: {
|
|
586
|
-
id: z.number().describe('The location ID to delete'),
|
|
587
|
-
confirm: z.boolean().optional().describe('Set to true to confirm this high-risk operation'),
|
|
588
|
-
},
|
|
589
|
-
}, wrapHighRiskToolHandler('delete_location', async ({ id }) => {
|
|
590
|
-
await deleteLocation(id);
|
|
591
|
-
return textResponse(`Location ${id} deleted successfully.`);
|
|
592
|
-
}));
|
|
593
|
-
// ============================================================================
|
|
594
|
-
// Contract Tools
|
|
595
|
-
// ============================================================================
|
|
596
|
-
server.registerTool('get_employee_contracts', {
|
|
597
|
-
title: 'Get Employee Contracts',
|
|
598
|
-
description: 'Get contract versions for an employee. Returns contract summary (id, employee_id, job_title, effective_on).',
|
|
599
|
-
inputSchema: {
|
|
600
|
-
employee_id: z.number().describe('The employee ID'),
|
|
601
|
-
page: z.number().optional().default(1).describe('Page number'),
|
|
602
|
-
limit: z.number().optional().default(20).describe('Items per page (max: 100, default: 20)'),
|
|
603
|
-
},
|
|
604
|
-
}, async ({ employee_id, page, limit }) => {
|
|
605
|
-
try {
|
|
606
|
-
const result = await listContracts(employee_id, { page, limit });
|
|
607
|
-
// Create summary format
|
|
608
|
-
const summary = result.data.map(c => ({
|
|
609
|
-
id: c.id,
|
|
610
|
-
employee_id: c.employee_id,
|
|
611
|
-
job_title: c.job_title,
|
|
612
|
-
effective_on: c.effective_on,
|
|
613
|
-
}));
|
|
614
|
-
return {
|
|
615
|
-
content: [
|
|
616
|
-
{
|
|
617
|
-
type: 'text',
|
|
618
|
-
text: `Found ${result.data.length} contracts for employee ${employee_id} (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(summary, null, 2)}`,
|
|
619
|
-
},
|
|
620
|
-
],
|
|
621
|
-
};
|
|
622
|
-
}
|
|
623
|
-
catch (error) {
|
|
624
|
-
return {
|
|
625
|
-
content: [
|
|
626
|
-
{
|
|
627
|
-
type: 'text',
|
|
628
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
629
|
-
},
|
|
630
|
-
],
|
|
631
|
-
isError: true,
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
});
|
|
635
|
-
// ============================================================================
|
|
636
|
-
// Time Off / Leave Tools
|
|
637
|
-
// ============================================================================
|
|
638
|
-
server.registerTool('list_leaves', {
|
|
639
|
-
title: 'List Leaves',
|
|
640
|
-
description: 'Get time off/leave requests. Filter by employee, status, or date range. Supports pagination.',
|
|
641
|
-
inputSchema: {
|
|
642
|
-
employee_id: z.number().optional().describe('Filter by employee ID'),
|
|
643
|
-
status: z
|
|
644
|
-
.enum(['pending', 'approved', 'declined'])
|
|
645
|
-
.optional()
|
|
646
|
-
.describe('Filter by leave status'),
|
|
647
|
-
start_on_gte: z
|
|
648
|
-
.string()
|
|
649
|
-
.optional()
|
|
650
|
-
.describe('Filter leaves starting on or after this date (YYYY-MM-DD)'),
|
|
651
|
-
start_on_lte: z
|
|
652
|
-
.string()
|
|
653
|
-
.optional()
|
|
654
|
-
.describe('Filter leaves starting on or before this date (YYYY-MM-DD)'),
|
|
655
|
-
page: z.number().optional().default(1).describe('Page number'),
|
|
656
|
-
limit: z.number().optional().default(100).describe('Items per page (max: 100)'),
|
|
657
|
-
},
|
|
658
|
-
}, async ({ employee_id, status, start_on_gte, start_on_lte, page, limit }) => {
|
|
659
|
-
try {
|
|
660
|
-
const result = await listLeaves({
|
|
661
|
-
employee_id,
|
|
662
|
-
status,
|
|
663
|
-
start_on_gte,
|
|
664
|
-
start_on_lte,
|
|
665
|
-
page,
|
|
666
|
-
limit,
|
|
667
|
-
});
|
|
668
|
-
// Fetch enrichment data (both are cached)
|
|
669
|
-
const [employees, leaveTypes] = await Promise.all([listEmployees(), listLeaveTypes()]);
|
|
670
|
-
const empMap = new Map(employees.data.map(e => [e.id, e.full_name]));
|
|
671
|
-
const ltMap = new Map(leaveTypes.map(lt => [lt.id, lt.name]));
|
|
672
|
-
const summary = result.data.map(l => ({
|
|
673
|
-
id: l.id,
|
|
674
|
-
employee_id: l.employee_id,
|
|
675
|
-
employee_name: empMap.get(l.employee_id) || `Employee ${l.employee_id}`,
|
|
676
|
-
leave_type_id: l.leave_type_id,
|
|
677
|
-
leave_type_name: ltMap.get(l.leave_type_id) || `Type ${l.leave_type_id}`,
|
|
678
|
-
start_on: l.start_on,
|
|
679
|
-
finish_on: l.finish_on,
|
|
680
|
-
status: l.status,
|
|
681
|
-
days: l.duration_attributes?.days,
|
|
682
|
-
}));
|
|
683
|
-
return {
|
|
684
|
-
content: [
|
|
685
|
-
{
|
|
686
|
-
type: 'text',
|
|
687
|
-
text: `Found ${result.data.length} leaves (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(summary, null, 2)}`,
|
|
688
|
-
},
|
|
689
|
-
],
|
|
690
|
-
};
|
|
691
|
-
}
|
|
692
|
-
catch (error) {
|
|
693
|
-
return {
|
|
694
|
-
content: [
|
|
695
|
-
{
|
|
696
|
-
type: 'text',
|
|
697
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
698
|
-
},
|
|
699
|
-
],
|
|
700
|
-
isError: true,
|
|
701
|
-
};
|
|
702
|
-
}
|
|
703
|
-
});
|
|
704
|
-
server.registerTool('get_leave', {
|
|
705
|
-
title: 'Get Leave Details',
|
|
706
|
-
description: 'Get detailed information about a specific leave request.',
|
|
707
|
-
inputSchema: {
|
|
708
|
-
id: z.number().describe('The leave ID'),
|
|
709
|
-
},
|
|
710
|
-
}, async ({ id }) => {
|
|
711
|
-
try {
|
|
712
|
-
const leave = await getLeave(id);
|
|
713
|
-
return {
|
|
714
|
-
content: [
|
|
715
|
-
{
|
|
716
|
-
type: 'text',
|
|
717
|
-
text: `Leave details:\n\n${JSON.stringify(leave, null, 2)}`,
|
|
718
|
-
},
|
|
719
|
-
],
|
|
720
|
-
};
|
|
721
|
-
}
|
|
722
|
-
catch (error) {
|
|
723
|
-
return {
|
|
724
|
-
content: [
|
|
725
|
-
{
|
|
726
|
-
type: 'text',
|
|
727
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
728
|
-
},
|
|
729
|
-
],
|
|
730
|
-
isError: true,
|
|
731
|
-
};
|
|
732
|
-
}
|
|
733
|
-
});
|
|
734
|
-
server.registerTool('list_leave_types', {
|
|
735
|
-
title: 'List Leave Types',
|
|
736
|
-
description: 'Get all leave types (vacation, sick leave, etc.) configured in the organization.',
|
|
737
|
-
inputSchema: {},
|
|
738
|
-
}, async () => {
|
|
739
|
-
try {
|
|
740
|
-
const types = await listLeaveTypes();
|
|
741
|
-
return {
|
|
742
|
-
content: [
|
|
743
|
-
{
|
|
744
|
-
type: 'text',
|
|
745
|
-
text: `Found ${types.length} leave types:\n\n${JSON.stringify(types, null, 2)}`,
|
|
746
|
-
},
|
|
747
|
-
],
|
|
748
|
-
};
|
|
749
|
-
}
|
|
750
|
-
catch (error) {
|
|
751
|
-
return {
|
|
752
|
-
content: [
|
|
753
|
-
{
|
|
754
|
-
type: 'text',
|
|
755
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
756
|
-
},
|
|
757
|
-
],
|
|
758
|
-
isError: true,
|
|
759
|
-
};
|
|
760
|
-
}
|
|
761
|
-
});
|
|
762
|
-
server.registerTool('get_leave_type', {
|
|
763
|
-
title: 'Get Leave Type',
|
|
764
|
-
description: 'Get details about a specific leave type.',
|
|
765
|
-
inputSchema: {
|
|
766
|
-
id: z.number().describe('The leave type ID'),
|
|
767
|
-
},
|
|
768
|
-
}, async ({ id }) => {
|
|
769
|
-
try {
|
|
770
|
-
const leaveType = await getLeaveType(id);
|
|
771
|
-
return {
|
|
772
|
-
content: [
|
|
773
|
-
{
|
|
774
|
-
type: 'text',
|
|
775
|
-
text: JSON.stringify(leaveType, null, 2),
|
|
776
|
-
},
|
|
777
|
-
],
|
|
778
|
-
};
|
|
779
|
-
}
|
|
780
|
-
catch (error) {
|
|
781
|
-
return {
|
|
782
|
-
content: [
|
|
783
|
-
{
|
|
784
|
-
type: 'text',
|
|
785
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
786
|
-
},
|
|
787
|
-
],
|
|
788
|
-
isError: true,
|
|
789
|
-
};
|
|
790
|
-
}
|
|
791
|
-
});
|
|
792
|
-
server.registerTool('list_allowances', {
|
|
793
|
-
title: 'List Time Off Allowances',
|
|
794
|
-
description: 'Get time off balances/allowances for employees. Shows available, consumed, and total days.',
|
|
795
|
-
inputSchema: {
|
|
796
|
-
employee_id: z.number().optional().describe('Filter by employee ID'),
|
|
797
|
-
page: z.number().optional().default(1).describe('Page number'),
|
|
798
|
-
limit: z.number().optional().default(100).describe('Items per page (max: 100)'),
|
|
799
|
-
},
|
|
800
|
-
}, async ({ employee_id, page, limit }) => {
|
|
801
|
-
try {
|
|
802
|
-
const result = await listAllowances({ employee_id, page, limit });
|
|
803
|
-
// Fetch enrichment data (both are cached)
|
|
804
|
-
const [employees, leaveTypes] = await Promise.all([listEmployees(), listLeaveTypes()]);
|
|
805
|
-
const empMap = new Map(employees.data.map(e => [e.id, e.full_name]));
|
|
806
|
-
const ltMap = new Map(leaveTypes.map(lt => [lt.id, lt.name]));
|
|
807
|
-
const summary = result.data.map(a => ({
|
|
808
|
-
id: a.id,
|
|
809
|
-
employee_id: a.employee_id,
|
|
810
|
-
employee_name: empMap.get(a.employee_id) || `Employee ${a.employee_id}`,
|
|
811
|
-
leave_type_id: a.leave_type_id,
|
|
812
|
-
leave_type_name: ltMap.get(a.leave_type_id) || `Type ${a.leave_type_id}`,
|
|
813
|
-
available_days: a.available_days,
|
|
814
|
-
consumed_days: a.consumed_days,
|
|
815
|
-
balance_days: a.balance_days,
|
|
816
|
-
valid_from: a.valid_from,
|
|
817
|
-
valid_to: a.valid_to,
|
|
818
|
-
}));
|
|
819
|
-
return {
|
|
820
|
-
content: [
|
|
821
|
-
{
|
|
822
|
-
type: 'text',
|
|
823
|
-
text: `Found ${result.data.length} allowances (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(summary, null, 2)}`,
|
|
824
|
-
},
|
|
825
|
-
],
|
|
826
|
-
};
|
|
827
|
-
}
|
|
828
|
-
catch (error) {
|
|
829
|
-
return {
|
|
830
|
-
content: [
|
|
831
|
-
{
|
|
832
|
-
type: 'text',
|
|
833
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
834
|
-
},
|
|
835
|
-
],
|
|
836
|
-
isError: true,
|
|
837
|
-
};
|
|
838
|
-
}
|
|
839
|
-
});
|
|
840
|
-
server.registerTool('create_leave', {
|
|
841
|
-
title: 'Create Leave Request',
|
|
842
|
-
description: 'Create a new time off/leave request for an employee.',
|
|
843
|
-
inputSchema: {
|
|
844
|
-
employee_id: z.number().describe('Employee ID'),
|
|
845
|
-
leave_type_id: z.number().describe('Leave type ID'),
|
|
846
|
-
start_on: z.string().describe('Start date (YYYY-MM-DD)'),
|
|
847
|
-
finish_on: z.string().describe('End date (YYYY-MM-DD)'),
|
|
848
|
-
half_day: z.enum(['all_day', 'start', 'finish']).optional().describe('Half day option'),
|
|
849
|
-
description: z.string().max(500).optional().describe('Description/reason'),
|
|
850
|
-
},
|
|
851
|
-
}, async (input) => {
|
|
852
|
-
try {
|
|
853
|
-
const leave = await createLeave(input);
|
|
854
|
-
return {
|
|
855
|
-
content: [
|
|
856
|
-
{
|
|
857
|
-
type: 'text',
|
|
858
|
-
text: `Leave request created successfully:\n\n${JSON.stringify(leave, null, 2)}`,
|
|
859
|
-
},
|
|
860
|
-
],
|
|
861
|
-
};
|
|
862
|
-
}
|
|
863
|
-
catch (error) {
|
|
864
|
-
return {
|
|
865
|
-
content: [
|
|
866
|
-
{
|
|
867
|
-
type: 'text',
|
|
868
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
869
|
-
},
|
|
870
|
-
],
|
|
871
|
-
isError: true,
|
|
872
|
-
};
|
|
873
|
-
}
|
|
874
|
-
});
|
|
875
|
-
server.registerTool('update_leave', {
|
|
876
|
-
title: 'Update Leave Request',
|
|
877
|
-
description: 'Update an existing leave request.',
|
|
878
|
-
inputSchema: {
|
|
879
|
-
id: z.number().describe('The leave ID to update'),
|
|
880
|
-
leave_type_id: z.number().optional().describe('Leave type ID'),
|
|
881
|
-
start_on: z.string().optional().describe('Start date (YYYY-MM-DD)'),
|
|
882
|
-
finish_on: z.string().optional().describe('End date (YYYY-MM-DD)'),
|
|
883
|
-
half_day: z.enum(['all_day', 'start', 'finish']).optional().describe('Half day option'),
|
|
884
|
-
description: z.string().max(500).optional().describe('Description/reason'),
|
|
885
|
-
},
|
|
886
|
-
}, async ({ id, ...input }) => {
|
|
887
|
-
try {
|
|
888
|
-
const leave = await updateLeave(id, input);
|
|
889
|
-
return {
|
|
890
|
-
content: [
|
|
891
|
-
{
|
|
892
|
-
type: 'text',
|
|
893
|
-
text: `Leave request updated successfully:\n\n${JSON.stringify(leave, null, 2)}`,
|
|
894
|
-
},
|
|
895
|
-
],
|
|
896
|
-
};
|
|
897
|
-
}
|
|
898
|
-
catch (error) {
|
|
899
|
-
return {
|
|
900
|
-
content: [
|
|
901
|
-
{
|
|
902
|
-
type: 'text',
|
|
903
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
904
|
-
},
|
|
905
|
-
],
|
|
906
|
-
isError: true,
|
|
907
|
-
};
|
|
908
|
-
}
|
|
909
|
-
});
|
|
910
|
-
server.registerTool('cancel_leave', {
|
|
911
|
-
title: 'Cancel Leave Request',
|
|
912
|
-
description: 'Cancel a leave request. This operation requires confirmation.',
|
|
913
|
-
inputSchema: {
|
|
914
|
-
id: z.number().describe('The leave ID to cancel'),
|
|
915
|
-
confirm: z.boolean().optional().describe('Set to true to confirm this operation'),
|
|
916
|
-
},
|
|
917
|
-
}, wrapHighRiskToolHandler('cancel_leave', async ({ id }) => {
|
|
918
|
-
await cancelLeave(id);
|
|
919
|
-
return textResponse(`Leave request ${id} cancelled successfully.`);
|
|
920
|
-
}));
|
|
921
|
-
server.registerTool('approve_leave', {
|
|
922
|
-
title: 'Approve Leave Request',
|
|
923
|
-
description: 'Approve a pending leave request.',
|
|
924
|
-
inputSchema: {
|
|
925
|
-
id: z.number().describe('The leave ID to approve'),
|
|
926
|
-
reason: z.string().max(500).optional().describe('Approval comment'),
|
|
927
|
-
},
|
|
928
|
-
}, async ({ id, reason }) => {
|
|
929
|
-
try {
|
|
930
|
-
const leave = await approveLeave(id, reason ? { reason } : undefined);
|
|
931
|
-
return {
|
|
932
|
-
content: [
|
|
933
|
-
{
|
|
934
|
-
type: 'text',
|
|
935
|
-
text: `Leave request approved successfully:\n\n${JSON.stringify(leave, null, 2)}`,
|
|
936
|
-
},
|
|
937
|
-
],
|
|
938
|
-
};
|
|
939
|
-
}
|
|
940
|
-
catch (error) {
|
|
941
|
-
return {
|
|
942
|
-
content: [
|
|
943
|
-
{
|
|
944
|
-
type: 'text',
|
|
945
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
946
|
-
},
|
|
947
|
-
],
|
|
948
|
-
isError: true,
|
|
949
|
-
};
|
|
950
|
-
}
|
|
951
|
-
});
|
|
952
|
-
server.registerTool('reject_leave', {
|
|
953
|
-
title: 'Reject Leave Request',
|
|
954
|
-
description: 'Reject a pending leave request. This operation requires confirmation.',
|
|
955
|
-
inputSchema: {
|
|
956
|
-
id: z.number().describe('The leave ID to reject'),
|
|
957
|
-
reason: z.string().max(500).optional().describe('Rejection reason'),
|
|
958
|
-
confirm: z.boolean().optional().describe('Set to true to confirm this operation'),
|
|
959
|
-
},
|
|
960
|
-
}, wrapHighRiskToolHandler('reject_leave', async ({ id, reason }) => {
|
|
961
|
-
const leave = await rejectLeave(id, reason ? { reason } : undefined);
|
|
962
|
-
return textResponse(`Leave request rejected:\n\n${JSON.stringify(leave, null, 2)}`);
|
|
963
|
-
}));
|
|
964
|
-
// ============================================================================
|
|
965
|
-
// Attendance / Shift Tools
|
|
966
|
-
// ============================================================================
|
|
967
|
-
server.registerTool('list_shifts', {
|
|
968
|
-
title: 'List Shifts',
|
|
969
|
-
description: 'Get employee attendance shifts. Filter by employee or date range. Read-only access.',
|
|
970
|
-
inputSchema: {
|
|
971
|
-
employee_id: z.number().optional().describe('Filter by employee ID'),
|
|
972
|
-
clock_in_gte: z
|
|
973
|
-
.string()
|
|
974
|
-
.optional()
|
|
975
|
-
.describe('Filter shifts clocking in after this time (ISO 8601)'),
|
|
976
|
-
clock_in_lte: z
|
|
977
|
-
.string()
|
|
978
|
-
.optional()
|
|
979
|
-
.describe('Filter shifts clocking in before this time (ISO 8601)'),
|
|
980
|
-
page: z.number().optional().default(1).describe('Page number'),
|
|
981
|
-
limit: z.number().optional().default(100).describe('Items per page (max: 100)'),
|
|
982
|
-
},
|
|
983
|
-
}, async ({ employee_id, clock_in_gte, clock_in_lte, page, limit }) => {
|
|
984
|
-
try {
|
|
985
|
-
const result = await listShifts({ employee_id, clock_in_gte, clock_in_lte, page, limit });
|
|
986
|
-
// Fetch employee names for enrichment (cached)
|
|
987
|
-
const employees = await listEmployees();
|
|
988
|
-
const empMap = new Map(employees.data.map(e => [e.id, e.full_name]));
|
|
989
|
-
const summary = result.data.map(s => ({
|
|
990
|
-
id: s.id,
|
|
991
|
-
employee_id: s.employee_id,
|
|
992
|
-
employee_name: empMap.get(s.employee_id) || `Employee ${s.employee_id}`,
|
|
993
|
-
clock_in: s.clock_in,
|
|
994
|
-
clock_out: s.clock_out,
|
|
995
|
-
worked_hours: s.worked_hours,
|
|
996
|
-
break_minutes: s.break_minutes,
|
|
997
|
-
}));
|
|
998
|
-
return {
|
|
999
|
-
content: [
|
|
1000
|
-
{
|
|
1001
|
-
type: 'text',
|
|
1002
|
-
text: `Found ${result.data.length} shifts (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(summary, null, 2)}`,
|
|
1003
|
-
},
|
|
1004
|
-
],
|
|
1005
|
-
};
|
|
1006
|
-
}
|
|
1007
|
-
catch (error) {
|
|
1008
|
-
return {
|
|
1009
|
-
content: [
|
|
1010
|
-
{
|
|
1011
|
-
type: 'text',
|
|
1012
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1013
|
-
},
|
|
1014
|
-
],
|
|
1015
|
-
isError: true,
|
|
1016
|
-
};
|
|
1017
|
-
}
|
|
1018
|
-
});
|
|
1019
|
-
server.registerTool('get_shift', {
|
|
1020
|
-
title: 'Get Shift Details',
|
|
1021
|
-
description: 'Get detailed information about a specific shift.',
|
|
1022
|
-
inputSchema: {
|
|
1023
|
-
id: z.number().describe('The shift ID'),
|
|
1024
|
-
},
|
|
1025
|
-
}, async ({ id }) => {
|
|
1026
|
-
try {
|
|
1027
|
-
const shift = await getShift(id);
|
|
1028
|
-
return {
|
|
1029
|
-
content: [
|
|
1030
|
-
{
|
|
1031
|
-
type: 'text',
|
|
1032
|
-
text: JSON.stringify(shift, null, 2),
|
|
1033
|
-
},
|
|
1034
|
-
],
|
|
1035
|
-
};
|
|
1036
|
-
}
|
|
1037
|
-
catch (error) {
|
|
1038
|
-
return {
|
|
1039
|
-
content: [
|
|
1040
|
-
{
|
|
1041
|
-
type: 'text',
|
|
1042
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1043
|
-
},
|
|
1044
|
-
],
|
|
1045
|
-
isError: true,
|
|
1046
|
-
};
|
|
1047
|
-
}
|
|
1048
|
-
});
|
|
1049
|
-
server.registerTool('create_shift', {
|
|
1050
|
-
title: 'Create Shift',
|
|
1051
|
-
description: 'Create a new attendance shift (clock in/out record).',
|
|
1052
|
-
inputSchema: {
|
|
1053
|
-
employee_id: z.number().describe('Employee ID'),
|
|
1054
|
-
clock_in: z.string().describe('Clock in time (ISO 8601)'),
|
|
1055
|
-
clock_out: z.string().optional().describe('Clock out time (ISO 8601)'),
|
|
1056
|
-
break_minutes: z.number().min(0).max(480).optional().describe('Break duration in minutes'),
|
|
1057
|
-
location: z.string().max(200).optional().describe('Work location'),
|
|
1058
|
-
notes: z.string().max(500).optional().describe('Notes'),
|
|
1059
|
-
},
|
|
1060
|
-
}, async (input) => {
|
|
1061
|
-
try {
|
|
1062
|
-
const shift = await createShift(input);
|
|
1063
|
-
return {
|
|
1064
|
-
content: [
|
|
1065
|
-
{
|
|
1066
|
-
type: 'text',
|
|
1067
|
-
text: `Shift created successfully:\n\n${JSON.stringify(shift, null, 2)}`,
|
|
1068
|
-
},
|
|
1069
|
-
],
|
|
1070
|
-
};
|
|
1071
|
-
}
|
|
1072
|
-
catch (error) {
|
|
1073
|
-
return {
|
|
1074
|
-
content: [
|
|
1075
|
-
{
|
|
1076
|
-
type: 'text',
|
|
1077
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1078
|
-
},
|
|
1079
|
-
],
|
|
1080
|
-
isError: true,
|
|
1081
|
-
};
|
|
1082
|
-
}
|
|
1083
|
-
});
|
|
1084
|
-
server.registerTool('update_shift', {
|
|
1085
|
-
title: 'Update Shift',
|
|
1086
|
-
description: 'Update an existing shift.',
|
|
1087
|
-
inputSchema: {
|
|
1088
|
-
id: z.number().describe('The shift ID to update'),
|
|
1089
|
-
clock_in: z.string().optional().describe('Clock in time (ISO 8601)'),
|
|
1090
|
-
clock_out: z.string().optional().describe('Clock out time (ISO 8601)'),
|
|
1091
|
-
break_minutes: z.number().min(0).max(480).optional().describe('Break duration in minutes'),
|
|
1092
|
-
location: z.string().max(200).optional().describe('Work location'),
|
|
1093
|
-
notes: z.string().max(500).optional().describe('Notes'),
|
|
1094
|
-
},
|
|
1095
|
-
}, async ({ id, ...input }) => {
|
|
1096
|
-
try {
|
|
1097
|
-
const shift = await updateShift(id, input);
|
|
1098
|
-
return {
|
|
1099
|
-
content: [
|
|
1100
|
-
{
|
|
1101
|
-
type: 'text',
|
|
1102
|
-
text: `Shift updated successfully:\n\n${JSON.stringify(shift, null, 2)}`,
|
|
1103
|
-
},
|
|
1104
|
-
],
|
|
1105
|
-
};
|
|
1106
|
-
}
|
|
1107
|
-
catch (error) {
|
|
1108
|
-
return {
|
|
1109
|
-
content: [
|
|
1110
|
-
{
|
|
1111
|
-
type: 'text',
|
|
1112
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1113
|
-
},
|
|
1114
|
-
],
|
|
1115
|
-
isError: true,
|
|
1116
|
-
};
|
|
1117
|
-
}
|
|
1118
|
-
});
|
|
1119
|
-
server.registerTool('delete_shift', {
|
|
1120
|
-
title: 'Delete Shift',
|
|
1121
|
-
description: 'Delete a shift record. This operation requires confirmation.',
|
|
1122
|
-
inputSchema: {
|
|
1123
|
-
id: z.number().describe('The shift ID to delete'),
|
|
1124
|
-
confirm: z.boolean().optional().describe('Set to true to confirm this operation'),
|
|
1125
|
-
},
|
|
1126
|
-
}, wrapHighRiskToolHandler('delete_shift', async ({ id }) => {
|
|
1127
|
-
await deleteShift(id);
|
|
1128
|
-
return textResponse(`Shift ${id} deleted successfully.`);
|
|
1129
|
-
}));
|
|
1130
|
-
// ============================================================================
|
|
1131
|
-
// Document Tools (Read-Only)
|
|
1132
|
-
// ============================================================================
|
|
1133
|
-
server.registerTool('list_folders', {
|
|
1134
|
-
title: 'List Folders',
|
|
1135
|
-
description: 'Get all document folders. Read-only access.',
|
|
1136
|
-
inputSchema: {},
|
|
1137
|
-
}, async () => {
|
|
1138
|
-
try {
|
|
1139
|
-
const folders = await listFolders();
|
|
1140
|
-
return {
|
|
1141
|
-
content: [
|
|
1142
|
-
{
|
|
1143
|
-
type: 'text',
|
|
1144
|
-
text: `Found ${folders.length} folders:\n\n${JSON.stringify(folders, null, 2)}`,
|
|
1145
|
-
},
|
|
1146
|
-
],
|
|
1147
|
-
};
|
|
1148
|
-
}
|
|
1149
|
-
catch (error) {
|
|
1150
|
-
return {
|
|
1151
|
-
content: [
|
|
1152
|
-
{
|
|
1153
|
-
type: 'text',
|
|
1154
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1155
|
-
},
|
|
1156
|
-
],
|
|
1157
|
-
isError: true,
|
|
1158
|
-
};
|
|
1159
|
-
}
|
|
1160
|
-
});
|
|
1161
|
-
server.registerTool('get_folder', {
|
|
1162
|
-
title: 'Get Folder',
|
|
1163
|
-
description: 'Get details about a specific folder.',
|
|
1164
|
-
inputSchema: {
|
|
1165
|
-
id: z.number().describe('The folder ID'),
|
|
1166
|
-
},
|
|
1167
|
-
}, async ({ id }) => {
|
|
1168
|
-
try {
|
|
1169
|
-
const folder = await getFolder(id);
|
|
1170
|
-
return {
|
|
1171
|
-
content: [
|
|
1172
|
-
{
|
|
1173
|
-
type: 'text',
|
|
1174
|
-
text: JSON.stringify(folder, 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('list_documents', {
|
|
1192
|
-
title: 'List Documents',
|
|
1193
|
-
description: 'Get documents with folder and employee name enrichment. Filter by folder or employee. Read-only access.',
|
|
1194
|
-
inputSchema: {
|
|
1195
|
-
folder_id: z.number().optional().describe('Filter by folder ID'),
|
|
1196
|
-
employee_ids: z.array(z.number()).optional().describe('Filter by employee IDs'),
|
|
1197
|
-
page: z.number().optional().default(1).describe('Page number'),
|
|
1198
|
-
limit: z.number().optional().default(100).describe('Items per page (max: 100)'),
|
|
1199
|
-
},
|
|
1200
|
-
}, async ({ folder_id, employee_ids, page, limit }) => {
|
|
1201
|
-
try {
|
|
1202
|
-
const result = await listDocuments({ folder_id, employee_ids, page, limit });
|
|
1203
|
-
// Fetch employee names for enrichment (cached)
|
|
1204
|
-
const employees = await listEmployees();
|
|
1205
|
-
const empMap = new Map(employees.data.map(e => [e.id, e.full_name]));
|
|
1206
|
-
// Fetch folders for name enrichment (cached)
|
|
1207
|
-
const folders = await listFolders();
|
|
1208
|
-
const folderMap = new Map(folders.map(f => [f.id, f.name]));
|
|
1209
|
-
const summary = result.data.map(d => ({
|
|
1210
|
-
id: d.id,
|
|
1211
|
-
name: d.name,
|
|
1212
|
-
folder_id: d.folder_id,
|
|
1213
|
-
folder_name: d.folder_id ? folderMap.get(d.folder_id) || null : null,
|
|
1214
|
-
employee_id: d.employee_id,
|
|
1215
|
-
employee_name: d.employee_id
|
|
1216
|
-
? empMap.get(d.employee_id) || `Employee ${d.employee_id}`
|
|
1217
|
-
: null,
|
|
1218
|
-
author_id: d.author_id,
|
|
1219
|
-
author_name: d.author_id ? empMap.get(d.author_id) || `Employee ${d.author_id}` : null,
|
|
1220
|
-
mime_type: d.mime_type,
|
|
1221
|
-
size_bytes: d.size_bytes,
|
|
1222
|
-
}));
|
|
1223
|
-
return {
|
|
1224
|
-
content: [
|
|
1225
|
-
{
|
|
1226
|
-
type: 'text',
|
|
1227
|
-
text: `Found ${result.data.length} documents (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(summary, null, 2)}`,
|
|
1228
|
-
},
|
|
1229
|
-
],
|
|
1230
|
-
};
|
|
1231
|
-
}
|
|
1232
|
-
catch (error) {
|
|
1233
|
-
return {
|
|
1234
|
-
content: [
|
|
1235
|
-
{
|
|
1236
|
-
type: 'text',
|
|
1237
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1238
|
-
},
|
|
1239
|
-
],
|
|
1240
|
-
isError: true,
|
|
1241
|
-
};
|
|
1242
|
-
}
|
|
1243
|
-
});
|
|
1244
|
-
server.registerTool('get_document', {
|
|
1245
|
-
title: 'Get Document',
|
|
1246
|
-
description: 'Get document metadata and URL. Read-only access.',
|
|
1247
|
-
inputSchema: {
|
|
1248
|
-
id: z.number().describe('The document ID'),
|
|
1249
|
-
},
|
|
1250
|
-
}, async ({ id }) => {
|
|
1251
|
-
try {
|
|
1252
|
-
const document = await getDocument(id);
|
|
1253
|
-
// Defensive check for undefined/null document
|
|
1254
|
-
if (!document) {
|
|
1255
|
-
return {
|
|
1256
|
-
content: [
|
|
1257
|
-
{
|
|
1258
|
-
type: 'text',
|
|
1259
|
-
text: `Error: Document with ID ${id} exists but returned no data. This may be a Factorial API issue.`,
|
|
1260
|
-
},
|
|
1261
|
-
],
|
|
1262
|
-
isError: true,
|
|
1263
|
-
};
|
|
1264
|
-
}
|
|
1265
|
-
return {
|
|
1266
|
-
content: [
|
|
1267
|
-
{
|
|
1268
|
-
type: 'text',
|
|
1269
|
-
text: JSON.stringify(document, null, 2),
|
|
1270
|
-
},
|
|
1271
|
-
],
|
|
1272
|
-
};
|
|
1273
|
-
}
|
|
1274
|
-
catch (error) {
|
|
1275
|
-
return {
|
|
1276
|
-
content: [
|
|
1277
|
-
{
|
|
1278
|
-
type: 'text',
|
|
1279
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1280
|
-
},
|
|
1281
|
-
],
|
|
1282
|
-
isError: true,
|
|
1283
|
-
};
|
|
1284
|
-
}
|
|
1285
|
-
});
|
|
1286
|
-
server.registerTool('get_employee_documents', {
|
|
1287
|
-
title: 'Get Employee Documents',
|
|
1288
|
-
description: 'Get all documents for a specific employee. Returns document summary with folder_name enrichment. Use get_document for full details.',
|
|
1289
|
-
inputSchema: {
|
|
1290
|
-
employee_id: z.number().describe('The employee ID'),
|
|
1291
|
-
page: z.number().optional().default(1).describe('Page number'),
|
|
1292
|
-
limit: z.number().optional().default(20).describe('Items per page (max: 100, default: 20)'),
|
|
1293
|
-
},
|
|
1294
|
-
}, async ({ employee_id, page, limit }) => {
|
|
1295
|
-
try {
|
|
1296
|
-
const result = await listDocuments({ employee_ids: [employee_id], page, limit });
|
|
1297
|
-
// Fetch folders for name enrichment (cached)
|
|
1298
|
-
const folders = await listFolders();
|
|
1299
|
-
const folderMap = new Map(folders.map(f => [f.id, f.name]));
|
|
1300
|
-
// Create summary format with folder name enrichment
|
|
1301
|
-
const summary = result.data.map(d => ({
|
|
1302
|
-
id: d.id,
|
|
1303
|
-
name: d.name ?? '[No name]',
|
|
1304
|
-
folder_id: d.folder_id,
|
|
1305
|
-
folder_name: d.folder_id ? folderMap.get(d.folder_id) || null : null,
|
|
1306
|
-
employee_id: d.employee_id,
|
|
1307
|
-
author_id: d.author_id,
|
|
1308
|
-
mime_type: d.mime_type ?? 'unknown',
|
|
1309
|
-
size_bytes: d.size_bytes ?? 0,
|
|
1310
|
-
}));
|
|
1311
|
-
return {
|
|
1312
|
-
content: [
|
|
1313
|
-
{
|
|
1314
|
-
type: 'text',
|
|
1315
|
-
text: `Found ${result.data.length} documents for employee ${employee_id} (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(summary, null, 2)}`,
|
|
1316
|
-
},
|
|
1317
|
-
],
|
|
1318
|
-
};
|
|
1319
|
-
}
|
|
1320
|
-
catch (error) {
|
|
1321
|
-
return {
|
|
1322
|
-
content: [
|
|
1323
|
-
{
|
|
1324
|
-
type: 'text',
|
|
1325
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1326
|
-
},
|
|
1327
|
-
],
|
|
1328
|
-
isError: true,
|
|
1329
|
-
};
|
|
1330
|
-
}
|
|
1331
|
-
});
|
|
1332
|
-
server.registerTool('search_employee_documents', {
|
|
1333
|
-
title: 'Search Employee Documents',
|
|
1334
|
-
description: 'Search documents by employee name and optional document name pattern. Example: search for "Taig\'s resume" or "certifications for Saray".',
|
|
1335
|
-
inputSchema: {
|
|
1336
|
-
employee_name: z
|
|
1337
|
-
.string()
|
|
1338
|
-
.min(2)
|
|
1339
|
-
.describe('Employee name to search for (partial match, min 2 chars)'),
|
|
1340
|
-
document_query: z
|
|
1341
|
-
.string()
|
|
1342
|
-
.optional()
|
|
1343
|
-
.describe('Document name pattern to filter by (e.g., "resume", "certification")'),
|
|
1344
|
-
page: z.number().optional().default(1).describe('Page number'),
|
|
1345
|
-
limit: z.number().optional().default(20).describe('Items per page (max: 100, default: 20)'),
|
|
1346
|
-
},
|
|
1347
|
-
}, async ({ employee_name, document_query, page, limit }) => {
|
|
1348
|
-
try {
|
|
1349
|
-
// Step 1: Search for employees by name
|
|
1350
|
-
const matchingEmployees = await searchEmployees(employee_name);
|
|
1351
|
-
if (matchingEmployees.length === 0) {
|
|
1352
|
-
return {
|
|
1353
|
-
content: [
|
|
1354
|
-
{
|
|
1355
|
-
type: 'text',
|
|
1356
|
-
text: `No employees found matching "${employee_name}".`,
|
|
1357
|
-
},
|
|
1358
|
-
],
|
|
1359
|
-
};
|
|
1360
|
-
}
|
|
1361
|
-
// Step 2: Get documents for matching employees
|
|
1362
|
-
const employeeIds = matchingEmployees.map(e => e.id);
|
|
1363
|
-
const result = await listDocuments({ employee_ids: employeeIds, page, limit });
|
|
1364
|
-
// Step 3: Filter by document name pattern if provided
|
|
1365
|
-
let filteredDocs = result.data;
|
|
1366
|
-
if (document_query) {
|
|
1367
|
-
const queryLower = document_query.toLowerCase();
|
|
1368
|
-
filteredDocs = result.data.filter(d => d.name && d.name.toLowerCase().includes(queryLower));
|
|
1369
|
-
}
|
|
1370
|
-
// Step 4: Enrich with employee and folder names
|
|
1371
|
-
const empMap = new Map(matchingEmployees.map(e => [e.id, e.full_name]));
|
|
1372
|
-
const folders = await listFolders();
|
|
1373
|
-
const folderMap = new Map(folders.map(f => [f.id, f.name]));
|
|
1374
|
-
const summary = filteredDocs.map(d => ({
|
|
1375
|
-
id: d.id,
|
|
1376
|
-
name: d.name ?? '[No name]',
|
|
1377
|
-
folder_id: d.folder_id,
|
|
1378
|
-
folder_name: d.folder_id ? folderMap.get(d.folder_id) || null : null,
|
|
1379
|
-
employee_id: d.employee_id,
|
|
1380
|
-
employee_name: d.employee_id
|
|
1381
|
-
? empMap.get(d.employee_id) || `Employee ${d.employee_id}`
|
|
1382
|
-
: null,
|
|
1383
|
-
mime_type: d.mime_type ?? 'unknown',
|
|
1384
|
-
size_bytes: d.size_bytes ?? 0,
|
|
1385
|
-
}));
|
|
1386
|
-
const employeeNames = matchingEmployees.map(e => e.full_name).join(', ');
|
|
1387
|
-
const queryInfo = document_query ? ` matching "${document_query}"` : '';
|
|
1388
|
-
return {
|
|
1389
|
-
content: [
|
|
1390
|
-
{
|
|
1391
|
-
type: 'text',
|
|
1392
|
-
text: `Found ${summary.length} documents${queryInfo} for employees matching "${employee_name}" (${employeeNames}):\n\n${JSON.stringify(summary, null, 2)}`,
|
|
1393
|
-
},
|
|
1394
|
-
],
|
|
1395
|
-
};
|
|
1396
|
-
}
|
|
1397
|
-
catch (error) {
|
|
1398
|
-
return {
|
|
1399
|
-
content: [
|
|
1400
|
-
{
|
|
1401
|
-
type: 'text',
|
|
1402
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1403
|
-
},
|
|
1404
|
-
],
|
|
1405
|
-
isError: true,
|
|
1406
|
-
};
|
|
1407
|
-
}
|
|
1408
|
-
});
|
|
1409
|
-
// ============================================================================
|
|
1410
|
-
// Job Catalog Tools
|
|
1411
|
-
// ============================================================================
|
|
1412
|
-
server.registerTool('list_job_roles', {
|
|
1413
|
-
title: 'List Job Roles',
|
|
1414
|
-
description: 'Get all job roles defined in the job catalog.',
|
|
1415
|
-
inputSchema: {},
|
|
1416
|
-
}, async () => {
|
|
1417
|
-
try {
|
|
1418
|
-
const roles = await listJobRoles();
|
|
1419
|
-
return {
|
|
1420
|
-
content: [
|
|
1421
|
-
{
|
|
1422
|
-
type: 'text',
|
|
1423
|
-
text: `Found ${roles.length} job roles:\n\n${JSON.stringify(roles, null, 2)}`,
|
|
1424
|
-
},
|
|
1425
|
-
],
|
|
1426
|
-
};
|
|
1427
|
-
}
|
|
1428
|
-
catch (error) {
|
|
1429
|
-
return {
|
|
1430
|
-
content: [
|
|
1431
|
-
{
|
|
1432
|
-
type: 'text',
|
|
1433
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1434
|
-
},
|
|
1435
|
-
],
|
|
1436
|
-
isError: true,
|
|
1437
|
-
};
|
|
1438
|
-
}
|
|
1439
|
-
});
|
|
1440
|
-
server.registerTool('get_job_role', {
|
|
1441
|
-
title: 'Get Job Role',
|
|
1442
|
-
description: 'Get details about a specific job role.',
|
|
1443
|
-
inputSchema: {
|
|
1444
|
-
id: z.number().describe('The job role ID'),
|
|
1445
|
-
},
|
|
1446
|
-
}, async ({ id }) => {
|
|
1447
|
-
try {
|
|
1448
|
-
const role = await getJobRole(id);
|
|
1449
|
-
return {
|
|
1450
|
-
content: [
|
|
1451
|
-
{
|
|
1452
|
-
type: 'text',
|
|
1453
|
-
text: JSON.stringify(role, null, 2),
|
|
1454
|
-
},
|
|
1455
|
-
],
|
|
1456
|
-
};
|
|
1457
|
-
}
|
|
1458
|
-
catch (error) {
|
|
1459
|
-
return {
|
|
1460
|
-
content: [
|
|
1461
|
-
{
|
|
1462
|
-
type: 'text',
|
|
1463
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1464
|
-
},
|
|
1465
|
-
],
|
|
1466
|
-
isError: true,
|
|
1467
|
-
};
|
|
1468
|
-
}
|
|
1469
|
-
});
|
|
1470
|
-
server.registerTool('list_job_levels', {
|
|
1471
|
-
title: 'List Job Levels',
|
|
1472
|
-
description: 'Get all job levels defined in the job catalog.',
|
|
1473
|
-
inputSchema: {},
|
|
1474
|
-
}, async () => {
|
|
1475
|
-
try {
|
|
1476
|
-
const levels = await listJobLevels();
|
|
1477
|
-
return {
|
|
1478
|
-
content: [
|
|
1479
|
-
{
|
|
1480
|
-
type: 'text',
|
|
1481
|
-
text: `Found ${levels.length} job levels:\n\n${JSON.stringify(levels, null, 2)}`,
|
|
1482
|
-
},
|
|
1483
|
-
],
|
|
1484
|
-
};
|
|
1485
|
-
}
|
|
1486
|
-
catch (error) {
|
|
1487
|
-
return {
|
|
1488
|
-
content: [
|
|
1489
|
-
{
|
|
1490
|
-
type: 'text',
|
|
1491
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1492
|
-
},
|
|
1493
|
-
],
|
|
1494
|
-
isError: true,
|
|
1495
|
-
};
|
|
1496
|
-
}
|
|
1497
|
-
});
|
|
1498
|
-
// ============================================================================
|
|
1499
|
-
// Project Tools
|
|
1500
|
-
// ============================================================================
|
|
1501
|
-
server.registerTool('list_projects', {
|
|
1502
|
-
title: 'List Projects',
|
|
1503
|
-
description: 'Get all projects. Supports pagination.',
|
|
1504
|
-
inputSchema: {
|
|
1505
|
-
page: z.number().optional().default(1).describe('Page number'),
|
|
1506
|
-
limit: z.number().optional().default(100).describe('Items per page'),
|
|
1507
|
-
},
|
|
1508
|
-
}, async ({ page, limit }) => {
|
|
1509
|
-
try {
|
|
1510
|
-
const result = await listProjects({ page, limit });
|
|
1511
|
-
return {
|
|
1512
|
-
content: [
|
|
1513
|
-
{
|
|
1514
|
-
type: 'text',
|
|
1515
|
-
text: `Found ${result.data.length} projects (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(result.data, null, 2)}`,
|
|
1516
|
-
},
|
|
1517
|
-
],
|
|
1518
|
-
};
|
|
1519
|
-
}
|
|
1520
|
-
catch (error) {
|
|
1521
|
-
return {
|
|
1522
|
-
content: [
|
|
1523
|
-
{
|
|
1524
|
-
type: 'text',
|
|
1525
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1526
|
-
},
|
|
1527
|
-
],
|
|
1528
|
-
isError: true,
|
|
1529
|
-
};
|
|
1530
|
-
}
|
|
1531
|
-
});
|
|
1532
|
-
server.registerTool('get_project', {
|
|
1533
|
-
title: 'Get Project',
|
|
1534
|
-
description: 'Get detailed information about a specific project.',
|
|
1535
|
-
inputSchema: {
|
|
1536
|
-
id: z.number().describe('The project ID'),
|
|
1537
|
-
},
|
|
1538
|
-
}, async ({ id }) => {
|
|
1539
|
-
try {
|
|
1540
|
-
const project = await getProject(id);
|
|
1541
|
-
return {
|
|
1542
|
-
content: [{ type: 'text', text: JSON.stringify(project, null, 2) }],
|
|
1543
|
-
};
|
|
1544
|
-
}
|
|
1545
|
-
catch (error) {
|
|
1546
|
-
return {
|
|
1547
|
-
content: [
|
|
1548
|
-
{
|
|
1549
|
-
type: 'text',
|
|
1550
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1551
|
-
},
|
|
1552
|
-
],
|
|
1553
|
-
isError: true,
|
|
1554
|
-
};
|
|
1555
|
-
}
|
|
1556
|
-
});
|
|
1557
|
-
server.registerTool('create_project', {
|
|
1558
|
-
title: 'Create Project',
|
|
1559
|
-
description: 'Create a new project.',
|
|
1560
|
-
inputSchema: {
|
|
1561
|
-
name: z.string().min(1).max(100).describe('Project name'),
|
|
1562
|
-
code: z.string().max(20).optional().describe('Project code'),
|
|
1563
|
-
description: z.string().max(500).optional().describe('Description'),
|
|
1564
|
-
employees_assignment: z.enum(['manual', 'company']).optional().describe('Assignment mode'),
|
|
1565
|
-
},
|
|
1566
|
-
}, async (input) => {
|
|
1567
|
-
try {
|
|
1568
|
-
const project = await createProject(input);
|
|
1569
|
-
return {
|
|
1570
|
-
content: [
|
|
1571
|
-
{ type: 'text', text: `Project created:\n\n${JSON.stringify(project, null, 2)}` },
|
|
1572
|
-
],
|
|
1573
|
-
};
|
|
1574
|
-
}
|
|
1575
|
-
catch (error) {
|
|
1576
|
-
return {
|
|
1577
|
-
content: [
|
|
1578
|
-
{
|
|
1579
|
-
type: 'text',
|
|
1580
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1581
|
-
},
|
|
1582
|
-
],
|
|
1583
|
-
isError: true,
|
|
1584
|
-
};
|
|
1585
|
-
}
|
|
1586
|
-
});
|
|
1587
|
-
server.registerTool('update_project', {
|
|
1588
|
-
title: 'Update Project',
|
|
1589
|
-
description: 'Update an existing project.',
|
|
1590
|
-
inputSchema: {
|
|
1591
|
-
id: z.number().describe('The project ID'),
|
|
1592
|
-
name: z.string().min(1).max(100).optional().describe('Project name'),
|
|
1593
|
-
code: z.string().max(20).optional().describe('Project code'),
|
|
1594
|
-
description: z.string().max(500).optional().describe('Description'),
|
|
1595
|
-
status: z.enum(['active', 'inactive', 'archived']).optional().describe('Status'),
|
|
1596
|
-
},
|
|
1597
|
-
}, async ({ id, ...input }) => {
|
|
1598
|
-
try {
|
|
1599
|
-
const project = await updateProject(id, input);
|
|
1600
|
-
return {
|
|
1601
|
-
content: [
|
|
1602
|
-
{ type: 'text', text: `Project updated:\n\n${JSON.stringify(project, null, 2)}` },
|
|
1603
|
-
],
|
|
1604
|
-
};
|
|
1605
|
-
}
|
|
1606
|
-
catch (error) {
|
|
1607
|
-
return {
|
|
1608
|
-
content: [
|
|
1609
|
-
{
|
|
1610
|
-
type: 'text',
|
|
1611
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1612
|
-
},
|
|
1613
|
-
],
|
|
1614
|
-
isError: true,
|
|
1615
|
-
};
|
|
1616
|
-
}
|
|
1617
|
-
});
|
|
1618
|
-
server.registerTool('delete_project', {
|
|
1619
|
-
title: 'Delete Project',
|
|
1620
|
-
description: 'Delete a project. This is a HIGH-RISK operation that requires confirmation.',
|
|
1621
|
-
inputSchema: {
|
|
1622
|
-
id: z.number().describe('The project ID to delete'),
|
|
1623
|
-
confirm: z.boolean().optional().describe('Set to true to confirm this high-risk operation'),
|
|
1624
|
-
},
|
|
1625
|
-
}, wrapHighRiskToolHandler('delete_project', async ({ id }) => {
|
|
1626
|
-
await deleteProject(id);
|
|
1627
|
-
return textResponse(`Project ${id} deleted successfully.`);
|
|
1628
|
-
}));
|
|
1629
|
-
server.registerTool('list_project_tasks', {
|
|
1630
|
-
title: 'List Project Tasks',
|
|
1631
|
-
description: 'Get tasks for a project.',
|
|
1632
|
-
inputSchema: {
|
|
1633
|
-
project_id: z.number().optional().describe('Filter by project ID'),
|
|
1634
|
-
page: z.number().optional().default(1).describe('Page number'),
|
|
1635
|
-
limit: z.number().optional().default(100).describe('Items per page'),
|
|
1636
|
-
},
|
|
1637
|
-
}, async ({ project_id, page, limit }) => {
|
|
1638
|
-
try {
|
|
1639
|
-
const result = await listProjectTasks(project_id, { page, limit });
|
|
1640
|
-
return {
|
|
1641
|
-
content: [
|
|
1642
|
-
{
|
|
1643
|
-
type: 'text',
|
|
1644
|
-
text: `Found ${result.data.length} tasks (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(result.data, null, 2)}`,
|
|
1645
|
-
},
|
|
1646
|
-
],
|
|
1647
|
-
};
|
|
1648
|
-
}
|
|
1649
|
-
catch (error) {
|
|
1650
|
-
return {
|
|
1651
|
-
content: [
|
|
1652
|
-
{
|
|
1653
|
-
type: 'text',
|
|
1654
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1655
|
-
},
|
|
1656
|
-
],
|
|
1657
|
-
isError: true,
|
|
1658
|
-
};
|
|
1659
|
-
}
|
|
1660
|
-
});
|
|
1661
|
-
server.registerTool('create_project_task', {
|
|
1662
|
-
title: 'Create Project Task',
|
|
1663
|
-
description: 'Create a new task for a project.',
|
|
1664
|
-
inputSchema: {
|
|
1665
|
-
name: z.string().min(1).max(100).describe('Task name'),
|
|
1666
|
-
project_id: z.number().describe('Project ID'),
|
|
1667
|
-
description: z.string().max(500).optional().describe('Description'),
|
|
1668
|
-
due_on: z.string().optional().describe('Due date (YYYY-MM-DD)'),
|
|
1669
|
-
},
|
|
1670
|
-
}, async (input) => {
|
|
1671
|
-
try {
|
|
1672
|
-
const task = await createProjectTask(input);
|
|
1673
|
-
return {
|
|
1674
|
-
content: [{ type: 'text', text: `Task created:\n\n${JSON.stringify(task, null, 2)}` }],
|
|
1675
|
-
};
|
|
1676
|
-
}
|
|
1677
|
-
catch (error) {
|
|
1678
|
-
return {
|
|
1679
|
-
content: [
|
|
1680
|
-
{
|
|
1681
|
-
type: 'text',
|
|
1682
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1683
|
-
},
|
|
1684
|
-
],
|
|
1685
|
-
isError: true,
|
|
1686
|
-
};
|
|
1687
|
-
}
|
|
1688
|
-
});
|
|
1689
|
-
server.registerTool('update_project_task', {
|
|
1690
|
-
title: 'Update Project Task',
|
|
1691
|
-
description: 'Update a project task.',
|
|
1692
|
-
inputSchema: {
|
|
1693
|
-
id: z.number().describe('The task ID'),
|
|
1694
|
-
name: z.string().min(1).max(100).optional().describe('Task name'),
|
|
1695
|
-
description: z.string().max(500).optional().describe('Description'),
|
|
1696
|
-
due_on: z.string().optional().describe('Due date (YYYY-MM-DD)'),
|
|
1697
|
-
completed: z.boolean().optional().describe('Mark as completed'),
|
|
1698
|
-
},
|
|
1699
|
-
}, async ({ id, ...input }) => {
|
|
1700
|
-
try {
|
|
1701
|
-
const task = await updateProjectTask(id, input);
|
|
1702
|
-
return {
|
|
1703
|
-
content: [{ type: 'text', text: `Task updated:\n\n${JSON.stringify(task, null, 2)}` }],
|
|
1704
|
-
};
|
|
1705
|
-
}
|
|
1706
|
-
catch (error) {
|
|
1707
|
-
return {
|
|
1708
|
-
content: [
|
|
1709
|
-
{
|
|
1710
|
-
type: 'text',
|
|
1711
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1712
|
-
},
|
|
1713
|
-
],
|
|
1714
|
-
isError: true,
|
|
1715
|
-
};
|
|
1716
|
-
}
|
|
1717
|
-
});
|
|
1718
|
-
server.registerTool('delete_project_task', {
|
|
1719
|
-
title: 'Delete Project Task',
|
|
1720
|
-
description: 'Delete a project task.',
|
|
1721
|
-
inputSchema: {
|
|
1722
|
-
id: z.number().describe('The task ID to delete'),
|
|
1723
|
-
},
|
|
1724
|
-
}, async ({ id }) => {
|
|
1725
|
-
try {
|
|
1726
|
-
await deleteProjectTask(id);
|
|
1727
|
-
return {
|
|
1728
|
-
content: [{ type: 'text', text: `Task ${id} deleted successfully.` }],
|
|
1729
|
-
};
|
|
1730
|
-
}
|
|
1731
|
-
catch (error) {
|
|
1732
|
-
return {
|
|
1733
|
-
content: [
|
|
1734
|
-
{
|
|
1735
|
-
type: 'text',
|
|
1736
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1737
|
-
},
|
|
1738
|
-
],
|
|
1739
|
-
isError: true,
|
|
1740
|
-
};
|
|
1741
|
-
}
|
|
1742
|
-
});
|
|
1743
|
-
server.registerTool('list_project_workers', {
|
|
1744
|
-
title: 'List Project Workers',
|
|
1745
|
-
description: 'Get workers assigned to projects.',
|
|
1746
|
-
inputSchema: {
|
|
1747
|
-
project_id: z.number().optional().describe('Filter by project ID'),
|
|
1748
|
-
page: z.number().optional().default(1).describe('Page number'),
|
|
1749
|
-
limit: z.number().optional().default(100).describe('Items per page'),
|
|
1750
|
-
},
|
|
1751
|
-
}, async ({ project_id, page, limit }) => {
|
|
1752
|
-
try {
|
|
1753
|
-
const result = await listProjectWorkers(project_id, { page, limit });
|
|
1754
|
-
// Fetch enrichment data
|
|
1755
|
-
const [projects, employees] = await Promise.all([listProjects(), listEmployees()]);
|
|
1756
|
-
const projectMap = new Map(projects.data.map(p => [p.id, p.name]));
|
|
1757
|
-
const empMap = new Map(employees.data.map(e => [e.id, e.full_name]));
|
|
1758
|
-
const summary = result.data.map(w => ({
|
|
1759
|
-
id: w.id,
|
|
1760
|
-
project_id: w.project_id,
|
|
1761
|
-
project_name: projectMap.get(w.project_id) || `Project ${w.project_id}`,
|
|
1762
|
-
employee_id: w.employee_id,
|
|
1763
|
-
employee_name: empMap.get(w.employee_id) || `Employee ${w.employee_id}`,
|
|
1764
|
-
}));
|
|
1765
|
-
return {
|
|
1766
|
-
content: [
|
|
1767
|
-
{
|
|
1768
|
-
type: 'text',
|
|
1769
|
-
text: `Found ${result.data.length} project workers (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(summary, null, 2)}`,
|
|
1770
|
-
},
|
|
1771
|
-
],
|
|
1772
|
-
};
|
|
1773
|
-
}
|
|
1774
|
-
catch (error) {
|
|
1775
|
-
return {
|
|
1776
|
-
content: [
|
|
1777
|
-
{
|
|
1778
|
-
type: 'text',
|
|
1779
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1780
|
-
},
|
|
1781
|
-
],
|
|
1782
|
-
isError: true,
|
|
1783
|
-
};
|
|
1784
|
-
}
|
|
1785
|
-
});
|
|
1786
|
-
server.registerTool('assign_project_worker', {
|
|
1787
|
-
title: 'Assign Project Worker',
|
|
1788
|
-
description: 'Assign an employee to a project.',
|
|
1789
|
-
inputSchema: {
|
|
1790
|
-
project_id: z.number().describe('Project ID'),
|
|
1791
|
-
employee_id: z.number().describe('Employee ID'),
|
|
1792
|
-
},
|
|
1793
|
-
}, async (input) => {
|
|
1794
|
-
try {
|
|
1795
|
-
const worker = await assignProjectWorker(input);
|
|
1796
|
-
return {
|
|
1797
|
-
content: [{ type: 'text', text: `Worker assigned:\n\n${JSON.stringify(worker, null, 2)}` }],
|
|
1798
|
-
};
|
|
1799
|
-
}
|
|
1800
|
-
catch (error) {
|
|
1801
|
-
return {
|
|
1802
|
-
content: [
|
|
1803
|
-
{
|
|
1804
|
-
type: 'text',
|
|
1805
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1806
|
-
},
|
|
1807
|
-
],
|
|
1808
|
-
isError: true,
|
|
1809
|
-
};
|
|
1810
|
-
}
|
|
1811
|
-
});
|
|
1812
|
-
server.registerTool('remove_project_worker', {
|
|
1813
|
-
title: 'Remove Project Worker',
|
|
1814
|
-
description: 'Remove an employee from a project.',
|
|
1815
|
-
inputSchema: {
|
|
1816
|
-
id: z.number().describe('The project worker ID to remove'),
|
|
1817
|
-
},
|
|
1818
|
-
}, async ({ id }) => {
|
|
1819
|
-
try {
|
|
1820
|
-
await removeProjectWorker(id);
|
|
1821
|
-
return {
|
|
1822
|
-
content: [{ type: 'text', text: `Project worker ${id} removed successfully.` }],
|
|
1823
|
-
};
|
|
1824
|
-
}
|
|
1825
|
-
catch (error) {
|
|
1826
|
-
return {
|
|
1827
|
-
content: [
|
|
1828
|
-
{
|
|
1829
|
-
type: 'text',
|
|
1830
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1831
|
-
},
|
|
1832
|
-
],
|
|
1833
|
-
isError: true,
|
|
1834
|
-
};
|
|
1835
|
-
}
|
|
1836
|
-
});
|
|
1837
|
-
server.registerTool('list_time_records', {
|
|
1838
|
-
title: 'List Time Records',
|
|
1839
|
-
description: 'Get time records for project workers.',
|
|
1840
|
-
inputSchema: {
|
|
1841
|
-
project_worker_id: z.number().optional().describe('Filter by project worker ID'),
|
|
1842
|
-
page: z.number().optional().default(1).describe('Page number'),
|
|
1843
|
-
limit: z.number().optional().default(100).describe('Items per page'),
|
|
1844
|
-
},
|
|
1845
|
-
}, async ({ project_worker_id, page, limit }) => {
|
|
1846
|
-
try {
|
|
1847
|
-
const result = await listTimeRecords(project_worker_id, { page, limit });
|
|
1848
|
-
return {
|
|
1849
|
-
content: [
|
|
1850
|
-
{
|
|
1851
|
-
type: 'text',
|
|
1852
|
-
text: `Found ${result.data.length} time records (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(result.data, null, 2)}`,
|
|
1853
|
-
},
|
|
1854
|
-
],
|
|
1855
|
-
};
|
|
1856
|
-
}
|
|
1857
|
-
catch (error) {
|
|
1858
|
-
return {
|
|
1859
|
-
content: [
|
|
1860
|
-
{
|
|
1861
|
-
type: 'text',
|
|
1862
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1863
|
-
},
|
|
1864
|
-
],
|
|
1865
|
-
isError: true,
|
|
1866
|
-
};
|
|
1867
|
-
}
|
|
1868
|
-
});
|
|
1869
|
-
server.registerTool('create_time_record', {
|
|
1870
|
-
title: 'Create Time Record',
|
|
1871
|
-
description: 'Log time for a project worker.',
|
|
1872
|
-
inputSchema: {
|
|
1873
|
-
project_worker_id: z.number().describe('Project worker ID'),
|
|
1874
|
-
date: z.string().describe('Date (YYYY-MM-DD)'),
|
|
1875
|
-
minutes: z.number().min(1).max(1440).describe('Minutes worked'),
|
|
1876
|
-
description: z.string().max(500).optional().describe('Description'),
|
|
1877
|
-
},
|
|
1878
|
-
}, async (input) => {
|
|
1879
|
-
try {
|
|
1880
|
-
const record = await createTimeRecord(input);
|
|
1881
|
-
return {
|
|
1882
|
-
content: [
|
|
1883
|
-
{ type: 'text', text: `Time record created:\n\n${JSON.stringify(record, null, 2)}` },
|
|
1884
|
-
],
|
|
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
|
-
server.registerTool('update_time_record', {
|
|
1900
|
-
title: 'Update Time Record',
|
|
1901
|
-
description: 'Update a time record.',
|
|
1902
|
-
inputSchema: {
|
|
1903
|
-
id: z.number().describe('Time record ID'),
|
|
1904
|
-
date: z.string().optional().describe('Date (YYYY-MM-DD)'),
|
|
1905
|
-
minutes: z.number().min(1).max(1440).optional().describe('Minutes worked'),
|
|
1906
|
-
description: z.string().max(500).optional().describe('Description'),
|
|
1907
|
-
},
|
|
1908
|
-
}, async ({ id, ...input }) => {
|
|
1909
|
-
try {
|
|
1910
|
-
const record = await updateTimeRecord(id, input);
|
|
1911
|
-
return {
|
|
1912
|
-
content: [
|
|
1913
|
-
{ type: 'text', text: `Time record updated:\n\n${JSON.stringify(record, null, 2)}` },
|
|
1914
|
-
],
|
|
1915
|
-
};
|
|
1916
|
-
}
|
|
1917
|
-
catch (error) {
|
|
1918
|
-
return {
|
|
1919
|
-
content: [
|
|
1920
|
-
{
|
|
1921
|
-
type: 'text',
|
|
1922
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1923
|
-
},
|
|
1924
|
-
],
|
|
1925
|
-
isError: true,
|
|
1926
|
-
};
|
|
1927
|
-
}
|
|
1928
|
-
});
|
|
1929
|
-
server.registerTool('delete_time_record', {
|
|
1930
|
-
title: 'Delete Time Record',
|
|
1931
|
-
description: 'Delete a time record.',
|
|
1932
|
-
inputSchema: {
|
|
1933
|
-
id: z.number().describe('Time record ID to delete'),
|
|
1934
|
-
},
|
|
1935
|
-
}, async ({ id }) => {
|
|
1936
|
-
try {
|
|
1937
|
-
await deleteTimeRecord(id);
|
|
1938
|
-
return {
|
|
1939
|
-
content: [{ type: 'text', text: `Time record ${id} deleted successfully.` }],
|
|
1940
|
-
};
|
|
1941
|
-
}
|
|
1942
|
-
catch (error) {
|
|
1943
|
-
return {
|
|
1944
|
-
content: [
|
|
1945
|
-
{
|
|
1946
|
-
type: 'text',
|
|
1947
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1948
|
-
},
|
|
1949
|
-
],
|
|
1950
|
-
isError: true,
|
|
1951
|
-
};
|
|
1952
|
-
}
|
|
1953
|
-
});
|
|
1954
|
-
// ============================================================================
|
|
1955
|
-
// Training Tools
|
|
1956
|
-
// ============================================================================
|
|
1957
|
-
server.registerTool('list_trainings', {
|
|
1958
|
-
title: 'List Trainings',
|
|
1959
|
-
description: 'Get all training programs.',
|
|
1960
|
-
inputSchema: {
|
|
1961
|
-
page: z.number().optional().default(1).describe('Page number'),
|
|
1962
|
-
limit: z.number().optional().default(100).describe('Items per page'),
|
|
1963
|
-
},
|
|
1964
|
-
}, async ({ page, limit }) => {
|
|
1965
|
-
try {
|
|
1966
|
-
const result = await listTrainings({ page, limit });
|
|
1967
|
-
return {
|
|
1968
|
-
content: [
|
|
1969
|
-
{
|
|
1970
|
-
type: 'text',
|
|
1971
|
-
text: `Found ${result.data.length} trainings (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(result.data, null, 2)}`,
|
|
1972
|
-
},
|
|
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('get_training', {
|
|
1989
|
-
title: 'Get Training',
|
|
1990
|
-
description: 'Get details about a training program.',
|
|
1991
|
-
inputSchema: {
|
|
1992
|
-
id: z.number().describe('The training ID'),
|
|
1993
|
-
},
|
|
1994
|
-
}, async ({ id }) => {
|
|
1995
|
-
try {
|
|
1996
|
-
const training = await getTraining(id);
|
|
1997
|
-
return {
|
|
1998
|
-
content: [{ type: 'text', text: JSON.stringify(training, null, 2) }],
|
|
1999
|
-
};
|
|
2000
|
-
}
|
|
2001
|
-
catch (error) {
|
|
2002
|
-
return {
|
|
2003
|
-
content: [
|
|
2004
|
-
{
|
|
2005
|
-
type: 'text',
|
|
2006
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2007
|
-
},
|
|
2008
|
-
],
|
|
2009
|
-
isError: true,
|
|
2010
|
-
};
|
|
2011
|
-
}
|
|
2012
|
-
});
|
|
2013
|
-
server.registerTool('create_training', {
|
|
2014
|
-
title: 'Create Training',
|
|
2015
|
-
description: 'Create a new training program.',
|
|
2016
|
-
inputSchema: {
|
|
2017
|
-
name: z.string().min(1).max(100).describe('Training name'),
|
|
2018
|
-
description: z.string().max(1000).optional().describe('Description'),
|
|
2019
|
-
category_id: z.number().optional().describe('Category ID'),
|
|
2020
|
-
subsidized: z.boolean().optional().describe('Is subsidized'),
|
|
2021
|
-
},
|
|
2022
|
-
}, async (input) => {
|
|
2023
|
-
try {
|
|
2024
|
-
const training = await createTraining(input);
|
|
2025
|
-
return {
|
|
2026
|
-
content: [
|
|
2027
|
-
{ type: 'text', text: `Training created:\n\n${JSON.stringify(training, null, 2)}` },
|
|
2028
|
-
],
|
|
2029
|
-
};
|
|
2030
|
-
}
|
|
2031
|
-
catch (error) {
|
|
2032
|
-
return {
|
|
2033
|
-
content: [
|
|
2034
|
-
{
|
|
2035
|
-
type: 'text',
|
|
2036
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2037
|
-
},
|
|
2038
|
-
],
|
|
2039
|
-
isError: true,
|
|
2040
|
-
};
|
|
2041
|
-
}
|
|
2042
|
-
});
|
|
2043
|
-
server.registerTool('update_training', {
|
|
2044
|
-
title: 'Update Training',
|
|
2045
|
-
description: 'Update a training program.',
|
|
2046
|
-
inputSchema: {
|
|
2047
|
-
id: z.number().describe('The training ID'),
|
|
2048
|
-
name: z.string().min(1).max(100).optional().describe('Training name'),
|
|
2049
|
-
description: z.string().max(1000).optional().describe('Description'),
|
|
2050
|
-
category_id: z.number().optional().describe('Category ID'),
|
|
2051
|
-
subsidized: z.boolean().optional().describe('Is subsidized'),
|
|
2052
|
-
},
|
|
2053
|
-
}, async ({ id, ...input }) => {
|
|
2054
|
-
try {
|
|
2055
|
-
const training = await updateTraining(id, input);
|
|
2056
|
-
return {
|
|
2057
|
-
content: [
|
|
2058
|
-
{ type: 'text', text: `Training updated:\n\n${JSON.stringify(training, null, 2)}` },
|
|
2059
|
-
],
|
|
2060
|
-
};
|
|
2061
|
-
}
|
|
2062
|
-
catch (error) {
|
|
2063
|
-
return {
|
|
2064
|
-
content: [
|
|
2065
|
-
{
|
|
2066
|
-
type: 'text',
|
|
2067
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2068
|
-
},
|
|
2069
|
-
],
|
|
2070
|
-
isError: true,
|
|
2071
|
-
};
|
|
2072
|
-
}
|
|
2073
|
-
});
|
|
2074
|
-
server.registerTool('delete_training', {
|
|
2075
|
-
title: 'Delete Training',
|
|
2076
|
-
description: 'Delete a training program. This is a HIGH-RISK operation that requires confirmation.',
|
|
2077
|
-
inputSchema: {
|
|
2078
|
-
id: z.number().describe('The training ID to delete'),
|
|
2079
|
-
confirm: z.boolean().optional().describe('Set to true to confirm this high-risk operation'),
|
|
2080
|
-
},
|
|
2081
|
-
}, wrapHighRiskToolHandler('delete_training', async ({ id }) => {
|
|
2082
|
-
await deleteTraining(id);
|
|
2083
|
-
return textResponse(`Training ${id} deleted successfully.`);
|
|
2084
|
-
}));
|
|
2085
|
-
server.registerTool('list_training_sessions', {
|
|
2086
|
-
title: 'List Training Sessions',
|
|
2087
|
-
description: 'Get sessions for a training program.',
|
|
2088
|
-
inputSchema: {
|
|
2089
|
-
training_id: z.number().optional().describe('Filter by training ID'),
|
|
2090
|
-
page: z.number().optional().default(1).describe('Page number'),
|
|
2091
|
-
limit: z.number().optional().default(100).describe('Items per page'),
|
|
2092
|
-
},
|
|
2093
|
-
}, async ({ training_id, page, limit }) => {
|
|
2094
|
-
try {
|
|
2095
|
-
const result = await listTrainingSessions(training_id, { page, limit });
|
|
2096
|
-
// Fetch training names for enrichment
|
|
2097
|
-
const trainings = await listTrainings();
|
|
2098
|
-
const trainingMap = new Map(trainings.data.map(t => [t.id, t.name]));
|
|
2099
|
-
const summary = result.data.map(s => ({
|
|
2100
|
-
id: s.id,
|
|
2101
|
-
training_id: s.training_id,
|
|
2102
|
-
training_name: trainingMap.get(s.training_id) || `Training ${s.training_id}`,
|
|
2103
|
-
name: s.name,
|
|
2104
|
-
start_date: s.start_date,
|
|
2105
|
-
end_date: s.end_date,
|
|
2106
|
-
location: s.location,
|
|
2107
|
-
max_attendees: s.max_attendees,
|
|
2108
|
-
}));
|
|
2109
|
-
return {
|
|
2110
|
-
content: [
|
|
2111
|
-
{
|
|
2112
|
-
type: 'text',
|
|
2113
|
-
text: `Found ${result.data.length} sessions (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(summary, null, 2)}`,
|
|
2114
|
-
},
|
|
2115
|
-
],
|
|
2116
|
-
};
|
|
2117
|
-
}
|
|
2118
|
-
catch (error) {
|
|
2119
|
-
return {
|
|
2120
|
-
content: [
|
|
2121
|
-
{
|
|
2122
|
-
type: 'text',
|
|
2123
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2124
|
-
},
|
|
2125
|
-
],
|
|
2126
|
-
isError: true,
|
|
2127
|
-
};
|
|
2128
|
-
}
|
|
2129
|
-
});
|
|
2130
|
-
server.registerTool('create_training_session', {
|
|
2131
|
-
title: 'Create Training Session',
|
|
2132
|
-
description: 'Create a session for a training program.',
|
|
2133
|
-
inputSchema: {
|
|
2134
|
-
training_id: z.number().describe('Training ID'),
|
|
2135
|
-
name: z.string().max(100).optional().describe('Session name'),
|
|
2136
|
-
start_date: z.string().optional().describe('Start date (YYYY-MM-DD)'),
|
|
2137
|
-
end_date: z.string().optional().describe('End date (YYYY-MM-DD)'),
|
|
2138
|
-
location: z.string().max(200).optional().describe('Location'),
|
|
2139
|
-
max_attendees: z.number().optional().describe('Max attendees'),
|
|
2140
|
-
},
|
|
2141
|
-
}, async (input) => {
|
|
2142
|
-
try {
|
|
2143
|
-
const session = await createTrainingSession(input);
|
|
2144
|
-
return {
|
|
2145
|
-
content: [
|
|
2146
|
-
{ type: 'text', text: `Session created:\n\n${JSON.stringify(session, null, 2)}` },
|
|
2147
|
-
],
|
|
2148
|
-
};
|
|
2149
|
-
}
|
|
2150
|
-
catch (error) {
|
|
2151
|
-
return {
|
|
2152
|
-
content: [
|
|
2153
|
-
{
|
|
2154
|
-
type: 'text',
|
|
2155
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2156
|
-
},
|
|
2157
|
-
],
|
|
2158
|
-
isError: true,
|
|
2159
|
-
};
|
|
2160
|
-
}
|
|
2161
|
-
});
|
|
2162
|
-
server.registerTool('update_training_session', {
|
|
2163
|
-
title: 'Update Training Session',
|
|
2164
|
-
description: 'Update a training session.',
|
|
2165
|
-
inputSchema: {
|
|
2166
|
-
id: z.number().describe('Session ID'),
|
|
2167
|
-
name: z.string().max(100).optional().describe('Session name'),
|
|
2168
|
-
start_date: z.string().optional().describe('Start date (YYYY-MM-DD)'),
|
|
2169
|
-
end_date: z.string().optional().describe('End date (YYYY-MM-DD)'),
|
|
2170
|
-
location: z.string().max(200).optional().describe('Location'),
|
|
2171
|
-
max_attendees: z.number().optional().describe('Max attendees'),
|
|
2172
|
-
},
|
|
2173
|
-
}, async ({ id, ...input }) => {
|
|
2174
|
-
try {
|
|
2175
|
-
const session = await updateTrainingSession(id, input);
|
|
2176
|
-
return {
|
|
2177
|
-
content: [
|
|
2178
|
-
{ type: 'text', text: `Session updated:\n\n${JSON.stringify(session, null, 2)}` },
|
|
2179
|
-
],
|
|
2180
|
-
};
|
|
2181
|
-
}
|
|
2182
|
-
catch (error) {
|
|
2183
|
-
return {
|
|
2184
|
-
content: [
|
|
2185
|
-
{
|
|
2186
|
-
type: 'text',
|
|
2187
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2188
|
-
},
|
|
2189
|
-
],
|
|
2190
|
-
isError: true,
|
|
2191
|
-
};
|
|
2192
|
-
}
|
|
2193
|
-
});
|
|
2194
|
-
server.registerTool('delete_training_session', {
|
|
2195
|
-
title: 'Delete Training Session',
|
|
2196
|
-
description: 'Delete a training session.',
|
|
2197
|
-
inputSchema: {
|
|
2198
|
-
id: z.number().describe('Session ID to delete'),
|
|
2199
|
-
},
|
|
2200
|
-
}, async ({ id }) => {
|
|
2201
|
-
try {
|
|
2202
|
-
await deleteTrainingSession(id);
|
|
2203
|
-
return {
|
|
2204
|
-
content: [{ type: 'text', text: `Session ${id} deleted successfully.` }],
|
|
2205
|
-
};
|
|
2206
|
-
}
|
|
2207
|
-
catch (error) {
|
|
2208
|
-
return {
|
|
2209
|
-
content: [
|
|
2210
|
-
{
|
|
2211
|
-
type: 'text',
|
|
2212
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2213
|
-
},
|
|
2214
|
-
],
|
|
2215
|
-
isError: true,
|
|
2216
|
-
};
|
|
2217
|
-
}
|
|
2218
|
-
});
|
|
2219
|
-
server.registerTool('list_training_enrollments', {
|
|
2220
|
-
title: 'List Training Enrollments',
|
|
2221
|
-
description: 'Get enrollments for a training program.',
|
|
2222
|
-
inputSchema: {
|
|
2223
|
-
training_id: z.number().optional().describe('Filter by training ID'),
|
|
2224
|
-
page: z.number().optional().default(1).describe('Page number'),
|
|
2225
|
-
limit: z.number().optional().default(100).describe('Items per page'),
|
|
2226
|
-
},
|
|
2227
|
-
}, async ({ training_id, page, limit }) => {
|
|
2228
|
-
try {
|
|
2229
|
-
const result = await listTrainingEnrollments(training_id, { page, limit });
|
|
2230
|
-
// Fetch enrichment data
|
|
2231
|
-
const [trainings, employees] = await Promise.all([listTrainings(), listEmployees()]);
|
|
2232
|
-
const trainingMap = new Map(trainings.data.map(t => [t.id, t.name]));
|
|
2233
|
-
const empMap = new Map(employees.data.map(e => [e.id, e.full_name]));
|
|
2234
|
-
const summary = result.data.map(e => ({
|
|
2235
|
-
id: e.id,
|
|
2236
|
-
training_id: e.training_id,
|
|
2237
|
-
training_name: trainingMap.get(e.training_id) || `Training ${e.training_id}`,
|
|
2238
|
-
employee_id: e.employee_id,
|
|
2239
|
-
employee_name: empMap.get(e.employee_id) || `Employee ${e.employee_id}`,
|
|
2240
|
-
session_id: e.session_id,
|
|
2241
|
-
status: e.status,
|
|
2242
|
-
enrolled_at: e.enrolled_at,
|
|
2243
|
-
completed_at: e.completed_at,
|
|
2244
|
-
}));
|
|
2245
|
-
return {
|
|
2246
|
-
content: [
|
|
2247
|
-
{
|
|
2248
|
-
type: 'text',
|
|
2249
|
-
text: `Found ${result.data.length} enrollments (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(summary, null, 2)}`,
|
|
2250
|
-
},
|
|
2251
|
-
],
|
|
2252
|
-
};
|
|
2253
|
-
}
|
|
2254
|
-
catch (error) {
|
|
2255
|
-
return {
|
|
2256
|
-
content: [
|
|
2257
|
-
{
|
|
2258
|
-
type: 'text',
|
|
2259
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2260
|
-
},
|
|
2261
|
-
],
|
|
2262
|
-
isError: true,
|
|
2263
|
-
};
|
|
2264
|
-
}
|
|
2265
|
-
});
|
|
2266
|
-
server.registerTool('enroll_in_training', {
|
|
2267
|
-
title: 'Enroll in Training',
|
|
2268
|
-
description: 'Enroll an employee in a training program.',
|
|
2269
|
-
inputSchema: {
|
|
2270
|
-
training_id: z.number().describe('Training ID'),
|
|
2271
|
-
employee_id: z.number().describe('Employee ID'),
|
|
2272
|
-
session_id: z.number().optional().describe('Session ID'),
|
|
2273
|
-
},
|
|
2274
|
-
}, async (input) => {
|
|
2275
|
-
try {
|
|
2276
|
-
const enrollment = await enrollInTraining(input);
|
|
2277
|
-
return {
|
|
2278
|
-
content: [{ type: 'text', text: `Enrolled:\n\n${JSON.stringify(enrollment, null, 2)}` }],
|
|
2279
|
-
};
|
|
2280
|
-
}
|
|
2281
|
-
catch (error) {
|
|
2282
|
-
return {
|
|
2283
|
-
content: [
|
|
2284
|
-
{
|
|
2285
|
-
type: 'text',
|
|
2286
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2287
|
-
},
|
|
2288
|
-
],
|
|
2289
|
-
isError: true,
|
|
2290
|
-
};
|
|
2291
|
-
}
|
|
2292
|
-
});
|
|
2293
|
-
server.registerTool('unenroll_from_training', {
|
|
2294
|
-
title: 'Unenroll from Training',
|
|
2295
|
-
description: 'Remove an enrollment from a training.',
|
|
2296
|
-
inputSchema: {
|
|
2297
|
-
id: z.number().describe('Enrollment ID to remove'),
|
|
2298
|
-
},
|
|
2299
|
-
}, async ({ id }) => {
|
|
2300
|
-
try {
|
|
2301
|
-
await unenrollFromTraining(id);
|
|
2302
|
-
return {
|
|
2303
|
-
content: [{ type: 'text', text: `Enrollment ${id} removed successfully.` }],
|
|
2304
|
-
};
|
|
2305
|
-
}
|
|
2306
|
-
catch (error) {
|
|
2307
|
-
return {
|
|
2308
|
-
content: [
|
|
2309
|
-
{
|
|
2310
|
-
type: 'text',
|
|
2311
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2312
|
-
},
|
|
2313
|
-
],
|
|
2314
|
-
isError: true,
|
|
2315
|
-
};
|
|
2316
|
-
}
|
|
2317
|
-
});
|
|
2318
|
-
// ============================================================================
|
|
2319
|
-
// Work Area Tools
|
|
2320
|
-
// ============================================================================
|
|
2321
|
-
server.registerTool('list_work_areas', {
|
|
2322
|
-
title: 'List Work Areas',
|
|
2323
|
-
description: 'Get all work areas within locations.',
|
|
2324
|
-
inputSchema: {
|
|
2325
|
-
page: z.number().optional().default(1).describe('Page number'),
|
|
2326
|
-
limit: z.number().optional().default(100).describe('Items per page'),
|
|
2327
|
-
},
|
|
2328
|
-
}, async ({ page, limit }) => {
|
|
2329
|
-
try {
|
|
2330
|
-
const result = await listWorkAreas({ page, limit });
|
|
2331
|
-
// Fetch location names for enrichment (cached)
|
|
2332
|
-
const locations = await listLocations();
|
|
2333
|
-
const locMap = new Map(locations.data.map(l => [l.id, l.name]));
|
|
2334
|
-
const summary = result.data.map(w => ({
|
|
2335
|
-
id: w.id,
|
|
2336
|
-
name: w.name,
|
|
2337
|
-
description: w.description,
|
|
2338
|
-
location_id: w.location_id,
|
|
2339
|
-
location_name: w.location_id
|
|
2340
|
-
? locMap.get(w.location_id) || `Location ${w.location_id}`
|
|
2341
|
-
: null,
|
|
2342
|
-
archived: w.archived,
|
|
2343
|
-
}));
|
|
2344
|
-
return {
|
|
2345
|
-
content: [
|
|
2346
|
-
{
|
|
2347
|
-
type: 'text',
|
|
2348
|
-
text: `Found ${result.data.length} work areas (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(summary, null, 2)}`,
|
|
2349
|
-
},
|
|
2350
|
-
],
|
|
2351
|
-
};
|
|
2352
|
-
}
|
|
2353
|
-
catch (error) {
|
|
2354
|
-
return {
|
|
2355
|
-
content: [
|
|
2356
|
-
{
|
|
2357
|
-
type: 'text',
|
|
2358
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2359
|
-
},
|
|
2360
|
-
],
|
|
2361
|
-
isError: true,
|
|
2362
|
-
};
|
|
2363
|
-
}
|
|
2364
|
-
});
|
|
2365
|
-
server.registerTool('get_work_area', {
|
|
2366
|
-
title: 'Get Work Area',
|
|
2367
|
-
description: 'Get details about a work area.',
|
|
2368
|
-
inputSchema: {
|
|
2369
|
-
id: z.number().describe('The work area ID'),
|
|
2370
|
-
},
|
|
2371
|
-
}, async ({ id }) => {
|
|
2372
|
-
try {
|
|
2373
|
-
const workArea = await getWorkArea(id);
|
|
2374
|
-
return {
|
|
2375
|
-
content: [{ type: 'text', text: JSON.stringify(workArea, null, 2) }],
|
|
2376
|
-
};
|
|
2377
|
-
}
|
|
2378
|
-
catch (error) {
|
|
2379
|
-
return {
|
|
2380
|
-
content: [
|
|
2381
|
-
{
|
|
2382
|
-
type: 'text',
|
|
2383
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2384
|
-
},
|
|
2385
|
-
],
|
|
2386
|
-
isError: true,
|
|
2387
|
-
};
|
|
2388
|
-
}
|
|
2389
|
-
});
|
|
2390
|
-
server.registerTool('create_work_area', {
|
|
2391
|
-
title: 'Create Work Area',
|
|
2392
|
-
description: 'Create a new work area within a location.',
|
|
2393
|
-
inputSchema: {
|
|
2394
|
-
name: z.string().min(1).max(100).describe('Work area name'),
|
|
2395
|
-
description: z.string().max(500).optional().describe('Description'),
|
|
2396
|
-
location_id: z.number().optional().describe('Location ID'),
|
|
2397
|
-
},
|
|
2398
|
-
}, async (input) => {
|
|
2399
|
-
try {
|
|
2400
|
-
const workArea = await createWorkArea(input);
|
|
2401
|
-
return {
|
|
2402
|
-
content: [
|
|
2403
|
-
{ type: 'text', text: `Work area created:\n\n${JSON.stringify(workArea, null, 2)}` },
|
|
2404
|
-
],
|
|
2405
|
-
};
|
|
2406
|
-
}
|
|
2407
|
-
catch (error) {
|
|
2408
|
-
return {
|
|
2409
|
-
content: [
|
|
2410
|
-
{
|
|
2411
|
-
type: 'text',
|
|
2412
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2413
|
-
},
|
|
2414
|
-
],
|
|
2415
|
-
isError: true,
|
|
2416
|
-
};
|
|
2417
|
-
}
|
|
2418
|
-
});
|
|
2419
|
-
server.registerTool('update_work_area', {
|
|
2420
|
-
title: 'Update Work Area',
|
|
2421
|
-
description: 'Update a work area.',
|
|
2422
|
-
inputSchema: {
|
|
2423
|
-
id: z.number().describe('Work area ID'),
|
|
2424
|
-
name: z.string().min(1).max(100).optional().describe('Work area name'),
|
|
2425
|
-
description: z.string().max(500).optional().describe('Description'),
|
|
2426
|
-
location_id: z.number().optional().describe('Location ID'),
|
|
2427
|
-
},
|
|
2428
|
-
}, async ({ id, ...input }) => {
|
|
2429
|
-
try {
|
|
2430
|
-
const workArea = await updateWorkArea(id, input);
|
|
2431
|
-
return {
|
|
2432
|
-
content: [
|
|
2433
|
-
{ type: 'text', text: `Work area updated:\n\n${JSON.stringify(workArea, null, 2)}` },
|
|
2434
|
-
],
|
|
2435
|
-
};
|
|
2436
|
-
}
|
|
2437
|
-
catch (error) {
|
|
2438
|
-
return {
|
|
2439
|
-
content: [
|
|
2440
|
-
{
|
|
2441
|
-
type: 'text',
|
|
2442
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2443
|
-
},
|
|
2444
|
-
],
|
|
2445
|
-
isError: true,
|
|
2446
|
-
};
|
|
2447
|
-
}
|
|
2448
|
-
});
|
|
2449
|
-
server.registerTool('archive_work_area', {
|
|
2450
|
-
title: 'Archive Work Area',
|
|
2451
|
-
description: 'Archive a work area.',
|
|
2452
|
-
inputSchema: {
|
|
2453
|
-
id: z.number().describe('Work area ID to archive'),
|
|
2454
|
-
},
|
|
2455
|
-
}, async ({ id }) => {
|
|
2456
|
-
try {
|
|
2457
|
-
const workArea = await archiveWorkArea(id);
|
|
2458
|
-
return {
|
|
2459
|
-
content: [
|
|
2460
|
-
{ type: 'text', text: `Work area archived:\n\n${JSON.stringify(workArea, null, 2)}` },
|
|
2461
|
-
],
|
|
2462
|
-
};
|
|
2463
|
-
}
|
|
2464
|
-
catch (error) {
|
|
2465
|
-
return {
|
|
2466
|
-
content: [
|
|
2467
|
-
{
|
|
2468
|
-
type: 'text',
|
|
2469
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2470
|
-
},
|
|
2471
|
-
],
|
|
2472
|
-
isError: true,
|
|
2473
|
-
};
|
|
2474
|
-
}
|
|
2475
|
-
});
|
|
2476
|
-
server.registerTool('unarchive_work_area', {
|
|
2477
|
-
title: 'Unarchive Work Area',
|
|
2478
|
-
description: 'Unarchive a work area.',
|
|
2479
|
-
inputSchema: {
|
|
2480
|
-
id: z.number().describe('Work area ID to unarchive'),
|
|
2481
|
-
},
|
|
2482
|
-
}, async ({ id }) => {
|
|
2483
|
-
try {
|
|
2484
|
-
const workArea = await unarchiveWorkArea(id);
|
|
2485
|
-
return {
|
|
2486
|
-
content: [
|
|
2487
|
-
{ type: 'text', text: `Work area unarchived:\n\n${JSON.stringify(workArea, null, 2)}` },
|
|
2488
|
-
],
|
|
2489
|
-
};
|
|
2490
|
-
}
|
|
2491
|
-
catch (error) {
|
|
2492
|
-
return {
|
|
2493
|
-
content: [
|
|
2494
|
-
{
|
|
2495
|
-
type: 'text',
|
|
2496
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2497
|
-
},
|
|
2498
|
-
],
|
|
2499
|
-
isError: true,
|
|
2500
|
-
};
|
|
2501
|
-
}
|
|
2502
|
-
});
|
|
2503
|
-
// ============================================================================
|
|
2504
|
-
// ATS (Recruiting) Tools
|
|
2505
|
-
// ============================================================================
|
|
2506
|
-
server.registerTool('list_job_postings', {
|
|
2507
|
-
title: 'List Job Postings',
|
|
2508
|
-
description: 'Get all job postings for recruiting.',
|
|
2509
|
-
inputSchema: {
|
|
2510
|
-
page: z.number().optional().default(1).describe('Page number'),
|
|
2511
|
-
limit: z.number().optional().default(100).describe('Items per page'),
|
|
2512
|
-
},
|
|
2513
|
-
}, async ({ page, limit }) => {
|
|
2514
|
-
try {
|
|
2515
|
-
const result = await listJobPostings({ page, limit });
|
|
2516
|
-
// Fetch enrichment data (cached)
|
|
2517
|
-
const [teams, locations] = await Promise.all([listTeams(), listLocations()]);
|
|
2518
|
-
const teamMap = new Map(teams.data.map(t => [t.id, t.name]));
|
|
2519
|
-
const locMap = new Map(locations.data.map(l => [l.id, l.name]));
|
|
2520
|
-
const summary = result.data.map(j => ({
|
|
2521
|
-
id: j.id,
|
|
2522
|
-
title: j.title,
|
|
2523
|
-
department: j.department,
|
|
2524
|
-
team_id: j.team_id,
|
|
2525
|
-
team_name: j.team_id ? teamMap.get(j.team_id) || `Team ${j.team_id}` : null,
|
|
2526
|
-
location_id: j.location_id,
|
|
2527
|
-
location_name: j.location_id
|
|
2528
|
-
? locMap.get(j.location_id) || `Location ${j.location_id}`
|
|
2529
|
-
: null,
|
|
2530
|
-
status: j.status,
|
|
2531
|
-
employment_type: j.employment_type,
|
|
2532
|
-
remote_status: j.remote_status,
|
|
2533
|
-
published_at: j.published_at,
|
|
2534
|
-
}));
|
|
2535
|
-
return {
|
|
2536
|
-
content: [
|
|
2537
|
-
{
|
|
2538
|
-
type: 'text',
|
|
2539
|
-
text: `Found ${result.data.length} job postings (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(summary, null, 2)}`,
|
|
2540
|
-
},
|
|
2541
|
-
],
|
|
2542
|
-
};
|
|
2543
|
-
}
|
|
2544
|
-
catch (error) {
|
|
2545
|
-
return {
|
|
2546
|
-
content: [
|
|
2547
|
-
{
|
|
2548
|
-
type: 'text',
|
|
2549
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2550
|
-
},
|
|
2551
|
-
],
|
|
2552
|
-
isError: true,
|
|
2553
|
-
};
|
|
2554
|
-
}
|
|
2555
|
-
});
|
|
2556
|
-
server.registerTool('get_job_posting', {
|
|
2557
|
-
title: 'Get Job Posting',
|
|
2558
|
-
description: 'Get details about a job posting.',
|
|
2559
|
-
inputSchema: {
|
|
2560
|
-
id: z.number().describe('The job posting ID'),
|
|
2561
|
-
},
|
|
2562
|
-
}, async ({ id }) => {
|
|
2563
|
-
try {
|
|
2564
|
-
const posting = await getJobPosting(id);
|
|
2565
|
-
return {
|
|
2566
|
-
content: [{ type: 'text', text: JSON.stringify(posting, null, 2) }],
|
|
2567
|
-
};
|
|
2568
|
-
}
|
|
2569
|
-
catch (error) {
|
|
2570
|
-
return {
|
|
2571
|
-
content: [
|
|
2572
|
-
{
|
|
2573
|
-
type: 'text',
|
|
2574
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2575
|
-
},
|
|
2576
|
-
],
|
|
2577
|
-
isError: true,
|
|
2578
|
-
};
|
|
2579
|
-
}
|
|
2580
|
-
});
|
|
2581
|
-
server.registerTool('create_job_posting', {
|
|
2582
|
-
title: 'Create Job Posting',
|
|
2583
|
-
description: 'Create a new job posting.',
|
|
2584
|
-
inputSchema: {
|
|
2585
|
-
title: z.string().min(1).max(200).describe('Job title'),
|
|
2586
|
-
description: z.string().max(5000).optional().describe('Job description'),
|
|
2587
|
-
department: z.string().max(100).optional().describe('Department'),
|
|
2588
|
-
location_id: z.number().optional().describe('Location ID'),
|
|
2589
|
-
team_id: z.number().optional().describe('Team ID'),
|
|
2590
|
-
employment_type: z.string().max(50).optional().describe('Employment type'),
|
|
2591
|
-
remote_status: z.string().max(50).optional().describe('Remote status'),
|
|
2592
|
-
},
|
|
2593
|
-
}, async (input) => {
|
|
2594
|
-
try {
|
|
2595
|
-
const posting = await createJobPosting(input);
|
|
2596
|
-
return {
|
|
2597
|
-
content: [
|
|
2598
|
-
{ type: 'text', text: `Job posting created:\n\n${JSON.stringify(posting, null, 2)}` },
|
|
2599
|
-
],
|
|
2600
|
-
};
|
|
2601
|
-
}
|
|
2602
|
-
catch (error) {
|
|
2603
|
-
return {
|
|
2604
|
-
content: [
|
|
2605
|
-
{
|
|
2606
|
-
type: 'text',
|
|
2607
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2608
|
-
},
|
|
2609
|
-
],
|
|
2610
|
-
isError: true,
|
|
2611
|
-
};
|
|
2612
|
-
}
|
|
2613
|
-
});
|
|
2614
|
-
server.registerTool('update_job_posting', {
|
|
2615
|
-
title: 'Update Job Posting',
|
|
2616
|
-
description: 'Update a job posting.',
|
|
2617
|
-
inputSchema: {
|
|
2618
|
-
id: z.number().describe('Job posting ID'),
|
|
2619
|
-
title: z.string().min(1).max(200).optional().describe('Job title'),
|
|
2620
|
-
description: z.string().max(5000).optional().describe('Job description'),
|
|
2621
|
-
department: z.string().max(100).optional().describe('Department'),
|
|
2622
|
-
location_id: z.number().optional().describe('Location ID'),
|
|
2623
|
-
team_id: z.number().optional().describe('Team ID'),
|
|
2624
|
-
status: z.enum(['draft', 'published', 'closed', 'archived']).optional().describe('Status'),
|
|
2625
|
-
},
|
|
2626
|
-
}, async ({ id, ...input }) => {
|
|
2627
|
-
try {
|
|
2628
|
-
const posting = await updateJobPosting(id, input);
|
|
2629
|
-
return {
|
|
2630
|
-
content: [
|
|
2631
|
-
{ type: 'text', text: `Job posting updated:\n\n${JSON.stringify(posting, null, 2)}` },
|
|
2632
|
-
],
|
|
2633
|
-
};
|
|
2634
|
-
}
|
|
2635
|
-
catch (error) {
|
|
2636
|
-
return {
|
|
2637
|
-
content: [
|
|
2638
|
-
{
|
|
2639
|
-
type: 'text',
|
|
2640
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2641
|
-
},
|
|
2642
|
-
],
|
|
2643
|
-
isError: true,
|
|
2644
|
-
};
|
|
2645
|
-
}
|
|
2646
|
-
});
|
|
2647
|
-
server.registerTool('delete_job_posting', {
|
|
2648
|
-
title: 'Delete Job Posting',
|
|
2649
|
-
description: 'Delete a job posting. This is a HIGH-RISK operation that requires confirmation.',
|
|
2650
|
-
inputSchema: {
|
|
2651
|
-
id: z.number().describe('Job posting ID to delete'),
|
|
2652
|
-
confirm: z.boolean().optional().describe('Set to true to confirm this high-risk operation'),
|
|
2653
|
-
},
|
|
2654
|
-
}, wrapHighRiskToolHandler('delete_job_posting', async ({ id }) => {
|
|
2655
|
-
await deleteJobPosting(id);
|
|
2656
|
-
return textResponse(`Job posting ${id} deleted successfully.`);
|
|
2657
|
-
}));
|
|
2658
|
-
server.registerTool('list_candidates', {
|
|
2659
|
-
title: 'List Candidates',
|
|
2660
|
-
description: 'Get all candidates.',
|
|
2661
|
-
inputSchema: {
|
|
2662
|
-
page: z.number().optional().default(1).describe('Page number'),
|
|
2663
|
-
limit: z.number().optional().default(100).describe('Items per page'),
|
|
2664
|
-
},
|
|
2665
|
-
}, async ({ page, limit }) => {
|
|
2666
|
-
try {
|
|
2667
|
-
const result = await listCandidates({ page, limit });
|
|
2668
|
-
return {
|
|
2669
|
-
content: [
|
|
2670
|
-
{
|
|
2671
|
-
type: 'text',
|
|
2672
|
-
text: `Found ${result.data.length} candidates (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(result.data, null, 2)}`,
|
|
2673
|
-
},
|
|
2674
|
-
],
|
|
2675
|
-
};
|
|
2676
|
-
}
|
|
2677
|
-
catch (error) {
|
|
2678
|
-
return {
|
|
2679
|
-
content: [
|
|
2680
|
-
{
|
|
2681
|
-
type: 'text',
|
|
2682
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2683
|
-
},
|
|
2684
|
-
],
|
|
2685
|
-
isError: true,
|
|
2686
|
-
};
|
|
2687
|
-
}
|
|
2688
|
-
});
|
|
2689
|
-
server.registerTool('get_candidate', {
|
|
2690
|
-
title: 'Get Candidate',
|
|
2691
|
-
description: 'Get details about a candidate.',
|
|
2692
|
-
inputSchema: {
|
|
2693
|
-
id: z.number().describe('The candidate ID'),
|
|
2694
|
-
},
|
|
2695
|
-
}, async ({ id }) => {
|
|
2696
|
-
try {
|
|
2697
|
-
const candidate = await getCandidate(id);
|
|
2698
|
-
return {
|
|
2699
|
-
content: [{ type: 'text', text: JSON.stringify(candidate, null, 2) }],
|
|
2700
|
-
};
|
|
2701
|
-
}
|
|
2702
|
-
catch (error) {
|
|
2703
|
-
return {
|
|
2704
|
-
content: [
|
|
2705
|
-
{
|
|
2706
|
-
type: 'text',
|
|
2707
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2708
|
-
},
|
|
2709
|
-
],
|
|
2710
|
-
isError: true,
|
|
2711
|
-
};
|
|
2712
|
-
}
|
|
2713
|
-
});
|
|
2714
|
-
server.registerTool('create_candidate', {
|
|
2715
|
-
title: 'Create Candidate',
|
|
2716
|
-
description: 'Create a new candidate.',
|
|
2717
|
-
inputSchema: {
|
|
2718
|
-
first_name: z.string().min(1).max(100).describe('First name'),
|
|
2719
|
-
last_name: z.string().min(1).max(100).describe('Last name'),
|
|
2720
|
-
email: z.string().email().optional().describe('Email'),
|
|
2721
|
-
phone: z.string().max(30).optional().describe('Phone'),
|
|
2722
|
-
source: z.string().max(100).optional().describe('Source'),
|
|
2723
|
-
linkedin_url: z.string().optional().describe('LinkedIn URL'),
|
|
2724
|
-
},
|
|
2725
|
-
}, async (input) => {
|
|
2726
|
-
try {
|
|
2727
|
-
const candidate = await createCandidate(input);
|
|
2728
|
-
return {
|
|
2729
|
-
content: [
|
|
2730
|
-
{ type: 'text', text: `Candidate created:\n\n${JSON.stringify(candidate, null, 2)}` },
|
|
2731
|
-
],
|
|
2732
|
-
};
|
|
2733
|
-
}
|
|
2734
|
-
catch (error) {
|
|
2735
|
-
return {
|
|
2736
|
-
content: [
|
|
2737
|
-
{
|
|
2738
|
-
type: 'text',
|
|
2739
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2740
|
-
},
|
|
2741
|
-
],
|
|
2742
|
-
isError: true,
|
|
2743
|
-
};
|
|
2744
|
-
}
|
|
2745
|
-
});
|
|
2746
|
-
server.registerTool('update_candidate', {
|
|
2747
|
-
title: 'Update Candidate',
|
|
2748
|
-
description: 'Update a candidate.',
|
|
2749
|
-
inputSchema: {
|
|
2750
|
-
id: z.number().describe('Candidate ID'),
|
|
2751
|
-
first_name: z.string().min(1).max(100).optional().describe('First name'),
|
|
2752
|
-
last_name: z.string().min(1).max(100).optional().describe('Last name'),
|
|
2753
|
-
email: z.string().email().optional().describe('Email'),
|
|
2754
|
-
phone: z.string().max(30).optional().describe('Phone'),
|
|
2755
|
-
source: z.string().max(100).optional().describe('Source'),
|
|
2756
|
-
linkedin_url: z.string().optional().describe('LinkedIn URL'),
|
|
2757
|
-
},
|
|
2758
|
-
}, async ({ id, ...input }) => {
|
|
2759
|
-
try {
|
|
2760
|
-
const candidate = await updateCandidate(id, input);
|
|
2761
|
-
return {
|
|
2762
|
-
content: [
|
|
2763
|
-
{ type: 'text', text: `Candidate updated:\n\n${JSON.stringify(candidate, null, 2)}` },
|
|
2764
|
-
],
|
|
2765
|
-
};
|
|
2766
|
-
}
|
|
2767
|
-
catch (error) {
|
|
2768
|
-
return {
|
|
2769
|
-
content: [
|
|
2770
|
-
{
|
|
2771
|
-
type: 'text',
|
|
2772
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2773
|
-
},
|
|
2774
|
-
],
|
|
2775
|
-
isError: true,
|
|
2776
|
-
};
|
|
2777
|
-
}
|
|
2778
|
-
});
|
|
2779
|
-
server.registerTool('delete_candidate', {
|
|
2780
|
-
title: 'Delete Candidate',
|
|
2781
|
-
description: 'Delete a candidate. This is a HIGH-RISK operation that requires confirmation.',
|
|
2782
|
-
inputSchema: {
|
|
2783
|
-
id: z.number().describe('Candidate ID to delete'),
|
|
2784
|
-
confirm: z.boolean().optional().describe('Set to true to confirm this high-risk operation'),
|
|
2785
|
-
},
|
|
2786
|
-
}, wrapHighRiskToolHandler('delete_candidate', async ({ id }) => {
|
|
2787
|
-
await deleteCandidate(id);
|
|
2788
|
-
return textResponse(`Candidate ${id} deleted successfully.`);
|
|
2789
|
-
}));
|
|
2790
|
-
server.registerTool('list_applications', {
|
|
2791
|
-
title: 'List Applications',
|
|
2792
|
-
description: 'Get job applications.',
|
|
2793
|
-
inputSchema: {
|
|
2794
|
-
job_posting_id: z.number().optional().describe('Filter by job posting ID'),
|
|
2795
|
-
page: z.number().optional().default(1).describe('Page number'),
|
|
2796
|
-
limit: z.number().optional().default(100).describe('Items per page'),
|
|
2797
|
-
},
|
|
2798
|
-
}, async ({ job_posting_id, page, limit }) => {
|
|
2799
|
-
try {
|
|
2800
|
-
const result = await listApplications(job_posting_id, { page, limit });
|
|
2801
|
-
// Fetch enrichment data
|
|
2802
|
-
const [jobPostings, candidates, hiringStages] = await Promise.all([
|
|
2803
|
-
listJobPostings(),
|
|
2804
|
-
listCandidates(),
|
|
2805
|
-
listHiringStages(),
|
|
2806
|
-
]);
|
|
2807
|
-
const jobMap = new Map(jobPostings.data.map(j => [j.id, j.title]));
|
|
2808
|
-
const candidateMap = new Map(candidates.data.map(c => [
|
|
2809
|
-
c.id,
|
|
2810
|
-
`${c.first_name || ''} ${c.last_name || ''}`.trim() || `Candidate ${c.id}`,
|
|
2811
|
-
]));
|
|
2812
|
-
const stageMap = new Map(hiringStages.map(s => [s.id, s.name]));
|
|
2813
|
-
const summary = result.data.map(a => ({
|
|
2814
|
-
id: a.id,
|
|
2815
|
-
job_posting_id: a.job_posting_id,
|
|
2816
|
-
job_title: jobMap.get(a.job_posting_id) || `Posting ${a.job_posting_id}`,
|
|
2817
|
-
candidate_id: a.candidate_id,
|
|
2818
|
-
candidate_name: candidateMap.get(a.candidate_id) || `Candidate ${a.candidate_id}`,
|
|
2819
|
-
hiring_stage_id: a.hiring_stage_id,
|
|
2820
|
-
hiring_stage_name: a.hiring_stage_id
|
|
2821
|
-
? stageMap.get(a.hiring_stage_id) || `Stage ${a.hiring_stage_id}`
|
|
2822
|
-
: null,
|
|
2823
|
-
status: a.status,
|
|
2824
|
-
applied_at: a.applied_at,
|
|
2825
|
-
}));
|
|
2826
|
-
return {
|
|
2827
|
-
content: [
|
|
2828
|
-
{
|
|
2829
|
-
type: 'text',
|
|
2830
|
-
text: `Found ${result.data.length} applications (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(summary, null, 2)}`,
|
|
2831
|
-
},
|
|
2832
|
-
],
|
|
2833
|
-
};
|
|
2834
|
-
}
|
|
2835
|
-
catch (error) {
|
|
2836
|
-
return {
|
|
2837
|
-
content: [
|
|
2838
|
-
{
|
|
2839
|
-
type: 'text',
|
|
2840
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2841
|
-
},
|
|
2842
|
-
],
|
|
2843
|
-
isError: true,
|
|
2844
|
-
};
|
|
2845
|
-
}
|
|
2846
|
-
});
|
|
2847
|
-
server.registerTool('get_application', {
|
|
2848
|
-
title: 'Get Application',
|
|
2849
|
-
description: 'Get details about an application.',
|
|
2850
|
-
inputSchema: {
|
|
2851
|
-
id: z.number().describe('The application ID'),
|
|
2852
|
-
},
|
|
2853
|
-
}, async ({ id }) => {
|
|
2854
|
-
try {
|
|
2855
|
-
const application = await getApplication(id);
|
|
2856
|
-
return {
|
|
2857
|
-
content: [{ type: 'text', text: JSON.stringify(application, null, 2) }],
|
|
2858
|
-
};
|
|
2859
|
-
}
|
|
2860
|
-
catch (error) {
|
|
2861
|
-
return {
|
|
2862
|
-
content: [
|
|
2863
|
-
{
|
|
2864
|
-
type: 'text',
|
|
2865
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2866
|
-
},
|
|
2867
|
-
],
|
|
2868
|
-
isError: true,
|
|
2869
|
-
};
|
|
2870
|
-
}
|
|
2871
|
-
});
|
|
2872
|
-
server.registerTool('create_application', {
|
|
2873
|
-
title: 'Create Application',
|
|
2874
|
-
description: 'Create a job application for a candidate.',
|
|
2875
|
-
inputSchema: {
|
|
2876
|
-
job_posting_id: z.number().describe('Job posting ID'),
|
|
2877
|
-
candidate_id: z.number().describe('Candidate ID'),
|
|
2878
|
-
notes: z.string().max(2000).optional().describe('Notes'),
|
|
2879
|
-
},
|
|
2880
|
-
}, async (input) => {
|
|
2881
|
-
try {
|
|
2882
|
-
const application = await createApplication(input);
|
|
2883
|
-
return {
|
|
2884
|
-
content: [
|
|
2885
|
-
{ type: 'text', text: `Application created:\n\n${JSON.stringify(application, null, 2)}` },
|
|
2886
|
-
],
|
|
2887
|
-
};
|
|
2888
|
-
}
|
|
2889
|
-
catch (error) {
|
|
2890
|
-
return {
|
|
2891
|
-
content: [
|
|
2892
|
-
{
|
|
2893
|
-
type: 'text',
|
|
2894
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2895
|
-
},
|
|
2896
|
-
],
|
|
2897
|
-
isError: true,
|
|
2898
|
-
};
|
|
2899
|
-
}
|
|
2900
|
-
});
|
|
2901
|
-
server.registerTool('update_application', {
|
|
2902
|
-
title: 'Update Application',
|
|
2903
|
-
description: 'Update a job application.',
|
|
2904
|
-
inputSchema: {
|
|
2905
|
-
id: z.number().describe('Application ID'),
|
|
2906
|
-
hiring_stage_id: z.number().optional().describe('Hiring stage ID'),
|
|
2907
|
-
rating: z.number().min(0).max(5).optional().describe('Rating (0-5)'),
|
|
2908
|
-
notes: z.string().max(2000).optional().describe('Notes'),
|
|
2909
|
-
},
|
|
2910
|
-
}, async ({ id, ...input }) => {
|
|
2911
|
-
try {
|
|
2912
|
-
const application = await updateApplication(id, input);
|
|
2913
|
-
return {
|
|
2914
|
-
content: [
|
|
2915
|
-
{ type: 'text', text: `Application updated:\n\n${JSON.stringify(application, null, 2)}` },
|
|
2916
|
-
],
|
|
2917
|
-
};
|
|
2918
|
-
}
|
|
2919
|
-
catch (error) {
|
|
2920
|
-
return {
|
|
2921
|
-
content: [
|
|
2922
|
-
{
|
|
2923
|
-
type: 'text',
|
|
2924
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2925
|
-
},
|
|
2926
|
-
],
|
|
2927
|
-
isError: true,
|
|
2928
|
-
};
|
|
2929
|
-
}
|
|
2930
|
-
});
|
|
2931
|
-
server.registerTool('delete_application', {
|
|
2932
|
-
title: 'Delete Application',
|
|
2933
|
-
description: 'Delete a job application. This is a HIGH-RISK operation that requires confirmation.',
|
|
2934
|
-
inputSchema: {
|
|
2935
|
-
id: z.number().describe('Application ID to delete'),
|
|
2936
|
-
confirm: z.boolean().optional().describe('Set to true to confirm this high-risk operation'),
|
|
2937
|
-
},
|
|
2938
|
-
}, wrapHighRiskToolHandler('delete_application', async ({ id }) => {
|
|
2939
|
-
await deleteApplication(id);
|
|
2940
|
-
return textResponse(`Application ${id} deleted successfully.`);
|
|
2941
|
-
}));
|
|
2942
|
-
server.registerTool('advance_application', {
|
|
2943
|
-
title: 'Advance Application',
|
|
2944
|
-
description: 'Move an application to the next hiring stage.',
|
|
2945
|
-
inputSchema: {
|
|
2946
|
-
id: z.number().describe('Application ID'),
|
|
2947
|
-
},
|
|
2948
|
-
}, async ({ id }) => {
|
|
2949
|
-
try {
|
|
2950
|
-
const application = await advanceApplication(id);
|
|
2951
|
-
return {
|
|
2952
|
-
content: [
|
|
2953
|
-
{
|
|
2954
|
-
type: 'text',
|
|
2955
|
-
text: `Application advanced:\n\n${JSON.stringify(application, null, 2)}`,
|
|
2956
|
-
},
|
|
2957
|
-
],
|
|
2958
|
-
};
|
|
2959
|
-
}
|
|
2960
|
-
catch (error) {
|
|
2961
|
-
return {
|
|
2962
|
-
content: [
|
|
2963
|
-
{
|
|
2964
|
-
type: 'text',
|
|
2965
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2966
|
-
},
|
|
2967
|
-
],
|
|
2968
|
-
isError: true,
|
|
2969
|
-
};
|
|
2970
|
-
}
|
|
2971
|
-
});
|
|
2972
|
-
server.registerTool('list_hiring_stages', {
|
|
2973
|
-
title: 'List Hiring Stages',
|
|
2974
|
-
description: 'Get all hiring stages for the recruiting workflow.',
|
|
2975
|
-
inputSchema: {},
|
|
2976
|
-
}, async () => {
|
|
2977
|
-
try {
|
|
2978
|
-
const stages = await listHiringStages();
|
|
2979
|
-
return {
|
|
2980
|
-
content: [
|
|
2981
|
-
{
|
|
2982
|
-
type: 'text',
|
|
2983
|
-
text: `Found ${stages.length} hiring stages:\n\n${JSON.stringify(stages, null, 2)}`,
|
|
2984
|
-
},
|
|
2985
|
-
],
|
|
2986
|
-
};
|
|
2987
|
-
}
|
|
2988
|
-
catch (error) {
|
|
2989
|
-
return {
|
|
2990
|
-
content: [
|
|
2991
|
-
{
|
|
2992
|
-
type: 'text',
|
|
2993
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2994
|
-
},
|
|
2995
|
-
],
|
|
2996
|
-
isError: true,
|
|
2997
|
-
};
|
|
2998
|
-
}
|
|
2999
|
-
});
|
|
3000
|
-
// ============================================================================
|
|
3001
|
-
// Payroll Tools (Read-Only)
|
|
3002
|
-
// ============================================================================
|
|
3003
|
-
server.registerTool('list_payroll_supplements', {
|
|
3004
|
-
title: 'List Payroll Supplements',
|
|
3005
|
-
description: 'Get payroll supplements (bonuses, allowances, etc.). Read-only.',
|
|
3006
|
-
inputSchema: {
|
|
3007
|
-
employee_id: z.number().optional().describe('Filter by employee ID'),
|
|
3008
|
-
page: z.number().optional().default(1).describe('Page number'),
|
|
3009
|
-
limit: z.number().optional().default(100).describe('Items per page'),
|
|
3010
|
-
},
|
|
3011
|
-
}, async ({ employee_id, page, limit }) => {
|
|
3012
|
-
try {
|
|
3013
|
-
const result = await listPayrollSupplements(employee_id, { page, limit });
|
|
3014
|
-
// Fetch employee names for enrichment (cached)
|
|
3015
|
-
const employees = await listEmployees();
|
|
3016
|
-
const empMap = new Map(employees.data.map(e => [e.id, e.full_name]));
|
|
3017
|
-
const summary = result.data.map(s => ({
|
|
3018
|
-
...s,
|
|
3019
|
-
employee_name: empMap.get(s.employee_id) || `Employee ${s.employee_id}`,
|
|
3020
|
-
}));
|
|
3021
|
-
return {
|
|
3022
|
-
content: [
|
|
3023
|
-
{
|
|
3024
|
-
type: 'text',
|
|
3025
|
-
text: `Found ${result.data.length} supplements (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(summary, null, 2)}`,
|
|
3026
|
-
},
|
|
3027
|
-
],
|
|
3028
|
-
};
|
|
3029
|
-
}
|
|
3030
|
-
catch (error) {
|
|
3031
|
-
return {
|
|
3032
|
-
content: [
|
|
3033
|
-
{
|
|
3034
|
-
type: 'text',
|
|
3035
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
3036
|
-
},
|
|
3037
|
-
],
|
|
3038
|
-
isError: true,
|
|
3039
|
-
};
|
|
3040
|
-
}
|
|
3041
|
-
});
|
|
3042
|
-
server.registerTool('get_payroll_supplement', {
|
|
3043
|
-
title: 'Get Payroll Supplement',
|
|
3044
|
-
description: 'Get details about a payroll supplement. Read-only.',
|
|
3045
|
-
inputSchema: {
|
|
3046
|
-
id: z.number().describe('The supplement ID'),
|
|
3047
|
-
},
|
|
3048
|
-
}, async ({ id }) => {
|
|
3049
|
-
try {
|
|
3050
|
-
const supplement = await getPayrollSupplement(id);
|
|
3051
|
-
return {
|
|
3052
|
-
content: [{ type: 'text', text: JSON.stringify(supplement, null, 2) }],
|
|
3053
|
-
};
|
|
3054
|
-
}
|
|
3055
|
-
catch (error) {
|
|
3056
|
-
return {
|
|
3057
|
-
content: [
|
|
3058
|
-
{
|
|
3059
|
-
type: 'text',
|
|
3060
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
3061
|
-
},
|
|
3062
|
-
],
|
|
3063
|
-
isError: true,
|
|
3064
|
-
};
|
|
3065
|
-
}
|
|
3066
|
-
});
|
|
3067
|
-
server.registerTool('list_tax_identifiers', {
|
|
3068
|
-
title: 'List Tax Identifiers',
|
|
3069
|
-
description: 'Get employee tax identifiers. Read-only.',
|
|
3070
|
-
inputSchema: {
|
|
3071
|
-
employee_id: z.number().optional().describe('Filter by employee ID'),
|
|
3072
|
-
page: z.number().optional().default(1).describe('Page number'),
|
|
3073
|
-
limit: z.number().optional().default(100).describe('Items per page'),
|
|
3074
|
-
},
|
|
3075
|
-
}, async ({ employee_id, page, limit }) => {
|
|
3076
|
-
try {
|
|
3077
|
-
const result = await listTaxIdentifiers(employee_id, { page, limit });
|
|
3078
|
-
return {
|
|
3079
|
-
content: [
|
|
3080
|
-
{
|
|
3081
|
-
type: 'text',
|
|
3082
|
-
text: `Found ${result.data.length} tax identifiers (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(result.data, null, 2)}`,
|
|
3083
|
-
},
|
|
3084
|
-
],
|
|
3085
|
-
};
|
|
3086
|
-
}
|
|
3087
|
-
catch (error) {
|
|
3088
|
-
return {
|
|
3089
|
-
content: [
|
|
3090
|
-
{
|
|
3091
|
-
type: 'text',
|
|
3092
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
3093
|
-
},
|
|
3094
|
-
],
|
|
3095
|
-
isError: true,
|
|
3096
|
-
};
|
|
3097
|
-
}
|
|
3098
|
-
});
|
|
3099
|
-
server.registerTool('get_tax_identifier', {
|
|
3100
|
-
title: 'Get Tax Identifier',
|
|
3101
|
-
description: 'Get details about a tax identifier. Read-only.',
|
|
3102
|
-
inputSchema: {
|
|
3103
|
-
id: z.number().describe('The tax identifier ID'),
|
|
3104
|
-
},
|
|
3105
|
-
}, async ({ id }) => {
|
|
3106
|
-
try {
|
|
3107
|
-
const identifier = await getTaxIdentifier(id);
|
|
3108
|
-
return {
|
|
3109
|
-
content: [{ type: 'text', text: JSON.stringify(identifier, null, 2) }],
|
|
3110
|
-
};
|
|
3111
|
-
}
|
|
3112
|
-
catch (error) {
|
|
3113
|
-
return {
|
|
3114
|
-
content: [
|
|
3115
|
-
{
|
|
3116
|
-
type: 'text',
|
|
3117
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
3118
|
-
},
|
|
3119
|
-
],
|
|
3120
|
-
isError: true,
|
|
3121
|
-
};
|
|
3122
|
-
}
|
|
3123
|
-
});
|
|
3124
|
-
server.registerTool('list_family_situations', {
|
|
3125
|
-
title: 'List Family Situations',
|
|
3126
|
-
description: 'Get employee family situations. Read-only.',
|
|
3127
|
-
inputSchema: {
|
|
3128
|
-
employee_id: z.number().optional().describe('Filter by employee ID'),
|
|
3129
|
-
page: z.number().optional().default(1).describe('Page number'),
|
|
3130
|
-
limit: z.number().optional().default(100).describe('Items per page'),
|
|
3131
|
-
},
|
|
3132
|
-
}, async ({ employee_id, page, limit }) => {
|
|
3133
|
-
try {
|
|
3134
|
-
const result = await listFamilySituations(employee_id, { page, limit });
|
|
3135
|
-
// Fetch employee names for enrichment (cached)
|
|
3136
|
-
const employees = await listEmployees();
|
|
3137
|
-
const empMap = new Map(employees.data.map(e => [e.id, e.full_name]));
|
|
3138
|
-
const summary = result.data.map(s => ({
|
|
3139
|
-
...s,
|
|
3140
|
-
employee_name: empMap.get(s.employee_id) || `Employee ${s.employee_id}`,
|
|
3141
|
-
}));
|
|
3142
|
-
return {
|
|
3143
|
-
content: [
|
|
3144
|
-
{
|
|
3145
|
-
type: 'text',
|
|
3146
|
-
text: `Found ${result.data.length} family situations (${formatPaginationInfo(result.meta)}):\n\n${JSON.stringify(summary, null, 2)}`,
|
|
3147
|
-
},
|
|
3148
|
-
],
|
|
3149
|
-
};
|
|
3150
|
-
}
|
|
3151
|
-
catch (error) {
|
|
3152
|
-
return {
|
|
3153
|
-
content: [
|
|
3154
|
-
{
|
|
3155
|
-
type: 'text',
|
|
3156
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
3157
|
-
},
|
|
3158
|
-
],
|
|
3159
|
-
isError: true,
|
|
3160
|
-
};
|
|
3161
|
-
}
|
|
3162
|
-
});
|
|
3163
|
-
server.registerTool('get_family_situation', {
|
|
3164
|
-
title: 'Get Family Situation',
|
|
3165
|
-
description: 'Get details about an employee family situation. Read-only.',
|
|
3166
|
-
inputSchema: {
|
|
3167
|
-
id: z.number().describe('The family situation ID'),
|
|
3168
|
-
},
|
|
3169
|
-
}, async ({ id }) => {
|
|
3170
|
-
try {
|
|
3171
|
-
const situation = await getFamilySituation(id);
|
|
3172
|
-
return {
|
|
3173
|
-
content: [{ type: 'text', text: JSON.stringify(situation, null, 2) }],
|
|
3174
|
-
};
|
|
3175
|
-
}
|
|
3176
|
-
catch (error) {
|
|
3177
|
-
return {
|
|
3178
|
-
content: [
|
|
3179
|
-
{
|
|
3180
|
-
type: 'text',
|
|
3181
|
-
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
3182
|
-
},
|
|
3183
|
-
],
|
|
3184
|
-
isError: true,
|
|
3185
|
-
};
|
|
3186
|
-
}
|
|
3187
|
-
});
|
|
3188
|
-
// ============================================================================
|
|
3189
|
-
// MCP Resources
|
|
3190
|
-
// ============================================================================
|
|
3191
|
-
/**
|
|
3192
|
-
* Build an org chart showing manager-report relationships
|
|
3193
|
-
*/
|
|
3194
|
-
async function buildOrgChart() {
|
|
3195
|
-
const result = await listEmployees();
|
|
3196
|
-
const employees = result.data;
|
|
3197
|
-
// Build a map of manager_id -> reports
|
|
3198
|
-
const managerMap = new Map();
|
|
3199
|
-
for (const emp of employees) {
|
|
3200
|
-
const managerId = emp.manager_id;
|
|
3201
|
-
if (!managerMap.has(managerId)) {
|
|
3202
|
-
managerMap.set(managerId, []);
|
|
3203
|
-
}
|
|
3204
|
-
managerMap.get(managerId).push(emp);
|
|
3205
|
-
}
|
|
3206
|
-
// Find top-level employees (no manager)
|
|
3207
|
-
const topLevel = managerMap.get(null) || [];
|
|
3208
|
-
// Recursive function to build tree
|
|
3209
|
-
function buildTree(managerId, depth = 0) {
|
|
3210
|
-
const reports = managerMap.get(managerId) || [];
|
|
3211
|
-
const lines = [];
|
|
3212
|
-
for (const emp of reports) {
|
|
3213
|
-
const indent = ' '.repeat(depth);
|
|
3214
|
-
lines.push(`${indent}- ${emp.full_name || 'Unknown'} [ID: ${emp.id}]`);
|
|
3215
|
-
lines.push(...buildTree(emp.id, depth + 1));
|
|
3216
|
-
}
|
|
3217
|
-
return lines;
|
|
3218
|
-
}
|
|
3219
|
-
const lines = ['# Organization Chart\n'];
|
|
3220
|
-
// Add top-level employees
|
|
3221
|
-
for (const emp of topLevel) {
|
|
3222
|
-
lines.push(`## ${emp.full_name || 'Unknown'} [ID: ${emp.id}]`);
|
|
3223
|
-
lines.push(...buildTree(emp.id, 1));
|
|
3224
|
-
lines.push('');
|
|
3225
|
-
}
|
|
3226
|
-
return lines.join('\n');
|
|
3227
|
-
}
|
|
3228
|
-
/**
|
|
3229
|
-
* Build employee directory organized by team
|
|
3230
|
-
*/
|
|
3231
|
-
async function buildEmployeeDirectory() {
|
|
3232
|
-
const [empResult, teamsResult] = await Promise.all([listEmployees(), listTeams()]);
|
|
3233
|
-
const employees = empResult.data;
|
|
3234
|
-
const teams = teamsResult.data;
|
|
3235
|
-
const lines = ['# Employee Directory\n'];
|
|
3236
|
-
// Create employee lookup map
|
|
3237
|
-
const employeeMap = new Map(employees.map(e => [e.id, e]));
|
|
3238
|
-
// Track which employees are assigned to teams
|
|
3239
|
-
const assignedEmployeeIds = new Set();
|
|
3240
|
-
// Output by team (using team's employee_ids)
|
|
3241
|
-
for (const team of teams) {
|
|
3242
|
-
const teamEmployeeIds = team.employee_ids || [];
|
|
3243
|
-
lines.push(`## ${team.name}`);
|
|
3244
|
-
if (team.description)
|
|
3245
|
-
lines.push(`*${team.description}*\n`);
|
|
3246
|
-
if (teamEmployeeIds.length === 0) {
|
|
3247
|
-
lines.push('No employees assigned.\n');
|
|
3248
|
-
}
|
|
3249
|
-
else {
|
|
3250
|
-
for (const empId of teamEmployeeIds) {
|
|
3251
|
-
const emp = employeeMap.get(empId);
|
|
3252
|
-
if (emp) {
|
|
3253
|
-
assignedEmployeeIds.add(empId);
|
|
3254
|
-
lines.push(`- **${emp.full_name || 'Unknown'}**`);
|
|
3255
|
-
if (emp.email)
|
|
3256
|
-
lines.push(` - Email: ${emp.email}`);
|
|
3257
|
-
}
|
|
3258
|
-
}
|
|
3259
|
-
lines.push('');
|
|
3260
|
-
}
|
|
3261
|
-
}
|
|
3262
|
-
// Employees without teams
|
|
3263
|
-
const noTeam = employees.filter(e => !assignedEmployeeIds.has(e.id));
|
|
3264
|
-
if (noTeam.length > 0) {
|
|
3265
|
-
lines.push('## Unassigned\n');
|
|
3266
|
-
for (const emp of noTeam) {
|
|
3267
|
-
lines.push(`- **${emp.full_name || 'Unknown'}**`);
|
|
3268
|
-
if (emp.email)
|
|
3269
|
-
lines.push(` - Email: ${emp.email}`);
|
|
3270
|
-
}
|
|
3271
|
-
}
|
|
3272
|
-
return lines.join('\n');
|
|
3273
|
-
}
|
|
3274
|
-
/**
|
|
3275
|
-
* Build location directory
|
|
3276
|
-
*/
|
|
3277
|
-
async function buildLocationDirectory() {
|
|
3278
|
-
const [locResult, empResult] = await Promise.all([listLocations(), listEmployees()]);
|
|
3279
|
-
const locations = locResult.data;
|
|
3280
|
-
const employees = empResult.data;
|
|
3281
|
-
// Group employees by location
|
|
3282
|
-
const locationEmployees = new Map();
|
|
3283
|
-
for (const emp of employees) {
|
|
3284
|
-
if (emp.location_id) {
|
|
3285
|
-
if (!locationEmployees.has(emp.location_id))
|
|
3286
|
-
locationEmployees.set(emp.location_id, []);
|
|
3287
|
-
locationEmployees.get(emp.location_id).push(emp);
|
|
3288
|
-
}
|
|
3289
|
-
}
|
|
3290
|
-
const lines = ['# Location Directory\n'];
|
|
3291
|
-
for (const loc of locations) {
|
|
3292
|
-
const emps = locationEmployees.get(loc.id) || [];
|
|
3293
|
-
lines.push(`## ${loc.name}`);
|
|
3294
|
-
const address = [loc.city, loc.state, loc.country].filter(Boolean).join(', ');
|
|
3295
|
-
if (address)
|
|
3296
|
-
lines.push(`📍 ${address}\n`);
|
|
3297
|
-
lines.push(`**Employees:** ${emps.length}`);
|
|
3298
|
-
if (emps.length > 0 && emps.length <= 10) {
|
|
3299
|
-
for (const emp of emps) {
|
|
3300
|
-
lines.push(`- ${emp.full_name || 'Unknown'}`);
|
|
3301
|
-
}
|
|
3302
|
-
}
|
|
3303
|
-
else if (emps.length > 10) {
|
|
3304
|
-
lines.push(`*... ${emps.length} employees (use list_employees with location_id=${loc.id} to see all)*`);
|
|
3305
|
-
}
|
|
3306
|
-
lines.push('');
|
|
3307
|
-
}
|
|
3308
|
-
return lines.join('\n');
|
|
3309
|
-
}
|
|
3310
|
-
// Register static resources
|
|
3311
|
-
server.registerResource('org-chart', 'factorial://org-chart', {
|
|
3312
|
-
description: 'Complete organizational hierarchy showing manager-report relationships. Useful for understanding team structure.',
|
|
3313
|
-
mimeType: 'text/markdown',
|
|
3314
|
-
}, async () => {
|
|
3315
|
-
return {
|
|
3316
|
-
contents: [
|
|
3317
|
-
{
|
|
3318
|
-
uri: 'factorial://org-chart',
|
|
3319
|
-
mimeType: 'text/markdown',
|
|
3320
|
-
text: await buildOrgChart(),
|
|
3321
|
-
},
|
|
3322
|
-
],
|
|
3323
|
-
};
|
|
3324
|
-
});
|
|
3325
|
-
server.registerResource('employees-directory', 'factorial://employees/directory', {
|
|
3326
|
-
description: 'Employee directory organized by team. Shows all employees with their roles and contact info.',
|
|
3327
|
-
mimeType: 'text/markdown',
|
|
3328
|
-
}, async () => {
|
|
3329
|
-
return {
|
|
3330
|
-
contents: [
|
|
3331
|
-
{
|
|
3332
|
-
uri: 'factorial://employees/directory',
|
|
3333
|
-
mimeType: 'text/markdown',
|
|
3334
|
-
text: await buildEmployeeDirectory(),
|
|
3335
|
-
},
|
|
3336
|
-
],
|
|
3337
|
-
};
|
|
3338
|
-
});
|
|
3339
|
-
server.registerResource('locations-directory', 'factorial://locations/directory', {
|
|
3340
|
-
description: 'Directory of all company locations with employee counts.',
|
|
3341
|
-
mimeType: 'text/markdown',
|
|
3342
|
-
}, async () => {
|
|
3343
|
-
return {
|
|
3344
|
-
contents: [
|
|
3345
|
-
{
|
|
3346
|
-
uri: 'factorial://locations/directory',
|
|
3347
|
-
mimeType: 'text/markdown',
|
|
3348
|
-
text: await buildLocationDirectory(),
|
|
3349
|
-
},
|
|
3350
|
-
],
|
|
3351
|
-
};
|
|
3352
|
-
});
|
|
3353
|
-
server.registerResource('timeoff-policies', 'factorial://timeoff/policies', {
|
|
3354
|
-
description: 'All leave types and time off policies configured in the organization.',
|
|
3355
|
-
mimeType: 'application/json',
|
|
3356
|
-
}, async () => {
|
|
3357
|
-
const types = await listLeaveTypes();
|
|
3358
|
-
return {
|
|
3359
|
-
contents: [
|
|
3360
|
-
{
|
|
3361
|
-
uri: 'factorial://timeoff/policies',
|
|
3362
|
-
mimeType: 'application/json',
|
|
3363
|
-
text: JSON.stringify(types, null, 2),
|
|
3364
|
-
},
|
|
3365
|
-
],
|
|
3366
|
-
};
|
|
3367
|
-
});
|
|
3368
|
-
// Register resource template for teams
|
|
3369
|
-
const teamTemplate = new ResourceTemplate('factorial://teams/{team_id}', {
|
|
3370
|
-
list: async () => {
|
|
3371
|
-
const result = await listTeams();
|
|
3372
|
-
return {
|
|
3373
|
-
resources: result.data.map(t => ({
|
|
3374
|
-
uri: `factorial://teams/${t.id}`,
|
|
3375
|
-
name: t.name,
|
|
3376
|
-
description: t.description || undefined,
|
|
3377
|
-
mimeType: 'application/json',
|
|
3378
|
-
})),
|
|
3379
|
-
};
|
|
3380
|
-
},
|
|
3381
|
-
});
|
|
3382
|
-
server.registerResource('team-details', teamTemplate, {
|
|
3383
|
-
description: 'Get detailed information about a specific team including all members.',
|
|
3384
|
-
mimeType: 'application/json',
|
|
3385
|
-
}, async (uri, variables) => {
|
|
3386
|
-
const teamId = parseInt(variables.team_id, 10);
|
|
3387
|
-
if (isNaN(teamId)) {
|
|
3388
|
-
throw new Error('Invalid team ID');
|
|
3389
|
-
}
|
|
3390
|
-
const [team, empResult] = await Promise.all([
|
|
3391
|
-
getTeam(teamId),
|
|
3392
|
-
listEmployees({ team_id: teamId }),
|
|
3393
|
-
]);
|
|
3394
|
-
const teamDetails = {
|
|
3395
|
-
...team,
|
|
3396
|
-
members: empResult.data.map(e => ({
|
|
3397
|
-
id: e.id,
|
|
3398
|
-
name: e.full_name,
|
|
3399
|
-
email: e.email,
|
|
3400
|
-
})),
|
|
3401
|
-
};
|
|
3402
|
-
return {
|
|
3403
|
-
contents: [
|
|
3404
|
-
{
|
|
3405
|
-
uri: uri.toString(),
|
|
3406
|
-
mimeType: 'application/json',
|
|
3407
|
-
text: JSON.stringify(teamDetails, null, 2),
|
|
3408
|
-
},
|
|
3409
|
-
],
|
|
3410
|
-
};
|
|
3411
|
-
});
|
|
3412
|
-
// ============================================================================
|
|
3413
|
-
// MCP Prompts
|
|
3414
|
-
// ============================================================================
|
|
3415
|
-
server.registerPrompt('onboard-employee', {
|
|
3416
|
-
description: 'Generate a personalized onboarding checklist for a new employee based on their team and role.',
|
|
3417
|
-
argsSchema: {
|
|
3418
|
-
employee_id: z.string().describe('The ID of the employee to onboard'),
|
|
3419
|
-
},
|
|
3420
|
-
}, async ({ employee_id }) => {
|
|
3421
|
-
const empId = parseInt(employee_id, 10);
|
|
3422
|
-
const employee = await getEmployee(empId);
|
|
3423
|
-
const teamsResult = await listTeams();
|
|
3424
|
-
const teams = teamsResult.data;
|
|
3425
|
-
// Find teams that have this employee (relationship is on Team, not Employee)
|
|
3426
|
-
const employeeTeams = teams.filter(t => t.employee_ids?.includes(empId));
|
|
3427
|
-
const teamNames = employeeTeams.map(t => t.name).join(', ') || 'No team assigned';
|
|
3428
|
-
return {
|
|
3429
|
-
messages: [
|
|
3430
|
-
{
|
|
3431
|
-
role: 'user',
|
|
3432
|
-
content: {
|
|
3433
|
-
type: 'text',
|
|
3434
|
-
text: `Please create a comprehensive onboarding checklist for the following new employee:
|
|
3435
|
-
|
|
3436
|
-
**Employee Details:**
|
|
3437
|
-
- Name: ${employee.full_name}
|
|
3438
|
-
- Team(s): ${teamNames}
|
|
3439
|
-
- Start Date: ${employee.seniority_calculation_date || 'Not specified'}
|
|
3440
|
-
- Email: ${employee.email}
|
|
3441
|
-
|
|
3442
|
-
Please include:
|
|
3443
|
-
1. First day essentials
|
|
3444
|
-
2. First week goals
|
|
3445
|
-
3. Team introductions
|
|
3446
|
-
4. Tools and access setup
|
|
3447
|
-
5. Key meetings to schedule
|
|
3448
|
-
6. 30/60/90 day milestones
|
|
3449
|
-
|
|
3450
|
-
Tailor the checklist to their team.`,
|
|
3451
|
-
},
|
|
3452
|
-
},
|
|
3453
|
-
],
|
|
3454
|
-
};
|
|
3455
|
-
});
|
|
3456
|
-
server.registerPrompt('analyze-org-structure', {
|
|
3457
|
-
description: 'Analyze the organizational structure for insights on reporting lines, team sizes, and distribution.',
|
|
3458
|
-
argsSchema: {
|
|
3459
|
-
focus_area: z
|
|
3460
|
-
.string()
|
|
3461
|
-
.optional()
|
|
3462
|
-
.describe('Area to focus on: reporting_lines, team_sizes, location_distribution'),
|
|
3463
|
-
},
|
|
3464
|
-
}, async ({ focus_area }) => {
|
|
3465
|
-
const [empResult, teamsResult, locResult] = await Promise.all([
|
|
3466
|
-
listEmployees(),
|
|
3467
|
-
listTeams(),
|
|
3468
|
-
listLocations(),
|
|
3469
|
-
]);
|
|
3470
|
-
const employees = empResult.data;
|
|
3471
|
-
const teams = teamsResult.data;
|
|
3472
|
-
const locations = locResult.data;
|
|
3473
|
-
// Compute basic stats
|
|
3474
|
-
const totalEmployees = employees.length;
|
|
3475
|
-
const managersCount = new Set(employees.map(e => e.manager_id).filter(Boolean)).size;
|
|
3476
|
-
const avgTeamSize = teams.length > 0
|
|
3477
|
-
? (teams.reduce((sum, t) => sum + (t.employee_ids?.length || 0), 0) / teams.length).toFixed(1)
|
|
3478
|
-
: 0;
|
|
3479
|
-
let focusPrompt = '';
|
|
3480
|
-
if (focus_area === 'reporting_lines') {
|
|
3481
|
-
focusPrompt =
|
|
3482
|
-
'Focus particularly on reporting line depth, span of control, and potential bottlenecks.';
|
|
3483
|
-
}
|
|
3484
|
-
else if (focus_area === 'team_sizes') {
|
|
3485
|
-
focusPrompt =
|
|
3486
|
-
'Focus particularly on team size distribution, under/over-staffed teams, and growth patterns.';
|
|
3487
|
-
}
|
|
3488
|
-
else if (focus_area === 'location_distribution') {
|
|
3489
|
-
focusPrompt =
|
|
3490
|
-
'Focus particularly on geographic distribution, remote vs on-site, and location-based team composition.';
|
|
3491
|
-
}
|
|
3492
|
-
return {
|
|
3493
|
-
messages: [
|
|
3494
|
-
{
|
|
3495
|
-
role: 'user',
|
|
3496
|
-
content: {
|
|
3497
|
-
type: 'text',
|
|
3498
|
-
text: `Please analyze the following organizational structure:
|
|
3499
|
-
|
|
3500
|
-
**Summary Statistics:**
|
|
3501
|
-
- Total Employees: ${totalEmployees}
|
|
3502
|
-
- Total Teams: ${teams.length}
|
|
3503
|
-
- Total Locations: ${locations.length}
|
|
3504
|
-
- Unique Managers: ${managersCount}
|
|
3505
|
-
- Average Team Size: ${avgTeamSize}
|
|
3506
|
-
|
|
3507
|
-
**Teams:**
|
|
3508
|
-
${teams.map(t => `- ${t.name}: ${t.employee_ids?.length || 0} members`).join('\n')}
|
|
3509
|
-
|
|
3510
|
-
**Locations:**
|
|
3511
|
-
${locations.map(l => `- ${l.name} (${[l.city, l.country].filter(Boolean).join(', ')})`).join('\n')}
|
|
3512
|
-
|
|
3513
|
-
${focusPrompt}
|
|
3514
|
-
|
|
3515
|
-
Please provide:
|
|
3516
|
-
1. Key observations about the org structure
|
|
3517
|
-
2. Potential areas of concern
|
|
3518
|
-
3. Recommendations for improvement
|
|
3519
|
-
4. Comparison to industry best practices`,
|
|
3520
|
-
},
|
|
3521
|
-
},
|
|
3522
|
-
],
|
|
3523
|
-
};
|
|
3524
|
-
});
|
|
3525
|
-
server.registerPrompt('timeoff-report', {
|
|
3526
|
-
description: 'Generate a time off report for a team or date range.',
|
|
3527
|
-
argsSchema: {
|
|
3528
|
-
team_id: z.string().optional().describe('Team ID to filter by'),
|
|
3529
|
-
start_date: z.string().optional().describe('Start date (YYYY-MM-DD)'),
|
|
3530
|
-
end_date: z.string().optional().describe('End date (YYYY-MM-DD)'),
|
|
3531
|
-
include_pending: z.string().optional().describe('Include pending requests (true/false)'),
|
|
3532
|
-
},
|
|
3533
|
-
}, async ({ team_id, start_date, end_date, include_pending }) => {
|
|
3534
|
-
const teamId = team_id ? parseInt(team_id, 10) : undefined;
|
|
3535
|
-
const includePending = include_pending === 'true';
|
|
3536
|
-
// Get leaves
|
|
3537
|
-
const leavesResult = await listLeaves({
|
|
3538
|
-
start_on_gte: start_date,
|
|
3539
|
-
start_on_lte: end_date,
|
|
3540
|
-
});
|
|
3541
|
-
// Get leave types for names
|
|
3542
|
-
const leaveTypes = await listLeaveTypes();
|
|
3543
|
-
const leaveTypeMap = new Map(leaveTypes.map(lt => [lt.id, lt.name]));
|
|
3544
|
-
// Filter by team if needed
|
|
3545
|
-
let leaves = leavesResult.data;
|
|
3546
|
-
if (teamId) {
|
|
3547
|
-
const teamEmps = (await listEmployees({ team_id: teamId })).data;
|
|
3548
|
-
const teamEmpIds = new Set(teamEmps.map(e => e.id));
|
|
3549
|
-
leaves = leaves.filter(l => teamEmpIds.has(l.employee_id));
|
|
3550
|
-
}
|
|
3551
|
-
// Filter by status
|
|
3552
|
-
if (!includePending) {
|
|
3553
|
-
leaves = leaves.filter(l => l.status === 'approved');
|
|
3554
|
-
}
|
|
3555
|
-
// Get employee names
|
|
3556
|
-
const empResult = await listEmployees();
|
|
3557
|
-
const empMap = new Map(empResult.data.map(e => [e.id, e.full_name]));
|
|
3558
|
-
const leaveSummary = leaves.map(l => ({
|
|
3559
|
-
employee: empMap.get(l.employee_id) || `Employee ${l.employee_id}`,
|
|
3560
|
-
type: leaveTypeMap.get(l.leave_type_id) || `Type ${l.leave_type_id}`,
|
|
3561
|
-
dates: `${l.start_on} to ${l.finish_on}`,
|
|
3562
|
-
days: l.duration_attributes?.days || 'N/A',
|
|
3563
|
-
status: l.status,
|
|
3564
|
-
}));
|
|
3565
|
-
return {
|
|
3566
|
-
messages: [
|
|
3567
|
-
{
|
|
3568
|
-
role: 'user',
|
|
3569
|
-
content: {
|
|
3570
|
-
type: 'text',
|
|
3571
|
-
text: `Please generate a time off report based on the following data:
|
|
3572
|
-
|
|
3573
|
-
**Report Parameters:**
|
|
3574
|
-
- Date Range: ${start_date || 'All'} to ${end_date || 'All'}
|
|
3575
|
-
- Team: ${teamId ? `Team ${teamId}` : 'All teams'}
|
|
3576
|
-
- Including Pending: ${includePending ? 'Yes' : 'No'}
|
|
3577
|
-
|
|
3578
|
-
**Time Off Requests (${leaves.length} total):**
|
|
3579
|
-
${JSON.stringify(leaveSummary, null, 2)}
|
|
3580
|
-
|
|
3581
|
-
Please provide:
|
|
3582
|
-
1. Summary of time off by type
|
|
3583
|
-
2. Peak absence periods
|
|
3584
|
-
3. Coverage concerns (if any patterns suggest coverage gaps)
|
|
3585
|
-
4. Recommendations for planning`,
|
|
3586
|
-
},
|
|
3587
|
-
},
|
|
3588
|
-
],
|
|
3589
|
-
};
|
|
3590
|
-
});
|
|
3591
|
-
server.registerPrompt('team-document-summary', {
|
|
3592
|
-
description: 'Summarize documents across a team: who has certifications, payslips, missing required documents.',
|
|
3593
|
-
argsSchema: {
|
|
3594
|
-
team_id: z.string().describe('Team ID to analyze'),
|
|
3595
|
-
document_type: z
|
|
3596
|
-
.string()
|
|
3597
|
-
.optional()
|
|
3598
|
-
.describe('Focus on specific folder/type (e.g., "Certifications", "Payslips")'),
|
|
3599
|
-
},
|
|
3600
|
-
}, async ({ team_id, document_type }) => {
|
|
3601
|
-
const teamId = parseInt(team_id, 10);
|
|
3602
|
-
const team = await getTeam(teamId);
|
|
3603
|
-
const employeeIds = team.employee_ids || [];
|
|
3604
|
-
if (employeeIds.length === 0) {
|
|
3605
|
-
return {
|
|
3606
|
-
messages: [
|
|
3607
|
-
{
|
|
3608
|
-
role: 'user',
|
|
3609
|
-
content: {
|
|
3610
|
-
type: 'text',
|
|
3611
|
-
text: `Team "${team.name}" has no members. Cannot generate document summary.`,
|
|
3612
|
-
},
|
|
3613
|
-
},
|
|
3614
|
-
],
|
|
3615
|
-
};
|
|
3616
|
-
}
|
|
3617
|
-
// Fetch documents for all team members
|
|
3618
|
-
const docsResult = await listDocuments({ employee_ids: employeeIds });
|
|
3619
|
-
const docs = docsResult.data;
|
|
3620
|
-
// Fetch folders for categorization
|
|
3621
|
-
const folders = await listFolders();
|
|
3622
|
-
const folderMap = new Map(folders.map(f => [f.id, f.name]));
|
|
3623
|
-
// Fetch employees for names
|
|
3624
|
-
const empResult = await listEmployees();
|
|
3625
|
-
const empMap = new Map(empResult.data.map(e => [e.id, e.full_name]));
|
|
3626
|
-
// Group documents by employee and folder
|
|
3627
|
-
const docsByEmployee = new Map();
|
|
3628
|
-
for (const empId of employeeIds) {
|
|
3629
|
-
docsByEmployee.set(empId, new Map());
|
|
3630
|
-
}
|
|
3631
|
-
for (const doc of docs) {
|
|
3632
|
-
if (!doc.employee_id)
|
|
3633
|
-
continue;
|
|
3634
|
-
const folderName = doc.folder_id ? folderMap.get(doc.folder_id) || 'Unknown' : 'Unfiled';
|
|
3635
|
-
const empDocs = docsByEmployee.get(doc.employee_id);
|
|
3636
|
-
if (empDocs) {
|
|
3637
|
-
empDocs.set(folderName, (empDocs.get(folderName) || 0) + 1);
|
|
3638
|
-
}
|
|
3639
|
-
}
|
|
3640
|
-
// Build summary text
|
|
3641
|
-
const allFolderNames = new Set();
|
|
3642
|
-
for (const empDocs of docsByEmployee.values()) {
|
|
3643
|
-
for (const folderName of empDocs.keys()) {
|
|
3644
|
-
allFolderNames.add(folderName);
|
|
3645
|
-
}
|
|
3646
|
-
}
|
|
3647
|
-
const summaryLines = [];
|
|
3648
|
-
for (const empId of employeeIds) {
|
|
3649
|
-
const empName = empMap.get(empId) || `Employee ${empId}`;
|
|
3650
|
-
const empDocs = docsByEmployee.get(empId) || new Map();
|
|
3651
|
-
const docCounts = [];
|
|
3652
|
-
for (const folderName of allFolderNames) {
|
|
3653
|
-
const count = empDocs.get(folderName) || 0;
|
|
3654
|
-
docCounts.push(`${count} ${folderName}`);
|
|
3655
|
-
}
|
|
3656
|
-
summaryLines.push(`- ${empName}: ${docCounts.join(', ') || 'No documents'}`);
|
|
3657
|
-
}
|
|
3658
|
-
const focusInfo = document_type ? `\nFocus: ${document_type}` : '';
|
|
3659
|
-
return {
|
|
3660
|
-
messages: [
|
|
3661
|
-
{
|
|
3662
|
-
role: 'user',
|
|
3663
|
-
content: {
|
|
3664
|
-
type: 'text',
|
|
3665
|
-
text: `Please analyze the document summary for team "${team.name}":
|
|
3666
|
-
|
|
3667
|
-
**Team:** ${team.name}
|
|
3668
|
-
**Members:** ${employeeIds.length}
|
|
3669
|
-
**Total Documents:** ${docs.length}${focusInfo}
|
|
3670
|
-
|
|
3671
|
-
**Document Summary by Employee:**
|
|
3672
|
-
${summaryLines.join('\n')}
|
|
3673
|
-
|
|
3674
|
-
Please analyze:
|
|
3675
|
-
1. Document completeness across the team
|
|
3676
|
-
2. Any employees with missing or fewer documents than others
|
|
3677
|
-
3. Patterns in document distribution
|
|
3678
|
-
4. Recommendations for improving documentation`,
|
|
3679
|
-
},
|
|
3680
|
-
},
|
|
3681
|
-
],
|
|
3682
|
-
};
|
|
3683
|
-
});
|
|
3684
|
-
// ============================================================================
|
|
3685
|
-
// Graceful Shutdown
|
|
3686
|
-
// ============================================================================
|
|
3687
|
-
/**
|
|
3688
|
-
* Clean up resources on process termination
|
|
3689
|
-
*/
|
|
3690
|
-
function shutdown() {
|
|
3691
|
-
cache.destroy();
|
|
3692
|
-
process.exit(0);
|
|
3693
|
-
}
|
|
3694
|
-
process.on('SIGINT', shutdown);
|
|
3695
|
-
process.on('SIGTERM', shutdown);
|
|
3696
|
-
// ============================================================================
|
|
3697
|
-
// Start Server
|
|
3698
|
-
// ============================================================================
|
|
3699
|
-
async function main() {
|
|
3700
|
-
const transport = new StdioServerTransport();
|
|
3701
|
-
await server.connect(transport);
|
|
3702
|
-
}
|
|
3703
|
-
main().catch(error => {
|
|
3704
|
-
console.error('Fatal error:', error);
|
|
3705
|
-
process.exit(1);
|
|
3706
|
-
});
|
|
11
|
+
export * from './tools/index.js';
|
|
3707
12
|
//# sourceMappingURL=index.js.map
|