@burdenoff/mcp-libs 2026.626.1 → 2026.627.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/modules/assethandler/index.cjs +1601 -0
  2. package/dist/modules/assethandler/index.cjs.map +1 -0
  3. package/dist/modules/assethandler/index.d.cts +8 -0
  4. package/dist/modules/assethandler/index.d.ts +8 -0
  5. package/dist/modules/assethandler/index.js +1540 -0
  6. package/dist/modules/assethandler/index.js.map +1 -0
  7. package/dist/modules/crewfoundry/index.cjs +1598 -0
  8. package/dist/modules/crewfoundry/index.cjs.map +1 -0
  9. package/dist/modules/crewfoundry/index.d.cts +8 -0
  10. package/dist/modules/crewfoundry/index.d.ts +8 -0
  11. package/dist/modules/crewfoundry/index.js +1537 -0
  12. package/dist/modules/crewfoundry/index.js.map +1 -0
  13. package/dist/modules/eventfullymanaged/index.cjs +3364 -0
  14. package/dist/modules/eventfullymanaged/index.cjs.map +1 -0
  15. package/dist/modules/eventfullymanaged/index.d.cts +8 -0
  16. package/dist/modules/eventfullymanaged/index.d.ts +8 -0
  17. package/dist/modules/eventfullymanaged/index.js +3303 -0
  18. package/dist/modules/eventfullymanaged/index.js.map +1 -0
  19. package/dist/modules/ggnomad/index.cjs +1068 -0
  20. package/dist/modules/ggnomad/index.cjs.map +1 -0
  21. package/dist/modules/ggnomad/index.d.cts +8 -0
  22. package/dist/modules/ggnomad/index.d.ts +8 -0
  23. package/dist/modules/ggnomad/index.js +1007 -0
  24. package/dist/modules/ggnomad/index.js.map +1 -0
  25. package/dist/modules/headshotmarketing/index.cjs +5031 -0
  26. package/dist/modules/headshotmarketing/index.cjs.map +1 -0
  27. package/dist/modules/headshotmarketing/index.d.cts +8 -0
  28. package/dist/modules/headshotmarketing/index.d.ts +8 -0
  29. package/dist/modules/headshotmarketing/index.js +4970 -0
  30. package/dist/modules/headshotmarketing/index.js.map +1 -0
  31. package/dist/modules/housingvista/index.cjs +1092 -0
  32. package/dist/modules/housingvista/index.cjs.map +1 -0
  33. package/dist/modules/housingvista/index.d.cts +8 -0
  34. package/dist/modules/housingvista/index.d.ts +8 -0
  35. package/dist/modules/housingvista/index.js +1031 -0
  36. package/dist/modules/housingvista/index.js.map +1 -0
  37. package/dist/modules/movethewheels/index.cjs +3915 -0
  38. package/dist/modules/movethewheels/index.cjs.map +1 -0
  39. package/dist/modules/movethewheels/index.d.cts +8 -0
  40. package/dist/modules/movethewheels/index.d.ts +8 -0
  41. package/dist/modules/movethewheels/index.js +3854 -0
  42. package/dist/modules/movethewheels/index.js.map +1 -0
  43. package/dist/modules/planmagnet/index.cjs +6171 -0
  44. package/dist/modules/planmagnet/index.cjs.map +1 -0
  45. package/dist/modules/planmagnet/index.d.cts +8 -0
  46. package/dist/modules/planmagnet/index.d.ts +8 -0
  47. package/dist/modules/planmagnet/index.js +6110 -0
  48. package/dist/modules/planmagnet/index.js.map +1 -0
  49. package/dist/modules/proserviceworld/index.cjs +1404 -0
  50. package/dist/modules/proserviceworld/index.cjs.map +1 -0
  51. package/dist/modules/proserviceworld/index.d.cts +8 -0
  52. package/dist/modules/proserviceworld/index.d.ts +8 -0
  53. package/dist/modules/proserviceworld/index.js +1343 -0
  54. package/dist/modules/proserviceworld/index.js.map +1 -0
  55. package/dist/modules/timecampus/index.cjs +6482 -0
  56. package/dist/modules/timecampus/index.cjs.map +1 -0
  57. package/dist/modules/timecampus/index.d.cts +8 -0
  58. package/dist/modules/timecampus/index.d.ts +8 -0
  59. package/dist/modules/timecampus/index.js +6421 -0
  60. package/dist/modules/timecampus/index.js.map +1 -0
  61. package/package.json +1 -1
