@sodiumhq/mcp-pm 0.1.0-beta.6355 → 0.1.0-beta.6359
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/http.js +50 -1
- package/dist/index.js +1 -1
- package/dist/{src--vw5xenq.mjs → src-B3pnHPhY.mjs} +293 -19
- package/package.json +1 -1
package/dist/http.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { n as buildServer, t as buildCatalogue } from "./src-B3pnHPhY.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { SUPPORTED_PROTOCOL_VERSIONS, createMcpHandler } from "@modelcontextprotocol/server";
|
|
5
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -270,6 +270,47 @@ const mcpHandler = createMcpHandler((ctx) => {
|
|
|
270
270
|
console.error(`[sodium-pm-mcp-http] handler: ${error.message}`);
|
|
271
271
|
} });
|
|
272
272
|
const serveMcp = toNodeHandler(mcpHandler);
|
|
273
|
+
let catalogueJson;
|
|
274
|
+
async function listRegisteredTools() {
|
|
275
|
+
const state = {
|
|
276
|
+
apiToken: "catalogue",
|
|
277
|
+
tenant: void 0,
|
|
278
|
+
writesAllowed: true,
|
|
279
|
+
wantsInstructions: false,
|
|
280
|
+
onToolCall: () => {}
|
|
281
|
+
};
|
|
282
|
+
const request = new Request("http://internal/", {
|
|
283
|
+
method: "POST",
|
|
284
|
+
headers: {
|
|
285
|
+
"Content-Type": "application/json",
|
|
286
|
+
Accept: "application/json, text/event-stream",
|
|
287
|
+
"MCP-Protocol-Version": "2025-06-18"
|
|
288
|
+
},
|
|
289
|
+
body: JSON.stringify({
|
|
290
|
+
jsonrpc: "2.0",
|
|
291
|
+
id: 1,
|
|
292
|
+
method: "tools/list",
|
|
293
|
+
params: {}
|
|
294
|
+
})
|
|
295
|
+
});
|
|
296
|
+
const text = await (await mcpHandler.fetch(request, { authInfo: {
|
|
297
|
+
token: "catalogue",
|
|
298
|
+
clientId: "catalogue",
|
|
299
|
+
scopes: [],
|
|
300
|
+
extra: { sodium: state }
|
|
301
|
+
} })).text();
|
|
302
|
+
const payload = text.trimStart().startsWith("{") ? text : text.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).trim()).pop() ?? "";
|
|
303
|
+
const tools = JSON.parse(payload).result?.tools;
|
|
304
|
+
if (!tools) throw new Error("tools/list returned no tools.");
|
|
305
|
+
return tools;
|
|
306
|
+
}
|
|
307
|
+
function getCatalogueJson() {
|
|
308
|
+
catalogueJson ??= listRegisteredTools().then((tools) => JSON.stringify(buildCatalogue(tools))).catch((error) => {
|
|
309
|
+
catalogueJson = void 0;
|
|
310
|
+
throw error;
|
|
311
|
+
});
|
|
312
|
+
return catalogueJson;
|
|
313
|
+
}
|
|
273
314
|
function jsonRpcMethod(body) {
|
|
274
315
|
if (typeof body === "object" && body !== null && "method" in body) return String(body.method);
|
|
275
316
|
}
|
|
@@ -393,6 +434,14 @@ async function handle(req, res) {
|
|
|
393
434
|
}));
|
|
394
435
|
return;
|
|
395
436
|
}
|
|
437
|
+
if (req.method === "GET" && url.pathname === "/tools.json") {
|
|
438
|
+
const body = await getCatalogueJson();
|
|
439
|
+
res.writeHead(200, {
|
|
440
|
+
"Content-Type": "application/json",
|
|
441
|
+
"Cache-Control": "public, max-age=300"
|
|
442
|
+
}).end(body);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
396
445
|
if (AUTH && req.method === "GET" && url.pathname.startsWith("/.well-known/oauth-protected-resource")) {
|
|
397
446
|
res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify(protectedResourceMetadata(AUTH)));
|
|
398
447
|
return;
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { n as buildServer } from "./src-B3pnHPhY.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
5
5
|
import { parseArgs } from "node:util";
|
|
@@ -4342,7 +4342,7 @@ const DEFAULT_CATCH_UP_FEE_LABEL = "Catch-up fee";
|
|
|
4342
4342
|
function labelInSentence(label) {
|
|
4343
4343
|
return /^[A-Z][A-Z]/.test(label) ? label : label.charAt(0).toLowerCase() + label.slice(1);
|
|
4344
4344
|
}
|
|
4345
|
-
const GetEngagementSummaryInputSchema = { code: z.string().min(1, "Engagement code is required").describe("REQUIRED: The engagement code (identifier, e.g. 'ENG-123'). This is the unique code of a specific engagement — NOT a description like 'the unsent proposal' or 'ACME's proposal'.
|
|
4345
|
+
const GetEngagementSummaryInputSchema = { code: z.string().min(1, "Engagement code is required").describe("REQUIRED: The engagement code (identifier, e.g. 'ENG-123'). This is the unique code of a specific engagement — NOT a description like 'the unsent proposal' or 'ACME's proposal'. list_proposals and list_engagement_letters return engagement codes and filter by status, client and date.") };
|
|
4346
4346
|
async function handleGetEngagementSummary(api, { code }) {
|
|
4347
4347
|
const [practice, [engagementResult, emailsResult, settingsResult]] = await Promise.all([api.getPracticeLocale(), Promise.allSettled([
|
|
4348
4348
|
api.getEngagement(code),
|
|
@@ -4943,12 +4943,12 @@ const stepTypeEnum$1 = z.enum([
|
|
|
4943
4943
|
"DocumentUpload"
|
|
4944
4944
|
]);
|
|
4945
4945
|
const ListTasksInputSchema = {
|
|
4946
|
-
user: z.array(z.string()).optional().describe("Filter by assigned user codes. For 'my tasks'
|
|
4946
|
+
user: z.array(z.string()).optional().describe("Filter by assigned user codes. For 'my tasks', this is the signed-in user's practice user code, which whoami shows. For another team member's tasks, pass their code. Omit to see tasks across all users (useful for practice managers)."),
|
|
4947
4947
|
client: z.array(z.string()).optional().describe("Filter by client codes — tasks belonging to these specific clients only."),
|
|
4948
|
-
status: z.array(statusEnum$14).optional().describe("Filter by task status. Typical: ['NotStarted', 'InProgress'] to exclude completed/skipped. Leave empty for all statuses.
|
|
4948
|
+
status: z.array(statusEnum$14).optional().describe("Filter by task status. Typical: ['NotStarted', 'InProgress'] to exclude completed/skipped. Leave empty for all statuses. If the query includes NotStarted (the default for new tasks), it also needs one of: dateRange, isOverdue=true, or restrict status to non-NotStarted values only — otherwise the API rejects the call to prevent unbounded queries."),
|
|
4949
4949
|
isOverdue: z.boolean().optional().describe("Set true to return only overdue tasks (DueDate before today, not completed/skipped). When true, no date range is required. Useful for sidestepping the NotStarted + date-range requirement."),
|
|
4950
4950
|
dateRange: dateRangeEnum$2.optional().describe("Preset date range. Use 'Today' for today's tasks, 'Next7Days' for 'due this week', 'Last30Days' for recent activity. If 'CustomDateRange', also provide startDate and/or endDate. REQUIRED when querying NotStarted tasks unless you pass isOverdue=true."),
|
|
4951
|
-
startDate: z.string().optional().describe("Start of custom date range (YYYY-MM-DD). Used when dateRange='CustomDateRange'.
|
|
4951
|
+
startDate: z.string().optional().describe("Start of custom date range (YYYY-MM-DD). Used when dateRange='CustomDateRange'. Both startDate and endDate with a narrow window perform best — broad ranges strain the API. If startDate > endDate the API swaps them silently. Maximum allowed total range is 2 years."),
|
|
4952
4952
|
endDate: z.string().optional().describe("End of custom date range (YYYY-MM-DD). Used when dateRange='CustomDateRange'. Pair with startDate to define a narrow window — avoid open-ended ranges."),
|
|
4953
4953
|
dateBasis: dateBasisEnum$1.optional().describe("Which date field to apply the date range filter to. Use 'DueDate' for 'what's due when' questions; 'StartDate' (default) for 'what's been worked on'."),
|
|
4954
4954
|
category: z.array(z.string()).optional().describe("Filter by task category codes (e.g. tax, audit, compliance categories)."),
|
|
@@ -4963,7 +4963,7 @@ const ListTasksInputSchema = {
|
|
|
4963
4963
|
currentStep: z.array(z.string()).optional().describe("Filter to tasks whose CURRENT workflow step has one of these exact names (as shown in get_workflow). Use for 'everything sitting at Partner review'."),
|
|
4964
4964
|
isUnassigned: z.boolean().optional().describe("Include tasks with no assignee. Alone: only unassigned tasks. Combined with `user`: unassigned OR assigned to those users. Use for 'what needs an owner'."),
|
|
4965
4965
|
isAutomationDisabled: z.boolean().optional().describe("true = only tasks with automation switched off; false = only those with it on. Task previews are excluded either way. Rarely needed."),
|
|
4966
|
-
search: z.string().min(3).optional().describe("Free-text search across task name, task code and primary client name (minimum 3 characters). Projected tasks are excluded when set.
|
|
4966
|
+
search: z.string().min(3).optional().describe("Free-text search across task name, task code and primary client name (minimum 3 characters). Projected tasks are excluded when set. `client` is more precise when the client code is known."),
|
|
4967
4967
|
savedFilter: z.string().optional().describe("Apply a user-saved filter by its code. Other filter parameters override fields from the saved filter."),
|
|
4968
4968
|
includeProjected: z.boolean().optional().describe("Set true to include projected (virtual, not-yet-materialised) tasks in the results. Projected tasks are always NotStarted. Useful for 'what's coming up' queries."),
|
|
4969
4969
|
includeWorkflowSteps: z.boolean().optional().describe("Set true for Agenda Mode: returns both tasks AND their workflow steps as individual rows. Each step row has its parent task's properties plus step-specific details. Useful for 'what's the next action on X' or when workflow step granularity matters."),
|
|
@@ -5376,7 +5376,7 @@ const CreateClientInputSchema = {
|
|
|
5376
5376
|
email: z.string().nullable().optional().describe("Client email address."),
|
|
5377
5377
|
telephone: z.string().nullable().optional().describe("Client telephone number."),
|
|
5378
5378
|
internalReference: z.string().nullable().optional().describe("Internal reference number/code for the client."),
|
|
5379
|
-
managerCode: z.string().nullable().optional().describe("Code of the user who will manage this client.
|
|
5379
|
+
managerCode: z.string().nullable().optional().describe("Code of the user who will manage this client. Codes are listed by list_users and shown in the team roster."),
|
|
5380
5380
|
partnerCode: z.string().nullable().optional().describe("Code of the partner user for this client."),
|
|
5381
5381
|
associateCode: z.string().nullable().optional().describe("Code of the associate user for this client."),
|
|
5382
5382
|
teamCode: z.string().nullable().optional().describe("Code of the team to assign to this client.")
|
|
@@ -5569,7 +5569,7 @@ function reportWindowFields(windowNoun) {
|
|
|
5569
5569
|
const reportEntityFilters = {
|
|
5570
5570
|
service: z.array(z.string()).optional().describe("Restrict the cohort to these billable service code(s). Use for 'how are we doing on VAT returns?' — get codes from list_services."),
|
|
5571
5571
|
client: z.array(z.string()).optional().describe("Restrict the cohort to these client code(s). Use for a single-client view — get codes from list_clients."),
|
|
5572
|
-
user: z.array(z.string()).optional().describe("Restrict the cohort to tasks assigned to these user code(s). For 'my …' questions
|
|
5572
|
+
user: z.array(z.string()).optional().describe("Restrict the cohort to tasks assigned to these user code(s). For 'my …' questions, this is the signed-in user's practice user code, which whoami shows."),
|
|
5573
5573
|
team: z.array(z.string()).optional().describe("Restrict the cohort to tasks assigned to these team code(s)."),
|
|
5574
5574
|
category: z.array(z.string()).optional().describe("Restrict the cohort to these task category code(s).")
|
|
5575
5575
|
};
|
|
@@ -6487,7 +6487,7 @@ async function handleUpdateServiceCategory(api, args) {
|
|
|
6487
6487
|
//#region ../mcp-core/src/tools/list-saved-task-filters.ts
|
|
6488
6488
|
const sortByEnum$13 = z.enum(["Name", "UpdatedDate"]);
|
|
6489
6489
|
const ListSavedTaskFiltersInputSchema = {
|
|
6490
|
-
menuOnly: z.boolean().optional().describe("Only return filters the user has pinned to their menu. These are the views they use daily
|
|
6490
|
+
menuOnly: z.boolean().optional().describe("Only return filters the user has pinned to their menu. These are the views they use daily."),
|
|
6491
6491
|
sortBy: sortByEnum$13.optional().describe("Field to sort by. Defaults to Name. Use UpdatedDate + sortDesc=true for recently edited views."),
|
|
6492
6492
|
sortDesc: z.boolean().optional().describe("Sort in descending order. Defaults to ascending."),
|
|
6493
6493
|
limit: z.number().int().min(0).max(50).optional().describe("Maximum filters per page. Default 10, max 50. Pass 0 for count-only."),
|
|
@@ -6552,7 +6552,7 @@ async function handleListSavedTaskFilters(api, args) {
|
|
|
6552
6552
|
const sortByEnum$12 = z.enum(["Name", "UpdatedDate"]);
|
|
6553
6553
|
const ListSavedClientFiltersInputSchema = {
|
|
6554
6554
|
search: z.string().optional().describe("Search across saved view name and description."),
|
|
6555
|
-
menuOnly: z.boolean().optional().describe("Only return filters the user has pinned to their menu.
|
|
6555
|
+
menuOnly: z.boolean().optional().describe("Only return filters the user has pinned to their menu. These are the views they use daily."),
|
|
6556
6556
|
sortBy: sortByEnum$12.optional().describe("Field to sort by. Defaults to Name. Use UpdatedDate + sortDesc=true for recently edited views."),
|
|
6557
6557
|
sortDesc: z.boolean().optional().describe("Sort in descending order. Defaults to ascending."),
|
|
6558
6558
|
limit: z.number().int().min(0).max(50).optional().describe("Maximum filters per page. Default 10, max 50. Pass 0 for count-only."),
|
|
@@ -6915,10 +6915,10 @@ const sortByEnum$9 = z.enum([
|
|
|
6915
6915
|
"IsBillable"
|
|
6916
6916
|
]);
|
|
6917
6917
|
const ListTimeEntriesInputSchema = {
|
|
6918
|
-
userCode: z.string().optional().describe("Filter by team member code. For 'my time this week'
|
|
6918
|
+
userCode: z.string().optional().describe("Filter by team member code. For 'my time this week', this is the signed-in user's practice user code, which whoami shows. Omit for practice-wide time."),
|
|
6919
6919
|
clientCode: z.string().optional().describe("Filter by linked client code — 'how much time have we spent on ACME?'."),
|
|
6920
6920
|
taskItemCode: z.string().optional().describe("Filter by linked task code — 'time logged against the Greggs year-end task'."),
|
|
6921
|
-
dateRange: dateRangeEnum$1.optional().describe("Preset date range for the entry window. Use 'Today' / 'ThisWeek' / 'ThisMonth' for recent-time questions; 'CustomDateRange' with fromDate + toDate for explicit windows.
|
|
6921
|
+
dateRange: dateRangeEnum$1.optional().describe("Preset date range for the entry window. Use 'Today' / 'ThisWeek' / 'ThisMonth' for recent-time questions; 'CustomDateRange' with fromDate + toDate for explicit windows. Narrow ranges are faster — time queries without a window scan everything."),
|
|
6922
6922
|
fromDate: z.string().optional().describe("Window start (YYYY-MM-DD), inclusive. Use with dateRange='CustomDateRange'."),
|
|
6923
6923
|
toDate: z.string().optional().describe("Window end (YYYY-MM-DD), inclusive. Use with dateRange='CustomDateRange'."),
|
|
6924
6924
|
isBillable: z.boolean().optional().describe("Filter by billable flag. true for 'billable time only', false for internal/non-chargeable time."),
|
|
@@ -7320,13 +7320,13 @@ const CreateTaskInputSchema = {
|
|
|
7320
7320
|
name: z.string().min(1).describe("The task name — short and action-oriented, e.g. 'Chase rental schedule for year-end'."),
|
|
7321
7321
|
description: z.string().optional().describe("Longer description of what needs doing. Optional."),
|
|
7322
7322
|
status: statusEnum$10.optional().describe("Initial status. Defaults to NotStarted — only override when the work is already underway (InProgress)."),
|
|
7323
|
-
startDate: z.string().describe("When work should start (YYYY-MM-DD). For ad-hoc tasks, today
|
|
7323
|
+
startDate: z.string().describe("When work should start (YYYY-MM-DD). For ad-hoc tasks, today's date is usually right."),
|
|
7324
7324
|
dueDate: z.string().describe("When the task is due (YYYY-MM-DD). Must be on or after startDate."),
|
|
7325
7325
|
statutoryDueDate: z.string().optional().describe("The statutory deadline (YYYY-MM-DD), for compliance tasks with a legal filing date distinct from the internal due date."),
|
|
7326
7326
|
timeEstimateMinutes: z.number().int().min(1).optional().describe("Estimated effort in whole minutes."),
|
|
7327
7327
|
clientCodes: z.array(z.string()).optional().describe("Client code(s) the task is for. Get codes from list_clients. Omit for internal practice work."),
|
|
7328
7328
|
primaryClientCode: z.string().optional().describe("Which of clientCodes is the primary client. Only needed when passing more than one client."),
|
|
7329
|
-
assignedUserCode: z.string().optional().describe("User code to assign the task to. For 'create a task for me',
|
|
7329
|
+
assignedUserCode: z.string().optional().describe("User code to assign the task to. For 'create a task for me', this is the signed-in user's practice user code, which whoami shows. Names resolve to codes via the roster or list_users."),
|
|
7330
7330
|
assignedTeamCode: z.string().optional().describe("Team code to assign to instead of an individual. Get codes from list_teams. Don't pass both this and assignedUserCode unless the user asked for both."),
|
|
7331
7331
|
clientBillableServiceCode: z.string().optional().describe("The client-billable-service code linking this task to a service the client subscribes to (visible in get_client_summary's services section)."),
|
|
7332
7332
|
categoryCode: z.string().optional().describe("Task category code. Get codes from list_task_categories."),
|
|
@@ -7446,7 +7446,7 @@ async function handleUpdateTask(api, args) {
|
|
|
7446
7446
|
//#endregion
|
|
7447
7447
|
//#region ../mcp-core/src/tools/log-time-entry.ts
|
|
7448
7448
|
const LogTimeEntryInputSchema = {
|
|
7449
|
-
entryDate: z.string().describe("The day the work was done (YYYY-MM-DD). For 'log 2 hours on ACME' with no date given,
|
|
7449
|
+
entryDate: z.string().describe("The day the work was done (YYYY-MM-DD). For 'log 2 hours on ACME' with no date given, today's date applies."),
|
|
7450
7450
|
durationMinutes: z.number().int().min(1).describe("How long the work took, in whole minutes. Convert user phrasing: '2 hours' → 120, 'half an hour' → 30."),
|
|
7451
7451
|
description: z.string().optional().describe("What the time was spent on. Strongly recommended — it's what appears on billing narratives."),
|
|
7452
7452
|
userCode: z.string().optional().describe("Who did the work. Defaults to the authenticated user — only pass this when logging time on someone else's behalf."),
|
|
@@ -7516,7 +7516,7 @@ const sortByEnum$7 = z.enum([
|
|
|
7516
7516
|
const ListSentEmailsInputSchema = {
|
|
7517
7517
|
status: statusEnum$8.optional().describe("Filter by delivery status. 'Failed' answers 'which emails didn't get through?' — the most actionable query. 'Queued'/'Processing' show what's still going out."),
|
|
7518
7518
|
search: z.string().optional().describe("Search in email subject and recipient email address — 'emails to john@acme.co.uk', 'the proposal email'."),
|
|
7519
|
-
fromDate: z.string().optional().describe("Only emails created on or after this date (YYYY-MM-DD).
|
|
7519
|
+
fromDate: z.string().optional().describe("Only emails created on or after this date (YYYY-MM-DD). A window keeps results small; unbounded queries scan all history."),
|
|
7520
7520
|
toDate: z.string().optional().describe("Only emails created on or before this date (YYYY-MM-DD)."),
|
|
7521
7521
|
metadataParam: z.array(z.string()).optional().describe("Filter by metadata stored with the email, each entry as 'key=value' (e.g. ['entityType=Proposal']). Only use when you know the metadata key from a previous result — otherwise omit."),
|
|
7522
7522
|
sortBy: sortByEnum$7.optional().describe("Field to sort by. Defaults to CreatedDate — pair with sortDesc=true for newest first."),
|
|
@@ -7830,7 +7830,7 @@ const ListBillingLineItemsInputSchema = {
|
|
|
7830
7830
|
clientCode: z.string().optional().describe("Filter to one client's billing lines — 'what are we about to bill ACME?'."),
|
|
7831
7831
|
billableServiceCode: z.string().optional().describe("Filter by the billable service — 'what bookkeeping revenue is queued?'. Get codes from list_services."),
|
|
7832
7832
|
clientBillableServiceCode: z.string().optional().describe("Filter by one client's specific service subscription (the code from get_client_summary's services section). Narrower than billableServiceCode."),
|
|
7833
|
-
billingDateFrom: z.string().optional().describe("Only lines with billing date on or after this date (YYYY-MM-DD).
|
|
7833
|
+
billingDateFrom: z.string().optional().describe("Only lines with billing date on or after this date (YYYY-MM-DD). A window keeps revenue questions fast."),
|
|
7834
7834
|
billingDateTo: z.string().optional().describe("Only lines with billing date on or before this date (YYYY-MM-DD)."),
|
|
7835
7835
|
sortBy: sortByEnum$5.optional().describe("Field to sort by. Defaults to BillingDate. Use UnitPrice + sortDesc=true for the biggest lines first."),
|
|
7836
7836
|
sortDesc: z.boolean().optional().describe("Sort in descending order. Defaults to ascending."),
|
|
@@ -7960,7 +7960,7 @@ const sortByEnum$4 = z.enum([
|
|
|
7960
7960
|
"StepType"
|
|
7961
7961
|
]);
|
|
7962
7962
|
const ListPendingWorkflowStepsInputSchema = {
|
|
7963
|
-
assignedUserCode: z.string().optional().describe("Filter to steps assigned to this user. For 'what steps are waiting on me?'
|
|
7963
|
+
assignedUserCode: z.string().optional().describe("Filter to steps assigned to this user. For 'what steps are waiting on me?', this is the signed-in user's practice user code, which whoami shows."),
|
|
7964
7964
|
assignedTeamCode: z.string().optional().describe("Filter to steps assigned to this team (codes from list_teams)."),
|
|
7965
7965
|
isUnassigned: z.boolean().optional().describe("true = only steps with no assigned user — 'what work is nobody picking up?'. Combines with assignedUserCode as OR (mine + unassigned)."),
|
|
7966
7966
|
stepType: stepTypeEnum.optional().describe("Filter by step type. 'Standard' = internal work steps; 'DocumentRequest'/'DocumentApproval'/'ClientConfirmation'/'SendDataForm' = client-facing steps."),
|
|
@@ -9189,7 +9189,7 @@ async function handleSendDataForm(api, args) {
|
|
|
9189
9189
|
const typeEnum = z.enum(["ProposalAndEngagementLetter", "EngagementLetter"]);
|
|
9190
9190
|
const CreateProposalInputSchema = {
|
|
9191
9191
|
clientCode: z.string().min(1).describe("The client the proposal is for. Get the code from list_clients."),
|
|
9192
|
-
date: z.string().describe("The engagement date (YYYY-MM-DD) — usually today
|
|
9192
|
+
date: z.string().describe("The engagement date (YYYY-MM-DD) — usually today's date."),
|
|
9193
9193
|
type: typeEnum.optional().describe("'ProposalAndEngagementLetter' (default) generates both documents; 'EngagementLetter' for existing clients who only need the letter."),
|
|
9194
9194
|
recipientFirstName: z.string().optional().describe("Recipient's first name. Usually the client's main contact — check get_client_summary."),
|
|
9195
9195
|
recipientLastName: z.string().optional().describe("Recipient's last name."),
|
|
@@ -10284,4 +10284,278 @@ async function buildServer(config) {
|
|
|
10284
10284
|
return server;
|
|
10285
10285
|
}
|
|
10286
10286
|
//#endregion
|
|
10287
|
-
|
|
10287
|
+
//#region ../mcp-core/src/tool-groups.json
|
|
10288
|
+
var groups = [
|
|
10289
|
+
{
|
|
10290
|
+
"name": "Clients & contacts",
|
|
10291
|
+
"tools": [
|
|
10292
|
+
"list_clients",
|
|
10293
|
+
"get_client_summary",
|
|
10294
|
+
"list_client_notes",
|
|
10295
|
+
"list_client_links",
|
|
10296
|
+
"list_contacts",
|
|
10297
|
+
"get_contact",
|
|
10298
|
+
"get_custom_field_details",
|
|
10299
|
+
"list_portal_users",
|
|
10300
|
+
"create_client",
|
|
10301
|
+
"create_contact",
|
|
10302
|
+
"update_contact",
|
|
10303
|
+
"link_contact_to_client",
|
|
10304
|
+
"update_client_business_details",
|
|
10305
|
+
"update_client_dates",
|
|
10306
|
+
"update_client_custom_fields",
|
|
10307
|
+
"add_client_note"
|
|
10308
|
+
]
|
|
10309
|
+
},
|
|
10310
|
+
{
|
|
10311
|
+
"name": "Tasks & workflows",
|
|
10312
|
+
"tools": [
|
|
10313
|
+
"list_tasks",
|
|
10314
|
+
"get_task_context",
|
|
10315
|
+
"get_task_history",
|
|
10316
|
+
"list_recurring_tasks",
|
|
10317
|
+
"list_workflows",
|
|
10318
|
+
"get_workflow",
|
|
10319
|
+
"list_workflow_occurrences",
|
|
10320
|
+
"list_pending_workflow_steps",
|
|
10321
|
+
"create_task",
|
|
10322
|
+
"update_task",
|
|
10323
|
+
"add_task_note",
|
|
10324
|
+
"update_workflow_step",
|
|
10325
|
+
"add_workflow_step_note"
|
|
10326
|
+
]
|
|
10327
|
+
},
|
|
10328
|
+
{
|
|
10329
|
+
"name": "Services & pricing",
|
|
10330
|
+
"tools": [
|
|
10331
|
+
"list_services",
|
|
10332
|
+
"get_service_details",
|
|
10333
|
+
"list_service_packages",
|
|
10334
|
+
"list_service_categories",
|
|
10335
|
+
"add_client_service",
|
|
10336
|
+
"apply_service_package",
|
|
10337
|
+
"update_client_service_stage",
|
|
10338
|
+
"create_service_category",
|
|
10339
|
+
"update_service_category"
|
|
10340
|
+
]
|
|
10341
|
+
},
|
|
10342
|
+
{
|
|
10343
|
+
"name": "Proposals & engagements",
|
|
10344
|
+
"tools": [
|
|
10345
|
+
"list_proposals",
|
|
10346
|
+
"get_proposal_summary",
|
|
10347
|
+
"list_engagement_letters",
|
|
10348
|
+
"get_engagement_letter_summary",
|
|
10349
|
+
"get_engagement_pipeline_summary",
|
|
10350
|
+
"create_proposal"
|
|
10351
|
+
]
|
|
10352
|
+
},
|
|
10353
|
+
{
|
|
10354
|
+
"name": "Documents & forms",
|
|
10355
|
+
"tools": [
|
|
10356
|
+
"list_document_requests",
|
|
10357
|
+
"create_document_request",
|
|
10358
|
+
"list_client_documents",
|
|
10359
|
+
"list_data_forms",
|
|
10360
|
+
"list_data_form_requests",
|
|
10361
|
+
"send_data_form"
|
|
10362
|
+
]
|
|
10363
|
+
},
|
|
10364
|
+
{
|
|
10365
|
+
"name": "Time & billing",
|
|
10366
|
+
"tools": [
|
|
10367
|
+
"list_time_entries",
|
|
10368
|
+
"log_time_entry",
|
|
10369
|
+
"list_unbilled_time_clients",
|
|
10370
|
+
"list_billing_line_items",
|
|
10371
|
+
"list_invoice_submissions",
|
|
10372
|
+
"get_service_time_report",
|
|
10373
|
+
"get_team_time_report"
|
|
10374
|
+
]
|
|
10375
|
+
},
|
|
10376
|
+
{
|
|
10377
|
+
"name": "Email & Communication",
|
|
10378
|
+
"tools": [
|
|
10379
|
+
"list_client_emails",
|
|
10380
|
+
"get_client_email_thread",
|
|
10381
|
+
"list_sent_emails",
|
|
10382
|
+
"get_sent_email",
|
|
10383
|
+
"list_email_broadcasts",
|
|
10384
|
+
"get_email_broadcast"
|
|
10385
|
+
]
|
|
10386
|
+
},
|
|
10387
|
+
{
|
|
10388
|
+
"name": "Deadlines & reports",
|
|
10389
|
+
"tools": [
|
|
10390
|
+
"get_practice_dashboard",
|
|
10391
|
+
"get_deadline_compliance_report",
|
|
10392
|
+
"get_service_delivery_report",
|
|
10393
|
+
"get_service_delivery_steps",
|
|
10394
|
+
"get_client_responsiveness_report",
|
|
10395
|
+
"get_team_workload_report",
|
|
10396
|
+
"get_wip_ageing_report",
|
|
10397
|
+
"get_workflow_step_analysis"
|
|
10398
|
+
]
|
|
10399
|
+
},
|
|
10400
|
+
{
|
|
10401
|
+
"name": "Compliance & lookups",
|
|
10402
|
+
"tools": [
|
|
10403
|
+
"list_aml_clients",
|
|
10404
|
+
"get_aml_client",
|
|
10405
|
+
"get_client_agent_authorisations",
|
|
10406
|
+
"get_onboarding_status",
|
|
10407
|
+
"search_companies_house",
|
|
10408
|
+
"get_companies_house_profile"
|
|
10409
|
+
]
|
|
10410
|
+
},
|
|
10411
|
+
{
|
|
10412
|
+
"name": "Practice & setup",
|
|
10413
|
+
"tools": [
|
|
10414
|
+
"get_practice_details",
|
|
10415
|
+
"whoami",
|
|
10416
|
+
"list_practices",
|
|
10417
|
+
"get_usage_examples",
|
|
10418
|
+
"list_users",
|
|
10419
|
+
"list_teams",
|
|
10420
|
+
"list_task_categories",
|
|
10421
|
+
"list_saved_task_filters",
|
|
10422
|
+
"list_saved_client_filters",
|
|
10423
|
+
"create_saved_task_filter"
|
|
10424
|
+
]
|
|
10425
|
+
}
|
|
10426
|
+
];
|
|
10427
|
+
var summaries = {
|
|
10428
|
+
"list_clients": "Find and filter your clients by name, status, type, manager, service and more.",
|
|
10429
|
+
"get_client_summary": "A full overview of one client: details, contacts, services, key dates and tasks.",
|
|
10430
|
+
"list_client_notes": "Read the notes on a client.",
|
|
10431
|
+
"list_client_links": "See the links saved against a client, such as a website or shared folder.",
|
|
10432
|
+
"list_contacts": "Search contacts across the practice, or list a client's contacts.",
|
|
10433
|
+
"get_contact": "Full details of one contact, including linked clients.",
|
|
10434
|
+
"get_custom_field_details": "The definition of a custom field, including its allowed options.",
|
|
10435
|
+
"list_portal_users": "See who has client portal access and when they last logged in.",
|
|
10436
|
+
"create_client": "Create a new client or prospect.",
|
|
10437
|
+
"create_contact": "Create a new contact.",
|
|
10438
|
+
"update_contact": "Update a contact's details.",
|
|
10439
|
+
"link_contact_to_client": "Link an existing contact to a client and set their role.",
|
|
10440
|
+
"update_client_business_details": "Update a client's trading name, address, VAT and tax details.",
|
|
10441
|
+
"update_client_dates": "Set a client's key dates, such as year end and VAT or filing due dates.",
|
|
10442
|
+
"update_client_custom_fields": "Update a client's custom field values.",
|
|
10443
|
+
"add_client_note": "Add a note to a client.",
|
|
10444
|
+
"list_tasks": "Find tasks by assignee, client, status, due date, category and more.",
|
|
10445
|
+
"get_task_context": "Everything about one task: details, notes, workflow progress and client.",
|
|
10446
|
+
"get_task_history": "The audit trail of a task: who changed what, and when.",
|
|
10447
|
+
"list_recurring_tasks": "See the recurring task templates that generate repeating work.",
|
|
10448
|
+
"list_workflows": "List your workflow templates.",
|
|
10449
|
+
"get_workflow": "The groups and steps of one workflow template.",
|
|
10450
|
+
"list_workflow_occurrences": "The completed runs of a workflow, slowest first.",
|
|
10451
|
+
"list_pending_workflow_steps": "Workflow steps ready to be worked on now, for you, your team or nobody.",
|
|
10452
|
+
"create_task": "Create a task for a client or the practice.",
|
|
10453
|
+
"update_task": "Update a task's status, dates, assignee or details.",
|
|
10454
|
+
"add_task_note": "Add a note to a task.",
|
|
10455
|
+
"update_workflow_step": "Complete, skip, block or reassign a workflow step.",
|
|
10456
|
+
"add_workflow_step_note": "Add a note to one step of a task's workflow.",
|
|
10457
|
+
"list_services": "List the services your practice offers.",
|
|
10458
|
+
"get_service_details": "A service's pricing, client types, stages and authorisations.",
|
|
10459
|
+
"list_service_packages": "List bundled service packages and what is in them.",
|
|
10460
|
+
"list_service_categories": "List your service categories.",
|
|
10461
|
+
"add_client_service": "Put a service on a client, with frequency, price and start date.",
|
|
10462
|
+
"apply_service_package": "Apply a service package to a client in one go.",
|
|
10463
|
+
"update_client_service_stage": "Move a client's service to another stage on the board.",
|
|
10464
|
+
"create_service_category": "Create a service category.",
|
|
10465
|
+
"update_service_category": "Rename, recolour or reorder a service category.",
|
|
10466
|
+
"list_proposals": "List proposals by status, client or date.",
|
|
10467
|
+
"get_proposal_summary": "Full detail of one proposal: services, pricing and acceptance.",
|
|
10468
|
+
"list_engagement_letters": "List letters of engagement by status, client or date.",
|
|
10469
|
+
"get_engagement_letter_summary": "Full detail of one letter of engagement.",
|
|
10470
|
+
"get_engagement_pipeline_summary": "Counts and values of proposals by stage: what is open and what is won.",
|
|
10471
|
+
"create_proposal": "Draft a proposal or letter of engagement for a client.",
|
|
10472
|
+
"list_document_requests": "See document requests sent to clients and what is still outstanding.",
|
|
10473
|
+
"create_document_request": "Draft a document request for a client.",
|
|
10474
|
+
"list_client_documents": "List documents held against clients and their review status.",
|
|
10475
|
+
"list_data_forms": "List your questionnaire templates.",
|
|
10476
|
+
"list_data_form_requests": "See which questionnaires were sent and who has responded.",
|
|
10477
|
+
"send_data_form": "Send a questionnaire to a client.",
|
|
10478
|
+
"list_time_entries": "List recorded time by person, client, task or date.",
|
|
10479
|
+
"log_time_entry": "Record time spent on a client or task.",
|
|
10480
|
+
"list_unbilled_time_clients": "Which clients have time recorded but not yet billed.",
|
|
10481
|
+
"list_billing_line_items": "List billing lines waiting for approval or invoicing.",
|
|
10482
|
+
"list_invoice_submissions": "See invoices sent to your accounting system, and any that failed.",
|
|
10483
|
+
"get_service_time_report": "Where your recorded time goes, by service or client.",
|
|
10484
|
+
"get_team_time_report": "Hours logged against expected hours for each team member.",
|
|
10485
|
+
"list_client_emails": "Email threads with a client from your synced mailbox.",
|
|
10486
|
+
"get_client_email_thread": "Read one email thread in full.",
|
|
10487
|
+
"list_sent_emails": "Emails Sodium sent for you, and whether they were delivered.",
|
|
10488
|
+
"get_sent_email": "Delivery detail for one sent email.",
|
|
10489
|
+
"list_email_broadcasts": "Your email campaigns and how far they have got.",
|
|
10490
|
+
"get_email_broadcast": "One campaign in full, with delivery results for each client.",
|
|
10491
|
+
"get_practice_dashboard": "A snapshot of what needs attention across the practice right now.",
|
|
10492
|
+
"get_deadline_compliance_report": "Which deadlines were met, missed or are at risk.",
|
|
10493
|
+
"get_service_delivery_report": "How quickly services are delivered, and how often on time.",
|
|
10494
|
+
"get_service_delivery_steps": "Where the time goes inside one service's workflow.",
|
|
10495
|
+
"get_client_responsiveness_report": "How quickly clients respond to requests, and how much chasing it takes.",
|
|
10496
|
+
"get_team_workload_report": "Who has too much on, and what each person completes.",
|
|
10497
|
+
"get_wip_ageing_report": "Open work that has been sitting untouched, and for how long.",
|
|
10498
|
+
"get_workflow_step_analysis": "Step-by-step timings for one workflow template.",
|
|
10499
|
+
"list_aml_clients": "AML and identity-check records with risk level and outstanding checks.",
|
|
10500
|
+
"get_aml_client": "One AML record: status, linked contacts, checks and risk assessments.",
|
|
10501
|
+
"get_client_agent_authorisations": "HMRC agent authorisation status for a client, by tax regime.",
|
|
10502
|
+
"get_onboarding_status": "Is a client ready for work: engagement, AML, authorisations and data.",
|
|
10503
|
+
"search_companies_house": "Search Companies House by company name.",
|
|
10504
|
+
"get_companies_house_profile": "The live Companies House record for a company.",
|
|
10505
|
+
"get_practice_details": "Your practice's details, counts, connections and settings.",
|
|
10506
|
+
"whoami": "Who you are signed in as, and which practice you are working in.",
|
|
10507
|
+
"list_practices": "The practices you belong to.",
|
|
10508
|
+
"get_usage_examples": "Example requests to try, grouped by topic.",
|
|
10509
|
+
"list_users": "Your team members and their roles.",
|
|
10510
|
+
"list_teams": "Your teams and who is in them.",
|
|
10511
|
+
"list_task_categories": "List your task categories.",
|
|
10512
|
+
"list_saved_task_filters": "Your saved task views.",
|
|
10513
|
+
"list_saved_client_filters": "Your saved client views.",
|
|
10514
|
+
"create_saved_task_filter": "Save a task search as a reusable view."
|
|
10515
|
+
};
|
|
10516
|
+
//#endregion
|
|
10517
|
+
//#region ../mcp-core/src/catalogue.ts
|
|
10518
|
+
const SUMMARY_MAX = 200;
|
|
10519
|
+
const OTHER_GROUP = "Other";
|
|
10520
|
+
const summaryOverrides = summaries;
|
|
10521
|
+
function summarise(tool) {
|
|
10522
|
+
const override = summaryOverrides[tool.name];
|
|
10523
|
+
if (override) return override;
|
|
10524
|
+
const text = (tool.description ?? "").replace(/\s+/g, " ").trim();
|
|
10525
|
+
const match = text.match(/^(.+?[.!?])(?=\s+[A-Z'"(]|$)/);
|
|
10526
|
+
const sentence = match ? match[1] : text;
|
|
10527
|
+
return sentence.length > SUMMARY_MAX ? `${sentence.slice(0, SUMMARY_MAX - 1).trimEnd()}…` : sentence;
|
|
10528
|
+
}
|
|
10529
|
+
function buildCatalogue(tools) {
|
|
10530
|
+
const byName = new Map(tools.map((t) => [t.name, t]));
|
|
10531
|
+
const placed = /* @__PURE__ */ new Set();
|
|
10532
|
+
const groups$1 = [];
|
|
10533
|
+
const toEntry = (tool) => ({
|
|
10534
|
+
name: tool.name,
|
|
10535
|
+
title: tool.title ?? tool.name,
|
|
10536
|
+
access: tool.annotations?.readOnlyHint === true ? "read" : "write",
|
|
10537
|
+
summary: summarise(tool)
|
|
10538
|
+
});
|
|
10539
|
+
for (const group of groups) {
|
|
10540
|
+
const entries = group.tools.map((name) => byName.get(name)).filter((t) => t !== void 0).map(toEntry);
|
|
10541
|
+
entries.forEach((e) => placed.add(e.name));
|
|
10542
|
+
if (entries.length > 0) groups$1.push({
|
|
10543
|
+
name: group.name,
|
|
10544
|
+
tools: entries
|
|
10545
|
+
});
|
|
10546
|
+
}
|
|
10547
|
+
const unmapped = tools.filter((t) => !placed.has(t.name)).map(toEntry);
|
|
10548
|
+
if (unmapped.length > 0) groups$1.push({
|
|
10549
|
+
name: OTHER_GROUP,
|
|
10550
|
+
tools: unmapped
|
|
10551
|
+
});
|
|
10552
|
+
const all = groups$1.flatMap((g) => g.tools);
|
|
10553
|
+
return {
|
|
10554
|
+
toolCount: all.length,
|
|
10555
|
+
readCount: all.filter((t) => t.access === "read").length,
|
|
10556
|
+
writeCount: all.filter((t) => t.access === "write").length,
|
|
10557
|
+
groups: groups$1
|
|
10558
|
+
};
|
|
10559
|
+
}
|
|
10560
|
+
//#endregion
|
|
10561
|
+
export { buildServer as n, buildCatalogue as t };
|