@sodiumhq/mcp-pm 0.1.0-beta.6359 → 0.1.0-beta.6554
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-B3pnHPhY.mjs → src-B4pIPf3c.mjs} +382 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -92,6 +92,7 @@ Only enable write mode with an AI client you trust — it hands the client the a
|
|
|
92
92
|
|
|
93
93
|
**Client requests**
|
|
94
94
|
- **`list_document_requests`** — document requests across the practice: status, deadline, chase history, linked task. "What are we waiting on from clients right now?"
|
|
95
|
+
- **`list_overdue_requests`** — document requests past their deadline and still waiting on the client, most overdue first, with chase history. "Who do we need to chase?"
|
|
95
96
|
- **`list_data_form_requests`** — questionnaire/data-form requests: who's been sent what, who's responded, which answers await review. "Who still hasn't returned the SA questionnaire?"
|
|
96
97
|
- **`list_data_forms`** — the questionnaire templates the practice can send.
|
|
97
98
|
|
|
@@ -162,6 +163,8 @@ Only enable write mode with an AI client you trust — it hands the client the a
|
|
|
162
163
|
- **`create_document_request`** — draft (never send) a document request for a client. Sending stays in the Sodium UI.
|
|
163
164
|
- **`send_data_form`** — send a questionnaire to a client. Sends immediately, so your assistant confirms with you first.
|
|
164
165
|
- **`create_proposal`** — draft a proposal/letter of engagement in Unsent state. Sending stays in the Sodium UI.
|
|
166
|
+
- **`list_upcoming_key_dates`** — clients whose accounts, confirmation statement, PAYE/CIS or other key date falls in a window, soonest first. "Whose accounts are due next month?"
|
|
167
|
+
- **`draft_email_to_client`** — save a draft email in your own connected mailbox. Never sends; you review and send it yourself.
|
|
165
168
|
- **`update_client_dates`** — set year-end, VAT, PAYE, and other key dates on a client.
|
|
166
169
|
- **`update_client_business_details`** — update UTR, PAYE ref, VAT registration, trading name, addresses. Only the fields you mention change.
|
|
167
170
|
- **`create_saved_task_filter`** — save a task query as a reusable view. "Save that as 'Monday Review' and pin it to my menu."
|
package/dist/http.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as buildServer, t as buildCatalogue } from "./src-
|
|
2
|
+
import { n as buildServer, t as buildCatalogue } from "./src-B4pIPf3c.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";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as buildServer } from "./src-
|
|
2
|
+
import { n as buildServer } from "./src-B4pIPf3c.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
5
5
|
import { parseArgs } from "node:util";
|
|
@@ -1364,6 +1364,22 @@ const getEmailBroadcastRecipients = (options) => (options.client ?? client).get(
|
|
|
1364
1364
|
...options
|
|
1365
1365
|
});
|
|
1366
1366
|
/**
|
|
1367
|
+
* List Email Configs
|
|
1368
|
+
*
|
|
1369
|
+
* Returns a paginated list of email configurations the current user can access (owned or shared). EmailConnections admins see all configurations including other users' private ones.
|
|
1370
|
+
*/
|
|
1371
|
+
const listEmailConfigs = (options) => (options.client ?? client).get({
|
|
1372
|
+
security: [{
|
|
1373
|
+
name: "x-api-key",
|
|
1374
|
+
type: "apiKey"
|
|
1375
|
+
}, {
|
|
1376
|
+
scheme: "bearer",
|
|
1377
|
+
type: "http"
|
|
1378
|
+
}],
|
|
1379
|
+
url: "/tenants/{tenant}/email-configs",
|
|
1380
|
+
...options
|
|
1381
|
+
});
|
|
1382
|
+
/**
|
|
1367
1383
|
* List Email History
|
|
1368
1384
|
*
|
|
1369
1385
|
* Returns a paginated list of emails sent for the tenant. Supports filtering by status, date range, search, and metadata fields. Metadata filters use the format metadataParam=key=value (e.g., metadataParam=entityType=Proposal).
|
|
@@ -1412,6 +1428,26 @@ const getMailboxConversation = (options) => (options.client ?? client).get({
|
|
|
1412
1428
|
...options
|
|
1413
1429
|
});
|
|
1414
1430
|
/**
|
|
1431
|
+
* Create Draft
|
|
1432
|
+
*
|
|
1433
|
+
* Creates a new draft via the email provider. The configuration must have ReadEnabled set to true. The returned UniqueId is used for subsequent Update/Send/Discard calls.
|
|
1434
|
+
*/
|
|
1435
|
+
const createMailboxDraft = (options) => (options.client ?? client).post({
|
|
1436
|
+
security: [{
|
|
1437
|
+
name: "x-api-key",
|
|
1438
|
+
type: "apiKey"
|
|
1439
|
+
}, {
|
|
1440
|
+
scheme: "bearer",
|
|
1441
|
+
type: "http"
|
|
1442
|
+
}],
|
|
1443
|
+
url: "/tenants/{tenant}/email-configs/{code}/drafts",
|
|
1444
|
+
...options,
|
|
1445
|
+
headers: {
|
|
1446
|
+
"Content-Type": "application/json",
|
|
1447
|
+
...options.headers
|
|
1448
|
+
}
|
|
1449
|
+
});
|
|
1450
|
+
/**
|
|
1415
1451
|
* Get engagement settings
|
|
1416
1452
|
*
|
|
1417
1453
|
* Returns the engagement settings for the specified tenant
|
|
@@ -3393,6 +3429,37 @@ var SodiumApiClient = class {
|
|
|
3393
3429
|
if (error !== void 0 || !data) throw this.toError(response, error, correlationId, `list emails for client ${clientCode}`);
|
|
3394
3430
|
return data;
|
|
3395
3431
|
}
|
|
3432
|
+
async findOwnDraftMailbox() {
|
|
3433
|
+
const correlationId = randomUUID();
|
|
3434
|
+
const userCode = await this.getCurrentTenantUserCode();
|
|
3435
|
+
const { data, error, response } = await listEmailConfigs({
|
|
3436
|
+
path: { tenant: this.tenant },
|
|
3437
|
+
query: {
|
|
3438
|
+
status: "Active",
|
|
3439
|
+
limit: 50,
|
|
3440
|
+
offset: 0
|
|
3441
|
+
},
|
|
3442
|
+
headers: { "X-Correlation-Id": correlationId },
|
|
3443
|
+
client: this.http
|
|
3444
|
+
});
|
|
3445
|
+
if (error !== void 0 || !data) throw this.toError(response, error, correlationId, "list your connected mailboxes");
|
|
3446
|
+
const own = (data.data ?? []).filter((c) => c.tenantUser?.code === userCode && c.readEnabled && c.code);
|
|
3447
|
+
return own.find((c) => c.isDefault) ?? own[0];
|
|
3448
|
+
}
|
|
3449
|
+
async createMailboxDraft(mailboxCode, body) {
|
|
3450
|
+
const correlationId = randomUUID();
|
|
3451
|
+
const { data, error, response } = await createMailboxDraft({
|
|
3452
|
+
path: {
|
|
3453
|
+
tenant: this.tenant,
|
|
3454
|
+
code: mailboxCode
|
|
3455
|
+
},
|
|
3456
|
+
body,
|
|
3457
|
+
headers: { "X-Correlation-Id": correlationId },
|
|
3458
|
+
client: this.http
|
|
3459
|
+
});
|
|
3460
|
+
if (error !== void 0 || !data) throw this.toError(response, error, correlationId, "save the email draft");
|
|
3461
|
+
return data;
|
|
3462
|
+
}
|
|
3396
3463
|
async listAmlClients(query = {}) {
|
|
3397
3464
|
const correlationId = randomUUID();
|
|
3398
3465
|
const { data, error, response } = await getAmlClients({
|
|
@@ -4052,7 +4119,7 @@ async function handleGetPracticeDetails(api) {
|
|
|
4052
4119
|
}
|
|
4053
4120
|
//#endregion
|
|
4054
4121
|
//#region ../mcp-core/src/tools/list-clients.ts
|
|
4055
|
-
const statusEnum$
|
|
4122
|
+
const statusEnum$16 = z.enum([
|
|
4056
4123
|
"Active",
|
|
4057
4124
|
"Inactive",
|
|
4058
4125
|
"Prospect",
|
|
@@ -4071,7 +4138,7 @@ const typeEnum$1 = z.enum([
|
|
|
4071
4138
|
const sortByEnum$20 = z.enum(["Name", "InternalReference"]);
|
|
4072
4139
|
const ListClientsInputSchema = {
|
|
4073
4140
|
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."),
|
|
4074
|
-
status: z.array(statusEnum$
|
|
4141
|
+
status: z.array(statusEnum$16).optional().describe("Filter by client status. Defaults to all statuses if omitted. Example: ['Active'] for active clients only."),
|
|
4075
4142
|
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."),
|
|
4076
4143
|
managerCode: z.array(z.string()).optional().describe("Filter by assigned manager user codes."),
|
|
4077
4144
|
partnerCode: z.array(z.string()).optional().describe("Filter by assigned partner user codes."),
|
|
@@ -4884,7 +4951,7 @@ function describeFilters$3(args) {
|
|
|
4884
4951
|
}
|
|
4885
4952
|
//#endregion
|
|
4886
4953
|
//#region ../mcp-core/src/tools/list-tasks.ts
|
|
4887
|
-
const statusEnum$
|
|
4954
|
+
const statusEnum$15 = z.enum([
|
|
4888
4955
|
"NotStarted",
|
|
4889
4956
|
"InProgress",
|
|
4890
4957
|
"Blocked",
|
|
@@ -4945,7 +5012,7 @@ const stepTypeEnum$1 = z.enum([
|
|
|
4945
5012
|
const ListTasksInputSchema = {
|
|
4946
5013
|
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
5014
|
client: z.array(z.string()).optional().describe("Filter by client codes — tasks belonging to these specific clients only."),
|
|
4948
|
-
status: z.array(statusEnum$
|
|
5015
|
+
status: z.array(statusEnum$15).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
5016
|
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
5017
|
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
5018
|
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."),
|
|
@@ -5045,7 +5112,7 @@ function describeFilters$2(args) {
|
|
|
5045
5112
|
}
|
|
5046
5113
|
//#endregion
|
|
5047
5114
|
//#region ../mcp-core/src/tools/list-users.ts
|
|
5048
|
-
const statusEnum$
|
|
5115
|
+
const statusEnum$14 = z.enum([
|
|
5049
5116
|
"Created",
|
|
5050
5117
|
"Invited",
|
|
5051
5118
|
"Active",
|
|
@@ -5065,7 +5132,7 @@ const sortByEnum$17 = z.enum([
|
|
|
5065
5132
|
]);
|
|
5066
5133
|
const ListUsersInputSchema = {
|
|
5067
5134
|
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)."),
|
|
5068
|
-
status: statusEnum$
|
|
5135
|
+
status: statusEnum$14.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."),
|
|
5069
5136
|
systemRole: systemRoleEnum.optional().describe("Filter by system role. Admin = full access, StandardUser = normal access, Viewer = read-only. Single value."),
|
|
5070
5137
|
isClientManager: z.boolean().optional().describe("Set true to return only users flagged as client managers; false to exclude them."),
|
|
5071
5138
|
isPartner: z.boolean().optional().describe("Set true to return only users flagged as partners; false to exclude them. Use for 'list partners' queries."),
|
|
@@ -5632,7 +5699,7 @@ const GetServiceDeliveryReportInputSchema = {
|
|
|
5632
5699
|
...reportEntityFilters,
|
|
5633
5700
|
...reportPagingFields
|
|
5634
5701
|
};
|
|
5635
|
-
function formatRow$
|
|
5702
|
+
function formatRow$8(r) {
|
|
5636
5703
|
const statutory = (r.statutoryCount ?? 0) > 0 ? ` · statutory ${fmtPct(r.statutoryOnTimePct)} (of ${r.statutoryCount})` : "";
|
|
5637
5704
|
const worst = fmtRef(r.worstTask);
|
|
5638
5705
|
return `- ${r.groupName}: ${r.completedCount} completed · avg ${fmtDays(r.avgTurnaroundDays)} / median ${fmtDays(r.medianTurnaroundDays)} · on-time ${fmtPct(r.onTimePct)}${statutory} · worst ${r.worstCaseDays ?? 0}d${worst ? ` — ${worst}` : ""}`;
|
|
@@ -5648,7 +5715,7 @@ async function handleGetServiceDeliveryReport(api, args) {
|
|
|
5648
5715
|
`Summary: ${s?.completedCount ?? 0} completed · avg ${fmtDays(s?.avgTurnaroundDays)} / median ${fmtDays(s?.medianTurnaroundDays)} turnaround · on-time ${fmtPct(s?.onTimePct)}` + ((s?.statutoryCount ?? 0) > 0 ? ` · statutory compliance ${fmtPct(s?.statutoryOnTimePct)} (of ${s?.statutoryCount} with statutory deadlines)` : " · no statutory deadlines in period") + ` · ${s?.overdueCount ?? 0} currently overdue (now-snapshot, ignores the window)`,
|
|
5649
5716
|
"",
|
|
5650
5717
|
`Breakdown by ${args.groupBy ?? "Service"} (${header}):`,
|
|
5651
|
-
...rows.map(formatRow$
|
|
5718
|
+
...rows.map(formatRow$8)
|
|
5652
5719
|
];
|
|
5653
5720
|
if (rows.length === 0) lines.push("(no tasks completed in the window)");
|
|
5654
5721
|
if (hint) lines.push("", hint);
|
|
@@ -5723,7 +5790,7 @@ const GetDeadlineComplianceReportInputSchema = {
|
|
|
5723
5790
|
...reportEntityFilters,
|
|
5724
5791
|
...reportPagingFields
|
|
5725
5792
|
};
|
|
5726
|
-
function formatRow$
|
|
5793
|
+
function formatRow$7(r) {
|
|
5727
5794
|
const worst = fmtRef(r.worstTask);
|
|
5728
5795
|
const pending = (r.pendingCount ?? 0) > 0 ? ` · ${r.pendingCount} not yet due` : "";
|
|
5729
5796
|
return `- ${r.groupName}: ${r.deadlineCount} deadlines · ${r.metCount} met (${fmtPct(r.compliancePct)}) · ${r.lateCount} completed late · ${r.missedOpenCount} missed & still open${pending} · avg ${fmtDays(r.avgDaysLate)} late${worst ? ` · worst ${r.worstDaysLate}d — ${worst}` : ""}`;
|
|
@@ -5742,7 +5809,7 @@ async function handleGetDeadlineComplianceReport(api, args) {
|
|
|
5742
5809
|
"Upcoming (now-snapshot of OPEN tasks, ignores the window): " + (report.upcomingDeadlines ?? []).map((b) => `${b.count} ${b.code === "overdue" ? "overdue" : `due in ${b.code} days`}`).join(" · "),
|
|
5743
5810
|
"",
|
|
5744
5811
|
`Breakdown by ${args.groupBy ?? "Service"} (${header}):`,
|
|
5745
|
-
...rows.map(formatRow$
|
|
5812
|
+
...rows.map(formatRow$7)
|
|
5746
5813
|
];
|
|
5747
5814
|
if (rows.length === 0) lines.push(`(no ${basis} fell in the window)`);
|
|
5748
5815
|
if (hint) lines.push("", hint);
|
|
@@ -5769,7 +5836,7 @@ const GetClientResponsivenessReportInputSchema = {
|
|
|
5769
5836
|
category: reportEntityFilters.category,
|
|
5770
5837
|
...reportPagingFields
|
|
5771
5838
|
};
|
|
5772
|
-
function formatRow$
|
|
5839
|
+
function formatRow$6(r) {
|
|
5773
5840
|
const worst = fmtRef(r.worstTask);
|
|
5774
5841
|
return `- ${r.groupName}: ${r.stepCount} steps · avg ${fmtDays(r.avgResponseDays)} / median ${fmtDays(r.medianResponseDays)} response · ${r.totalChases} chases (${fmtPct(r.chasedPct)} of steps chased)` + (r.worstCaseDays != null ? ` · worst ${fmtDays(r.worstCaseDays)}${worst ? ` — ${worst}` : ""}` : "");
|
|
5775
5842
|
}
|
|
@@ -5784,7 +5851,7 @@ async function handleGetClientResponsivenessReport(api, args) {
|
|
|
5784
5851
|
`Summary: ${s?.stepCount ?? 0} steps · avg ${fmtDays(s?.avgResponseDays)} / median ${fmtDays(s?.medianResponseDays)} response · ${s?.totalChases ?? 0} chases (${fmtPct(s?.chasedPct)} of steps chased) · ${s?.outstandingCount ?? 0} currently waiting on a client (now-snapshot${s?.oldestOutstandingDays != null ? `, oldest ${fmtDays(s.oldestOutstandingDays)}` : ""})`,
|
|
5785
5852
|
"",
|
|
5786
5853
|
`Breakdown by ${args.groupBy ?? "Client"} (${header}):`,
|
|
5787
|
-
...rows.map(formatRow$
|
|
5854
|
+
...rows.map(formatRow$6)
|
|
5788
5855
|
];
|
|
5789
5856
|
if (rows.length === 0) lines.push("(no client-facing steps completed in the window)");
|
|
5790
5857
|
if (hint) lines.push("", hint);
|
|
@@ -5807,7 +5874,7 @@ const GetTeamWorkloadReportInputSchema = {
|
|
|
5807
5874
|
category: reportEntityFilters.category,
|
|
5808
5875
|
...reportPagingFields
|
|
5809
5876
|
};
|
|
5810
|
-
function formatRow$
|
|
5877
|
+
function formatRow$5(r) {
|
|
5811
5878
|
const open = `${r.openCount} open (${r.overdueCount} overdue, ${r.blockedCount} blocked)`;
|
|
5812
5879
|
const done = (r.completedCount ?? 0) > 0 ? `${r.completedCount} completed · avg ${fmtDays(r.avgTurnaroundDays)} · on-time ${fmtPct(r.onTimePct)}` : "0 completed";
|
|
5813
5880
|
return `- ${r.groupName}: ${open} · ${done}`;
|
|
@@ -5823,7 +5890,7 @@ async function handleGetTeamWorkloadReport(api, args) {
|
|
|
5823
5890
|
`Summary: ${s?.openCount ?? 0} open (${s?.overdueCount ?? 0} overdue, ${s?.blockedCount ?? 0} blocked, ${s?.unassignedCount ?? 0} unassigned) · ${s?.completedCount ?? 0} completed in window · avg turnaround ${fmtDays(s?.avgTurnaroundDays)}`,
|
|
5824
5891
|
"",
|
|
5825
5892
|
`Breakdown by ${args.groupBy ?? "User"} (busiest first, ${header}):`,
|
|
5826
|
-
...rows.map(formatRow$
|
|
5893
|
+
...rows.map(formatRow$5)
|
|
5827
5894
|
];
|
|
5828
5895
|
if (rows.length === 0) lines.push("(no open or completed tasks match the filters)");
|
|
5829
5896
|
if (hint) lines.push("", hint);
|
|
@@ -5847,7 +5914,7 @@ const GetWipAgeingReportInputSchema = {
|
|
|
5847
5914
|
...reportEntityFilters,
|
|
5848
5915
|
...reportPagingFields
|
|
5849
5916
|
};
|
|
5850
|
-
function formatRow$
|
|
5917
|
+
function formatRow$4(r) {
|
|
5851
5918
|
const oldest = fmtRef(r.oldestTask);
|
|
5852
5919
|
return `- ${r.groupName}: ${r.count} open · avg age ${fmtDays(r.avgAgeDays)} · oldest ${r.oldestAgeDays ?? 0}d${oldest ? ` — ${oldest}` : ""} · ${r.blockedCount} blocked · ${r.overdueCount} overdue`;
|
|
5853
5920
|
}
|
|
@@ -5863,7 +5930,7 @@ async function handleGetWipAgeingReport(api, args) {
|
|
|
5863
5930
|
`Summary: ${s?.openCount ?? 0} open · ${s?.notStartedCount ?? 0} not started · ${s?.blockedCount ?? 0} blocked · ${s?.overdueCount ?? 0} overdue · avg age ${fmtDays(s?.avgAgeDays)}${s?.oldestAgeDays != null ? ` · oldest ${s.oldestAgeDays}d${oldest ? ` — ${oldest}` : ""}` : ""}`,
|
|
5864
5931
|
"",
|
|
5865
5932
|
`Breakdown by ${args.groupBy ?? "AgeBucket"} (${header}):`,
|
|
5866
|
-
...rows.map(formatRow$
|
|
5933
|
+
...rows.map(formatRow$4)
|
|
5867
5934
|
];
|
|
5868
5935
|
if (rows.length === 0) lines.push("(no open work matches the filters)");
|
|
5869
5936
|
if (hint) lines.push("", hint);
|
|
@@ -6742,7 +6809,7 @@ async function handleGetCompaniesHouseProfile(api, args) {
|
|
|
6742
6809
|
}
|
|
6743
6810
|
//#endregion
|
|
6744
6811
|
//#region ../mcp-core/src/tools/list-document-requests.ts
|
|
6745
|
-
const statusEnum$
|
|
6812
|
+
const statusEnum$13 = z.enum([
|
|
6746
6813
|
"Draft",
|
|
6747
6814
|
"Open",
|
|
6748
6815
|
"Submitted",
|
|
@@ -6756,7 +6823,7 @@ const sortByEnum$11 = z.enum([
|
|
|
6756
6823
|
"Deadline"
|
|
6757
6824
|
]);
|
|
6758
6825
|
const ListDocumentRequestsInputSchema = {
|
|
6759
|
-
status: statusEnum$
|
|
6826
|
+
status: statusEnum$13.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."),
|
|
6760
6827
|
clientCode: z.string().optional().describe("Filter to one client's document requests. Get the code from list_clients."),
|
|
6761
6828
|
managerCode: z.string().optional().describe("Filter by the client manager's user code — 'what are MY clients waiting to send us?' for a manager."),
|
|
6762
6829
|
partnerCode: z.string().optional().describe("Filter by the client partner's user code."),
|
|
@@ -6821,6 +6888,274 @@ async function handleListDocumentRequests(api, args) {
|
|
|
6821
6888
|
}
|
|
6822
6889
|
}
|
|
6823
6890
|
//#endregion
|
|
6891
|
+
//#region ../mcp-core/src/tools/list-overdue-requests.ts
|
|
6892
|
+
const PAGE_SIZE = 50;
|
|
6893
|
+
const MAX_PAGES = 4;
|
|
6894
|
+
const ListOverdueRequestsInputSchema = {
|
|
6895
|
+
clientCode: z.string().optional().describe("Only this client's overdue requests. Get the code from list_clients."),
|
|
6896
|
+
managerCode: z.string().optional().describe("Only requests for clients with this manager (user code) — 'which of Jane's clients owe us documents?'."),
|
|
6897
|
+
partnerCode: z.string().optional().describe("Only requests for clients with this partner (user code)."),
|
|
6898
|
+
taskCategoryCode: z.string().optional().describe("Only requests linked to tasks in this category. Codes come from list_task_categories."),
|
|
6899
|
+
limit: z.number().int().min(1).max(50).optional().describe("Most overdue requests to return. Default 20, max 50.")
|
|
6900
|
+
};
|
|
6901
|
+
function daysOverdue(deadline, today) {
|
|
6902
|
+
return Math.floor((Date.parse(today) - Date.parse(deadline.slice(0, 10))) / 864e5);
|
|
6903
|
+
}
|
|
6904
|
+
function formatRow$3(r, today) {
|
|
6905
|
+
const client = r.client?.name ? ` · ${r.client.name}${r.client.code ? ` (${r.client.code})` : ""}` : "";
|
|
6906
|
+
const days = daysOverdue(r.deadline, today);
|
|
6907
|
+
const chase = r.chase?.lastChasedDate ? `last chased ${r.chase.lastChasedDate.slice(0, 10)}` : "never chased";
|
|
6908
|
+
const next = r.chase?.nextChaseDate ? `, next chase ${r.chase.nextChaseDate.slice(0, 10)}` : "";
|
|
6909
|
+
return `- ${days} day${days === 1 ? "" : "s"} overdue · ${r.title ?? "(untitled)"} (${r.code ?? "no code"})${client} · deadline ${r.deadline.slice(0, 10)} · ${chase}${next}`;
|
|
6910
|
+
}
|
|
6911
|
+
async function handleListOverdueRequests(api, args) {
|
|
6912
|
+
try {
|
|
6913
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
6914
|
+
const overdue = [];
|
|
6915
|
+
let scanned = 0;
|
|
6916
|
+
let more = false;
|
|
6917
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
6918
|
+
const result = await api.listDocumentRequests({
|
|
6919
|
+
status: "Open",
|
|
6920
|
+
clientCode: args.clientCode,
|
|
6921
|
+
managerCode: args.managerCode,
|
|
6922
|
+
partnerCode: args.partnerCode,
|
|
6923
|
+
taskCategoryCode: args.taskCategoryCode,
|
|
6924
|
+
sortBy: "Deadline",
|
|
6925
|
+
sortDesc: false,
|
|
6926
|
+
limit: PAGE_SIZE,
|
|
6927
|
+
offset: page * PAGE_SIZE
|
|
6928
|
+
});
|
|
6929
|
+
const items = result.data ?? [];
|
|
6930
|
+
scanned += items.length;
|
|
6931
|
+
let reachedNotDue = false;
|
|
6932
|
+
for (const r of items) if (r.deadline && r.deadline.slice(0, 10) < today) overdue.push(r);
|
|
6933
|
+
else if (r.deadline) {
|
|
6934
|
+
reachedNotDue = true;
|
|
6935
|
+
break;
|
|
6936
|
+
}
|
|
6937
|
+
if (reachedNotDue || !result.hasMore) break;
|
|
6938
|
+
if (page === MAX_PAGES - 1) more = true;
|
|
6939
|
+
}
|
|
6940
|
+
if (overdue.length === 0) return { content: [{
|
|
6941
|
+
type: "text",
|
|
6942
|
+
text: more ? `No overdue document requests found in the first ${scanned} open requests checked.` : "No overdue document requests. Every open request is within its deadline or has none."
|
|
6943
|
+
}] };
|
|
6944
|
+
overdue.sort((a, b) => a.deadline.localeCompare(b.deadline));
|
|
6945
|
+
const limit = args.limit ?? 20;
|
|
6946
|
+
const shown = overdue.slice(0, limit);
|
|
6947
|
+
const lines = [
|
|
6948
|
+
overdue.length > shown.length ? `${overdue.length} overdue document requests (showing the ${shown.length} most overdue):` : `${overdue.length} overdue document request${overdue.length === 1 ? "" : "s"}, most overdue first:`,
|
|
6949
|
+
"",
|
|
6950
|
+
...shown.map((r) => formatRow$3(r, today))
|
|
6951
|
+
];
|
|
6952
|
+
if (more) lines.push("", `Checked the first ${scanned} open requests only; narrow with clientCode or managerCode to be sure nothing is missed.`);
|
|
6953
|
+
return { content: [{
|
|
6954
|
+
type: "text",
|
|
6955
|
+
text: lines.join("\n")
|
|
6956
|
+
}] };
|
|
6957
|
+
} catch (error) {
|
|
6958
|
+
return {
|
|
6959
|
+
content: [{
|
|
6960
|
+
type: "text",
|
|
6961
|
+
text: error instanceof SodiumApiError ? `Error listing overdue requests: ${error.message} (correlation: ${error.correlationId})` : `Error listing overdue requests: ${error instanceof Error ? error.message : String(error)}`
|
|
6962
|
+
}],
|
|
6963
|
+
isError: true
|
|
6964
|
+
};
|
|
6965
|
+
}
|
|
6966
|
+
}
|
|
6967
|
+
//#endregion
|
|
6968
|
+
//#region ../mcp-core/src/tools/list-upcoming-key-dates.ts
|
|
6969
|
+
const keyDateTypeEnum = z.enum([
|
|
6970
|
+
"AccountsNextDue",
|
|
6971
|
+
"ConfirmationStatementDue",
|
|
6972
|
+
"NextVatReturnDueAnnual",
|
|
6973
|
+
"NextPayeCisFilingDue",
|
|
6974
|
+
"PensionReEnrolmentDate",
|
|
6975
|
+
"CharityAnnualReturnDue",
|
|
6976
|
+
"CharityAccountsDue",
|
|
6977
|
+
"YearEnd",
|
|
6978
|
+
"AccountsNextPeriodStart",
|
|
6979
|
+
"AccountsNextPeriodEnd",
|
|
6980
|
+
"ConfirmationStatementNextMadeUpTo",
|
|
6981
|
+
"IncorporationDate",
|
|
6982
|
+
"AccountsLastPeriodFiled",
|
|
6983
|
+
"AccountsLastPeriodStart",
|
|
6984
|
+
"AccountsLastPeriodEnd",
|
|
6985
|
+
"AccountsLastMadeUpTo",
|
|
6986
|
+
"LastConfirmationStatementFiled",
|
|
6987
|
+
"LastConfirmationStatementMadeUpTo",
|
|
6988
|
+
"CharityLastAnnualReturnReceived",
|
|
6989
|
+
"CharityLastAccountsReceived",
|
|
6990
|
+
"CharityFinancialYearEnd"
|
|
6991
|
+
]);
|
|
6992
|
+
const rangeEnum = z.enum([
|
|
6993
|
+
"Today",
|
|
6994
|
+
"ThisWeek",
|
|
6995
|
+
"NextWeek",
|
|
6996
|
+
"Next7Days",
|
|
6997
|
+
"Next30Days",
|
|
6998
|
+
"ThisMonth",
|
|
6999
|
+
"NextMonth",
|
|
7000
|
+
"ThisQuarter",
|
|
7001
|
+
"NextQuarter",
|
|
7002
|
+
"ThisYear",
|
|
7003
|
+
"NextYear",
|
|
7004
|
+
"ThisTaxYear",
|
|
7005
|
+
"NextTaxYear",
|
|
7006
|
+
"CustomDateRange"
|
|
7007
|
+
]);
|
|
7008
|
+
const statusEnum$12 = z.enum([
|
|
7009
|
+
"Active",
|
|
7010
|
+
"Inactive",
|
|
7011
|
+
"Prospect",
|
|
7012
|
+
"LostProspect"
|
|
7013
|
+
]);
|
|
7014
|
+
const ListUpcomingKeyDatesInputSchema = {
|
|
7015
|
+
keyDateType: keyDateTypeEnum.describe("Which key date to look at. 'AccountsNextDue' = statutory accounts filing deadline, 'ConfirmationStatementDue' = Companies House confirmation statement, 'NextVatReturnDueAnnual' = annual-scheme VAT return, 'NextPayeCisFilingDue' = PAYE/CIS filing, 'PensionReEnrolmentDate' = auto-enrolment re-enrolment, 'CharityAnnualReturnDue' / 'CharityAccountsDue' = charity filings, 'YearEnd' = accounting year end. The quarterly and monthly VAT return dates and MTD ITSA quarter dates are calculated per client and cannot be searched this way."),
|
|
7016
|
+
keyDateRange: rangeEnum.optional().describe("Window the date must fall in. Defaults to Next30Days. Use 'Next7Days' for 'due this week or so', 'ThisMonth' / 'NextMonth' for month views, 'ThisQuarter' / 'NextQuarter' for quarter planning. 'CustomDateRange' needs keyDateFrom and keyDateTo."),
|
|
7017
|
+
keyDateFrom: z.string().optional().describe("Start of the window (YYYY-MM-DD, inclusive). Only with keyDateRange='CustomDateRange'."),
|
|
7018
|
+
keyDateTo: z.string().optional().describe("End of the window (YYYY-MM-DD, inclusive). Only with keyDateRange='CustomDateRange'."),
|
|
7019
|
+
status: z.array(statusEnum$12).optional().describe("Client statuses to include. Defaults to ['Active'] so dormant clients and prospects don't clutter the list."),
|
|
7020
|
+
managerCode: z.array(z.string()).optional().describe("Only clients with these managers (user codes). Use for 'Jane's clients with accounts due soon'."),
|
|
7021
|
+
partnerCode: z.array(z.string()).optional().describe("Only clients with these partners (user codes)."),
|
|
7022
|
+
teamCode: z.array(z.string()).optional().describe("Only clients assigned to these teams. Codes come from list_teams."),
|
|
7023
|
+
limit: z.number().int().min(0).max(50).optional().describe("Clients per page. Default 25, max 50. Pass 0 for a count only ('how many clients have accounts due next month?')."),
|
|
7024
|
+
offset: z.number().int().min(0).optional().describe("Number of clients to skip for pagination. Default 0.")
|
|
7025
|
+
};
|
|
7026
|
+
function formatRow$2(c, dateType) {
|
|
7027
|
+
const date = c.keyDates?.find((d) => d.dateType === dateType)?.date;
|
|
7028
|
+
return `- ${date ? date.slice(0, 10) : "no date"} · ${c.name ?? "(no name)"} (${c.code ?? "no code"})`;
|
|
7029
|
+
}
|
|
7030
|
+
async function handleListUpcomingKeyDates(api, args) {
|
|
7031
|
+
try {
|
|
7032
|
+
const range = args.keyDateRange ?? "Next30Days";
|
|
7033
|
+
if (range === "CustomDateRange" && (!args.keyDateFrom || !args.keyDateTo)) return {
|
|
7034
|
+
content: [{
|
|
7035
|
+
type: "text",
|
|
7036
|
+
text: "keyDateFrom and keyDateTo are both needed when keyDateRange is CustomDateRange."
|
|
7037
|
+
}],
|
|
7038
|
+
isError: true
|
|
7039
|
+
};
|
|
7040
|
+
const limit = args.limit ?? 25;
|
|
7041
|
+
const query = {
|
|
7042
|
+
keyDateType: args.keyDateType,
|
|
7043
|
+
keyDateRange: range,
|
|
7044
|
+
keyDateFrom: range === "CustomDateRange" ? args.keyDateFrom : void 0,
|
|
7045
|
+
keyDateTo: range === "CustomDateRange" ? args.keyDateTo : void 0,
|
|
7046
|
+
status: args.status?.length ? args.status : ["Active"],
|
|
7047
|
+
managerCode: args.managerCode,
|
|
7048
|
+
partnerCode: args.partnerCode,
|
|
7049
|
+
teamCode: args.teamCode,
|
|
7050
|
+
sortBy: "KeyDate",
|
|
7051
|
+
sortKeyDateType: args.keyDateType,
|
|
7052
|
+
limit,
|
|
7053
|
+
offset: args.offset
|
|
7054
|
+
};
|
|
7055
|
+
const result = await api.listClients(query);
|
|
7056
|
+
const items = result.data ?? [];
|
|
7057
|
+
const total = result.totalCount ?? items.length;
|
|
7058
|
+
const window = range === "CustomDateRange" ? `${args.keyDateFrom} to ${args.keyDateTo}` : range;
|
|
7059
|
+
if (limit === 0) return { content: [{
|
|
7060
|
+
type: "text",
|
|
7061
|
+
text: `${total} client${total === 1 ? "" : "s"} with ${args.keyDateType} in ${window}.`
|
|
7062
|
+
}] };
|
|
7063
|
+
if (items.length === 0) return { content: [{
|
|
7064
|
+
type: "text",
|
|
7065
|
+
text: `No clients have ${args.keyDateType} in ${window}.`
|
|
7066
|
+
}] };
|
|
7067
|
+
const lines = [
|
|
7068
|
+
total > items.length ? `${total} clients have ${args.keyDateType} in ${window} (showing ${items.length}, soonest first):` : `${items.length} client${items.length === 1 ? "" : "s"} with ${args.keyDateType} in ${window}, soonest first:`,
|
|
7069
|
+
"",
|
|
7070
|
+
...items.map((c) => formatRow$2(c, args.keyDateType))
|
|
7071
|
+
];
|
|
7072
|
+
if (result.hasMore) lines.push("", `More results available — call again with offset: ${(args.offset ?? 0) + items.length}.`);
|
|
7073
|
+
return { content: [{
|
|
7074
|
+
type: "text",
|
|
7075
|
+
text: lines.join("\n")
|
|
7076
|
+
}] };
|
|
7077
|
+
} catch (error) {
|
|
7078
|
+
return {
|
|
7079
|
+
content: [{
|
|
7080
|
+
type: "text",
|
|
7081
|
+
text: error instanceof SodiumApiError ? `Error listing upcoming key dates: ${error.message} (correlation: ${error.correlationId})` : `Error listing upcoming key dates: ${error instanceof Error ? error.message : String(error)}`
|
|
7082
|
+
}],
|
|
7083
|
+
isError: true
|
|
7084
|
+
};
|
|
7085
|
+
}
|
|
7086
|
+
}
|
|
7087
|
+
//#endregion
|
|
7088
|
+
//#region ../mcp-core/src/tools/draft-email-to-client.ts
|
|
7089
|
+
const recipient = z.object({
|
|
7090
|
+
email: z.string().email().describe("Email address."),
|
|
7091
|
+
name: z.string().optional().describe("Display name (optional).")
|
|
7092
|
+
});
|
|
7093
|
+
const DraftEmailToClientInputSchema = {
|
|
7094
|
+
to: z.array(recipient).min(1, "At least one recipient is required").describe("Who the draft is addressed to. A client's contact emails come from get_client_summary or list_contacts."),
|
|
7095
|
+
subject: z.string().optional().describe("Subject line."),
|
|
7096
|
+
body: z.string().min(1, "Email body cannot be empty").describe("The email body as HTML (paragraphs in <p>, line breaks as <br>). The user's mailbox signature is not added automatically."),
|
|
7097
|
+
cc: z.array(recipient).optional().describe("CC recipients.")
|
|
7098
|
+
};
|
|
7099
|
+
const noMailbox = "I couldn't save a draft because you don't have a mailbox connected to Sodium with permission to read email. Connect one under Settings > Email in Sodium, or ask a practice admin to give you access, then try again.";
|
|
7100
|
+
const needsReconnect = "I couldn't save a draft because your connected mailbox needs reconnecting. Reconnect it under Settings > Email in Sodium, then try again.";
|
|
7101
|
+
async function handleDraftEmailToClient(api, args) {
|
|
7102
|
+
try {
|
|
7103
|
+
const mailbox = await api.findOwnDraftMailbox();
|
|
7104
|
+
if (!mailbox?.code) return {
|
|
7105
|
+
content: [{
|
|
7106
|
+
type: "text",
|
|
7107
|
+
text: noMailbox
|
|
7108
|
+
}],
|
|
7109
|
+
isError: true
|
|
7110
|
+
};
|
|
7111
|
+
let result;
|
|
7112
|
+
try {
|
|
7113
|
+
result = await api.createMailboxDraft(mailbox.code, {
|
|
7114
|
+
subject: args.subject,
|
|
7115
|
+
htmlBody: args.body,
|
|
7116
|
+
toRecipients: args.to,
|
|
7117
|
+
ccRecipients: args.cc
|
|
7118
|
+
});
|
|
7119
|
+
} catch (error) {
|
|
7120
|
+
if (error instanceof SodiumApiError && error.statusCode === 403) return {
|
|
7121
|
+
content: [{
|
|
7122
|
+
type: "text",
|
|
7123
|
+
text: noMailbox
|
|
7124
|
+
}],
|
|
7125
|
+
isError: true
|
|
7126
|
+
};
|
|
7127
|
+
throw error;
|
|
7128
|
+
}
|
|
7129
|
+
if (result.reconnectRequired) return {
|
|
7130
|
+
content: [{
|
|
7131
|
+
type: "text",
|
|
7132
|
+
text: needsReconnect
|
|
7133
|
+
}],
|
|
7134
|
+
isError: true
|
|
7135
|
+
};
|
|
7136
|
+
if (!result.success) return {
|
|
7137
|
+
content: [{
|
|
7138
|
+
type: "text",
|
|
7139
|
+
text: `The draft could not be saved: ${result.errorMessage ?? "the mail provider refused it"}.`
|
|
7140
|
+
}],
|
|
7141
|
+
isError: true
|
|
7142
|
+
};
|
|
7143
|
+
const who = args.to.map((r) => r.name ?? r.email).join(", ");
|
|
7144
|
+
return { content: [{
|
|
7145
|
+
type: "text",
|
|
7146
|
+
text: `Draft saved in ${mailbox.fromEmail ?? "your mailbox"}, addressed to ${who}. It has not been sent; open your Drafts folder to review and send it.`
|
|
7147
|
+
}] };
|
|
7148
|
+
} catch (error) {
|
|
7149
|
+
return {
|
|
7150
|
+
content: [{
|
|
7151
|
+
type: "text",
|
|
7152
|
+
text: error instanceof SodiumApiError ? `Error saving email draft: ${error.message} (correlation: ${error.correlationId})` : `Error saving email draft: ${error instanceof Error ? error.message : String(error)}`
|
|
7153
|
+
}],
|
|
7154
|
+
isError: true
|
|
7155
|
+
};
|
|
7156
|
+
}
|
|
7157
|
+
}
|
|
7158
|
+
//#endregion
|
|
6824
7159
|
//#region ../mcp-core/src/tools/list-data-form-requests.ts
|
|
6825
7160
|
const statusEnum$11 = z.enum(["PendingResponse", "ResponseReceived"]);
|
|
6826
7161
|
const sortByEnum$10 = z.enum(["CreatedDate", "Status"]);
|
|
@@ -9866,6 +10201,18 @@ async function buildServer(config) {
|
|
|
9866
10201
|
inputSchema: ListDocumentRequestsInputSchema,
|
|
9867
10202
|
annotations: lookupAnnotations
|
|
9868
10203
|
}, (args) => handleListDocumentRequests(api, args));
|
|
10204
|
+
registerTenantTool(server, api, "list_overdue_requests", {
|
|
10205
|
+
title: "List overdue document requests",
|
|
10206
|
+
description: "List document requests that have been sent to clients, are still waiting on them, and are past their deadline, most overdue first. Each row shows days overdue, the client, the deadline and chase history (last chased, next chase). Optional filters: client, manager, partner, task category. Answers 'who do we need to chase?', 'which of Jane's clients are overdue sending records?', 'is ACME overdue on anything?'. Covers document requests only; data form requests have no deadline. Requests without a deadline are never overdue. For every open request regardless of deadline, use list_document_requests.",
|
|
10207
|
+
inputSchema: ListOverdueRequestsInputSchema,
|
|
10208
|
+
annotations: lookupAnnotations
|
|
10209
|
+
}, (args) => handleListOverdueRequests(api, args));
|
|
10210
|
+
registerTenantTool(server, api, "list_upcoming_key_dates", {
|
|
10211
|
+
title: "List clients with a key date coming up",
|
|
10212
|
+
description: "List clients whose chosen key date falls inside a window, soonest first, across the whole practice. Pick the date type (accounts due, confirmation statement due, annual VAT return due, PAYE/CIS filing due, pension re-enrolment, charity returns, year end and others) and a window (Next7Days, Next30Days, ThisMonth, NextQuarter, a custom range and others). Defaults to active clients and the next 30 days. Filters: manager, partner, team, status. Answers 'whose accounts are due next month?', 'which confirmation statements are due in the next fortnight?', 'how many clients have a year end this quarter?' (limit=0). Quarterly and monthly VAT return dates and MTD ITSA quarter dates are calculated per client and cannot be searched here; get_client_summary shows them for one client. To change a date, use update_client_dates.",
|
|
10213
|
+
inputSchema: ListUpcomingKeyDatesInputSchema,
|
|
10214
|
+
annotations: lookupAnnotations
|
|
10215
|
+
}, (args) => handleListUpcomingKeyDates(api, args));
|
|
9869
10216
|
registerTenantTool(server, api, "list_data_form_requests", {
|
|
9870
10217
|
title: "List / filter data form (questionnaire) requests",
|
|
9871
10218
|
description: "List data-form (questionnaire) requests sent to clients — e.g. Self Assessment questionnaires, onboarding forms — with status, client, form, category, manager/partner, search, sort, and pagination. Each row shows status, sent/submitted dates, answers awaiting review, and chase history. Answers 'who still hasn't returned the SA questionnaire?' (formCode + status=PendingResponse), 'which form responses need reviewing?' (status=ResponseReceived — check pendingReviewCount per row), 'what forms has ACME been sent?' (clientCode). Statuses: PendingResponse (waiting on client) / ResponseReceived (submitted; answers may still need review).",
|
|
@@ -10237,6 +10584,17 @@ async function buildServer(config) {
|
|
|
10237
10584
|
openWorldHint: true
|
|
10238
10585
|
}
|
|
10239
10586
|
}, (args) => handleAddClientNote(api, args));
|
|
10587
|
+
registerWriteTool(server, api, config.context, "draft_email_to_client", {
|
|
10588
|
+
title: "Draft an email to a client",
|
|
10589
|
+
description: "Save a draft email in the signed-in user's own connected mailbox (their default mailbox if they have several). Takes recipients (email, optional name), an optional subject, an HTML body and optional CC. The draft is never sent: the user reviews and sends it from their Drafts folder. Needs the user to have a mailbox connected to Sodium with permission to read email; otherwise it says so. Recipient addresses for a client come from get_client_summary or list_contacts. Attachments are not supported.",
|
|
10590
|
+
inputSchema: DraftEmailToClientInputSchema,
|
|
10591
|
+
annotations: {
|
|
10592
|
+
readOnlyHint: false,
|
|
10593
|
+
destructiveHint: false,
|
|
10594
|
+
idempotentHint: false,
|
|
10595
|
+
openWorldHint: true
|
|
10596
|
+
}
|
|
10597
|
+
}, (args) => handleDraftEmailToClient(api, args));
|
|
10240
10598
|
registerWriteTool(server, api, config.context, "update_client_custom_fields", {
|
|
10241
10599
|
title: "Update custom field values on a client",
|
|
10242
10600
|
description: "Set or clear custom field values on a client. Accepts a map of field codes to values. Only fields included in the map are updated — omitted fields keep their current values. Pass null to clear a field. Field codes and current values are visible in the Custom Fields section of get_client_summary. Values are validated against the field's data type (Text, Number, Date, Boolean, Select, MultiSelect) before sending — invalid values return a validation error with guidance. Use this when the user says things like 'set the referral source on ACME to Google', 'update the fee review date for Smith & Co', 'clear the VAT scheme field on Greggs'. Multiple fields can be updated in a single call.",
|
|
@@ -10303,6 +10661,7 @@ var groups = [
|
|
|
10303
10661
|
"link_contact_to_client",
|
|
10304
10662
|
"update_client_business_details",
|
|
10305
10663
|
"update_client_dates",
|
|
10664
|
+
"list_upcoming_key_dates",
|
|
10306
10665
|
"update_client_custom_fields",
|
|
10307
10666
|
"add_client_note"
|
|
10308
10667
|
]
|
|
@@ -10354,6 +10713,7 @@ var groups = [
|
|
|
10354
10713
|
"name": "Documents & forms",
|
|
10355
10714
|
"tools": [
|
|
10356
10715
|
"list_document_requests",
|
|
10716
|
+
"list_overdue_requests",
|
|
10357
10717
|
"create_document_request",
|
|
10358
10718
|
"list_client_documents",
|
|
10359
10719
|
"list_data_forms",
|
|
@@ -10377,6 +10737,7 @@ var groups = [
|
|
|
10377
10737
|
"name": "Email & Communication",
|
|
10378
10738
|
"tools": [
|
|
10379
10739
|
"list_client_emails",
|
|
10740
|
+
"draft_email_to_client",
|
|
10380
10741
|
"get_client_email_thread",
|
|
10381
10742
|
"list_sent_emails",
|
|
10382
10743
|
"get_sent_email",
|
|
@@ -10469,6 +10830,9 @@ var summaries = {
|
|
|
10469
10830
|
"get_engagement_letter_summary": "Full detail of one letter of engagement.",
|
|
10470
10831
|
"get_engagement_pipeline_summary": "Counts and values of proposals by stage: what is open and what is won.",
|
|
10471
10832
|
"create_proposal": "Draft a proposal or letter of engagement for a client.",
|
|
10833
|
+
"list_upcoming_key_dates": "Find clients with accounts, filings or other key dates coming up.",
|
|
10834
|
+
"list_overdue_requests": "See document requests that are past their deadline and need chasing.",
|
|
10835
|
+
"draft_email_to_client": "Save a draft email in your own mailbox, ready for you to review and send.",
|
|
10472
10836
|
"list_document_requests": "See document requests sent to clients and what is still outstanding.",
|
|
10473
10837
|
"create_document_request": "Draft a document request for a client.",
|
|
10474
10838
|
"list_client_documents": "List documents held against clients and their review status.",
|