@@ -0,0 +1,1404 @@
1
+ 'use strict';
2
+
3
+ var zod = require('zod');
4
+ require('graphql-request');
5
+ require('cross-fetch');
6
+ require('@modelcontextprotocol/sdk/server/mcp.js');
7
+ require('@modelcontextprotocol/sdk/types.js');
8
+
9
+ // src/modules/proserviceworld/tools/clients.ts
10
+
11
+ // src/core/errors.ts
12
+ var McpInternalError = class extends Error {
13
+ cause;
14
+ constructor(message, cause) {
15
+ super(message);
16
+ this.name = "McpInternalError";
17
+ this.cause = cause;
18
+ }
19
+ };
20
+
21
+ // src/core/result.ts
22
+ function jsonResult(data, structured) {
23
+ const result = {
24
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
25
+ };
26
+ return result;
27
+ }
28
+ function errorResult(message, hint) {
29
+ const text = hint ? `${message}
30
+
31
+ Hint: ${hint}` : message;
32
+ return { content: [{ type: "text", text }], isError: true };
33
+ }
34
+
35
+ // src/core/tool-name.ts
36
+ var MAX_TOOL_NAME_LENGTH = 63;
37
+ function assertSafeName(name, kind) {
38
+ if (name.length > MAX_TOOL_NAME_LENGTH) {
39
+ throw new McpInternalError(
40
+ `Tool name '${name}' exceeds ${MAX_TOOL_NAME_LENGTH} characters (some MCP clients truncate at 64).`
41
+ );
42
+ }
43
+ return name;
44
+ }
45
+ function mcpToolName(productName, ...parts) {
46
+ if (!productName) {
47
+ throw new McpInternalError("mcpToolName: productName is required");
48
+ }
49
+ const name = [productName, ...parts].filter(Boolean).join("_");
50
+ return assertSafeName(name);
51
+ }
52
+ function mcpResourceUri(productName, ...parts) {
53
+ if (!productName) {
54
+ throw new McpInternalError("mcpResourceUri: productName is required");
55
+ }
56
+ return `${productName}://${parts.filter(Boolean).join("/")}`;
57
+ }
58
+ function mcpPromptName(productName, ...parts) {
59
+ if (!productName) {
60
+ throw new McpInternalError("mcpPromptName: productName is required");
61
+ }
62
+ return [productName, ...parts].filter(Boolean).join("-");
63
+ }
64
+
65
+ // src/modules/proserviceworld/operations.ts
66
+ var CLIENT_FIELDS = `
67
+ id name type status industry contactName contactEmail contactPhone address
68
+ notes tags relationshipOwnerId workspaceId createdAt updatedAt
69
+ `;
70
+ var ENGAGEMENT_FIELDS = `
71
+ id clientId name description status feeArrangement feeAmount currency
72
+ practiceAreaId responsiblePartnerId startDate endDate tags workspaceId
73
+ createdAt updatedAt
74
+ `;
75
+ var MATTER_FIELDS = `
76
+ id clientId engagementId title reference description status priority
77
+ isPrivileged leadPractitionerId practiceAreaId dueDate closedAt tags
78
+ workspaceId createdAt updatedAt
79
+ `;
80
+ var INVOICE_FIELDS = `
81
+ id clientId engagementId number status issueDate dueDate paidAt subtotal
82
+ taxAmount total currency lineItems notes workspaceId createdAt updatedAt
83
+ `;
84
+ var TIME_ENTRY_FIELDS = `
85
+ id matterId engagementId practitionerId date minutes rate billable narrative
86
+ status invoiceId workspaceId createdAt updatedAt
87
+ `;
88
+ var COMPLIANCE_FIELDS = `
89
+ id type status title details subjectType subjectId reviewerId dueDate
90
+ resolvedAt evidenceFileId workspaceId createdAt updatedAt
91
+ `;
92
+ var CONFLICT_FIELDS = `
93
+ id clientId matterId status searchedParties findings waiverReason
94
+ clearedById resolvedAt workspaceId createdAt updatedAt
95
+ `;
96
+ var PRACTICE_AREA_FIELDS = `
97
+ id name code description isActive workspaceId createdAt updatedAt
98
+ `;
99
+ var MILESTONE_FIELDS = `
100
+ id engagementId name description status triggersInvoice triggerAmount
101
+ dueDate completedAt workspaceId createdAt updatedAt
102
+ `;
103
+ var DELIVERABLE_FIELDS = `
104
+ id engagementId name description status version acceptanceCriteria
105
+ acceptedAt dueDate workspaceId createdAt updatedAt
106
+ `;
107
+ var PAGE_INFO = `pageInfo { cursor hasMore }`;
108
+ var GET_CLIENT = `
109
+ query PswGetClient($id: ID!) {
110
+ pswClient(id: $id) {
111
+ ${CLIENT_FIELDS}
112
+ engagements { id name status feeArrangement feeAmount currency }
113
+ matters { id title status priority }
114
+ }
115
+ }
116
+ `;
117
+ var LIST_CLIENTS = `
118
+ query PswListClients($pagination: PaginationInput, $status: PswClientStatus) {
119
+ pswListClients(pagination: $pagination, status: $status) {
120
+ items { ${CLIENT_FIELDS} }
121
+ ${PAGE_INFO}
122
+ }
123
+ }
124
+ `;
125
+ var CREATE_CLIENT = `
126
+ mutation PswCreateClient($input: PswCreateClientInput!) {
127
+ pswCreateClient(input: $input) {
128
+ ${CLIENT_FIELDS}
129
+ }
130
+ }
131
+ `;
132
+ var UPDATE_CLIENT = `
133
+ mutation PswUpdateClient($id: ID!, $input: PswUpdateClientInput!) {
134
+ pswUpdateClient(id: $id, input: $input) {
135
+ ${CLIENT_FIELDS}
136
+ }
137
+ }
138
+ `;
139
+ var ARCHIVE_CLIENT = `
140
+ mutation PswArchiveClient($id: ID!) {
141
+ pswArchiveClient(id: $id)
142
+ }
143
+ `;
144
+ var GET_ENGAGEMENT = `
145
+ query PswGetEngagement($id: ID!) {
146
+ pswEngagement(id: $id) {
147
+ ${ENGAGEMENT_FIELDS}
148
+ client { id name status }
149
+ milestones { ${MILESTONE_FIELDS} }
150
+ deliverables { ${DELIVERABLE_FIELDS} }
151
+ }
152
+ }
153
+ `;
154
+ var LIST_ENGAGEMENTS = `
155
+ query PswListEngagements(
156
+ $clientId: ID
157
+ $status: PswEngagementStatus
158
+ $pagination: PaginationInput
159
+ ) {
160
+ pswListEngagements(clientId: $clientId, status: $status, pagination: $pagination) {
161
+ items { ${ENGAGEMENT_FIELDS} }
162
+ ${PAGE_INFO}
163
+ }
164
+ }
165
+ `;
166
+ var CREATE_ENGAGEMENT = `
167
+ mutation PswCreateEngagement($input: PswCreateEngagementInput!) {
168
+ pswCreateEngagement(input: $input) {
169
+ ${ENGAGEMENT_FIELDS}
170
+ }
171
+ }
172
+ `;
173
+ var UPDATE_ENGAGEMENT = `
174
+ mutation PswUpdateEngagement($id: ID!, $input: PswUpdateEngagementInput!) {
175
+ pswUpdateEngagement(id: $id, input: $input) {
176
+ ${ENGAGEMENT_FIELDS}
177
+ }
178
+ }
179
+ `;
180
+ var TRANSITION_ENGAGEMENT = `
181
+ mutation PswTransitionEngagement($id: ID!, $status: PswEngagementStatus!) {
182
+ pswTransitionEngagement(id: $id, status: $status) {
183
+ ${ENGAGEMENT_FIELDS}
184
+ }
185
+ }
186
+ `;
187
+ var GET_MATTER = `
188
+ query PswGetMatter($id: ID!) {
189
+ pswMatter(id: $id) {
190
+ ${MATTER_FIELDS}
191
+ client { id name status }
192
+ }
193
+ }
194
+ `;
195
+ var LIST_MATTERS = `
196
+ query PswListMatters(
197
+ $clientId: ID
198
+ $engagementId: ID
199
+ $status: PswMatterStatus
200
+ $pagination: PaginationInput
201
+ ) {
202
+ pswListMatters(
203
+ clientId: $clientId
204
+ engagementId: $engagementId
205
+ status: $status
206
+ pagination: $pagination
207
+ ) {
208
+ items { ${MATTER_FIELDS} }
209
+ ${PAGE_INFO}
210
+ }
211
+ }
212
+ `;
213
+ var CREATE_MATTER = `
214
+ mutation PswCreateMatter($input: PswCreateMatterInput!) {
215
+ pswCreateMatter(input: $input) {
216
+ ${MATTER_FIELDS}
217
+ }
218
+ }
219
+ `;
220
+ var UPDATE_MATTER = `
221
+ mutation PswUpdateMatter($id: ID!, $input: PswUpdateMatterInput!) {
222
+ pswUpdateMatter(id: $id, input: $input) {
223
+ ${MATTER_FIELDS}
224
+ }
225
+ }
226
+ `;
227
+ var CLOSE_MATTER = `
228
+ mutation PswCloseMatter($id: ID!) {
229
+ pswCloseMatter(id: $id) {
230
+ ${MATTER_FIELDS}
231
+ }
232
+ }
233
+ `;
234
+ var LIST_INVOICES = `
235
+ query PswListInvoices(
236
+ $clientId: ID
237
+ $status: PswInvoiceStatus
238
+ $pagination: PaginationInput
239
+ ) {
240
+ pswListInvoices(clientId: $clientId, status: $status, pagination: $pagination) {
241
+ items { ${INVOICE_FIELDS} }
242
+ ${PAGE_INFO}
243
+ }
244
+ }
245
+ `;
246
+ var CREATE_INVOICE = `
247
+ mutation PswCreateInvoice($input: PswCreateInvoiceInput!) {
248
+ pswCreateInvoice(input: $input) {
249
+ ${INVOICE_FIELDS}
250
+ }
251
+ }
252
+ `;
253
+ var MARK_INVOICE_PAID = `
254
+ mutation PswMarkInvoicePaid($id: ID!) {
255
+ pswMarkInvoicePaid(id: $id) {
256
+ ${INVOICE_FIELDS}
257
+ }
258
+ }
259
+ `;
260
+ var LOG_TIME_ENTRY = `
261
+ mutation PswLogTimeEntry($input: PswCreateTimeEntryInput!) {
262
+ pswLogTimeEntry(input: $input) {
263
+ ${TIME_ENTRY_FIELDS}
264
+ }
265
+ }
266
+ `;
267
+ var LIST_COMPLIANCE_CHECKS = `
268
+ query PswListComplianceChecks(
269
+ $status: PswComplianceStatus
270
+ $subjectId: ID
271
+ $subjectType: String
272
+ $pagination: PaginationInput
273
+ ) {
274
+ pswListComplianceChecks(
275
+ status: $status
276
+ subjectId: $subjectId
277
+ subjectType: $subjectType
278
+ pagination: $pagination
279
+ ) {
280
+ ${COMPLIANCE_FIELDS}
281
+ }
282
+ }
283
+ `;
284
+ var LIST_CONFLICT_CHECKS = `
285
+ query PswListConflictChecks($clientId: ID, $pagination: PaginationInput) {
286
+ pswListConflictChecks(clientId: $clientId, pagination: $pagination) {
287
+ ${CONFLICT_FIELDS}
288
+ }
289
+ }
290
+ `;
291
+ var LIST_PRACTICE_AREAS = `
292
+ query PswListPracticeAreas($pagination: PaginationInput) {
293
+ pswListPracticeAreas(pagination: $pagination) {
294
+ ${PRACTICE_AREA_FIELDS}
295
+ }
296
+ }
297
+ `;
298
+ var HEALTH = `
299
+ query PswHealth {
300
+ pswHealth
301
+ }
302
+ `;
303
+ var ClientStatusEnum = zod.z.enum(["ACTIVE", "ARCHIVED", "INACTIVE", "PROSPECT"]).describe("Lifecycle status of a client relationship");
304
+ var ClientTypeEnum = zod.z.enum(["INDIVIDUAL", "ORGANIZATION"]).describe("Whether the client is an INDIVIDUAL or an ORGANIZATION");
305
+ var EngagementStatusEnum = zod.z.enum([
306
+ "ACCEPTED",
307
+ "CANCELLED",
308
+ "CLOSED",
309
+ "DELIVERED",
310
+ "INVOICED",
311
+ "IN_PROGRESS",
312
+ "PROPOSAL",
313
+ "REVIEW"
314
+ ]).describe("Delivery-lifecycle status of an engagement");
315
+ var FeeArrangementEnum = zod.z.enum(["FIXED", "HOURLY", "RETAINER", "VALUE_BASED"]).describe("Fee arrangement for an engagement");
316
+ var MatterStatusEnum = zod.z.enum(["ARCHIVED", "CLOSED", "ON_HOLD", "OPEN"]).describe("Lifecycle status of a matter");
317
+ var MatterPriorityEnum = zod.z.enum(["CRITICAL", "HIGH", "LOW", "MEDIUM"]).describe("Priority of a matter");
318
+ var InvoiceStatusEnum = zod.z.enum(["CANCELLED", "DRAFT", "OVERDUE", "PAID", "SENT"]).describe("Status of an invoice");
319
+ var ComplianceStatusEnum = zod.z.enum(["FAILED", "IN_REVIEW", "PASSED", "PENDING", "WAIVED"]).describe("Status of a compliance check");
320
+ var JsonObject = zod.z.record(zod.z.string(), zod.z.unknown());
321
+ var LineItems = zod.z.union([zod.z.array(zod.z.unknown()), JsonObject]);
322
+
323
+ // src/modules/proserviceworld/tools/clients.ts
324
+ function registerClientTools(server, ctx, track) {
325
+ server.registerTool(
326
+ track(mcpToolName(ctx.productName, "clients_get")),
327
+ {
328
+ title: "Get Client",
329
+ description: "Fetch a single ProServiceWorld client by ID, including its engagements and matters.",
330
+ inputSchema: { id: zod.z.string().describe("Client ID") }
331
+ },
332
+ async ({ id }) => {
333
+ try {
334
+ const data = await ctx.gql.workspace.request(GET_CLIENT, { id });
335
+ if (!data.pswClient) return errorResult(`Client ${id} not found`);
336
+ return jsonResult(data.pswClient);
337
+ } catch (error) {
338
+ ctx.logger.error("proserviceworld clients_get failed", { error: String(error) });
339
+ return errorResult(
340
+ `Failed to get client: ${error instanceof Error ? error.message : String(error)}`
341
+ );
342
+ }
343
+ }
344
+ );
345
+ server.registerTool(
346
+ track(mcpToolName(ctx.productName, "clients_list")),
347
+ {
348
+ title: "List Clients",
349
+ description: "List clients in the bound workspace, optionally filtered by status, with limit/offset pagination.",
350
+ inputSchema: {
351
+ status: ClientStatusEnum.optional().describe("Filter by client status"),
352
+ limit: zod.z.number().int().optional().describe("Page size (default 50)"),
353
+ offset: zod.z.number().int().optional().describe("Pagination offset (default 0)")
354
+ }
355
+ },
356
+ async ({ status, limit, offset }) => {
357
+ try {
358
+ const data = await ctx.gql.workspace.request(LIST_CLIENTS, {
359
+ status,
360
+ pagination: { limit: limit ?? 50, offset: offset ?? 0 }
361
+ });
362
+ return jsonResult(data.pswListClients);
363
+ } catch (error) {
364
+ ctx.logger.error("proserviceworld clients_list failed", { error: String(error) });
365
+ return errorResult(
366
+ `Failed to list clients: ${error instanceof Error ? error.message : String(error)}`
367
+ );
368
+ }
369
+ }
370
+ );
371
+ server.registerTool(
372
+ track(mcpToolName(ctx.productName, "clients_create")),
373
+ {
374
+ title: "Create Client",
375
+ description: "Onboard a new client. Only `name` is required; `type` defaults to ORGANIZATION and `status` to PROSPECT.",
376
+ inputSchema: {
377
+ name: zod.z.string().min(1).describe("Client display name"),
378
+ type: ClientTypeEnum.optional().describe("Client type (default ORGANIZATION)"),
379
+ status: ClientStatusEnum.optional().describe("Initial status (default PROSPECT)"),
380
+ industry: zod.z.string().optional().describe("Industry / sector"),
381
+ contactName: zod.z.string().optional().describe("Primary contact name"),
382
+ contactEmail: zod.z.string().optional().describe("Primary contact email"),
383
+ contactPhone: zod.z.string().optional().describe("Primary contact phone"),
384
+ address: zod.z.string().optional().describe("Postal address"),
385
+ notes: zod.z.string().optional().describe("Free-text notes"),
386
+ tags: zod.z.array(zod.z.string()).optional().describe("Tag labels"),
387
+ relationshipOwnerId: zod.z.string().optional().describe("User ID of the relationship owner")
388
+ }
389
+ },
390
+ async ({
391
+ name,
392
+ type,
393
+ status,
394
+ industry,
395
+ contactName,
396
+ contactEmail,
397
+ contactPhone,
398
+ address,
399
+ notes,
400
+ tags,
401
+ relationshipOwnerId
402
+ }) => {
403
+ try {
404
+ const input = { name };
405
+ if (type !== void 0) input.type = type;
406
+ if (status !== void 0) input.status = status;
407
+ if (industry !== void 0) input.industry = industry;
408
+ if (contactName !== void 0) input.contactName = contactName;
409
+ if (contactEmail !== void 0) input.contactEmail = contactEmail;
410
+ if (contactPhone !== void 0) input.contactPhone = contactPhone;
411
+ if (address !== void 0) input.address = address;
412
+ if (notes !== void 0) input.notes = notes;
413
+ if (tags !== void 0) input.tags = tags;
414
+ if (relationshipOwnerId !== void 0) input.relationshipOwnerId = relationshipOwnerId;
415
+ const data = await ctx.gql.workspace.request(CREATE_CLIENT, { input });
416
+ return jsonResult(data.pswCreateClient);
417
+ } catch (error) {
418
+ ctx.logger.error("proserviceworld clients_create failed", { error: String(error) });
419
+ return errorResult(
420
+ `Failed to create client: ${error instanceof Error ? error.message : String(error)}`
421
+ );
422
+ }
423
+ }
424
+ );
425
+ server.registerTool(
426
+ track(mcpToolName(ctx.productName, "clients_update")),
427
+ {
428
+ title: "Update Client",
429
+ description: "Update a client. At least one mutable field must be provided. Use `status` to move a client through ACTIVE/INACTIVE/etc.",
430
+ inputSchema: {
431
+ id: zod.z.string().describe("Client ID"),
432
+ name: zod.z.string().optional().describe("Updated display name"),
433
+ type: ClientTypeEnum.optional().describe("Updated client type"),
434
+ status: ClientStatusEnum.optional().describe("Updated status"),
435
+ industry: zod.z.string().optional().describe("Updated industry"),
436
+ contactName: zod.z.string().optional().describe("Updated primary contact name"),
437
+ contactEmail: zod.z.string().optional().describe("Updated primary contact email"),
438
+ contactPhone: zod.z.string().optional().describe("Updated primary contact phone"),
439
+ address: zod.z.string().optional().describe("Updated postal address"),
440
+ notes: zod.z.string().optional().describe("Updated notes"),
441
+ tags: zod.z.array(zod.z.string()).optional().describe("Updated tag labels"),
442
+ relationshipOwnerId: zod.z.string().optional().describe("Updated relationship owner user ID")
443
+ }
444
+ },
445
+ async ({
446
+ id,
447
+ name,
448
+ type,
449
+ status,
450
+ industry,
451
+ contactName,
452
+ contactEmail,
453
+ contactPhone,
454
+ address,
455
+ notes,
456
+ tags,
457
+ relationshipOwnerId
458
+ }) => {
459
+ try {
460
+ const input = {};
461
+ if (name !== void 0) input.name = name;
462
+ if (type !== void 0) input.type = type;
463
+ if (status !== void 0) input.status = status;
464
+ if (industry !== void 0) input.industry = industry;
465
+ if (contactName !== void 0) input.contactName = contactName;
466
+ if (contactEmail !== void 0) input.contactEmail = contactEmail;
467
+ if (contactPhone !== void 0) input.contactPhone = contactPhone;
468
+ if (address !== void 0) input.address = address;
469
+ if (notes !== void 0) input.notes = notes;
470
+ if (tags !== void 0) input.tags = tags;
471
+ if (relationshipOwnerId !== void 0) input.relationshipOwnerId = relationshipOwnerId;
472
+ if (Object.keys(input).length === 0) {
473
+ return errorResult(
474
+ "No updates specified \u2014 provide at least one of name/type/status/industry/contactName/contactEmail/contactPhone/address/notes/tags/relationshipOwnerId"
475
+ );
476
+ }
477
+ const data = await ctx.gql.workspace.request(UPDATE_CLIENT, { id, input });
478
+ return jsonResult(data.pswUpdateClient);
479
+ } catch (error) {
480
+ ctx.logger.error("proserviceworld clients_update failed", { error: String(error) });
481
+ return errorResult(
482
+ `Failed to update client: ${error instanceof Error ? error.message : String(error)}`
483
+ );
484
+ }
485
+ }
486
+ );
487
+ server.registerTool(
488
+ track(mcpToolName(ctx.productName, "clients_archive")),
489
+ {
490
+ title: "Archive Client",
491
+ description: "Archive (soft-delete) a client by ID. Returns true on success.",
492
+ inputSchema: { id: zod.z.string().describe("Client ID") }
493
+ },
494
+ async ({ id }) => {
495
+ try {
496
+ const data = await ctx.gql.workspace.request(
497
+ ARCHIVE_CLIENT,
498
+ { id }
499
+ );
500
+ return jsonResult({ archived: data.pswArchiveClient, id });
501
+ } catch (error) {
502
+ ctx.logger.error("proserviceworld clients_archive failed", { error: String(error) });
503
+ return errorResult(
504
+ `Failed to archive client: ${error instanceof Error ? error.message : String(error)}`
505
+ );
506
+ }
507
+ }
508
+ );
509
+ }
510
+ function registerEngagementTools(server, ctx, track) {
511
+ server.registerTool(
512
+ track(mcpToolName(ctx.productName, "engagements_get")),
513
+ {
514
+ title: "Get Engagement",
515
+ description: "Fetch a single engagement by ID, including its client, milestones, and deliverables.",
516
+ inputSchema: { id: zod.z.string().describe("Engagement ID") }
517
+ },
518
+ async ({ id }) => {
519
+ try {
520
+ const data = await ctx.gql.workspace.request(GET_ENGAGEMENT, { id });
521
+ if (!data.pswEngagement) return errorResult(`Engagement ${id} not found`);
522
+ return jsonResult(data.pswEngagement);
523
+ } catch (error) {
524
+ ctx.logger.error("proserviceworld engagements_get failed", { error: String(error) });
525
+ return errorResult(
526
+ `Failed to get engagement: ${error instanceof Error ? error.message : String(error)}`
527
+ );
528
+ }
529
+ }
530
+ );
531
+ server.registerTool(
532
+ track(mcpToolName(ctx.productName, "engagements_list")),
533
+ {
534
+ title: "List Engagements",
535
+ description: "List engagements, optionally filtered by client and/or status, with limit/offset pagination.",
536
+ inputSchema: {
537
+ clientId: zod.z.string().optional().describe("Filter by client ID"),
538
+ status: EngagementStatusEnum.optional().describe("Filter by engagement status"),
539
+ limit: zod.z.number().int().optional().describe("Page size (default 50)"),
540
+ offset: zod.z.number().int().optional().describe("Pagination offset (default 0)")
541
+ }
542
+ },
543
+ async ({ clientId, status, limit, offset }) => {
544
+ try {
545
+ const data = await ctx.gql.workspace.request(LIST_ENGAGEMENTS, {
546
+ clientId,
547
+ status,
548
+ pagination: { limit: limit ?? 50, offset: offset ?? 0 }
549
+ });
550
+ return jsonResult(data.pswListEngagements);
551
+ } catch (error) {
552
+ ctx.logger.error("proserviceworld engagements_list failed", { error: String(error) });
553
+ return errorResult(
554
+ `Failed to list engagements: ${error instanceof Error ? error.message : String(error)}`
555
+ );
556
+ }
557
+ }
558
+ );
559
+ server.registerTool(
560
+ track(mcpToolName(ctx.productName, "engagements_create")),
561
+ {
562
+ title: "Create Engagement",
563
+ description: "Open a new engagement for a client. `clientId` and `name` are required; `feeArrangement` defaults to FIXED.",
564
+ inputSchema: {
565
+ clientId: zod.z.string().min(1).describe("Owning client ID"),
566
+ name: zod.z.string().min(1).describe("Engagement name"),
567
+ description: zod.z.string().optional().describe("Scope / description"),
568
+ feeArrangement: FeeArrangementEnum.optional().describe("Fee arrangement (default FIXED)"),
569
+ feeAmount: zod.z.number().optional().describe("Fee amount"),
570
+ currency: zod.z.string().optional().describe('ISO currency code (e.g. "USD")'),
571
+ practiceAreaId: zod.z.string().optional().describe("Practice-area ID"),
572
+ responsiblePartnerId: zod.z.string().optional().describe("Responsible partner user ID"),
573
+ startDate: zod.z.string().optional().describe("Start date (ISO-8601 DateTime)"),
574
+ endDate: zod.z.string().optional().describe("End date (ISO-8601 DateTime)"),
575
+ tags: zod.z.array(zod.z.string()).optional().describe("Tag labels")
576
+ }
577
+ },
578
+ async ({
579
+ clientId,
580
+ name,
581
+ description,
582
+ feeArrangement,
583
+ feeAmount,
584
+ currency,
585
+ practiceAreaId,
586
+ responsiblePartnerId,
587
+ startDate,
588
+ endDate,
589
+ tags
590
+ }) => {
591
+ try {
592
+ const input = { clientId, name };
593
+ if (description !== void 0) input.description = description;
594
+ if (feeArrangement !== void 0) input.feeArrangement = feeArrangement;
595
+ if (feeAmount !== void 0) input.feeAmount = feeAmount;
596
+ if (currency !== void 0) input.currency = currency;
597
+ if (practiceAreaId !== void 0) input.practiceAreaId = practiceAreaId;
598
+ if (responsiblePartnerId !== void 0) input.responsiblePartnerId = responsiblePartnerId;
599
+ if (startDate !== void 0) input.startDate = startDate;
600
+ if (endDate !== void 0) input.endDate = endDate;
601
+ if (tags !== void 0) input.tags = tags;
602
+ const data = await ctx.gql.workspace.request(CREATE_ENGAGEMENT, { input });
603
+ return jsonResult(data.pswCreateEngagement);
604
+ } catch (error) {
605
+ ctx.logger.error("proserviceworld engagements_create failed", { error: String(error) });
606
+ return errorResult(
607
+ `Failed to create engagement: ${error instanceof Error ? error.message : String(error)}`
608
+ );
609
+ }
610
+ }
611
+ );
612
+ server.registerTool(
613
+ track(mcpToolName(ctx.productName, "engagements_update")),
614
+ {
615
+ title: "Update Engagement",
616
+ description: "Update an engagement. At least one mutable field must be provided. Use `engagements_transition` to change lifecycle status.",
617
+ inputSchema: {
618
+ id: zod.z.string().describe("Engagement ID"),
619
+ name: zod.z.string().optional().describe("Updated name"),
620
+ description: zod.z.string().optional().describe("Updated scope / description"),
621
+ feeArrangement: FeeArrangementEnum.optional().describe("Updated fee arrangement"),
622
+ feeAmount: zod.z.number().optional().describe("Updated fee amount"),
623
+ currency: zod.z.string().optional().describe("Updated ISO currency code"),
624
+ practiceAreaId: zod.z.string().optional().describe("Updated practice-area ID"),
625
+ responsiblePartnerId: zod.z.string().optional().describe("Updated responsible partner user ID"),
626
+ startDate: zod.z.string().optional().describe("Updated start date (ISO-8601 DateTime)"),
627
+ endDate: zod.z.string().optional().describe("Updated end date (ISO-8601 DateTime)"),
628
+ tags: zod.z.array(zod.z.string()).optional().describe("Updated tag labels")
629
+ }
630
+ },
631
+ async ({
632
+ id,
633
+ name,
634
+ description,
635
+ feeArrangement,
636
+ feeAmount,
637
+ currency,
638
+ practiceAreaId,
639
+ responsiblePartnerId,
640
+ startDate,
641
+ endDate,
642
+ tags
643
+ }) => {
644
+ try {
645
+ const input = {};
646
+ if (name !== void 0) input.name = name;
647
+ if (description !== void 0) input.description = description;
648
+ if (feeArrangement !== void 0) input.feeArrangement = feeArrangement;
649
+ if (feeAmount !== void 0) input.feeAmount = feeAmount;
650
+ if (currency !== void 0) input.currency = currency;
651
+ if (practiceAreaId !== void 0) input.practiceAreaId = practiceAreaId;
652
+ if (responsiblePartnerId !== void 0) input.responsiblePartnerId = responsiblePartnerId;
653
+ if (startDate !== void 0) input.startDate = startDate;
654
+ if (endDate !== void 0) input.endDate = endDate;
655
+ if (tags !== void 0) input.tags = tags;
656
+ if (Object.keys(input).length === 0) {
657
+ return errorResult(
658
+ "No updates specified \u2014 provide at least one of name/description/feeArrangement/feeAmount/currency/practiceAreaId/responsiblePartnerId/startDate/endDate/tags"
659
+ );
660
+ }
661
+ const data = await ctx.gql.workspace.request(UPDATE_ENGAGEMENT, { id, input });
662
+ return jsonResult(data.pswUpdateEngagement);
663
+ } catch (error) {
664
+ ctx.logger.error("proserviceworld engagements_update failed", { error: String(error) });
665
+ return errorResult(
666
+ `Failed to update engagement: ${error instanceof Error ? error.message : String(error)}`
667
+ );
668
+ }
669
+ }
670
+ );
671
+ server.registerTool(
672
+ track(mcpToolName(ctx.productName, "engagements_transition")),
673
+ {
674
+ title: "Transition Engagement",
675
+ description: "Advance an engagement through its delivery lifecycle (validated transitions), e.g. PROPOSAL \u2192 ACCEPTED \u2192 IN_PROGRESS \u2192 REVIEW \u2192 DELIVERED \u2192 INVOICED \u2192 CLOSED, or CANCELLED.",
676
+ inputSchema: {
677
+ id: zod.z.string().describe("Engagement ID"),
678
+ status: EngagementStatusEnum.describe("Target engagement status")
679
+ }
680
+ },
681
+ async ({ id, status }) => {
682
+ try {
683
+ const data = await ctx.gql.workspace.request(TRANSITION_ENGAGEMENT, { id, status });
684
+ return jsonResult(data.pswTransitionEngagement);
685
+ } catch (error) {
686
+ ctx.logger.error("proserviceworld engagements_transition failed", { error: String(error) });
687
+ return errorResult(
688
+ `Failed to transition engagement: ${error instanceof Error ? error.message : String(error)}`
689
+ );
690
+ }
691
+ }
692
+ );
693
+ }
694
+ function registerMatterTools(server, ctx, track) {
695
+ server.registerTool(
696
+ track(mcpToolName(ctx.productName, "matters_get")),
697
+ {
698
+ title: "Get Matter",
699
+ description: "Fetch a single matter by ID, including its client.",
700
+ inputSchema: { id: zod.z.string().describe("Matter ID") }
701
+ },
702
+ async ({ id }) => {
703
+ try {
704
+ const data = await ctx.gql.workspace.request(GET_MATTER, { id });
705
+ if (!data.pswMatter) return errorResult(`Matter ${id} not found`);
706
+ return jsonResult(data.pswMatter);
707
+ } catch (error) {
708
+ ctx.logger.error("proserviceworld matters_get failed", { error: String(error) });
709
+ return errorResult(
710
+ `Failed to get matter: ${error instanceof Error ? error.message : String(error)}`
711
+ );
712
+ }
713
+ }
714
+ );
715
+ server.registerTool(
716
+ track(mcpToolName(ctx.productName, "matters_list")),
717
+ {
718
+ title: "List Matters",
719
+ description: "List matters, optionally filtered by client, engagement, and/or status, with limit/offset pagination.",
720
+ inputSchema: {
721
+ clientId: zod.z.string().optional().describe("Filter by client ID"),
722
+ engagementId: zod.z.string().optional().describe("Filter by engagement ID"),
723
+ status: MatterStatusEnum.optional().describe("Filter by matter status"),
724
+ limit: zod.z.number().int().optional().describe("Page size (default 50)"),
725
+ offset: zod.z.number().int().optional().describe("Pagination offset (default 0)")
726
+ }
727
+ },
728
+ async ({ clientId, engagementId, status, limit, offset }) => {
729
+ try {
730
+ const data = await ctx.gql.workspace.request(LIST_MATTERS, {
731
+ clientId,
732
+ engagementId,
733
+ status,
734
+ pagination: { limit: limit ?? 50, offset: offset ?? 0 }
735
+ });
736
+ return jsonResult(data.pswListMatters);
737
+ } catch (error) {
738
+ ctx.logger.error("proserviceworld matters_list failed", { error: String(error) });
739
+ return errorResult(
740
+ `Failed to list matters: ${error instanceof Error ? error.message : String(error)}`
741
+ );
742
+ }
743
+ }
744
+ );
745
+ server.registerTool(
746
+ track(mcpToolName(ctx.productName, "matters_create")),
747
+ {
748
+ title: "Create Matter",
749
+ description: "Open a new matter for a client. `clientId` and `title` are required; `priority` defaults to MEDIUM and `isPrivileged` to false.",
750
+ inputSchema: {
751
+ clientId: zod.z.string().min(1).describe("Owning client ID"),
752
+ title: zod.z.string().min(1).describe("Matter title"),
753
+ reference: zod.z.string().optional().describe("External matter reference / number"),
754
+ description: zod.z.string().optional().describe("Description"),
755
+ engagementId: zod.z.string().optional().describe("Parent engagement ID"),
756
+ practiceAreaId: zod.z.string().optional().describe("Practice-area ID"),
757
+ leadPractitionerId: zod.z.string().optional().describe("Lead practitioner user ID"),
758
+ priority: MatterPriorityEnum.optional().describe("Priority (default MEDIUM)"),
759
+ isPrivileged: zod.z.boolean().optional().describe("Whether the matter is privileged (default false)"),
760
+ dueDate: zod.z.string().optional().describe("Due date (ISO-8601 DateTime)"),
761
+ tags: zod.z.array(zod.z.string()).optional().describe("Tag labels")
762
+ }
763
+ },
764
+ async ({
765
+ clientId,
766
+ title,
767
+ reference,
768
+ description,
769
+ engagementId,
770
+ practiceAreaId,
771
+ leadPractitionerId,
772
+ priority,
773
+ isPrivileged,
774
+ dueDate,
775
+ tags
776
+ }) => {
777
+ try {
778
+ const input = { clientId, title };
779
+ if (reference !== void 0) input.reference = reference;
780
+ if (description !== void 0) input.description = description;
781
+ if (engagementId !== void 0) input.engagementId = engagementId;
782
+ if (practiceAreaId !== void 0) input.practiceAreaId = practiceAreaId;
783
+ if (leadPractitionerId !== void 0) input.leadPractitionerId = leadPractitionerId;
784
+ if (priority !== void 0) input.priority = priority;
785
+ if (isPrivileged !== void 0) input.isPrivileged = isPrivileged;
786
+ if (dueDate !== void 0) input.dueDate = dueDate;
787
+ if (tags !== void 0) input.tags = tags;
788
+ const data = await ctx.gql.workspace.request(CREATE_MATTER, { input });
789
+ return jsonResult(data.pswCreateMatter);
790
+ } catch (error) {
791
+ ctx.logger.error("proserviceworld matters_create failed", { error: String(error) });
792
+ return errorResult(
793
+ `Failed to create matter: ${error instanceof Error ? error.message : String(error)}`
794
+ );
795
+ }
796
+ }
797
+ );
798
+ server.registerTool(
799
+ track(mcpToolName(ctx.productName, "matters_update")),
800
+ {
801
+ title: "Update Matter",
802
+ description: "Update a matter. At least one mutable field must be provided. Use `matters_close` to close a matter.",
803
+ inputSchema: {
804
+ id: zod.z.string().describe("Matter ID"),
805
+ title: zod.z.string().optional().describe("Updated title"),
806
+ description: zod.z.string().optional().describe("Updated description"),
807
+ engagementId: zod.z.string().optional().describe("Updated parent engagement ID"),
808
+ practiceAreaId: zod.z.string().optional().describe("Updated practice-area ID"),
809
+ leadPractitionerId: zod.z.string().optional().describe("Updated lead practitioner user ID"),
810
+ priority: MatterPriorityEnum.optional().describe("Updated priority"),
811
+ isPrivileged: zod.z.boolean().optional().describe("Updated privileged flag"),
812
+ dueDate: zod.z.string().optional().describe("Updated due date (ISO-8601 DateTime)"),
813
+ tags: zod.z.array(zod.z.string()).optional().describe("Updated tag labels")
814
+ }
815
+ },
816
+ async ({
817
+ id,
818
+ title,
819
+ description,
820
+ engagementId,
821
+ practiceAreaId,
822
+ leadPractitionerId,
823
+ priority,
824
+ isPrivileged,
825
+ dueDate,
826
+ tags
827
+ }) => {
828
+ try {
829
+ const input = {};
830
+ if (title !== void 0) input.title = title;
831
+ if (description !== void 0) input.description = description;
832
+ if (engagementId !== void 0) input.engagementId = engagementId;
833
+ if (practiceAreaId !== void 0) input.practiceAreaId = practiceAreaId;
834
+ if (leadPractitionerId !== void 0) input.leadPractitionerId = leadPractitionerId;
835
+ if (priority !== void 0) input.priority = priority;
836
+ if (isPrivileged !== void 0) input.isPrivileged = isPrivileged;
837
+ if (dueDate !== void 0) input.dueDate = dueDate;
838
+ if (tags !== void 0) input.tags = tags;
839
+ if (Object.keys(input).length === 0) {
840
+ return errorResult(
841
+ "No updates specified \u2014 provide at least one of title/description/engagementId/practiceAreaId/leadPractitionerId/priority/isPrivileged/dueDate/tags"
842
+ );
843
+ }
844
+ const data = await ctx.gql.workspace.request(UPDATE_MATTER, { id, input });
845
+ return jsonResult(data.pswUpdateMatter);
846
+ } catch (error) {
847
+ ctx.logger.error("proserviceworld matters_update failed", { error: String(error) });
848
+ return errorResult(
849
+ `Failed to update matter: ${error instanceof Error ? error.message : String(error)}`
850
+ );
851
+ }
852
+ }
853
+ );
854
+ server.registerTool(
855
+ track(mcpToolName(ctx.productName, "matters_close")),
856
+ {
857
+ title: "Close Matter",
858
+ description: "Close a matter by ID. Returns the closed matter (status=CLOSED, closedAt set).",
859
+ inputSchema: { id: zod.z.string().describe("Matter ID") }
860
+ },
861
+ async ({ id }) => {
862
+ try {
863
+ const data = await ctx.gql.workspace.request(CLOSE_MATTER, { id });
864
+ return jsonResult(data.pswCloseMatter);
865
+ } catch (error) {
866
+ ctx.logger.error("proserviceworld matters_close failed", { error: String(error) });
867
+ return errorResult(
868
+ `Failed to close matter: ${error instanceof Error ? error.message : String(error)}`
869
+ );
870
+ }
871
+ }
872
+ );
873
+ }
874
+ function registerInvoiceTools(server, ctx, track) {
875
+ server.registerTool(
876
+ track(mcpToolName(ctx.productName, "invoices_list")),
877
+ {
878
+ title: "List Invoices",
879
+ description: "List invoices, optionally filtered by client and/or status, with limit/offset pagination.",
880
+ inputSchema: {
881
+ clientId: zod.z.string().optional().describe("Filter by client ID"),
882
+ status: InvoiceStatusEnum.optional().describe("Filter by invoice status"),
883
+ limit: zod.z.number().int().optional().describe("Page size (default 50)"),
884
+ offset: zod.z.number().int().optional().describe("Pagination offset (default 0)")
885
+ }
886
+ },
887
+ async ({ clientId, status, limit, offset }) => {
888
+ try {
889
+ const data = await ctx.gql.workspace.request(LIST_INVOICES, {
890
+ clientId,
891
+ status,
892
+ pagination: { limit: limit ?? 50, offset: offset ?? 0 }
893
+ });
894
+ return jsonResult(data.pswListInvoices);
895
+ } catch (error) {
896
+ ctx.logger.error("proserviceworld invoices_list failed", { error: String(error) });
897
+ return errorResult(
898
+ `Failed to list invoices: ${error instanceof Error ? error.message : String(error)}`
899
+ );
900
+ }
901
+ }
902
+ );
903
+ server.registerTool(
904
+ track(mcpToolName(ctx.productName, "invoices_create")),
905
+ {
906
+ title: "Create Invoice",
907
+ description: "Raise a new invoice for a client. `clientId` and `number` are required; amounts and line items are optional JSON/numbers.",
908
+ inputSchema: {
909
+ clientId: zod.z.string().min(1).describe("Billed client ID"),
910
+ number: zod.z.string().min(1).describe("Invoice number (unique within the workspace)"),
911
+ engagementId: zod.z.string().optional().describe("Related engagement ID"),
912
+ issueDate: zod.z.string().optional().describe("Issue date (ISO-8601 DateTime)"),
913
+ dueDate: zod.z.string().optional().describe("Due date (ISO-8601 DateTime)"),
914
+ subtotal: zod.z.number().optional().describe("Subtotal amount"),
915
+ taxAmount: zod.z.number().optional().describe("Tax amount"),
916
+ total: zod.z.number().optional().describe("Total amount"),
917
+ currency: zod.z.string().optional().describe('ISO currency code (e.g. "USD")'),
918
+ lineItems: LineItems.optional().describe("Line items JSON (array of line-item objects)"),
919
+ notes: zod.z.string().optional().describe("Invoice notes")
920
+ }
921
+ },
922
+ async ({
923
+ clientId,
924
+ number,
925
+ engagementId,
926
+ issueDate,
927
+ dueDate,
928
+ subtotal,
929
+ taxAmount,
930
+ total,
931
+ currency,
932
+ lineItems,
933
+ notes
934
+ }) => {
935
+ try {
936
+ const input = { clientId, number };
937
+ if (engagementId !== void 0) input.engagementId = engagementId;
938
+ if (issueDate !== void 0) input.issueDate = issueDate;
939
+ if (dueDate !== void 0) input.dueDate = dueDate;
940
+ if (subtotal !== void 0) input.subtotal = subtotal;
941
+ if (taxAmount !== void 0) input.taxAmount = taxAmount;
942
+ if (total !== void 0) input.total = total;
943
+ if (currency !== void 0) input.currency = currency;
944
+ if (lineItems !== void 0) input.lineItems = lineItems;
945
+ if (notes !== void 0) input.notes = notes;
946
+ const data = await ctx.gql.workspace.request(CREATE_INVOICE, { input });
947
+ return jsonResult(data.pswCreateInvoice);
948
+ } catch (error) {
949
+ ctx.logger.error("proserviceworld invoices_create failed", { error: String(error) });
950
+ return errorResult(
951
+ `Failed to create invoice: ${error instanceof Error ? error.message : String(error)}`
952
+ );
953
+ }
954
+ }
955
+ );
956
+ server.registerTool(
957
+ track(mcpToolName(ctx.productName, "invoices_mark_paid")),
958
+ {
959
+ title: "Mark Invoice Paid",
960
+ description: "Mark an invoice as paid by ID (sets status=PAID, paidAt set).",
961
+ inputSchema: { id: zod.z.string().describe("Invoice ID") }
962
+ },
963
+ async ({ id }) => {
964
+ try {
965
+ const data = await ctx.gql.workspace.request(MARK_INVOICE_PAID, { id });
966
+ return jsonResult(data.pswMarkInvoicePaid);
967
+ } catch (error) {
968
+ ctx.logger.error("proserviceworld invoices_mark_paid failed", { error: String(error) });
969
+ return errorResult(
970
+ `Failed to mark invoice paid: ${error instanceof Error ? error.message : String(error)}`
971
+ );
972
+ }
973
+ }
974
+ );
975
+ }
976
+ function registerTimeTools(server, ctx, track) {
977
+ server.registerTool(
978
+ track(mcpToolName(ctx.productName, "time_log")),
979
+ {
980
+ title: "Log Time Entry",
981
+ description: "Record a time entry against a matter and/or engagement. `date` and `minutes` are required; `billable` defaults to true.",
982
+ inputSchema: {
983
+ date: zod.z.string().describe("Work date (ISO-8601 DateTime)"),
984
+ minutes: zod.z.number().int().describe("Duration in minutes"),
985
+ matterId: zod.z.string().optional().describe("Matter the time is logged against"),
986
+ engagementId: zod.z.string().optional().describe("Engagement the time is logged against"),
987
+ rate: zod.z.number().optional().describe("Hourly rate applied"),
988
+ billable: zod.z.boolean().optional().describe("Whether the time is billable (default true)"),
989
+ narrative: zod.z.string().optional().describe("Narrative / description of the work")
990
+ }
991
+ },
992
+ async ({ date, minutes, matterId, engagementId, rate, billable, narrative }) => {
993
+ try {
994
+ const input = { date, minutes };
995
+ if (matterId !== void 0) input.matterId = matterId;
996
+ if (engagementId !== void 0) input.engagementId = engagementId;
997
+ if (rate !== void 0) input.rate = rate;
998
+ if (billable !== void 0) input.billable = billable;
999
+ if (narrative !== void 0) input.narrative = narrative;
1000
+ const data = await ctx.gql.workspace.request(LOG_TIME_ENTRY, { input });
1001
+ return jsonResult(data.pswLogTimeEntry);
1002
+ } catch (error) {
1003
+ ctx.logger.error("proserviceworld time_log failed", { error: String(error) });
1004
+ return errorResult(
1005
+ `Failed to log time entry: ${error instanceof Error ? error.message : String(error)}`
1006
+ );
1007
+ }
1008
+ }
1009
+ );
1010
+ }
1011
+ function registerComplianceTools(server, ctx, track) {
1012
+ server.registerTool(
1013
+ track(mcpToolName(ctx.productName, "compliance_list")),
1014
+ {
1015
+ title: "List Compliance Checks",
1016
+ description: "List compliance checks (licensing, independence, data retention, legal hold, etc.), optionally filtered by status and/or subject.",
1017
+ inputSchema: {
1018
+ status: ComplianceStatusEnum.optional().describe("Filter by compliance status"),
1019
+ subjectId: zod.z.string().optional().describe("Filter by subject ID"),
1020
+ subjectType: zod.z.string().optional().describe('Filter by subject type (e.g. "client", "engagement", "matter")'),
1021
+ limit: zod.z.number().int().optional().describe("Page size (default 50)"),
1022
+ offset: zod.z.number().int().optional().describe("Pagination offset (default 0)")
1023
+ }
1024
+ },
1025
+ async ({ status, subjectId, subjectType, limit, offset }) => {
1026
+ try {
1027
+ const data = await ctx.gql.workspace.request(LIST_COMPLIANCE_CHECKS, {
1028
+ status,
1029
+ subjectId,
1030
+ subjectType,
1031
+ pagination: { limit: limit ?? 50, offset: offset ?? 0 }
1032
+ });
1033
+ return jsonResult(data.pswListComplianceChecks);
1034
+ } catch (error) {
1035
+ ctx.logger.error("proserviceworld compliance_list failed", { error: String(error) });
1036
+ return errorResult(
1037
+ `Failed to list compliance checks: ${error instanceof Error ? error.message : String(error)}`
1038
+ );
1039
+ }
1040
+ }
1041
+ );
1042
+ server.registerTool(
1043
+ track(mcpToolName(ctx.productName, "conflicts_list")),
1044
+ {
1045
+ title: "List Conflict Checks",
1046
+ description: "List conflict-of-interest checks, optionally filtered by client, with limit/offset pagination.",
1047
+ inputSchema: {
1048
+ clientId: zod.z.string().optional().describe("Filter by client ID"),
1049
+ limit: zod.z.number().int().optional().describe("Page size (default 50)"),
1050
+ offset: zod.z.number().int().optional().describe("Pagination offset (default 0)")
1051
+ }
1052
+ },
1053
+ async ({ clientId, limit, offset }) => {
1054
+ try {
1055
+ const data = await ctx.gql.workspace.request(LIST_CONFLICT_CHECKS, {
1056
+ clientId,
1057
+ pagination: { limit: limit ?? 50, offset: offset ?? 0 }
1058
+ });
1059
+ return jsonResult(data.pswListConflictChecks);
1060
+ } catch (error) {
1061
+ ctx.logger.error("proserviceworld conflicts_list failed", { error: String(error) });
1062
+ return errorResult(
1063
+ `Failed to list conflict checks: ${error instanceof Error ? error.message : String(error)}`
1064
+ );
1065
+ }
1066
+ }
1067
+ );
1068
+ }
1069
+ function registerPracticeTools(server, ctx, track) {
1070
+ server.registerTool(
1071
+ track(mcpToolName(ctx.productName, "practice_areas_list")),
1072
+ {
1073
+ title: "List Practice Areas",
1074
+ description: "List the firm's practice areas (e.g. tax, audit, litigation, advisory) in the bound workspace.",
1075
+ inputSchema: {
1076
+ limit: zod.z.number().int().optional().describe("Page size (default 50)"),
1077
+ offset: zod.z.number().int().optional().describe("Pagination offset (default 0)")
1078
+ }
1079
+ },
1080
+ async ({ limit, offset }) => {
1081
+ try {
1082
+ const data = await ctx.gql.workspace.request(LIST_PRACTICE_AREAS, {
1083
+ pagination: { limit: limit ?? 50, offset: offset ?? 0 }
1084
+ });
1085
+ return jsonResult(data.pswListPracticeAreas);
1086
+ } catch (error) {
1087
+ ctx.logger.error("proserviceworld practice_areas_list failed", { error: String(error) });
1088
+ return errorResult(
1089
+ `Failed to list practice areas: ${error instanceof Error ? error.message : String(error)}`
1090
+ );
1091
+ }
1092
+ }
1093
+ );
1094
+ }
1095
+
1096
+ // src/modules/proserviceworld/tools/health.ts
1097
+ function registerHealthTools(server, ctx, track) {
1098
+ server.registerTool(
1099
+ track(mcpToolName(ctx.productName, "health")),
1100
+ {
1101
+ title: "ProServiceWorld Health Check",
1102
+ description: "Public health probe for the ProServiceWorld subgraph (pswHealth). Requires no authentication.",
1103
+ inputSchema: {}
1104
+ },
1105
+ async () => {
1106
+ try {
1107
+ const data = await ctx.gql.workspace.request(HEALTH, {});
1108
+ return jsonResult({ ok: true, status: data.pswHealth });
1109
+ } catch (error) {
1110
+ ctx.logger.error("proserviceworld health failed", { error: String(error) });
1111
+ return errorResult(
1112
+ `ProServiceWorld health check failed: ${error instanceof Error ? error.message : String(error)}`
1113
+ );
1114
+ }
1115
+ }
1116
+ );
1117
+ }
1118
+
1119
+ // src/modules/proserviceworld/resources.ts
1120
+ function registerProserviceworldResources(server, ctx, track) {
1121
+ const schemaUri = track(mcpResourceUri(ctx.productName, "schema"));
1122
+ const clientsUri = track(mcpResourceUri(ctx.productName, "clients"));
1123
+ const mattersUri = track(mcpResourceUri(ctx.productName, "matters"));
1124
+ const practiceAreasUri = track(mcpResourceUri(ctx.productName, "practice-areas"));
1125
+ server.registerResource(
1126
+ "proserviceworld-schema",
1127
+ schemaUri,
1128
+ {
1129
+ title: "ProServiceWorld Domain Schema",
1130
+ description: "Overview of the ProServiceWorld professional-services domain \u2014 entities, enums, and the per-entity tool surface.",
1131
+ mimeType: "text/plain"
1132
+ },
1133
+ async () => {
1134
+ ctx.logger.debug(`Resource ${schemaUri} requested`);
1135
+ const schema = `# ProServiceWorld Domain (Overview)
1136
+
1137
+ The Professional-Services Operating System \u2014 system of record for clients,
1138
+ engagements, matters, time, billing, and compliance across professional-services
1139
+ firms (finance, insurance, legal, accounting, tax, advisory, consulting).
1140
+
1141
+ ## Entities
1142
+ - PswClient \u2014 a client/relationship (has engagements + matters)
1143
+ - PswEngagement \u2014 proposal\u2192delivery engagement (has milestones + deliverables)
1144
+ - PswMatter \u2014 a unit of professional work for a client
1145
+ - PswInvoice \u2014 a billing document
1146
+ - PswTimeEntry \u2014 logged billable/non-billable time
1147
+ - PswComplianceCheck \u2014 a governance/compliance check
1148
+ - PswConflictCheck \u2014 a conflict-of-interest check
1149
+ - PswPracticeArea \u2014 the firm's practice-area catalog
1150
+ - PswMilestone / PswDeliverable / PswTeamAssignment \u2014 engagement sub-records
1151
+
1152
+ ## Enums
1153
+ - PswClientStatus: ACTIVE, ARCHIVED, INACTIVE, PROSPECT
1154
+ - PswClientType: INDIVIDUAL, ORGANIZATION
1155
+ - PswEngagementStatus: PROPOSAL, ACCEPTED, IN_PROGRESS, REVIEW, DELIVERED, INVOICED, CLOSED, CANCELLED
1156
+ - PswFeeArrangement: FIXED, HOURLY, RETAINER, VALUE_BASED
1157
+ - PswMatterStatus: OPEN, ON_HOLD, CLOSED, ARCHIVED
1158
+ - PswMatterPriority: LOW, MEDIUM, HIGH, CRITICAL
1159
+ - PswInvoiceStatus: DRAFT, SENT, PAID, OVERDUE, CANCELLED
1160
+ - PswComplianceStatus: PENDING, IN_REVIEW, PASSED, FAILED, WAIVED
1161
+
1162
+ ## Tool groups
1163
+ clients, engagements, matters, invoices, time, compliance (compliance + conflict
1164
+ checks), practice (practice areas), health. All run against the WORKSPACE gateway
1165
+ (workspace-scoped) except the public health probe.
1166
+ `;
1167
+ return {
1168
+ contents: [{ uri: schemaUri, text: schema, mimeType: "text/plain" }]
1169
+ };
1170
+ }
1171
+ );
1172
+ server.registerResource(
1173
+ "proserviceworld-clients",
1174
+ clientsUri,
1175
+ {
1176
+ title: "ProServiceWorld Client Directory",
1177
+ description: "The first 50 clients in the bound workspace.",
1178
+ mimeType: "application/json"
1179
+ },
1180
+ async () => {
1181
+ ctx.logger.debug(`Resource ${clientsUri} requested`);
1182
+ try {
1183
+ const data = await ctx.gql.workspace.request(LIST_CLIENTS, {
1184
+ pagination: { limit: 50, offset: 0 }
1185
+ });
1186
+ return {
1187
+ contents: [
1188
+ {
1189
+ uri: clientsUri,
1190
+ text: JSON.stringify(data.pswListClients, null, 2),
1191
+ mimeType: "application/json"
1192
+ }
1193
+ ]
1194
+ };
1195
+ } catch (error) {
1196
+ return {
1197
+ contents: [
1198
+ {
1199
+ uri: clientsUri,
1200
+ text: JSON.stringify({
1201
+ error: error instanceof Error ? error.message : String(error)
1202
+ }),
1203
+ mimeType: "application/json"
1204
+ }
1205
+ ]
1206
+ };
1207
+ }
1208
+ }
1209
+ );
1210
+ server.registerResource(
1211
+ "proserviceworld-matters",
1212
+ mattersUri,
1213
+ {
1214
+ title: "ProServiceWorld Matters",
1215
+ description: "The first 50 matters in the bound workspace.",
1216
+ mimeType: "application/json"
1217
+ },
1218
+ async () => {
1219
+ ctx.logger.debug(`Resource ${mattersUri} requested`);
1220
+ try {
1221
+ const data = await ctx.gql.workspace.request(LIST_MATTERS, {
1222
+ pagination: { limit: 50, offset: 0 }
1223
+ });
1224
+ return {
1225
+ contents: [
1226
+ {
1227
+ uri: mattersUri,
1228
+ text: JSON.stringify(data.pswListMatters, null, 2),
1229
+ mimeType: "application/json"
1230
+ }
1231
+ ]
1232
+ };
1233
+ } catch (error) {
1234
+ return {
1235
+ contents: [
1236
+ {
1237
+ uri: mattersUri,
1238
+ text: JSON.stringify({
1239
+ error: error instanceof Error ? error.message : String(error)
1240
+ }),
1241
+ mimeType: "application/json"
1242
+ }
1243
+ ]
1244
+ };
1245
+ }
1246
+ }
1247
+ );
1248
+ server.registerResource(
1249
+ "proserviceworld-practice-areas",
1250
+ practiceAreasUri,
1251
+ {
1252
+ title: "ProServiceWorld Practice Areas",
1253
+ description: "The firm's practice-area catalog in the bound workspace.",
1254
+ mimeType: "application/json"
1255
+ },
1256
+ async () => {
1257
+ ctx.logger.debug(`Resource ${practiceAreasUri} requested`);
1258
+ try {
1259
+ const data = await ctx.gql.workspace.request(
1260
+ LIST_PRACTICE_AREAS,
1261
+ { pagination: { limit: 50, offset: 0 } }
1262
+ );
1263
+ return {
1264
+ contents: [
1265
+ {
1266
+ uri: practiceAreasUri,
1267
+ text: JSON.stringify(data.pswListPracticeAreas, null, 2),
1268
+ mimeType: "application/json"
1269
+ }
1270
+ ]
1271
+ };
1272
+ } catch (error) {
1273
+ return {
1274
+ contents: [
1275
+ {
1276
+ uri: practiceAreasUri,
1277
+ text: JSON.stringify({
1278
+ error: error instanceof Error ? error.message : String(error)
1279
+ }),
1280
+ mimeType: "application/json"
1281
+ }
1282
+ ]
1283
+ };
1284
+ }
1285
+ }
1286
+ );
1287
+ }
1288
+ function registerProserviceworldPrompts(server, ctx, track) {
1289
+ const p = ctx.productName;
1290
+ const onboardingName = track(mcpPromptName(p, "client-onboarding"));
1291
+ const deliveryName = track(mcpPromptName(p, "engagement-delivery"));
1292
+ server.registerPrompt(
1293
+ onboardingName,
1294
+ {
1295
+ title: "Client Onboarding",
1296
+ description: "Guide for onboarding a new ProServiceWorld client end to end: create the client, open an engagement, run a conflict check, and open the first matter.",
1297
+ argsSchema: {
1298
+ name: zod.z.string().optional().describe("Client / organization name"),
1299
+ industry: zod.z.string().optional().describe("Client industry / sector")
1300
+ }
1301
+ },
1302
+ ({ name, industry }) => {
1303
+ ctx.logger.debug(`Prompt ${onboardingName} called`, { name, industry });
1304
+ const nameNote = name ? `Use the client name "${name}".` : "Ask the user for the client name.";
1305
+ const industryNote = industry ? `Industry is ${industry}.` : "Ask the user for the industry.";
1306
+ return {
1307
+ messages: [
1308
+ {
1309
+ role: "user",
1310
+ content: {
1311
+ type: "text",
1312
+ text: `Help me onboard a new ProServiceWorld client.
1313
+
1314
+ 1. **Create Client**: Use ${p}_clients_create. ${nameNote} ${industryNote} Set type (INDIVIDUAL/ORGANIZATION) and capture contact details.
1315
+ 2. **Conflict Check**: Use ${p}_conflicts_list filtered by the new clientId to confirm there are no unresolved conflicts of interest before engaging.
1316
+ 3. **Pick Practice Area**: Use ${p}_practice_areas_list and choose the relevant practiceAreaId.
1317
+ 4. **Open Engagement**: Use ${p}_engagements_create with the clientId, a name, a feeArrangement (FIXED/HOURLY/RETAINER/VALUE_BASED), and the practiceAreaId.
1318
+ 5. **Open First Matter**: Use ${p}_matters_create for the initial unit of work (clientId, title, priority, optional engagementId).
1319
+ 6. **Activate**: Use ${p}_clients_update with status=ACTIVE once engaged.
1320
+
1321
+ Show results at each step and confirm before activation.`
1322
+ }
1323
+ }
1324
+ ]
1325
+ };
1326
+ }
1327
+ );
1328
+ server.registerPrompt(
1329
+ deliveryName,
1330
+ {
1331
+ title: "Engagement Delivery",
1332
+ description: "Workflow to drive an engagement through delivery: advance its lifecycle, log time, check compliance, raise an invoice, and mark it paid.",
1333
+ argsSchema: {
1334
+ engagementId: zod.z.string().optional().describe("Engagement ID to operate")
1335
+ }
1336
+ },
1337
+ ({ engagementId }) => {
1338
+ ctx.logger.debug(`Prompt ${deliveryName} called`, { engagementId });
1339
+ const engagementNote = engagementId ? `Operate engagement "${engagementId}".` : `First list engagements with ${p}_engagements_list and pick one.`;
1340
+ return {
1341
+ messages: [
1342
+ {
1343
+ role: "user",
1344
+ content: {
1345
+ type: "text",
1346
+ text: `Run ProServiceWorld engagement delivery. ${engagementNote}
1347
+
1348
+ 1. **Review**: Use ${p}_engagements_get to inspect milestones and deliverables.
1349
+ 2. **Advance**: Use ${p}_engagements_transition to move the engagement (e.g. ACCEPTED \u2192 IN_PROGRESS \u2192 REVIEW \u2192 DELIVERED).
1350
+ 3. **Log Time**: Use ${p}_time_log to record billable time against the engagement and/or its matters.
1351
+ 4. **Compliance**: Use ${p}_compliance_list (subjectId=engagementId, subjectType="engagement") to confirm no PENDING/IN_REVIEW checks block invoicing.
1352
+ 5. **Invoice**: When DELIVERED, use ${p}_invoices_create (clientId, number, amounts/line items), then ${p}_engagements_transition to INVOICED.
1353
+ 6. **Collect**: Once paid, use ${p}_invoices_mark_paid and ${p}_engagements_transition to CLOSED.
1354
+
1355
+ Summarize what needs attention after each step.`
1356
+ }
1357
+ }
1358
+ ]
1359
+ };
1360
+ }
1361
+ );
1362
+ }
1363
+
1364
+ // src/modules/proserviceworld/index.ts
1365
+ var proserviceworldModule = {
1366
+ id: "proserviceworld",
1367
+ version: "0.0.1",
1368
+ displayName: "ProServiceWorld",
1369
+ dependsOn: ["auth"],
1370
+ capabilities: { tools: {}, resources: {}, prompts: {} },
1371
+ register(server, ctx) {
1372
+ const tools = [];
1373
+ const resources = [];
1374
+ const prompts = [];
1375
+ const trackTool = (n) => {
1376
+ tools.push(n);
1377
+ return n;
1378
+ };
1379
+ const trackResource = (uri) => {
1380
+ resources.push(uri);
1381
+ return uri;
1382
+ };
1383
+ const trackPrompt = (n) => {
1384
+ prompts.push(n);
1385
+ return n;
1386
+ };
1387
+ registerClientTools(server, ctx, trackTool);
1388
+ registerEngagementTools(server, ctx, trackTool);
1389
+ registerMatterTools(server, ctx, trackTool);
1390
+ registerInvoiceTools(server, ctx, trackTool);
1391
+ registerTimeTools(server, ctx, trackTool);
1392
+ registerComplianceTools(server, ctx, trackTool);
1393
+ registerPracticeTools(server, ctx, trackTool);
1394
+ registerHealthTools(server, ctx, trackTool);
1395
+ registerProserviceworldResources(server, ctx, trackResource);
1396
+ registerProserviceworldPrompts(server, ctx, trackPrompt);
1397
+ return { tools, resources, prompts };
1398
+ }
1399
+ };
1400
+ var proserviceworld_default = proserviceworldModule;
1401
+
1402
+ module.exports = proserviceworld_default;
1403
+ //# sourceMappingURL=index.cjs.map
1404
+ //# sourceMappingURL=index.cjs.map