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