@sodiumhq/mcp-pm 0.1.0-beta.2767 → 0.1.0-beta.2772
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +239 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -441,7 +441,7 @@ const createConfig = (override = {}) => ({
|
|
|
441
441
|
});
|
|
442
442
|
//#endregion
|
|
443
443
|
//#region ../mcp-core/src/generated/client/client.gen.ts
|
|
444
|
-
const createClient = (config = {}) => {
|
|
444
|
+
const createClient$1 = (config = {}) => {
|
|
445
445
|
let _config = mergeConfigs(createConfig(), config);
|
|
446
446
|
const getConfig = () => ({ ..._config });
|
|
447
447
|
const setConfig = (config) => {
|
|
@@ -620,7 +620,7 @@ const createClient = (config = {}) => {
|
|
|
620
620
|
};
|
|
621
621
|
//#endregion
|
|
622
622
|
//#region ../mcp-core/src/generated/client.gen.ts
|
|
623
|
-
const client = createClient(createConfig());
|
|
623
|
+
const client = createClient$1(createConfig());
|
|
624
624
|
//#endregion
|
|
625
625
|
//#region ../mcp-core/src/generated/sdk.gen.ts
|
|
626
626
|
/**
|
|
@@ -706,6 +706,22 @@ const getClientDates = (options) => (options.client ?? client).get({
|
|
|
706
706
|
...options
|
|
707
707
|
});
|
|
708
708
|
/**
|
|
709
|
+
* List Notes for Client
|
|
710
|
+
*
|
|
711
|
+
* Lists all Notes for the specified client.
|
|
712
|
+
*/
|
|
713
|
+
const listClientNotesForClient = (options) => (options.client ?? client).get({
|
|
714
|
+
security: [{
|
|
715
|
+
name: "x-api-key",
|
|
716
|
+
type: "apiKey"
|
|
717
|
+
}, {
|
|
718
|
+
scheme: "bearer",
|
|
719
|
+
type: "http"
|
|
720
|
+
}],
|
|
721
|
+
url: "/tenants/{tenant}/clients/{client}/clientnote",
|
|
722
|
+
...options
|
|
723
|
+
});
|
|
724
|
+
/**
|
|
709
725
|
* Create Note for Client
|
|
710
726
|
*
|
|
711
727
|
* Creates a new Note for the specified client.
|
|
@@ -761,6 +777,26 @@ const listClients = (options) => (options.client ?? client).get({
|
|
|
761
777
|
...options
|
|
762
778
|
});
|
|
763
779
|
/**
|
|
780
|
+
* Create Client
|
|
781
|
+
*
|
|
782
|
+
* Creates a new Client for the specified tenant.
|
|
783
|
+
*/
|
|
784
|
+
const createClient = (options) => (options.client ?? client).post({
|
|
785
|
+
security: [{
|
|
786
|
+
name: "x-api-key",
|
|
787
|
+
type: "apiKey"
|
|
788
|
+
}, {
|
|
789
|
+
scheme: "bearer",
|
|
790
|
+
type: "http"
|
|
791
|
+
}],
|
|
792
|
+
url: "/tenants/{tenant}/clients",
|
|
793
|
+
...options,
|
|
794
|
+
headers: {
|
|
795
|
+
"Content-Type": "application/json",
|
|
796
|
+
...options.headers
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
/**
|
|
764
800
|
* Get Client
|
|
765
801
|
*
|
|
766
802
|
* Gets a Client for the specified tenant.
|
|
@@ -1437,6 +1473,33 @@ var SodiumApiClient = class {
|
|
|
1437
1473
|
if (error !== void 0 || !data) throw this.toError(response, error, correlationId, `add note to client ${clientCode}`);
|
|
1438
1474
|
return data;
|
|
1439
1475
|
}
|
|
1476
|
+
async createClient(body) {
|
|
1477
|
+
const correlationId = randomUUID();
|
|
1478
|
+
const { data, error, response } = await createClient({
|
|
1479
|
+
path: { tenant: this.ctx.tenant },
|
|
1480
|
+
body,
|
|
1481
|
+
headers: { "X-Correlation-Id": correlationId }
|
|
1482
|
+
});
|
|
1483
|
+
if (error !== void 0 || !data) throw this.toError(response, error, correlationId, "create client");
|
|
1484
|
+
return data;
|
|
1485
|
+
}
|
|
1486
|
+
async listClientNotes(clientCode, query = {}) {
|
|
1487
|
+
const correlationId = randomUUID();
|
|
1488
|
+
const { data, error, response } = await listClientNotesForClient({
|
|
1489
|
+
path: {
|
|
1490
|
+
tenant: this.ctx.tenant,
|
|
1491
|
+
client: clientCode
|
|
1492
|
+
},
|
|
1493
|
+
query: {
|
|
1494
|
+
...query,
|
|
1495
|
+
limit: query.limit ?? 10,
|
|
1496
|
+
offset: query.offset ?? 0
|
|
1497
|
+
},
|
|
1498
|
+
headers: { "X-Correlation-Id": correlationId }
|
|
1499
|
+
});
|
|
1500
|
+
if (error !== void 0 || !data) throw this.toError(response, error, correlationId, `list notes for client ${clientCode}`);
|
|
1501
|
+
return data;
|
|
1502
|
+
}
|
|
1440
1503
|
async listCustomFieldDefinitions(query = {}) {
|
|
1441
1504
|
const correlationId = randomUUID();
|
|
1442
1505
|
const { data, error, response } = await listCustomFieldDefinitions({
|
|
@@ -2194,7 +2257,7 @@ function format(input) {
|
|
|
2194
2257
|
return (b.date ?? b.createdDate ?? "").localeCompare(da);
|
|
2195
2258
|
});
|
|
2196
2259
|
lines.push("", `--- Notes (${notes.length}) ---`);
|
|
2197
|
-
for (const note of sorted.slice(0, 20)) lines.push(formatNote(note));
|
|
2260
|
+
for (const note of sorted.slice(0, 20)) lines.push(formatNote$1(note));
|
|
2198
2261
|
if (notes.length > 20) lines.push(`... and ${notes.length - 20} more notes`);
|
|
2199
2262
|
}
|
|
2200
2263
|
const clients = task.clients ?? [];
|
|
@@ -2219,7 +2282,7 @@ function formatStep(step) {
|
|
|
2219
2282
|
if (step.blockedReason) bits.push(`blocked: ${step.blockedReason}`);
|
|
2220
2283
|
return `Step ${num}: ${name}${bits.length > 0 ? ` — ${bits.join(" · ")}` : ""}`;
|
|
2221
2284
|
}
|
|
2222
|
-
function formatNote(note) {
|
|
2285
|
+
function formatNote$1(note) {
|
|
2223
2286
|
const pinPrefix = (note.pinnedLevel ?? 0) > 0 ? "[pinned] " : "";
|
|
2224
2287
|
const dateStr = note.date ?? note.createdDate ?? "";
|
|
2225
2288
|
const author = note.noteFromUser?.name ?? "(unknown)";
|
|
@@ -2369,7 +2432,7 @@ const categoryEnum = z.enum([
|
|
|
2369
2432
|
"Advisory",
|
|
2370
2433
|
"SoftwareAndTraining"
|
|
2371
2434
|
]);
|
|
2372
|
-
const clientTypeEnum = z.enum([
|
|
2435
|
+
const clientTypeEnum$1 = z.enum([
|
|
2373
2436
|
"PrivateLimitedCompany",
|
|
2374
2437
|
"PublicLimitedCompany",
|
|
2375
2438
|
"LimitedLiabilityPartnership",
|
|
@@ -2387,7 +2450,7 @@ const sortByEnum$2 = z.enum([
|
|
|
2387
2450
|
const ListServicesInputSchema = {
|
|
2388
2451
|
search: z.string().min(3, "Search must be at least 3 characters when provided").optional().describe("Free-text search across service code and name. Minimum 3 characters. Use for 'find our VAT services' or 'does the practice have a bookkeeping service?' when the exact code isn't known."),
|
|
2389
2452
|
category: categoryEnum.optional().describe("Filter by service category. Use 'Tax' for 'all tax services', 'Payroll' for payroll, 'CoreAccounting' for year-end / accounts / bookkeeping, 'CompanySecretarial' for confirmation statements / registered office, 'Advisory' for consulting / planning, 'SoftwareAndTraining' for software setup / training. Single value — to see multiple categories, call once per category or omit to see all."),
|
|
2390
|
-
clientType: clientTypeEnum.optional().describe("Filter by the client type the service applies to. Use for service-audit questions like 'which services do we offer to individuals?' or 'what do we offer private limited companies?'. Returns services configured for that client type plus any service with no client-type restriction (those apply to everyone)."),
|
|
2453
|
+
clientType: clientTypeEnum$1.optional().describe("Filter by the client type the service applies to. Use for service-audit questions like 'which services do we offer to individuals?' or 'what do we offer private limited companies?'. Returns services configured for that client type plus any service with no client-type restriction (those apply to everyone)."),
|
|
2391
2454
|
isArchived: z.boolean().optional().describe("Filter by archived status. Omit (default) to return everything; pass false for active services only; pass true for the archive. Most practice-manager questions ('what do we offer?') want isArchived=false."),
|
|
2392
2455
|
sortBy: sortByEnum$2.optional().describe("Field to sort by. Defaults to Name. Use 'Category' to group the output by category, 'AccountingCode' when reconciling against a chart of accounts."),
|
|
2393
2456
|
sortDesc: z.boolean().optional().describe("Sort in descending order. Defaults to ascending."),
|
|
@@ -2853,6 +2916,145 @@ async function handleGetCustomFieldDetails(api, args) {
|
|
|
2853
2916
|
}
|
|
2854
2917
|
}
|
|
2855
2918
|
//#endregion
|
|
2919
|
+
//#region ../mcp-core/src/tools/create-client.ts
|
|
2920
|
+
const clientTypeEnum = z.enum([
|
|
2921
|
+
"PrivateLimitedCompany",
|
|
2922
|
+
"PublicLimitedCompany",
|
|
2923
|
+
"LimitedLiabilityPartnership",
|
|
2924
|
+
"Partnership",
|
|
2925
|
+
"Individual",
|
|
2926
|
+
"Trust",
|
|
2927
|
+
"Charity",
|
|
2928
|
+
"SoleTrader"
|
|
2929
|
+
]);
|
|
2930
|
+
const clientStatusEnum = z.enum([
|
|
2931
|
+
"Active",
|
|
2932
|
+
"Inactive",
|
|
2933
|
+
"Prospect",
|
|
2934
|
+
"LostProspect"
|
|
2935
|
+
]);
|
|
2936
|
+
const CreateClientInputSchema = {
|
|
2937
|
+
name: z.string().min(1, "Client name is required").describe("The name of the client (e.g. 'ACME Ltd', 'John Smith')."),
|
|
2938
|
+
type: clientTypeEnum.describe("The client type. Determines which services and custom fields are applicable. Common types: PrivateLimitedCompany (most UK companies), Individual (sole traders' personal tax), SoleTrader (unincorporated businesses), Partnership, LimitedLiabilityPartnership."),
|
|
2939
|
+
status: clientStatusEnum.optional().describe("Client status (default: Active). Use Prospect for leads not yet onboarded."),
|
|
2940
|
+
email: z.string().nullable().optional().describe("Client email address."),
|
|
2941
|
+
telephone: z.string().nullable().optional().describe("Client telephone number."),
|
|
2942
|
+
internalReference: z.string().nullable().optional().describe("Internal reference number/code for the client."),
|
|
2943
|
+
managerCode: z.string().nullable().optional().describe("Code of the user who will manage this client. Discoverable via list_users or the team roster in the startup context."),
|
|
2944
|
+
partnerCode: z.string().nullable().optional().describe("Code of the partner user for this client."),
|
|
2945
|
+
associateCode: z.string().nullable().optional().describe("Code of the associate user for this client."),
|
|
2946
|
+
teamCode: z.string().nullable().optional().describe("Code of the team to assign to this client.")
|
|
2947
|
+
};
|
|
2948
|
+
async function handleCreateClient(api, args) {
|
|
2949
|
+
try {
|
|
2950
|
+
const created = await api.createClient(args);
|
|
2951
|
+
const lines = [
|
|
2952
|
+
`Created client: ${created.name} (${created.code ?? "(no code)"})`,
|
|
2953
|
+
`Type: ${created.type ?? args.type}`,
|
|
2954
|
+
`Status: ${created.status ?? "Active"}`
|
|
2955
|
+
];
|
|
2956
|
+
if (created.email) lines.push(`Email: ${created.email}`);
|
|
2957
|
+
if (created.manager) lines.push(`Manager: ${created.manager.name} (${created.manager.code})`);
|
|
2958
|
+
if (created.partner) lines.push(`Partner: ${created.partner.name} (${created.partner.code})`);
|
|
2959
|
+
return { content: [{
|
|
2960
|
+
type: "text",
|
|
2961
|
+
text: lines.join("\n")
|
|
2962
|
+
}] };
|
|
2963
|
+
} catch (error) {
|
|
2964
|
+
return {
|
|
2965
|
+
content: [{
|
|
2966
|
+
type: "text",
|
|
2967
|
+
text: error instanceof SodiumApiError ? `Error creating client: ${error.message} (correlation: ${error.correlationId})` : `Error creating client: ${error instanceof Error ? error.message : String(error)}`
|
|
2968
|
+
}],
|
|
2969
|
+
isError: true
|
|
2970
|
+
};
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
//#endregion
|
|
2974
|
+
//#region ../mcp-core/src/tools/get-contact.ts
|
|
2975
|
+
const GetContactInputSchema = { contactCode: z.string().min(1, "Contact code is required").describe("The contact code (identifier). Discoverable via list_contacts or the contacts section of get_client_summary.") };
|
|
2976
|
+
async function handleGetContact(api, args) {
|
|
2977
|
+
try {
|
|
2978
|
+
const c = await api.getContact(args.contactCode);
|
|
2979
|
+
const lines = [];
|
|
2980
|
+
const name = [
|
|
2981
|
+
c.title,
|
|
2982
|
+
c.firstName,
|
|
2983
|
+
c.middleName,
|
|
2984
|
+
c.lastName
|
|
2985
|
+
].filter(Boolean).join(" ") || "(no name)";
|
|
2986
|
+
lines.push(`Contact: ${name} (${c.code ?? args.contactCode})`);
|
|
2987
|
+
if (c.email) lines.push(`Email: ${c.email}`);
|
|
2988
|
+
if (c.phone) lines.push(`Phone: ${c.phone}`);
|
|
2989
|
+
if (c.mobile) lines.push(`Mobile: ${c.mobile}`);
|
|
2990
|
+
if (c.address) lines.push(`Address: ${c.address}`);
|
|
2991
|
+
if (c.dateOfBirth) lines.push(`Date of birth: ${c.dateOfBirth}`);
|
|
2992
|
+
if (c.nationality) lines.push(`Nationality: ${c.nationality}`);
|
|
2993
|
+
if (c.maritalStatus) lines.push(`Marital status: ${c.maritalStatus}`);
|
|
2994
|
+
if (c.utr) lines.push(`UTR: ${c.utr}`);
|
|
2995
|
+
if (c.niNumber) lines.push(`NI number: ${c.niNumber}`);
|
|
2996
|
+
if (c.personalCode) lines.push(`Companies House person code: ${c.personalCode}`);
|
|
2997
|
+
if (c.isDeceased) lines.push(`Deceased: yes${c.deceasedDate ? ` (${c.deceasedDate})` : ""}`);
|
|
2998
|
+
if (c.clientCount !== void 0) lines.push(`Linked to ${c.clientCount} client(s)`);
|
|
2999
|
+
if (c.client) lines.push(`Primary client: ${c.client.name} (${c.client.code})`);
|
|
3000
|
+
if (c.individualClient) lines.push(`Individual client record: ${c.individualClient.name} (${c.individualClient.code})`);
|
|
3001
|
+
if (c.portalUser) lines.push(`Portal user: ${c.portalUser.name} (${c.portalUser.code})`);
|
|
3002
|
+
return { content: [{
|
|
3003
|
+
type: "text",
|
|
3004
|
+
text: lines.join("\n")
|
|
3005
|
+
}] };
|
|
3006
|
+
} catch (error) {
|
|
3007
|
+
return {
|
|
3008
|
+
content: [{
|
|
3009
|
+
type: "text",
|
|
3010
|
+
text: error instanceof SodiumApiError ? `Error getting contact: ${error.message} (correlation: ${error.correlationId})` : `Error getting contact: ${error instanceof Error ? error.message : String(error)}`
|
|
3011
|
+
}],
|
|
3012
|
+
isError: true
|
|
3013
|
+
};
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
//#endregion
|
|
3017
|
+
//#region ../mcp-core/src/tools/list-client-notes.ts
|
|
3018
|
+
const sortFieldEnum$1 = z.enum(["Date", "UpdatedDate"]);
|
|
3019
|
+
const ListClientNotesInputSchema = {
|
|
3020
|
+
clientCode: z.string().min(1, "Client code is required").describe("The client code whose notes to list. Discoverable via list_clients or get_client_summary."),
|
|
3021
|
+
search: z.string().min(3, "Search must be at least 3 characters when provided").optional().describe("Search across note text. Minimum 3 characters."),
|
|
3022
|
+
authorCode: z.string().optional().describe("Filter by the user who wrote the note. Use the current user's code for 'my notes on ACME', or another user's code for 'what has Jane noted about this client?'."),
|
|
3023
|
+
sortBy: sortFieldEnum$1.optional().describe("Sort by Date (note date) or UpdatedDate. Default: Date descending (newest first)."),
|
|
3024
|
+
sortDesc: z.boolean().optional().describe("Sort descending (default: true for newest first)."),
|
|
3025
|
+
limit: z.number().int().min(0).max(50).optional().describe("Max results to return (default: 10, max: 50)."),
|
|
3026
|
+
offset: z.number().int().min(0).optional().describe("Number of records to skip for pagination.")
|
|
3027
|
+
};
|
|
3028
|
+
async function handleListClientNotes(api, args) {
|
|
3029
|
+
try {
|
|
3030
|
+
const { clientCode, ...query } = args;
|
|
3031
|
+
const result = await api.listClientNotes(clientCode, query);
|
|
3032
|
+
const notes = result.data ?? [];
|
|
3033
|
+
const total = result.totalCount ?? notes.length;
|
|
3034
|
+
if (notes.length === 0) return { content: [{
|
|
3035
|
+
type: "text",
|
|
3036
|
+
text: total === 0 ? `No notes found for client ${clientCode}.` : `${total} note(s) found, but none in this page (offset ${result.offset}).`
|
|
3037
|
+
}] };
|
|
3038
|
+
const lines = [`Notes for ${clientCode}: ${notes.length} of ${total}${result.hasMore ? " (more available)" : ""}`, ""];
|
|
3039
|
+
for (const n of notes) lines.push(formatNote(n));
|
|
3040
|
+
return { content: [{
|
|
3041
|
+
type: "text",
|
|
3042
|
+
text: lines.join("\n")
|
|
3043
|
+
}] };
|
|
3044
|
+
} catch (error) {
|
|
3045
|
+
return {
|
|
3046
|
+
content: [{
|
|
3047
|
+
type: "text",
|
|
3048
|
+
text: error instanceof SodiumApiError ? `Error listing client notes: ${error.message} (correlation: ${error.correlationId})` : `Error listing client notes: ${error instanceof Error ? error.message : String(error)}`
|
|
3049
|
+
}],
|
|
3050
|
+
isError: true
|
|
3051
|
+
};
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
function formatNote(n) {
|
|
3055
|
+
return `- [${n.code ?? "(no code)"}] ${n.date ? n.date.slice(0, 10) : "(no date)"} by ${n.noteFromUser?.name ?? "(unknown)"}${n.pinnedLevel && n.pinnedLevel > 0 ? " [pinned]" : ""}: ${n.text ? n.text.length > 200 ? n.text.slice(0, 200) + "..." : n.text : "(empty)"}`;
|
|
3056
|
+
}
|
|
3057
|
+
//#endregion
|
|
2856
3058
|
//#region ../mcp-core/src/tools/list-contacts.ts
|
|
2857
3059
|
const sortFieldEnum = z.enum([
|
|
2858
3060
|
"Name",
|
|
@@ -3081,6 +3283,16 @@ async function buildServer(config) {
|
|
|
3081
3283
|
openWorldHint: true
|
|
3082
3284
|
}
|
|
3083
3285
|
}, (args) => handleGetCustomFieldDetails(api, args));
|
|
3286
|
+
server.registerTool("list_client_notes", {
|
|
3287
|
+
title: "List / search notes on a client",
|
|
3288
|
+
description: "List notes attached to a client, with optional search and author filter. Answers 'show me the notes on ACME', 'what has Jane noted about this client?', 'search notes for VAT'. Notes are returned newest first by default. Each note shows its code, date, author, pinned status, and text (truncated to 200 chars).",
|
|
3289
|
+
inputSchema: ListClientNotesInputSchema,
|
|
3290
|
+
annotations: {
|
|
3291
|
+
readOnlyHint: true,
|
|
3292
|
+
idempotentHint: true,
|
|
3293
|
+
openWorldHint: true
|
|
3294
|
+
}
|
|
3295
|
+
}, (args) => handleListClientNotes(api, args));
|
|
3084
3296
|
server.registerTool("list_tasks", {
|
|
3085
3297
|
title: "List / filter tasks across the practice",
|
|
3086
3298
|
description: "List tasks with any combination of filters: assigned user(s), client(s), status, overdue flag, preset date range (Today / ThisWeek / Next7Days / CustomDateRange etc), category, team, recurring task template, saved filter, include-projected, include-workflow-steps (Agenda Mode), sort, and pagination. Use for: 'my tasks' (pass current user's code from startup context), 'Jane's overdue tasks' (user + isOverdue), 'tasks for ACME due this week' (client + dateRange=Next7Days + dateBasis=DueDate), 'what is the team working on this month' (dateRange=ThisMonth, no user filter). Returns up to 50 tasks per page. IMPORTANT constraints to avoid API errors and keep queries efficient: (1) Querying NotStarted tasks requires one of — a dateRange, isOverdue=true, or restricting status to non-NotStarted values. (2) Prefer the narrowest date range that answers the question — broad ranges (quarterly/yearly) are expensive; prefer Today / ThisWeek / Next7Days / ThisMonth over larger windows unless explicitly asked. (3) For 'oldest incomplete tasks' prefer status=['InProgress','Blocked'] with sortBy=StartDate (no date range needed), or add isOverdue=true if 'oldest overdue' is meant. (4) For 'how many X?' questions, pass limit=0 to get just the total count without fetching any task data — much cheaper than fetching a full page and counting.",
|
|
@@ -3181,6 +3393,16 @@ async function buildServer(config) {
|
|
|
3181
3393
|
openWorldHint: true
|
|
3182
3394
|
}
|
|
3183
3395
|
}, (args) => handleListContacts(api, args));
|
|
3396
|
+
server.registerTool("get_contact", {
|
|
3397
|
+
title: "Get full details of a contact",
|
|
3398
|
+
description: "Get all fields for a single contact by code: name, title, email, phone, mobile, address, date of birth, nationality, marital status, UTR, NI number, Companies House person code, deceased status, linked clients, and portal user. Use after list_contacts identifies the contact of interest, or when the user asks for details about a specific contact. Also useful before update_contact to see current values.",
|
|
3399
|
+
inputSchema: GetContactInputSchema,
|
|
3400
|
+
annotations: {
|
|
3401
|
+
readOnlyHint: true,
|
|
3402
|
+
idempotentHint: true,
|
|
3403
|
+
openWorldHint: true
|
|
3404
|
+
}
|
|
3405
|
+
}, (args) => handleGetContact(api, args));
|
|
3184
3406
|
registerWriteTool(server, config.context, "add_task_note", {
|
|
3185
3407
|
title: "Add a note to a task",
|
|
3186
3408
|
description: "Create a new note on a task. Additive — does not modify or delete existing notes. The note is attributed to the authenticated API user (the current practice member) and timestamped to 'now'. Use this when the user asks you to capture something on a task: 'add a note on the Greggs year-end task that we're waiting on the rental schedule', 'log on the task that I called John today and got voicemail'. Notes can be pinned; only pin when the user explicitly asks for it. The user can always edit or delete notes in the Sodium UI if the wording isn't right.",
|
|
@@ -3192,6 +3414,17 @@ async function buildServer(config) {
|
|
|
3192
3414
|
openWorldHint: true
|
|
3193
3415
|
}
|
|
3194
3416
|
}, (args) => handleAddTaskNote(api, args));
|
|
3417
|
+
registerWriteTool(server, config.context, "create_client", {
|
|
3418
|
+
title: "Create a new client",
|
|
3419
|
+
description: "Create a new client record in the practice. Requires name and type (PrivateLimitedCompany, Individual, SoleTrader, etc.). Optionally set status (Active/Prospect/Inactive/LostProspect), email, telephone, internal reference, and assign a manager/partner/associate/team. The client code is auto-generated. Use when the user says 'add a new client', 'create client ACME Ltd', 'onboard a new prospect'.",
|
|
3420
|
+
inputSchema: CreateClientInputSchema,
|
|
3421
|
+
annotations: {
|
|
3422
|
+
readOnlyHint: false,
|
|
3423
|
+
destructiveHint: false,
|
|
3424
|
+
idempotentHint: false,
|
|
3425
|
+
openWorldHint: true
|
|
3426
|
+
}
|
|
3427
|
+
}, (args) => handleCreateClient(api, args));
|
|
3195
3428
|
registerWriteTool(server, config.context, "add_client_note", {
|
|
3196
3429
|
title: "Add a note to a client",
|
|
3197
3430
|
description: "Create a new note on a client. Additive — does not modify or delete existing notes. The note is attributed to the authenticated API user and timestamped to 'now'. Use this when the user asks you to capture something on a client record: 'add a note on ACME that they mentioned expanding into Ireland', 'log on Greggs that they're switching bookkeeping software next quarter'. Client notes are the right place for persistent, client-level context; for task-specific notes use add_task_note. The user can edit or delete notes in the Sodium UI.",
|