@sodiumhq/mcp-pm 0.1.0-beta.2611 → 0.1.0-beta.2749
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 +2 -1
- package/dist/index.js +231 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ Only enable write mode with an AI client you trust — it hands the client the a
|
|
|
63
63
|
|
|
64
64
|
**Clients**
|
|
65
65
|
- **`list_clients`** — list and filter clients by search, status, type, assignee, services, saved filters
|
|
66
|
-
- **`get_client_summary`** — one-call composite for a single client: identity + contacts + active services with pricing + business details (company number, VAT, UTR, trading address) + key statutory dates (year-end, accounts due, VAT return due, confirmation statement due) + overdue tasks + tasks due in next 7 days
|
|
66
|
+
- **`get_client_summary`** — one-call composite for a single client: identity + contacts + active services with pricing + business details (company number, VAT, UTR, trading address) + **custom fields** (all user-defined fields with current values, data types, and field codes) + key statutory dates (year-end, accounts due, VAT return due, confirmation statement due) + overdue tasks + tasks due in next 7 days
|
|
67
67
|
|
|
68
68
|
**Tasks**
|
|
69
69
|
- **`list_tasks`** — find and count tasks by assignee (including "my tasks"), client, status, overdue, date range, category, team, or workflow. Answers "what's on my plate?", "what's Jane working on?", "what's overdue for ACME?", "how many tasks are due this week?"
|
|
@@ -83,6 +83,7 @@ Only enable write mode with an AI client you trust — it hands the client the a
|
|
|
83
83
|
**Write tools** (require `--enable-writes` — see [Write mode](#write-mode))
|
|
84
84
|
- **`add_task_note`** — capture a note against a specific task. Attributed to your API user, timestamped to now.
|
|
85
85
|
- **`add_client_note`** — capture a note against a client record. Same shape as `add_task_note` but scoped to the client.
|
|
86
|
+
- **`update_client_custom_fields`** — set or clear custom field values on a client. Accepts a map of field codes to values, validates against the field's data type (Text, Number, Date, Boolean, Select, MultiSelect). Pass null to clear a field. Field codes are discoverable via `get_client_summary`.
|
|
86
87
|
|
|
87
88
|
More tools land iteratively as the beta progresses.
|
|
88
89
|
|
package/dist/index.js
CHANGED
|
@@ -654,6 +654,42 @@ const listClientContactsForClient = (options) => (options.client ?? client).get(
|
|
|
654
654
|
...options
|
|
655
655
|
});
|
|
656
656
|
/**
|
|
657
|
+
* Get Custom Field Values
|
|
658
|
+
*
|
|
659
|
+
* Returns field code and value pairs for the specified client. Use the custom field definitions endpoint to retrieve data types, labels, and allowed options.
|
|
660
|
+
*/
|
|
661
|
+
const getClientCustomFieldValues = (options) => (options.client ?? client).get({
|
|
662
|
+
security: [{
|
|
663
|
+
name: "x-api-key",
|
|
664
|
+
type: "apiKey"
|
|
665
|
+
}, {
|
|
666
|
+
scheme: "bearer",
|
|
667
|
+
type: "http"
|
|
668
|
+
}],
|
|
669
|
+
url: "/tenants/{tenant}/clients/{client}/custom-field-values",
|
|
670
|
+
...options
|
|
671
|
+
});
|
|
672
|
+
/**
|
|
673
|
+
* Set Custom Field Values
|
|
674
|
+
*
|
|
675
|
+
* Sets or updates custom field values for the specified client. Only fields included in the request are updated. Use the custom field definitions endpoint to determine valid field codes and data types.
|
|
676
|
+
*/
|
|
677
|
+
const setClientCustomFieldValues = (options) => (options.client ?? client).put({
|
|
678
|
+
security: [{
|
|
679
|
+
name: "x-api-key",
|
|
680
|
+
type: "apiKey"
|
|
681
|
+
}, {
|
|
682
|
+
scheme: "bearer",
|
|
683
|
+
type: "http"
|
|
684
|
+
}],
|
|
685
|
+
url: "/tenants/{tenant}/clients/{client}/custom-field-values",
|
|
686
|
+
...options,
|
|
687
|
+
headers: {
|
|
688
|
+
"Content-Type": "application/json",
|
|
689
|
+
...options.headers
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
/**
|
|
657
693
|
* Get Client Dates
|
|
658
694
|
*
|
|
659
695
|
* Returns all key dates for the specified client
|
|
@@ -741,6 +777,22 @@ const getClient = (options) => (options.client ?? client).get({
|
|
|
741
777
|
...options
|
|
742
778
|
});
|
|
743
779
|
/**
|
|
780
|
+
* List CustomFieldDefinitions
|
|
781
|
+
*
|
|
782
|
+
* Lists all CustomFieldDefinitions for the given tenant.
|
|
783
|
+
*/
|
|
784
|
+
const listCustomFieldDefinitions = (options) => (options.client ?? client).get({
|
|
785
|
+
security: [{
|
|
786
|
+
name: "x-api-key",
|
|
787
|
+
type: "apiKey"
|
|
788
|
+
}, {
|
|
789
|
+
scheme: "bearer",
|
|
790
|
+
type: "http"
|
|
791
|
+
}],
|
|
792
|
+
url: "/tenants/{tenant}/custom-fields",
|
|
793
|
+
...options
|
|
794
|
+
});
|
|
795
|
+
/**
|
|
744
796
|
* List Engagements
|
|
745
797
|
*
|
|
746
798
|
* Lists all Engagements for the given tenant.
|
|
@@ -1317,6 +1369,44 @@ var SodiumApiClient = class {
|
|
|
1317
1369
|
if (error !== void 0 || !data) throw this.toError(response, error, correlationId, `add note to client ${clientCode}`);
|
|
1318
1370
|
return data;
|
|
1319
1371
|
}
|
|
1372
|
+
async listCustomFieldDefinitions(query = {}) {
|
|
1373
|
+
const correlationId = randomUUID();
|
|
1374
|
+
const { data, error, response } = await listCustomFieldDefinitions({
|
|
1375
|
+
path: { tenant: this.ctx.tenant },
|
|
1376
|
+
query: {
|
|
1377
|
+
...query,
|
|
1378
|
+
limit: query.limit ?? 50,
|
|
1379
|
+
offset: query.offset ?? 0
|
|
1380
|
+
},
|
|
1381
|
+
headers: { "X-Correlation-Id": correlationId }
|
|
1382
|
+
});
|
|
1383
|
+
if (error !== void 0 || !data) throw this.toError(response, error, correlationId, "list custom field definitions");
|
|
1384
|
+
return data;
|
|
1385
|
+
}
|
|
1386
|
+
async getClientCustomFieldValues(clientCode) {
|
|
1387
|
+
const correlationId = randomUUID();
|
|
1388
|
+
const { data, error, response } = await getClientCustomFieldValues({
|
|
1389
|
+
path: {
|
|
1390
|
+
tenant: this.ctx.tenant,
|
|
1391
|
+
client: clientCode
|
|
1392
|
+
},
|
|
1393
|
+
headers: { "X-Correlation-Id": correlationId }
|
|
1394
|
+
});
|
|
1395
|
+
if (error !== void 0 || !data) throw this.toError(response, error, correlationId, `get custom field values for client ${clientCode}`);
|
|
1396
|
+
return data;
|
|
1397
|
+
}
|
|
1398
|
+
async setClientCustomFieldValues(clientCode, values) {
|
|
1399
|
+
const correlationId = randomUUID();
|
|
1400
|
+
const { error, response } = await setClientCustomFieldValues({
|
|
1401
|
+
path: {
|
|
1402
|
+
tenant: this.ctx.tenant,
|
|
1403
|
+
client: clientCode
|
|
1404
|
+
},
|
|
1405
|
+
body: values,
|
|
1406
|
+
headers: { "X-Correlation-Id": correlationId }
|
|
1407
|
+
});
|
|
1408
|
+
if (error !== void 0) throw this.toError(response, error, correlationId, `set custom field values for client ${clientCode}`);
|
|
1409
|
+
}
|
|
1320
1410
|
toError(response, error, correlationId, operation) {
|
|
1321
1411
|
const status = response.status;
|
|
1322
1412
|
let message = `Failed to ${operation} (HTTP ${status})`;
|
|
@@ -1340,6 +1430,14 @@ async function buildInstructions(api, writesEnabled) {
|
|
|
1340
1430
|
})
|
|
1341
1431
|
]);
|
|
1342
1432
|
const now = /* @__PURE__ */ new Date();
|
|
1433
|
+
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1434
|
+
const parts = Object.fromEntries(new Intl.DateTimeFormat("en-GB", {
|
|
1435
|
+
timeZone: tz,
|
|
1436
|
+
year: "numeric",
|
|
1437
|
+
month: "2-digit",
|
|
1438
|
+
day: "2-digit",
|
|
1439
|
+
weekday: "long"
|
|
1440
|
+
}).formatToParts(now).map((p) => [p.type, p.value]));
|
|
1343
1441
|
const lines = [
|
|
1344
1442
|
"You are assisting a user of Sodium Practice Management — software used by accountancy practices to run their business.",
|
|
1345
1443
|
"",
|
|
@@ -1353,7 +1451,7 @@ async function buildInstructions(api, writesEnabled) {
|
|
|
1353
1451
|
"- For prompts like 'brief me on my call with X' or 'summarise X', produce a practice-side view: what services the practice delivers to X, what work is outstanding, what the user should raise or action with the client.",
|
|
1354
1452
|
"- Never frame output as if the named entity is the one being briefed — it is the practice (the user) being briefed ABOUT the client.",
|
|
1355
1453
|
"",
|
|
1356
|
-
`Today: ${
|
|
1454
|
+
`Today: ${`${parts.year}-${parts.month}-${parts.day}`} (${parts.weekday}, ${tz})`,
|
|
1357
1455
|
`Current UTC time: ${now.toISOString()}`
|
|
1358
1456
|
];
|
|
1359
1457
|
if (user.status === "fulfilled") {
|
|
@@ -1526,7 +1624,7 @@ function describeFilters$4(args) {
|
|
|
1526
1624
|
//#region ../mcp-core/src/tools/get-client-summary.ts
|
|
1527
1625
|
const GetClientSummaryInputSchema = { code: z.string().min(1, "Client code is required").describe("The client code (identifier). Usually discovered via list_clients first.") };
|
|
1528
1626
|
async function handleGetClientSummary(api, { code }) {
|
|
1529
|
-
const [clientResult, contactsResult, servicesResult, businessResult, datesResult, overdueResult, upcomingResult] = await Promise.allSettled([
|
|
1627
|
+
const [clientResult, contactsResult, servicesResult, businessResult, datesResult, overdueResult, upcomingResult, customFieldDefsResult, customFieldValsResult] = await Promise.allSettled([
|
|
1530
1628
|
api.getClient(code),
|
|
1531
1629
|
api.listClientContacts(code),
|
|
1532
1630
|
api.listClientServices(code),
|
|
@@ -1542,7 +1640,13 @@ async function handleGetClientSummary(api, { code }) {
|
|
|
1542
1640
|
dateRange: "Next7Days",
|
|
1543
1641
|
dateBasis: "DueDate",
|
|
1544
1642
|
limit: 50
|
|
1545
|
-
})
|
|
1643
|
+
}),
|
|
1644
|
+
api.listCustomFieldDefinitions({
|
|
1645
|
+
entityType: "Client",
|
|
1646
|
+
isArchived: false,
|
|
1647
|
+
limit: 50
|
|
1648
|
+
}),
|
|
1649
|
+
api.getClientCustomFieldValues(code)
|
|
1546
1650
|
]);
|
|
1547
1651
|
if (clientResult.status === "rejected") {
|
|
1548
1652
|
const err = clientResult.reason;
|
|
@@ -1554,6 +1658,7 @@ async function handleGetClientSummary(api, { code }) {
|
|
|
1554
1658
|
isError: true
|
|
1555
1659
|
};
|
|
1556
1660
|
}
|
|
1661
|
+
const customFieldsAvailable = customFieldDefsResult.status === "fulfilled" && customFieldValsResult.status === "fulfilled";
|
|
1557
1662
|
return { content: [{
|
|
1558
1663
|
type: "text",
|
|
1559
1664
|
text: format$3({
|
|
@@ -1564,13 +1669,16 @@ async function handleGetClientSummary(api, { code }) {
|
|
|
1564
1669
|
clientDates: datesResult.status === "fulfilled" ? datesResult.value : [],
|
|
1565
1670
|
overdueTasks: extract(overdueResult),
|
|
1566
1671
|
upcomingTasks: extract(upcomingResult),
|
|
1672
|
+
customFieldDefs: customFieldsAvailable ? customFieldDefsResult.value.data ?? [] : [],
|
|
1673
|
+
customFieldValues: customFieldsAvailable ? customFieldValsResult.value : [],
|
|
1567
1674
|
gaps: [
|
|
1568
1675
|
contactsResult.status === "rejected" ? "contacts" : null,
|
|
1569
1676
|
servicesResult.status === "rejected" ? "services" : null,
|
|
1570
1677
|
businessResult.status === "rejected" ? "business details" : null,
|
|
1571
1678
|
datesResult.status === "rejected" ? "key dates" : null,
|
|
1572
1679
|
overdueResult.status === "rejected" ? "overdue tasks" : null,
|
|
1573
|
-
upcomingResult.status === "rejected" ? "upcoming tasks" : null
|
|
1680
|
+
upcomingResult.status === "rejected" ? "upcoming tasks" : null,
|
|
1681
|
+
!customFieldsAvailable ? "custom fields" : null
|
|
1574
1682
|
].filter((v) => v !== null)
|
|
1575
1683
|
})
|
|
1576
1684
|
}] };
|
|
@@ -1580,7 +1688,7 @@ function extract(result) {
|
|
|
1580
1688
|
return result.value.data ?? [];
|
|
1581
1689
|
}
|
|
1582
1690
|
function format$3(input) {
|
|
1583
|
-
const { client, contacts, services, businessDetails, clientDates, overdueTasks, upcomingTasks, gaps } = input;
|
|
1691
|
+
const { client, contacts, services, businessDetails, clientDates, overdueTasks, upcomingTasks, customFieldDefs, customFieldValues, gaps } = input;
|
|
1584
1692
|
const lines = [];
|
|
1585
1693
|
const name = client.name ?? "(no name)";
|
|
1586
1694
|
const code = client.code ?? "(no code)";
|
|
@@ -1595,6 +1703,18 @@ function format$3(input) {
|
|
|
1595
1703
|
if (client.associate) lines.push(`Associate: ${client.associate.name} (${client.associate.code})`);
|
|
1596
1704
|
const businessLines = formatBusinessDetails(businessDetails);
|
|
1597
1705
|
if (businessLines.length > 0) lines.push("", "--- Business Details ---", ...businessLines);
|
|
1706
|
+
if (customFieldDefs.length > 0) {
|
|
1707
|
+
const valueMap = /* @__PURE__ */ new Map();
|
|
1708
|
+
for (const v of customFieldValues) if (v.fieldCode) valueMap.set(v.fieldCode, v.value ?? null);
|
|
1709
|
+
lines.push("", `--- Custom Fields (${customFieldDefs.length}) ---`);
|
|
1710
|
+
for (const def of customFieldDefs) {
|
|
1711
|
+
const code = def.code ?? "(no code)";
|
|
1712
|
+
const label = def.label ?? code;
|
|
1713
|
+
const val = valueMap.get(code);
|
|
1714
|
+
const typeHint = def.dataType ? ` [${def.dataType}]` : "";
|
|
1715
|
+
lines.push(`- ${label} (${code})${typeHint}: ${val ?? "(not set)"}`);
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1598
1718
|
if (clientDates.length > 0) {
|
|
1599
1719
|
lines.push("", `--- Key Dates (${clientDates.length}) ---`);
|
|
1600
1720
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -2479,6 +2599,90 @@ async function handleAddTaskNote(api, args) {
|
|
|
2479
2599
|
}
|
|
2480
2600
|
}
|
|
2481
2601
|
//#endregion
|
|
2602
|
+
//#region ../mcp-core/src/tools/set-client-custom-fields.ts
|
|
2603
|
+
const SetClientCustomFieldsInputSchema = {
|
|
2604
|
+
clientCode: z.string().min(1, "Client code is required").describe("The client code (identifier). Usually discovered via list_clients or get_client_summary."),
|
|
2605
|
+
fields: z.record(z.string(), z.string().nullable()).describe("A map of custom field codes to values. Use null to clear a field. Values must match the field's data type: Text — any string; Number — a numeric string like \"42\" or \"3.14\"; Date — ISO 8601 date like \"2025-06-15\"; Boolean — \"true\" or \"false\"; Select — one of the field's allowed options (exact match); MultiSelect — comma-separated list of allowed options. Use get_client_summary to discover available field codes and their current values before setting.")
|
|
2606
|
+
};
|
|
2607
|
+
async function handleSetClientCustomFields(api, args) {
|
|
2608
|
+
try {
|
|
2609
|
+
const entries = Object.entries(args.fields);
|
|
2610
|
+
if (entries.length === 0) return { content: [{
|
|
2611
|
+
type: "text",
|
|
2612
|
+
text: "No fields provided — nothing to update."
|
|
2613
|
+
}] };
|
|
2614
|
+
const definitions = await api.listCustomFieldDefinitions({
|
|
2615
|
+
entityType: "Client",
|
|
2616
|
+
isArchived: false,
|
|
2617
|
+
limit: 50
|
|
2618
|
+
});
|
|
2619
|
+
const defMap = /* @__PURE__ */ new Map();
|
|
2620
|
+
for (const d of definitions.data ?? []) if (d.code) defMap.set(d.code, d);
|
|
2621
|
+
const errors = [];
|
|
2622
|
+
for (const [fieldCode, value] of entries) {
|
|
2623
|
+
const def = defMap.get(fieldCode);
|
|
2624
|
+
if (!def) {
|
|
2625
|
+
errors.push(`Unknown custom field code: "${fieldCode}"`);
|
|
2626
|
+
continue;
|
|
2627
|
+
}
|
|
2628
|
+
if (value === null || value === "") continue;
|
|
2629
|
+
const validation = validateFieldValue(def, value);
|
|
2630
|
+
if (validation) errors.push(`${fieldCode}: ${validation}`);
|
|
2631
|
+
}
|
|
2632
|
+
if (errors.length > 0) return {
|
|
2633
|
+
content: [{
|
|
2634
|
+
type: "text",
|
|
2635
|
+
text: `Validation failed:\n${errors.map((e) => `- ${e}`).join("\n")}`
|
|
2636
|
+
}],
|
|
2637
|
+
isError: true
|
|
2638
|
+
};
|
|
2639
|
+
await api.setClientCustomFieldValues(args.clientCode, entries.map(([fieldCode, value]) => ({
|
|
2640
|
+
fieldCode,
|
|
2641
|
+
value: value ?? null
|
|
2642
|
+
})));
|
|
2643
|
+
const summary = entries.map(([code, val]) => `- ${defMap.get(code)?.label ?? code}: ${val === null ? "(cleared)" : val}`).join("\n");
|
|
2644
|
+
return { content: [{
|
|
2645
|
+
type: "text",
|
|
2646
|
+
text: `Updated ${entries.length} custom field(s) on client ${args.clientCode}:\n${summary}`
|
|
2647
|
+
}] };
|
|
2648
|
+
} catch (error) {
|
|
2649
|
+
return {
|
|
2650
|
+
content: [{
|
|
2651
|
+
type: "text",
|
|
2652
|
+
text: error instanceof SodiumApiError ? `Error setting custom fields: ${error.message} (correlation: ${error.correlationId})` : `Error setting custom fields: ${error instanceof Error ? error.message : String(error)}`
|
|
2653
|
+
}],
|
|
2654
|
+
isError: true
|
|
2655
|
+
};
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
function validateFieldValue(def, value) {
|
|
2659
|
+
switch (def.dataType) {
|
|
2660
|
+
case "Number":
|
|
2661
|
+
if (isNaN(Number(value))) return `Expected a number, got "${value}"`;
|
|
2662
|
+
return null;
|
|
2663
|
+
case "Date": {
|
|
2664
|
+
const d = new Date(value);
|
|
2665
|
+
if (isNaN(d.getTime())) return `Expected an ISO 8601 date, got "${value}"`;
|
|
2666
|
+
return null;
|
|
2667
|
+
}
|
|
2668
|
+
case "Boolean":
|
|
2669
|
+
if (value !== "true" && value !== "false") return `Expected "true" or "false", got "${value}"`;
|
|
2670
|
+
return null;
|
|
2671
|
+
case "Select": {
|
|
2672
|
+
const options = def.options ?? [];
|
|
2673
|
+
if (!options.includes(value)) return `"${value}" is not a valid option. Allowed: ${options.join(", ")}`;
|
|
2674
|
+
return null;
|
|
2675
|
+
}
|
|
2676
|
+
case "MultiSelect": {
|
|
2677
|
+
const options = def.options ?? [];
|
|
2678
|
+
const invalid = value.split(",").map((s) => s.trim()).filter((s) => !options.includes(s));
|
|
2679
|
+
if (invalid.length > 0) return `Invalid option(s): ${invalid.join(", ")}. Allowed: ${options.join(", ")}`;
|
|
2680
|
+
return null;
|
|
2681
|
+
}
|
|
2682
|
+
default: return null;
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
//#endregion
|
|
2482
2686
|
//#region ../mcp-core/src/server.ts
|
|
2483
2687
|
function registerWriteTool(server, ctx, name, config, cb) {
|
|
2484
2688
|
if (!ctx.writesEnabled) return;
|
|
@@ -2489,7 +2693,16 @@ async function buildServer(config) {
|
|
|
2489
2693
|
const instructions = await buildInstructions(api, config.context.writesEnabled);
|
|
2490
2694
|
const server = new McpServer({
|
|
2491
2695
|
name: config.serverName,
|
|
2492
|
-
version: config.serverVersion
|
|
2696
|
+
version: config.serverVersion,
|
|
2697
|
+
icons: [{
|
|
2698
|
+
src: "https://sodiumhq.com/assets/logo-no-text.png",
|
|
2699
|
+
mimeType: "image/png",
|
|
2700
|
+
theme: "light"
|
|
2701
|
+
}, {
|
|
2702
|
+
src: "https://sodiumhq.com/assets/logo-no-text-darkbg.png",
|
|
2703
|
+
mimeType: "image/png",
|
|
2704
|
+
theme: "dark"
|
|
2705
|
+
}]
|
|
2493
2706
|
}, {
|
|
2494
2707
|
instructions,
|
|
2495
2708
|
capabilities: { tools: {} }
|
|
@@ -2523,7 +2736,7 @@ async function buildServer(config) {
|
|
|
2523
2736
|
}, (args) => handleListClients(api, args));
|
|
2524
2737
|
server.registerTool("get_client_summary", {
|
|
2525
2738
|
title: "Get a full summary of one client",
|
|
2526
|
-
description: "Get a consolidated overview of a single client by code: identity (name, status, type, assignments), business details (company number, incorporation date, trading name, registered address, VAT status, UTR, PAYE ref), key statutory dates (year-end, accounts due, VAT return due, confirmation statement due, etc. — upcoming first, then past), all contacts, active services with pricing, overdue task count + top 5, and tasks due in the next 7 days. Use this AFTER list_clients identifies the client of interest, or when the user references a specific client by code. Also answers 'when is ACME's year-end?' / 'is ACME VAT registered?' in a single call. Tolerates partial failures — if one section can't be loaded, the rest is still returned with a note about what's missing.",
|
|
2739
|
+
description: "Get a consolidated overview of a single client by code: identity (name, status, type, assignments), business details (company number, incorporation date, trading name, registered address, VAT status, UTR, PAYE ref), custom fields (all user-defined fields with current values, data types, and field codes — use these codes with update_client_custom_fields), key statutory dates (year-end, accounts due, VAT return due, confirmation statement due, etc. — upcoming first, then past), all contacts, active services with pricing, overdue task count + top 5, and tasks due in the next 7 days. Use this AFTER list_clients identifies the client of interest, or when the user references a specific client by code. Also answers 'when is ACME's year-end?' / 'is ACME VAT registered?' / 'what are ACME's custom fields?' in a single call. Tolerates partial failures — if one section can't be loaded, the rest is still returned with a note about what's missing.",
|
|
2527
2740
|
inputSchema: GetClientSummaryInputSchema,
|
|
2528
2741
|
annotations: {
|
|
2529
2742
|
readOnlyHint: true,
|
|
@@ -2643,6 +2856,17 @@ async function buildServer(config) {
|
|
|
2643
2856
|
openWorldHint: true
|
|
2644
2857
|
}
|
|
2645
2858
|
}, (args) => handleAddClientNote(api, args));
|
|
2859
|
+
registerWriteTool(server, config.context, "update_client_custom_fields", {
|
|
2860
|
+
title: "Update custom field values on a client",
|
|
2861
|
+
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.",
|
|
2862
|
+
inputSchema: SetClientCustomFieldsInputSchema,
|
|
2863
|
+
annotations: {
|
|
2864
|
+
readOnlyHint: false,
|
|
2865
|
+
destructiveHint: false,
|
|
2866
|
+
idempotentHint: true,
|
|
2867
|
+
openWorldHint: true
|
|
2868
|
+
}
|
|
2869
|
+
}, (args) => handleSetClientCustomFields(api, args));
|
|
2646
2870
|
return server;
|
|
2647
2871
|
}
|
|
2648
2872
|
//#endregion
|