@sodiumhq/mcp-pm 0.1.0-beta.2772 → 0.1.0-beta.2778

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 +170 -3
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -654,6 +654,26 @@ const listClientContactsForClient = (options) => (options.client ?? client).get(
654
654
  ...options
655
655
  });
656
656
  /**
657
+ * Create Contact for Client
658
+ *
659
+ * Creates a client-contact relationship. If Contact.Code is provided and matches an existing contact, that contact will be linked to this client. If Contact.Code is not provided (or empty), a new Contact will be created using the provided contact details (FirstName, LastName, Email, etc.) and then linked to this client.
660
+ */
661
+ const createContactForClient = (options) => (options.client ?? client).post({
662
+ security: [{
663
+ name: "x-api-key",
664
+ type: "apiKey"
665
+ }, {
666
+ scheme: "bearer",
667
+ type: "http"
668
+ }],
669
+ url: "/tenants/{tenant}/clients/{client}/clientcontact",
670
+ ...options,
671
+ headers: {
672
+ "Content-Type": "application/json",
673
+ ...options.headers
674
+ }
675
+ });
676
+ /**
657
677
  * Get Custom Field Values
658
678
  *
659
679
  * Returns field code and value pairs for the specified client. Use the custom field definitions endpoint to retrieve data types, labels, and allowed options.
@@ -724,7 +744,7 @@ const listClientNotesForClient = (options) => (options.client ?? client).get({
724
744
  /**
725
745
  * Create Note for Client
726
746
  *
727
- * Creates a new Note for the specified client.
747
+ * Creates a new note for the specified client. If NoteFromUserCode is not provided, it defaults to the authenticated user.
728
748
  */
