@sodiumhq/mcp-pm 0.1.0-beta.2767 → 0.1.0-beta.2771
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 +186 -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,102 @@ 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/list-client-notes.ts
|
|
2975
|
+
const sortFieldEnum$1 = z.enum(["Date", "UpdatedDate"]);
|
|
2976
|
+
const ListClientNotesInputSchema = {
|
|
2977
|
+
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."),
|
|
2978
|
+
search: z.string().min(3, "Search must be at least 3 characters when provided").optional().describe("Search across note text. Minimum 3 characters."),
|
|
2979
|
+
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?'."),
|
|
2980
|
+
sortBy: sortFieldEnum$1.optional().describe("Sort by Date (note date) or UpdatedDate. Default: Date descending (newest first)."),
|
|
2981
|
+
sortDesc: z.boolean().optional().describe("Sort descending (default: true for newest first)."),
|
|
2982
|
+
limit: z.number().int().min(0).max(50).optional().describe("Max results to return (default: 10, max: 50)."),
|
|
2983
|
+
offset: z.number().int().min(0).optional().describe("Number of records to skip for pagination.")
|
|
2984
|
+
};
|
|
2985
|
+
async function handleListClientNotes(api, args) {
|
|
2986
|
+
try {
|
|
2987
|
+
const { clientCode, ...query } = args;
|
|
2988
|
+
const result = await api.listClientNotes(clientCode, query);
|
|
2989
|
+
const notes = result.data ?? [];
|
|
2990
|
+
const total = result.totalCount ?? notes.length;
|
|
2991
|
+
if (notes.length === 0) return { content: [{
|
|
2992
|
+
type: "text",
|
|
2993
|
+
text: total === 0 ? `No notes found for client ${clientCode}.` : `${total} note(s) found, but none in this page (offset ${result.offset}).`
|
|
2994
|
+
}] };
|
|
2995
|
+
const lines = [`Notes for ${clientCode}: ${notes.length} of ${total}${result.hasMore ? " (more available)" : ""}`, ""];
|
|
2996
|
+
for (const n of notes) lines.push(formatNote(n));
|
|
2997
|
+
return { content: [{
|
|
2998
|
+
type: "text",
|
|
2999
|
+
text: lines.join("\n")
|
|
3000
|
+
}] };
|
|
3001
|
+
} catch (error) {
|
|
3002
|
+
return {
|
|
3003
|
+
content: [{
|
|
3004
|
+
type: "text",
|
|
3005
|
+
text: error instanceof SodiumApiError ? `Error listing client notes: ${error.message} (correlation: ${error.correlationId})` : `Error listing client notes: ${error instanceof Error ? error.message : String(error)}`
|
|
3006
|
+
}],
|
|
3007
|
+
isError: true
|
|
3008
|
+
};
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
function formatNote(n) {
|
|
3012
|
+
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)"}`;
|
|
3013
|
+
}
|
|
3014
|
+
//#endregion
|
|
2856
3015
|
//#region ../mcp-core/src/tools/list-contacts.ts
|
|
2857
3016
|
const sortFieldEnum = z.enum([
|
|
2858
3017
|
"Name",
|
|
@@ -3081,6 +3240,16 @@ async function buildServer(config) {
|
|
|
3081
3240
|
openWorldHint: true
|
|
3082
3241
|
}
|
|
3083
3242
|
}, (args) => handleGetCustomFieldDetails(api, args));
|
|
3243
|
+
server.registerTool("list_client_notes", {
|
|
3244
|
+
title: "List / search notes on a client",
|
|
3245
|
+
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).",
|
|
3246
|
+
inputSchema: ListClientNotesInputSchema,
|
|
3247
|
+
annotations: {
|
|
3248
|
+
readOnlyHint: true,
|
|
3249
|
+
idempotentHint: true,
|
|
3250
|
+
openWorldHint: true
|
|
3251
|
+
}
|
|
3252
|
+
}, (args) => handleListClientNotes(api, args));
|
|
3084
3253
|
server.registerTool("list_tasks", {
|
|
3085
3254
|
title: "List / filter tasks across the practice",
|
|
3086
3255
|
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.",
|
|
@@ -3192,6 +3361,17 @@ async function buildServer(config) {
|
|
|
3192
3361
|
openWorldHint: true
|
|
3193
3362
|
}
|
|
3194
3363
|
}, (args) => handleAddTaskNote(api, args));
|
|
3364
|
+
registerWriteTool(server, config.context, "create_client", {
|
|
3365
|
+
title: "Create a new client",
|
|
3366
|
+
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'.",
|
|
3367
|
+
inputSchema: CreateClientInputSchema,
|
|
3368
|
+
annotations: {
|
|
3369
|
+
readOnlyHint: false,
|
|
3370
|
+
destructiveHint: false,
|
|
3371
|
+
idempotentHint: false,
|
|
3372
|
+
openWorldHint: true
|
|
3373
|
+
}
|
|
3374
|
+
}, (args) => handleCreateClient(api, args));
|
|
3195
3375
|
registerWriteTool(server, config.context, "add_client_note", {
|
|
3196
3376
|
title: "Add a note to a client",
|
|
3197
3377
|
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.",
|