@sodiumhq/mcp-pm 0.1.0-beta.4465 → 0.1.0-beta.4579
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/http.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{src-uJ9liBF-.mjs → src-dwGvEajk.mjs} +137 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Model Context Protocol (MCP) server for [Sodium Practice Management](https://sodiumhq.com). Lets AI assistants like Claude Desktop, Claude Code, Cursor, and VS Code interact with your Sodium tenant.
|
|
4
4
|
|
|
5
|
+
> **Using a browser assistant?** claude.ai, ChatGPT, and Gemini can connect to Sodium without installing anything — add `https://mcp.sodiumhq.com` as a custom connector and sign in with your Sodium account. This npm package is the local (stdio) alternative, authenticated with an API key.
|
|
6
|
+
|
|
5
7
|
> **Full setup guide and example prompts:** [sodiumhq.com/features/mcp](https://sodiumhq.com/features/mcp)
|
|
6
8
|
|
|
7
9
|
## Status
|
|
@@ -151,6 +153,7 @@ Only enable write mode with an AI client you trust — it hands the client the a
|
|
|
151
153
|
- **`update_task`** — change a task's status, dates, assignment, or details. Only the fields you mention change; everything else is preserved. "Mark TSK-123 complete", "push the deadline to Friday", "reassign it to Jane."
|
|
152
154
|
- **`update_workflow_step`** — complete, skip, block, or reassign a single workflow step on a task. "Mark the review step as done", "skip the client-approval step — they confirmed by phone."
|
|
153
155
|
- **`add_client_service`** — put a client on a catalogue service with frequency, pricing, and start date. "Put ACME on monthly bookkeeping."
|
|
156
|
+
- **`apply_service_package`** — put a client on one of the practice's bundled packages in a single call; contents and pricing follow the package definition. "Put ACME on the Growth package from 1 April."
|
|
154
157
|
- **`update_client_service_stage`** — move a client's service across the kanban board. "Move ACME's VAT return to Records In."
|
|
155
158
|
- **`create_document_request`** — draft (never send) a document request for a client. Sending stays in the Sodium UI.
|
|
156
159
|
- **`send_data_form`** — send a questionnaire to a client. Sends immediately, so your assistant confirms with you first.
|
package/dist/http.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as buildServer } from "./src-
|
|
2
|
+
import { t as buildServer } from "./src-dwGvEajk.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { createHash, randomUUID } from "node:crypto";
|
|
5
5
|
import { createServer } from "node:http";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as buildServer } from "./src-
|
|
2
|
+
import { t as buildServer } from "./src-dwGvEajk.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
5
|
import { parseArgs } from "node:util";
|
|
@@ -1719,6 +1719,26 @@ const listServicePackages = (options) => (options.client ?? client).get({
|
|
|
1719
1719
|
...options
|
|
1720
1720
|
});
|
|
1721
1721
|
/**
|
|
1722
|
+
* Apply Service Package to Client
|
|
1723
|
+
*
|
|
1724
|
+
* Applies a service package to a client, creating ClientBillableService records for each item in the package.
|
|
1725
|
+
*/
|
|
1726
|
+
const applyServicePackageToClient = (options) => (options.client ?? client).post({
|
|
1727
|
+
security: [{
|
|
1728
|
+
name: "x-api-key",
|
|
1729
|
+
type: "apiKey"
|
|
1730
|
+
}, {
|
|
1731
|
+
scheme: "bearer",
|
|
1732
|
+
type: "http"
|
|
1733
|
+
}],
|
|
1734
|
+
url: "/tenants/{tenant}/clients/{clientCode}/apply-service-package/{packageCode}",
|
|
1735
|
+
...options,
|
|
1736
|
+
headers: {
|
|
1737
|
+
"Content-Type": "application/json",
|
|
1738
|
+
...options.headers
|
|
1739
|
+
}
|
|
1740
|
+
});
|
|
1741
|
+
/**
|
|
1722
1742
|
* List BillableServices
|
|
1723
1743
|
*
|
|
1724
1744
|
* Lists all BillableServices for the given tenant.
|
|
@@ -3403,6 +3423,21 @@ var SodiumApiClient = class {
|
|
|
3403
3423
|
if (error !== void 0 || !data) throw this.toError(response, error, correlationId, `assign service to client ${clientCode}`);
|
|
3404
3424
|
return data;
|
|
3405
3425
|
}
|
|
3426
|
+
async applyServicePackageToClient(clientCode, packageCode, body) {
|
|
3427
|
+
const correlationId = randomUUID();
|
|
3428
|
+
const { data, error, response } = await applyServicePackageToClient({
|
|
3429
|
+
path: {
|
|
3430
|
+
tenant: this.tenant,
|
|
3431
|
+
clientCode,
|
|
3432
|
+
packageCode
|
|
3433
|
+
},
|
|
3434
|
+
body,
|
|
3435
|
+
headers: { "X-Correlation-Id": correlationId },
|
|
3436
|
+
client: this.http
|
|
3437
|
+
});
|
|
3438
|
+
if (error !== void 0 || !data) throw this.toError(response, error, correlationId, `apply package ${packageCode} to client ${clientCode}`);
|
|
3439
|
+
return data;
|
|
3440
|
+
}
|
|
3406
3441
|
async updateClientServiceStage(clientCode, clientServiceCode, body) {
|
|
3407
3442
|
const correlationId = randomUUID();
|
|
3408
3443
|
const { error, response } = await updateClientBillableServiceStage({
|
|
@@ -3738,7 +3773,7 @@ async function handleGetPracticeDetails(api) {
|
|
|
3738
3773
|
}
|
|
3739
3774
|
//#endregion
|
|
3740
3775
|
//#region ../mcp-core/src/tools/list-clients.ts
|
|
3741
|
-
const statusEnum$
|
|
3776
|
+
const statusEnum$15 = z.enum([
|
|
3742
3777
|
"Active",
|
|
3743
3778
|
"Inactive",
|
|
3744
3779
|
"Prospect",
|
|
@@ -3757,7 +3792,7 @@ const typeEnum$1 = z.enum([
|
|
|
3757
3792
|
const sortByEnum$19 = z.enum(["Name", "InternalReference"]);
|
|
3758
3793
|
const ListClientsInputSchema = {
|
|
3759
3794
|
search: z.string().min(3, "Search must be at least 3 characters when provided").optional().describe("Free-text search across client code, name, and internal reference. Minimum 3 characters. Omit to browse by filter only."),
|
|
3760
|
-
status: z.array(statusEnum$
|
|
3795
|
+
status: z.array(statusEnum$15).optional().describe("Filter by client status. Defaults to all statuses if omitted. Example: ['Active'] for active clients only."),
|
|
3761
3796
|
type: z.array(typeEnum$1).optional().describe("Filter by organisation type. Use ['PrivateLimitedCompany', 'PublicLimitedCompany'] for 'limited companies'. Use ['LimitedLiabilityPartnership'] for LLPs. Defaults to all types if omitted."),
|
|
3762
3797
|
managerCode: z.array(z.string()).optional().describe("Filter by assigned manager user codes."),
|
|
3763
3798
|
partnerCode: z.array(z.string()).optional().describe("Filter by assigned partner user codes."),
|
|
@@ -4544,7 +4579,7 @@ function describeFilters$3(args) {
|
|
|
4544
4579
|
}
|
|
4545
4580
|
//#endregion
|
|
4546
4581
|
//#region ../mcp-core/src/tools/list-tasks.ts
|
|
4547
|
-
const statusEnum$
|
|
4582
|
+
const statusEnum$14 = z.enum([
|
|
4548
4583
|
"NotStarted",
|
|
4549
4584
|
"InProgress",
|
|
4550
4585
|
"Blocked",
|
|
@@ -4587,7 +4622,7 @@ const sortByEnum$17 = z.enum([
|
|
|
4587
4622
|
const ListTasksInputSchema = {
|
|
4588
4623
|
user: z.array(z.string()).optional().describe("Filter by assigned user codes. For 'my tasks' use the current user's code from the startup context. For another team member's tasks, pass their code. Omit to see tasks across all users (useful for practice managers)."),
|
|
4589
4624
|
client: z.array(z.string()).optional().describe("Filter by client codes — tasks belonging to these specific clients only."),
|
|
4590
|
-
status: z.array(statusEnum$
|
|
4625
|
+
status: z.array(statusEnum$14).optional().describe("Filter by task status. Typical: ['NotStarted', 'InProgress'] to exclude completed/skipped. Leave empty for all statuses. IMPORTANT: if the query includes NotStarted (the default for new tasks), you must ALSO provide one of: dateRange, isOverdue=true, or restrict status to non-NotStarted values only — otherwise the API rejects the call to prevent unbounded queries."),
|
|
4591
4626
|
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."),
|
|
4592
4627
|
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."),
|
|
4593
4628
|
startDate: z.string().optional().describe("Start of custom date range (YYYY-MM-DD). Used when dateRange='CustomDateRange'. Prefer specifying both startDate and endDate with a narrow window for performance — broad ranges strain the API. If startDate > endDate the API swaps them silently. Maximum allowed total range is 2 years."),
|
|
@@ -4665,7 +4700,7 @@ function describeFilters$2(args) {
|
|
|
4665
4700
|
}
|
|
4666
4701
|
//#endregion
|
|
4667
4702
|
//#region ../mcp-core/src/tools/list-users.ts
|
|
4668
|
-
const statusEnum$
|
|
4703
|
+
const statusEnum$13 = z.enum([
|
|
4669
4704
|
"Created",
|
|
4670
4705
|
"Invited",
|
|
4671
4706
|
"Active",
|
|
@@ -4685,7 +4720,7 @@ const sortByEnum$16 = z.enum([
|
|
|
4685
4720
|
]);
|
|
4686
4721
|
const ListUsersInputSchema = {
|
|
4687
4722
|
search: z.string().min(3, "Search must be at least 3 characters when provided").optional().describe("Free-text search across user code, first name, last name, and email. Minimum 3 characters. Use for 'find Jane' when the name isn't in the startup roster (large teams, non-active users)."),
|
|
4688
|
-
status: statusEnum$
|
|
4723
|
+
status: statusEnum$13.optional().describe("Filter by a single user status. Use 'Active' for currently-working members, 'Invited' for pending invitations, 'Disabled' for offboarded users, 'Deleted' for removed users. Note: this is a single value, not an array."),
|
|
4689
4724
|
systemRole: systemRoleEnum.optional().describe("Filter by system role. Admin = full access, StandardUser = normal access, Viewer = read-only. Single value."),
|
|
4690
4725
|
isClientManager: z.boolean().optional().describe("Set true to return only users flagged as client managers; false to exclude them."),
|
|
4691
4726
|
isPartner: z.boolean().optional().describe("Set true to return only users flagged as partners; false to exclude them. Use for 'list partners' queries."),
|
|
@@ -6199,7 +6234,7 @@ async function handleGetCompaniesHouseProfile(api, args) {
|
|
|
6199
6234
|
}
|
|
6200
6235
|
//#endregion
|
|
6201
6236
|
//#region ../mcp-core/src/tools/list-document-requests.ts
|
|
6202
|
-
const statusEnum$
|
|
6237
|
+
const statusEnum$12 = z.enum([
|
|
6203
6238
|
"Draft",
|
|
6204
6239
|
"Open",
|
|
6205
6240
|
"Submitted",
|
|
@@ -6213,7 +6248,7 @@ const sortByEnum$11 = z.enum([
|
|
|
6213
6248
|
"Deadline"
|
|
6214
6249
|
]);
|
|
6215
6250
|
const ListDocumentRequestsInputSchema = {
|
|
6216
|
-
status: statusEnum$
|
|
6251
|
+
status: statusEnum$12.optional().describe("Filter by request status. 'Open' = sent and waiting on the client (use for 'what are we waiting on from clients?'). 'Submitted' = client responded, awaiting practice review (use for 'what needs reviewing?'). 'Draft' = not yet sent. 'Accepted'/'Rejected' = reviewed. Single value — call per status for multiple."),
|
|
6217
6252
|
clientCode: z.string().optional().describe("Filter to one client's document requests. Get the code from list_clients."),
|
|
6218
6253
|
managerCode: z.string().optional().describe("Filter by the client manager's user code — 'what are MY clients waiting to send us?' for a manager."),
|
|
6219
6254
|
partnerCode: z.string().optional().describe("Filter by the client partner's user code."),
|
|
@@ -6279,10 +6314,10 @@ async function handleListDocumentRequests(api, args) {
|
|
|
6279
6314
|
}
|
|
6280
6315
|
//#endregion
|
|
6281
6316
|
//#region ../mcp-core/src/tools/list-data-form-requests.ts
|
|
6282
|
-
const statusEnum$
|
|
6317
|
+
const statusEnum$11 = z.enum(["PendingResponse", "ResponseReceived"]);
|
|
6283
6318
|
const sortByEnum$10 = z.enum(["CreatedDate", "Status"]);
|
|
6284
6319
|
const ListDataFormRequestsInputSchema = {
|
|
6285
|
-
status: statusEnum$
|
|
6320
|
+
status: statusEnum$11.optional().describe("Filter by request status. 'PendingResponse' = sent, waiting on the client to fill the form in (use for 'which questionnaires are clients sitting on?'). 'ResponseReceived' = the client submitted — answers may still need review (see pendingReviewCount in the output)."),
|
|
6286
6321
|
clientCode: z.string().optional().describe("Filter to one client's form requests. Get the code from list_clients."),
|
|
6287
6322
|
formCode: z.string().optional().describe("Filter by the data-form template code — 'who still hasn't returned the SA questionnaire?'."),
|
|
6288
6323
|
categoryCode: z.string().optional().describe("Filter by the form's category code."),
|
|
@@ -6667,6 +6702,7 @@ const TOPICS = {
|
|
|
6667
6702
|
- "Set the 'Referral source' custom field on ACME to 'Google'"
|
|
6668
6703
|
- "Mark the review step on the Greggs year-end task as complete" / "Skip the client-approval step — they confirmed by phone"
|
|
6669
6704
|
- "Put ACME on monthly bookkeeping at £350 from the 1st" / "Move their VAT return to 'Records In' on the board"
|
|
6705
|
+
- "Put Bristol Roofing on the Growth package from 1 April" — the whole bundle in one step
|
|
6670
6706
|
- "Draft a document request to ACME for their year-end records" (drafted — you press send)
|
|
6671
6707
|
- "Send the SA questionnaire to Greggs" (sends immediately — the assistant confirms first)
|
|
6672
6708
|
- "Draft a proposal for Bristol Roofing covering bookkeeping and VAT" (drafted Unsent — you review and send)
|
|
@@ -6698,7 +6734,7 @@ async function handleGetUsageExamples(args) {
|
|
|
6698
6734
|
}
|
|
6699
6735
|
//#endregion
|
|
6700
6736
|
//#region ../mcp-core/src/tools/create-task.ts
|
|
6701
|
-
const statusEnum$
|
|
6737
|
+
const statusEnum$10 = z.enum([
|
|
6702
6738
|
"NotStarted",
|
|
6703
6739
|
"InProgress",
|
|
6704
6740
|
"Blocked",
|
|
@@ -6708,7 +6744,7 @@ const statusEnum$9 = z.enum([
|
|
|
6708
6744
|
const CreateTaskInputSchema = {
|
|
6709
6745
|
name: z.string().min(1).describe("The task name — short and action-oriented, e.g. 'Chase rental schedule for year-end'."),
|
|
6710
6746
|
description: z.string().optional().describe("Longer description of what needs doing. Optional."),
|
|
6711
|
-
status: statusEnum$
|
|
6747
|
+
status: statusEnum$10.optional().describe("Initial status. Defaults to NotStarted — only override when the work is already underway (InProgress)."),
|
|
6712
6748
|
startDate: z.string().describe("When work should start (YYYY-MM-DD). For ad-hoc tasks, today is usually right — get today's date from the startup context."),
|
|
6713
6749
|
dueDate: z.string().describe("When the task is due (YYYY-MM-DD). Must be on or after startDate."),
|
|
6714
6750
|
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."),
|
|
@@ -6768,7 +6804,7 @@ async function handleCreateTask(api, args) {
|
|
|
6768
6804
|
}
|
|
6769
6805
|
//#endregion
|
|
6770
6806
|
//#region ../mcp-core/src/tools/update-task.ts
|
|
6771
|
-
const statusEnum$
|
|
6807
|
+
const statusEnum$9 = z.enum([
|
|
6772
6808
|
"NotStarted",
|
|
6773
6809
|
"InProgress",
|
|
6774
6810
|
"Blocked",
|
|
@@ -6777,7 +6813,7 @@ const statusEnum$8 = z.enum([
|
|
|
6777
6813
|
]);
|
|
6778
6814
|
const UpdateTaskInputSchema = {
|
|
6779
6815
|
taskCode: z.string().min(1).describe("The code of the task to update. Get it from list_tasks or get_task_context."),
|
|
6780
|
-
status: statusEnum$
|
|
6816
|
+
status: statusEnum$9.optional().describe("New status. 'Completed' for 'mark it done', 'InProgress' for 'I've started it', 'Blocked' when waiting on something. Note: completing a task does NOT complete its workflow steps — mention open steps to the user if get_task_context showed any."),
|
|
6781
6817
|
name: z.string().min(1).optional().describe("New task name. Omit to keep the current name."),
|
|
6782
6818
|
description: z.string().nullable().optional().describe("New description. Pass null to clear it. Omit to keep the current one."),
|
|
6783
6819
|
startDate: z.string().optional().describe("New start date (YYYY-MM-DD)."),
|
|
@@ -6893,7 +6929,7 @@ async function handleLogTimeEntry(api, args) {
|
|
|
6893
6929
|
}
|
|
6894
6930
|
//#endregion
|
|
6895
6931
|
//#region ../mcp-core/src/tools/list-sent-emails.ts
|
|
6896
|
-
const statusEnum$
|
|
6932
|
+
const statusEnum$8 = z.enum([
|
|
6897
6933
|
"Queued",
|
|
6898
6934
|
"Processing",
|
|
6899
6935
|
"Sent",
|
|
@@ -6906,7 +6942,7 @@ const sortByEnum$7 = z.enum([
|
|
|
6906
6942
|
"Status"
|
|
6907
6943
|
]);
|
|
6908
6944
|
const ListSentEmailsInputSchema = {
|
|
6909
|
-
status: statusEnum$
|
|
6945
|
+
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."),
|
|
6910
6946
|
search: z.string().optional().describe("Search in email subject and recipient email address — 'emails to john@acme.co.uk', 'the proposal email'."),
|
|
6911
6947
|
fromDate: z.string().optional().describe("Only emails created on or after this date (YYYY-MM-DD). Prefer setting a window — unbounded queries scan all history."),
|
|
6912
6948
|
toDate: z.string().optional().describe("Only emails created on or before this date (YYYY-MM-DD)."),
|
|
@@ -7189,7 +7225,7 @@ async function handleGetAmlClient(api, args) {
|
|
|
7189
7225
|
}
|
|
7190
7226
|
//#endregion
|
|
7191
7227
|
//#region ../mcp-core/src/tools/list-billing-line-items.ts
|
|
7192
|
-
const statusEnum$
|
|
7228
|
+
const statusEnum$7 = z.enum([
|
|
7193
7229
|
"Pending",
|
|
7194
7230
|
"Approved",
|
|
7195
7231
|
"Invoiced",
|
|
@@ -7202,7 +7238,7 @@ const sortByEnum$5 = z.enum([
|
|
|
7202
7238
|
"UnitPrice"
|
|
7203
7239
|
]);
|
|
7204
7240
|
const ListBillingLineItemsInputSchema = {
|
|
7205
|
-
status: statusEnum$
|
|
7241
|
+
status: statusEnum$7.optional().describe("Filter by line-item status. 'Pending' = generated, awaiting approval (the 'what's waiting to be billed?' question). 'Approved' = ready for invoicing. 'Invoiced' = sent to the accounting system. Single value — call per status for multiple."),
|
|
7206
7242
|
clientCode: z.string().optional().describe("Filter to one client's billing lines — 'what are we about to bill ACME?'."),
|
|
7207
7243
|
billableServiceCode: z.string().optional().describe("Filter by the billable service — 'what bookkeeping revenue is queued?'. Get codes from list_services."),
|
|
7208
7244
|
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."),
|
|
@@ -7265,7 +7301,7 @@ async function handleListBillingLineItems(api, args) {
|
|
|
7265
7301
|
}
|
|
7266
7302
|
//#endregion
|
|
7267
7303
|
//#region ../mcp-core/src/tools/update-workflow-step.ts
|
|
7268
|
-
const statusEnum$
|
|
7304
|
+
const statusEnum$6 = z.enum([
|
|
7269
7305
|
"NotStarted",
|
|
7270
7306
|
"InProgress",
|
|
7271
7307
|
"Blocked",
|
|
@@ -7276,7 +7312,7 @@ const UpdateWorkflowStepInputSchema = {
|
|
|
7276
7312
|
taskCode: z.string().min(1).describe("The task whose workflow step to update. Get it from list_tasks or get_task_context."),
|
|
7277
7313
|
groupNumber: z.number().int().min(1).describe("The workflow group number, exactly as shown in get_task_context's workflow section."),
|
|
7278
7314
|
stepNumber: z.number().int().min(1).describe("The step number within the group, exactly as shown in get_task_context."),
|
|
7279
|
-
status: statusEnum$
|
|
7315
|
+
status: statusEnum$6.optional().describe("New step status. 'Completed' for 'mark the review step done', 'Skipped' when the step doesn't apply, 'InProgress' when starting it, 'Blocked' with blockedReason when stuck. Steps may have dependencies — if the API rejects the change, report why rather than retrying."),
|
|
7280
7316
|
blockedReason: z.string().optional().describe("Why the step is blocked. Only meaningful with status='Blocked'."),
|
|
7281
7317
|
assignedUserCode: z.string().nullable().optional().describe("Reassign the step to this user code (resolve names via the roster or list_users). Pass null to unassign."),
|
|
7282
7318
|
assignedTeamCode: z.string().nullable().optional().describe("Assign the step to this team code (from list_teams), independent of the user assignment. Pass null to clear.")
|
|
@@ -7631,7 +7667,7 @@ async function handleListClientDocuments(api, args) {
|
|
|
7631
7667
|
}
|
|
7632
7668
|
//#endregion
|
|
7633
7669
|
//#region ../mcp-core/src/tools/list-data-forms.ts
|
|
7634
|
-
const statusEnum$
|
|
7670
|
+
const statusEnum$5 = z.enum([
|
|
7635
7671
|
"Draft",
|
|
7636
7672
|
"Published",
|
|
7637
7673
|
"Archived"
|
|
@@ -7639,7 +7675,7 @@ const statusEnum$4 = z.enum([
|
|
|
7639
7675
|
const sortByEnum$2 = z.enum(["Name", "Status"]);
|
|
7640
7676
|
const ListDataFormsInputSchema = {
|
|
7641
7677
|
search: z.string().optional().describe("Search over form name — 'the SA questionnaire', 'onboarding form'."),
|
|
7642
|
-
status: statusEnum$
|
|
7678
|
+
status: statusEnum$5.optional().describe("Filter by form status. 'Published' = sendable to clients (the usual filter); 'Draft' = still being built; 'Archived' = retired."),
|
|
7643
7679
|
sortBy: sortByEnum$2.optional().describe("Field to sort by. Defaults to Name."),
|
|
7644
7680
|
sortDesc: z.boolean().optional().describe("Sort in descending order. Defaults to ascending."),
|
|
7645
7681
|
limit: z.number().int().min(0).max(50).optional().describe("Maximum forms per page. Default 10, max 50. Pass 0 for count-only."),
|
|
@@ -7749,7 +7785,7 @@ async function handleGetTaskHistory(api, args) {
|
|
|
7749
7785
|
}
|
|
7750
7786
|
//#endregion
|
|
7751
7787
|
//#region ../mcp-core/src/tools/list-invoice-submissions.ts
|
|
7752
|
-
const statusEnum$
|
|
7788
|
+
const statusEnum$4 = z.enum([
|
|
7753
7789
|
"Pending",
|
|
7754
7790
|
"Submitted",
|
|
7755
7791
|
"Failed",
|
|
@@ -7762,7 +7798,7 @@ const sortByEnum$1 = z.enum([
|
|
|
7762
7798
|
"TotalAmount"
|
|
7763
7799
|
]);
|
|
7764
7800
|
const ListInvoiceSubmissionsInputSchema = {
|
|
7765
|
-
status: statusEnum$
|
|
7801
|
+
status: statusEnum$4.optional().describe("Filter by status. 'Failed' answers 'which invoices didn't reach the accounting system?' (the actionable one). 'Pending' = queued to submit; 'Submitted' = in the accounting system; 'Projected' = forecast only, not yet generated."),
|
|
7766
7802
|
clientCode: z.string().optional().describe("Filter to one client's invoice submissions."),
|
|
7767
7803
|
billingDateFrom: z.string().optional().describe("Only submissions with billing date on or after this date (YYYY-MM-DD)."),
|
|
7768
7804
|
billingDateTo: z.string().optional().describe("Only submissions with billing date on or before this date (YYYY-MM-DD)."),
|
|
@@ -7867,7 +7903,7 @@ async function handleListServicePackages(api, args) {
|
|
|
7867
7903
|
}
|
|
7868
7904
|
//#endregion
|
|
7869
7905
|
//#region ../mcp-core/src/tools/list-email-broadcasts.ts
|
|
7870
|
-
const statusEnum$
|
|
7906
|
+
const statusEnum$3 = z.enum([
|
|
7871
7907
|
"Draft",
|
|
7872
7908
|
"Queued",
|
|
7873
7909
|
"Sending",
|
|
@@ -7877,7 +7913,7 @@ const statusEnum$2 = z.enum([
|
|
|
7877
7913
|
]);
|
|
7878
7914
|
const sortByEnum = z.enum(["CreatedDate", "Status"]);
|
|
7879
7915
|
const ListEmailBroadcastsInputSchema = {
|
|
7880
|
-
status: statusEnum$
|
|
7916
|
+
status: statusEnum$3.optional().describe("Filter by broadcast status. 'Completed' for sent campaigns, 'Draft' for unsent, 'Failed' for broadcasts that errored."),
|
|
7881
7917
|
sortBy: sortByEnum.optional().describe("Field to sort by. Defaults to CreatedDate — pair with sortDesc=true for newest first."),
|
|
7882
7918
|
sortDesc: z.boolean().optional().describe("Sort in descending order. Defaults to ascending."),
|
|
7883
7919
|
limit: z.number().int().min(0).max(50).optional().describe("Maximum broadcasts per page. Default 10, max 50. Pass 0 for count-only."),
|
|
@@ -8308,7 +8344,7 @@ const frequencyEnum = z.enum([
|
|
|
8308
8344
|
"Quarterly",
|
|
8309
8345
|
"Monthly"
|
|
8310
8346
|
]);
|
|
8311
|
-
const statusEnum$
|
|
8347
|
+
const statusEnum$2 = z.enum([
|
|
8312
8348
|
"Active",
|
|
8313
8349
|
"Inactive",
|
|
8314
8350
|
"Paused",
|
|
@@ -8320,7 +8356,7 @@ const AddClientServiceInputSchema = {
|
|
|
8320
8356
|
billingFrequency: frequencyEnum.describe("How the service is billed to this client. Match one of the service's configured pricing options (visible in get_service_details)."),
|
|
8321
8357
|
startDate: z.string().describe("When the service starts for this client (YYYY-MM-DD)."),
|
|
8322
8358
|
endDate: z.string().optional().describe("Optional end date (YYYY-MM-DD). Omit for ongoing."),
|
|
8323
|
-
status: statusEnum$
|
|
8359
|
+
status: statusEnum$2.optional().describe("Service status. Defaults to Active. Use 'Proposed' when the service is being quoted but not yet agreed."),
|
|
8324
8360
|
price: z.number().min(0).optional().describe("Custom base price for this client. Only used with overridePricing=true — omit both to use the service's standard pricing."),
|
|
8325
8361
|
overridePricing: z.boolean().optional().describe("true = use the custom 'price' instead of the service's standard pricing. Must be accompanied by 'price'."),
|
|
8326
8362
|
pricingTierCode: z.string().optional().describe("Required when the service uses CustomTiers pricing (see get_service_details) — the tier code to apply."),
|
|
@@ -8374,6 +8410,66 @@ async function handleAddClientService(api, args) {
|
|
|
8374
8410
|
}
|
|
8375
8411
|
}
|
|
8376
8412
|
//#endregion
|
|
8413
|
+
//#region ../mcp-core/src/tools/apply-service-package.ts
|
|
8414
|
+
const statusEnum$1 = z.enum([
|
|
8415
|
+
"Active",
|
|
8416
|
+
"Inactive",
|
|
8417
|
+
"Paused",
|
|
8418
|
+
"Proposed"
|
|
8419
|
+
]);
|
|
8420
|
+
const ApplyServicePackageInputSchema = {
|
|
8421
|
+
clientCode: z.string().min(1).describe("The client to apply the package to. Get the code from list_clients."),
|
|
8422
|
+
packageCode: z.string().min(1).describe("The service package to apply. Get the code from list_service_packages — check its contents and pricing mode there first so you can tell the user what they are about to get and how it will be billed. Archived packages are rejected by the API."),
|
|
8423
|
+
startDate: z.string().optional().describe("When the package takes effect for this client (YYYY-MM-DD). Defaults to today. Use for 'put them on the Growth package from the 1st'."),
|
|
8424
|
+
status: statusEnum$1.optional().describe("Status the package is applied at. Defaults to Active. Use 'Proposed' when it is being quoted but not yet agreed — that also suppresses onboarding tasks, which are only created for Active services."),
|
|
8425
|
+
createOnboardingTasks: z.boolean().optional().describe("Whether to raise onboarding tasks from each service's templates. Defaults to true, which is right for a client genuinely starting the package now. Pass false when recording an arrangement that is already up and running — migrating historical data, or backdating a package the practice has been delivering for months — so the team isn't handed onboarding work for something already onboarded.")
|
|
8426
|
+
};
|
|
8427
|
+
function formatCreatedService(s) {
|
|
8428
|
+
const name = s.billableService?.name?.trim() || s.billableService?.code || "(unnamed service)";
|
|
8429
|
+
const frequency = s.billingFrequency ? ` · ${s.billingFrequency}` : "";
|
|
8430
|
+
return `- ${name} (${s.code ?? "no code"})${frequency}`;
|
|
8431
|
+
}
|
|
8432
|
+
async function handleApplyServicePackage(api, args) {
|
|
8433
|
+
try {
|
|
8434
|
+
const body = {
|
|
8435
|
+
status: args.status ?? "Active",
|
|
8436
|
+
startDate: args.startDate ?? null,
|
|
8437
|
+
createOnboardingTasks: args.createOnboardingTasks ?? true
|
|
8438
|
+
};
|
|
8439
|
+
const created = await api.applyServicePackageToClient(args.clientCode, args.packageCode, body);
|
|
8440
|
+
if (created.length === 0) return { content: [{
|
|
8441
|
+
type: "text",
|
|
8442
|
+
text: `Package ${args.packageCode} was applied to ${args.clientCode} but created no services — the package appears to be empty. Check its contents with list_service_packages.`
|
|
8443
|
+
}] };
|
|
8444
|
+
const packageName = created[0]?.servicePackage?.name?.trim() || args.packageCode;
|
|
8445
|
+
const startDate = created[0]?.startDate?.slice(0, 10);
|
|
8446
|
+
const status = created[0]?.status ?? args.status ?? "Active";
|
|
8447
|
+
const autoInvoiced = created.filter((s) => s.autoInvoice).length;
|
|
8448
|
+
const lines = [
|
|
8449
|
+
`Package applied: ${packageName} on client ${args.clientCode} — ${created.length} service${created.length === 1 ? "" : "s"} created`,
|
|
8450
|
+
`Status: ${status}${startDate ? ` · starting ${startDate}` : ""}`,
|
|
8451
|
+
"",
|
|
8452
|
+
...created.map(formatCreatedService),
|
|
8453
|
+
"",
|
|
8454
|
+
"What the client is billed follows the package's own pricing setup — either a single package price or each service priced separately. Check list_service_packages for the package's pricing, or get_client_summary for the client's resulting services."
|
|
8455
|
+
];
|
|
8456
|
+
if (args.createOnboardingTasks === false) lines.push("", "Onboarding tasks were suppressed for this application.");
|
|
8457
|
+
if (autoInvoiced > 0) lines.push("", `Auto-invoicing is on for ${autoInvoiced} of these services (the practice has client billing enabled), so they will generate billing lines automatically.`);
|
|
8458
|
+
return { content: [{
|
|
8459
|
+
type: "text",
|
|
8460
|
+
text: lines.join("\n")
|
|
8461
|
+
}] };
|
|
8462
|
+
} catch (error) {
|
|
8463
|
+
return {
|
|
8464
|
+
content: [{
|
|
8465
|
+
type: "text",
|
|
8466
|
+
text: error instanceof SodiumApiError ? `Error applying package ${args.packageCode} to ${args.clientCode}: ${error.message} (correlation: ${error.correlationId})` : `Error applying package ${args.packageCode} to ${args.clientCode}: ${error instanceof Error ? error.message : String(error)}`
|
|
8467
|
+
}],
|
|
8468
|
+
isError: true
|
|
8469
|
+
};
|
|
8470
|
+
}
|
|
8471
|
+
}
|
|
8472
|
+
//#endregion
|
|
8377
8473
|
//#region ../mcp-core/src/tools/update-client-service-stage.ts
|
|
8378
8474
|
const UpdateClientServiceStageInputSchema = {
|
|
8379
8475
|
clientCode: z.string().min(1).describe("The client whose service to move. Get the code from list_clients."),
|
|
@@ -8550,7 +8646,7 @@ const dateTypeEnum = z.enum([
|
|
|
8550
8646
|
"ConfirmationStatementNextMadeUpTo",
|
|
8551
8647
|
"LastConfirmationStatementFiled",
|
|
8552
8648
|
"LastConfirmationStatementMadeUpTo",
|
|
8553
|
-
"
|
|
8649
|
+
"NextVatReturnDueAnnual",
|
|
8554
8650
|
"NextVatPeriodEndQuarterly",
|
|
8555
8651
|
"NextVatReturnDueQuarterly",
|
|
8556
8652
|
"NextVatPeriodEndMonthly",
|
|
@@ -9262,7 +9358,7 @@ async function buildServer(config) {
|
|
|
9262
9358
|
}, (args) => handleListInvoiceSubmissions(api, args));
|
|
9263
9359
|
registerTenantTool(server, api, "list_service_packages", {
|
|
9264
9360
|
title: "List service packages (bundled offerings)",
|
|
9265
|
-
description: "List the practice's service packages — bundled offerings like a 'Growth package' — with contents (which services at which frequency), pricing mode (single package price vs per-service), and approximate annual value. Filters: search, contains-service (billableServiceCode array — 'which packages include bookkeeping?'), pagination. Complements list_services (individual catalogue).
|
|
9361
|
+
description: "List the practice's service packages — bundled offerings like a 'Growth package' — with contents (which services at which frequency), pricing mode (single package price vs per-service), and approximate annual value. Filters: search, contains-service (billableServiceCode array — 'which packages include bookkeeping?'), pagination. Complements list_services (individual catalogue). With write mode enabled, apply_service_package puts a client on one of these.",
|
|
9266
9362
|
inputSchema: ListServicePackagesInputSchema,
|
|
9267
9363
|
annotations: lookupAnnotations
|
|
9268
9364
|
}, (args) => handleListServicePackages(api, args));
|
|
@@ -9307,6 +9403,17 @@ async function buildServer(config) {
|
|
|
9307
9403
|
openWorldHint: true
|
|
9308
9404
|
}
|
|
9309
9405
|
}, (args) => handleAddClientService(api, args));
|
|
9406
|
+
registerWriteTool(server, api, config.context, "apply_service_package", {
|
|
9407
|
+
title: "Apply a service package to a client",
|
|
9408
|
+
description: "Put a client on one of the practice's bundled packages in a single call. The package definition decides everything: which services the client gets, at what frequency, and whether they are billed as one package price or service by service. Requires clientCode (list_clients) and packageCode (list_service_packages — check its contents and pricing mode there first so you can tell the user what they are agreeing to). Optional: startDate (defaults to today), status (defaults to Active; use 'Proposed' when quoting), and createOnboardingTasks (defaults to true; pass false when recording an arrangement that is already running, e.g. migrating or backdating). Use for 'put ACME on the Growth package', 'add the Startup bundle to Bristol Roofing from 1 April'. IMPORTANT: (1) The API does NOT check whether the client already has this package — calling twice applies it a SECOND time. Always check get_client_summary first and tell the user if it is already there rather than applying again. (2) Prefer this over repeated add_client_service calls: only this tool links the result to the package, which is what makes package pricing and package removal work in the Sodium UI. (3) Auto-invoicing follows the practice's client-billing settings; it is not controllable per call. (4) Nothing about the package can be overridden per client — use add_client_service for a bespoke arrangement. Archived packages are rejected.",
|
|
9409
|
+
inputSchema: ApplyServicePackageInputSchema,
|
|
9410
|
+
annotations: {
|
|
9411
|
+
readOnlyHint: false,
|
|
9412
|
+
destructiveHint: false,
|
|
9413
|
+
idempotentHint: false,
|
|
9414
|
+
openWorldHint: true
|
|
9415
|
+
}
|
|
9416
|
+
}, (args) => handleApplyServicePackage(api, args));
|
|
9310
9417
|
registerWriteTool(server, api, config.context, "update_client_service_stage", {
|
|
9311
9418
|
title: "Move a client's service to a different stage (kanban)",
|
|
9312
9419
|
description: "Move one client-service card to a different workflow stage — the kanban move, from chat. Requires the client code, the client's service-subscription code (from get_client_summary's services section — NOT the catalogue service code), and the target stageCode (stage codes are in get_service_details for the underlying service). Pass stageCode=null to clear the stage. Use for 'move ACME's VAT return to Records In', 'mark the bookkeeping as Finished on the board'. Note: workflows can also set stages automatically via SetServiceStage steps — a manual move may be overridden by the next automated step.",
|