@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.
Files changed (2) hide show
  1. package/dist/index.js +186 -6
  2. 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.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sodiumhq/mcp-pm",
3
- "version": "0.1.0-beta.2767",
3
+ "version": "0.1.0-beta.2771",
4
4
  "description": "Sodium Practice Management MCP server — lets AI assistants interact with your Sodium tenant",
5
5
  "type": "module",
6
6
  "bin": {