729
749
  const createClientNoteForClient = (options) => (options.client ?? client).post({
730
750
  security: [{
@@ -829,6 +849,26 @@ const listContacts = (options) => (options.client ?? client).get({
829
849
  ...options
830
850
  });
831
851
  /**
852
+ * Create Contact
853
+ *
854
+ * Creates a new Contact for the specified tenant.
855
+ */
856
+ const createContact = (options) => (options.client ?? client).post({
857
+ security: [{
858
+ name: "x-api-key",
859
+ type: "apiKey"
860
+ }, {
861
+ scheme: "bearer",
862
+ type: "http"
863
+ }],
864
+ url: "/tenants/{tenant}/contacts",
865
+ ...options,
866
+ headers: {
867
+ "Content-Type": "application/json",
868
+ ...options.headers
869
+ }
870
+ });
871
+ /**
832
872
  * Get Contact
833
873
  *
834
874
  * Gets a Contact for the specified tenant.
@@ -1483,6 +1523,19 @@ var SodiumApiClient = class {
1483
1523
  if (error !== void 0 || !data) throw this.toError(response, error, correlationId, "create client");
1484
1524
  return data;
1485
1525
  }
1526
+ async linkContactToClient(clientCode, body) {
1527
+ const correlationId = randomUUID();
1528
+ const { data, error, response } = await createContactForClient({
1529
+ path: {
1530
+ tenant: this.ctx.tenant,
1531
+ client: clientCode
1532
+ },
1533
+ body,
1534
+ headers: { "X-Correlation-Id": correlationId }
1535
+ });
1536
+ if (error !== void 0 || !data) throw this.toError(response, error, correlationId, `link contact to client ${clientCode}`);
1537
+ return data;
1538
+ }
1486
1539
  async listClientNotes(clientCode, query = {}) {
1487
1540
  const correlationId = randomUUID();
1488
1541
  const { data, error, response } = await listClientNotesForClient({
@@ -1576,6 +1629,16 @@ var SodiumApiClient = class {
1576
1629
  if (error !== void 0 || !data) throw this.toError(response, error, correlationId, `get contact ${code}`);
1577
1630
  return data;
1578
1631
  }
1632
+ async createContact(body) {
1633
+ const correlationId = randomUUID();
1634
+ const { data, error, response } = await createContact({
1635
+ path: { tenant: this.ctx.tenant },
1636
+ body,
1637
+ headers: { "X-Correlation-Id": correlationId }
1638
+ });
1639
+ if (error !== void 0 || !data) throw this.toError(response, error, correlationId, "create contact");
1640
+ return data;
1641
+ }
1579
1642
  async updateContact(code, body) {
1580
1643
  const correlationId = randomUUID();
1581
1644
  const { data, error, response } = await updateContact({
@@ -1596,6 +1659,15 @@ var SodiumApiClient = class {
1596
1659
  const obj = error;
1597
1660
  const detail = obj.detail ?? obj.message;
1598
1661
  if (typeof detail === "string") message = `${detail} (HTTP ${status}, ${operation})`;
1662
+ const errors = obj.errors;
1663
+ if (errors && typeof errors === "object" && !Array.isArray(errors)) {
1664
+ const fieldLines = [];
1665
+ for (const [field, msgs] of Object.entries(errors)) {
1666
+ const msgList = Array.isArray(msgs) ? msgs.join("; ") : String(msgs);
1667
+ fieldLines.push(` ${field}: ${msgList}`);
1668
+ }
1669
+ if (fieldLines.length > 0) message += `\nField errors:\n${fieldLines.join("\n")}`;
1670
+ }
1599
1671
  } else if (typeof error === "string") message = error;
1600
1672
  return new SodiumApiError(message, status, correlationId);
1601
1673
  }
@@ -2741,11 +2813,9 @@ const AddClientNoteInputSchema = {
2741
2813
  };
2742
2814
  async function handleAddClientNote(api, args) {
2743
2815
  try {
2744
- const user = await api.getCurrentUser();
2745
2816
  const note = await api.createClientNote(args.clientCode, {
2746
2817
  text: args.text,
2747
2818
  date: (/* @__PURE__ */ new Date()).toISOString(),
2748
- noteFromUserCode: user.code ?? "",
2749
2819
  pinnedLevel: args.pinnedLevel ?? 0
2750
2820
  });
2751
2821
  return { content: [{
@@ -2971,6 +3041,81 @@ async function handleCreateClient(api, args) {
2971
3041
  }
2972
3042
  }
2973
3043
  //#endregion
3044
+ //#region ../mcp-core/src/tools/create-contact.ts
3045
+ const CreateContactInputSchema = {
3046
+ lastName: z.string().min(1, "Last name is required").describe("The contact's last name (required)."),
3047
+ title: z.string().nullable().optional().describe("Title (e.g. Mr, Mrs, Dr)."),
3048
+ firstName: z.string().nullable().optional().describe("First name."),
3049
+ middleName: z.string().nullable().optional().describe("Middle name."),
3050
+ email: z.string().nullable().optional().describe("Email address."),
3051
+ phone: z.string().nullable().optional().describe("Landline phone number."),
3052
+ mobile: z.string().nullable().optional().describe("Mobile phone number."),
3053
+ dateOfBirth: z.string().nullable().optional().describe("Date of birth in ISO 8601 format (e.g. '1985-03-15')."),
3054
+ utr: z.string().nullable().optional().describe("Self Assessment UTR."),
3055
+ niNumber: z.string().nullable().optional().describe("National Insurance Number."),
3056
+ address: z.string().nullable().optional().describe("Contact address.")
3057
+ };
3058
+ async function handleCreateContact(api, args) {
3059
+ try {
3060
+ const created = await api.createContact(args);
3061
+ const lines = [`Created contact: ${[created.firstName, created.lastName].filter(Boolean).join(" ") || "(no name)"} (${created.code ?? "(no code)"})`];
3062
+ if (created.email) lines.push(`Email: ${created.email}`);
3063
+ if (created.phone) lines.push(`Phone: ${created.phone}`);
3064
+ if (created.mobile) lines.push(`Mobile: ${created.mobile}`);
3065
+ return { content: [{
3066
+ type: "text",
3067
+ text: lines.join("\n")
3068
+ }] };
3069
+ } catch (error) {
3070
+ return {
3071
+ content: [{
3072
+ type: "text",
3073
+ text: error instanceof SodiumApiError ? `Error creating contact: ${error.message} (correlation: ${error.correlationId})` : `Error creating contact: ${error instanceof Error ? error.message : String(error)}`
3074
+ }],
3075
+ isError: true
3076
+ };
3077
+ }
3078
+ }
3079
+ //#endregion
3080
+ //#region ../mcp-core/src/tools/link-contact-to-client.ts
3081
+ const contactTypeEnum = z.enum([
3082
+ "Main",
3083
+ "Billing",
3084
+ "Payroll",
3085
+ "Accounts",
3086
+ "Director",
3087
+ "Psc"
3088
+ ]);
3089
+ const LinkContactToClientInputSchema = {
3090
+ clientCode: z.string().min(1, "Client code is required").describe("The client code to link the contact to. Discoverable via list_clients or get_client_summary."),
3091
+ contactCode: z.string().min(1, "Contact code is required").describe("The code of an existing contact to link. Discoverable via list_contacts or get_contact."),
3092
+ types: z.array(contactTypeEnum).min(1, "At least one contact type is required").describe("The type(s) of this contact relationship. Main = primary contact, Billing = billing/invoicing, Payroll = payroll, Accounts = accounts, Director = company director, Psc = person with significant control. A contact can have multiple types (e.g. ['Main', 'Director'])."),
3093
+ role: z.string().nullable().optional().describe("Free-text role description (e.g. 'Managing Director', 'Company Secretary').")
3094
+ };
3095
+ async function handleLinkContactToClient(api, args) {
3096
+ try {
3097
+ const result = await api.linkContactToClient(args.clientCode, {
3098
+ contact: { code: args.contactCode },
3099
+ types: args.types,
3100
+ role: args.role
3101
+ });
3102
+ const contactName = result.contact ? [result.contact.firstName, result.contact.lastName].filter(Boolean).join(" ") : args.contactCode;
3103
+ const types = result.types?.join(", ") ?? args.types.join(", ");
3104
+ return { content: [{
3105
+ type: "text",
3106
+ text: `Linked contact ${contactName} (${args.contactCode}) to client ${args.clientCode} as ${types}.`
3107
+ }] };
3108
+ } catch (error) {
3109
+ return {
3110
+ content: [{
3111
+ type: "text",
3112
+ text: error instanceof SodiumApiError ? `Error linking contact to client: ${error.message} (correlation: ${error.correlationId})` : `Error linking contact to client: ${error instanceof Error ? error.message : String(error)}`
3113
+ }],
3114
+ isError: true
3115
+ };
3116
+ }
3117
+ }
3118
+ //#endregion
2974
3119
  //#region ../mcp-core/src/tools/get-contact.ts
2975
3120
  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
3121
  async function handleGetContact(api, args) {
@@ -3458,6 +3603,28 @@ async function buildServer(config) {
3458
3603
  openWorldHint: true
3459
3604
  }
3460
3605
  }, (args) => handleUpdateContact(api, args));
3606
+ registerWriteTool(server, config.context, "create_contact", {
3607
+ title: "Create a new contact",
3608
+ description: "Create a new contact record. Requires at least a last name. Optionally set title, first name, email, phone, mobile, date of birth, UTR, NI number, and address. The contact code is auto-generated. Use when the user says 'add a contact', 'create contact John Smith', 'add a new director for ACME'. Note: this creates the contact record only — to link it to a client, use the Sodium web UI.",
3609
+ inputSchema: CreateContactInputSchema,
3610
+ annotations: {
3611
+ readOnlyHint: false,
3612
+ destructiveHint: false,
3613
+ idempotentHint: false,
3614
+ openWorldHint: true
3615
+ }
3616
+ }, (args) => handleCreateContact(api, args));
3617
+ registerWriteTool(server, config.context, "link_contact_to_client", {
3618
+ title: "Link an existing contact to a client",
3619
+ description: "Link an existing contact to a client with one or more relationship types (Main, Billing, Payroll, Accounts, Director, Psc). Optionally set a free-text role (e.g. 'Managing Director'). The contact must already exist — use create_contact first if needed, then link with this tool. Use when the user says 'add John as a director on ACME', 'link contact CON-001 to client CLI-002 as the main contact'.",
3620
+ inputSchema: LinkContactToClientInputSchema,
3621
+ annotations: {
3622
+ readOnlyHint: false,
3623
+ destructiveHint: false,
3624
+ idempotentHint: false,
3625
+ openWorldHint: true
3626
+ }
3627
+ }, (args) => handleLinkContactToClient(api, args));
3461
3628
  return server;
3462
3629
  }
3463
3630
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sodiumhq/mcp-pm",
3
- "version": "0.1.0-beta.2772",
3
+ "version": "0.1.0-beta.2778",
4
4
  "description": "Sodium Practice Management MCP server — lets AI assistants interact with your Sodium tenant",
5
5
  "type": "module",
6
6
  "bin": {