@sodiumhq/mcp-pm 0.1.0-beta.2771 → 0.1.0-beta.2774

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 +139 -0
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -829,6 +829,26 @@ const listContacts = (options) => (options.client ?? client).get({
829
829
  ...options
830
830
  });
831
831
  /**
832
+ * Create Contact
833
+ *
834
+ * Creates a new Contact for the specified tenant.
835
+ */
836
+ const createContact = (options) => (options.client ?? client).post({
837
+ security: [{
838
+ name: "x-api-key",
839
+ type: "apiKey"
840
+ }, {
841
+ scheme: "bearer",
842
+ type: "http"
843
+ }],
844
+ url: "/tenants/{tenant}/contacts",
845
+ ...options,
846
+ headers: {
847
+ "Content-Type": "application/json",
848
+ ...options.headers
849
+ }
850
+ });
851
+ /**
832
852
  * Get Contact
833
853
  *
834
854
  * Gets a Contact for the specified tenant.
@@ -1576,6 +1596,16 @@ var SodiumApiClient = class {
1576
1596
  if (error !== void 0 || !data) throw this.toError(response, error, correlationId, `get contact ${code}`);
1577
1597
  return data;
1578
1598
  }
1599
+ async createContact(body) {
1600
+ const correlationId = randomUUID();
1601
+ const { data, error, response } = await createContact({
1602
+ path: { tenant: this.ctx.tenant },
1603
+ body,
1604
+ headers: { "X-Correlation-Id": correlationId }
1605
+ });
1606
+ if (error !== void 0 || !data) throw this.toError(response, error, correlationId, "create contact");
1607
+ return data;
1608
+ }
1579
1609
  async updateContact(code, body) {
1580
1610
  const correlationId = randomUUID();
1581
1611
  const { data, error, response } = await updateContact({
@@ -1596,6 +1626,15 @@ var SodiumApiClient = class {
1596
1626
  const obj = error;
1597
1627
  const detail = obj.detail ?? obj.message;
1598
1628
  if (typeof detail === "string") message = `${detail} (HTTP ${status}, ${operation})`;
1629
+ const errors = obj.errors;
1630
+ if (errors && typeof errors === "object" && !Array.isArray(errors)) {
1631
+ const fieldLines = [];
1632
+ for (const [field, msgs] of Object.entries(errors)) {
1633
+ const msgList = Array.isArray(msgs) ? msgs.join("; ") : String(msgs);
1634
+ fieldLines.push(` ${field}: ${msgList}`);
1635
+ }
1636
+ if (fieldLines.length > 0) message += `\nField errors:\n${fieldLines.join("\n")}`;
1637
+ }
1599
1638
  } else if (typeof error === "string") message = error;
1600
1639
  return new SodiumApiError(message, status, correlationId);
1601
1640
  }
@@ -2971,6 +3010,85 @@ async function handleCreateClient(api, args) {
2971
3010
  }
2972
3011
  }
2973
3012
  //#endregion
3013
+ //#region ../mcp-core/src/tools/create-contact.ts
3014
+ const CreateContactInputSchema = {
3015
+ lastName: z.string().min(1, "Last name is required").describe("The contact's last name (required)."),
3016
+ title: z.string().nullable().optional().describe("Title (e.g. Mr, Mrs, Dr)."),
3017
+ firstName: z.string().nullable().optional().describe("First name."),
3018
+ middleName: z.string().nullable().optional().describe("Middle name."),
3019
+ email: z.string().nullable().optional().describe("Email address."),
3020
+ phone: z.string().nullable().optional().describe("Landline phone number."),
3021
+ mobile: z.string().nullable().optional().describe("Mobile phone number."),
3022
+ dateOfBirth: z.string().nullable().optional().describe("Date of birth in ISO 8601 format (e.g. '1985-03-15')."),
3023
+ utr: z.string().nullable().optional().describe("Self Assessment UTR."),
3024
+ niNumber: z.string().nullable().optional().describe("National Insurance Number."),
3025
+ address: z.string().nullable().optional().describe("Contact address.")
3026
+ };
3027
+ async function handleCreateContact(api, args) {
3028
+ try {
3029
+ const created = await api.createContact(args);
3030
+ const lines = [`Created contact: ${[created.firstName, created.lastName].filter(Boolean).join(" ") || "(no name)"} (${created.code ?? "(no code)"})`];
3031
+ if (created.email) lines.push(`Email: ${created.email}`);
3032
+ if (created.phone) lines.push(`Phone: ${created.phone}`);
3033
+ if (created.mobile) lines.push(`Mobile: ${created.mobile}`);
3034
+ return { content: [{
3035
+ type: "text",
3036
+ text: lines.join("\n")
3037
+ }] };
3038
+ } catch (error) {
3039
+ return {
3040
+ content: [{
3041
+ type: "text",
3042
+ text: error instanceof SodiumApiError ? `Error creating contact: ${error.message} (correlation: ${error.correlationId})` : `Error creating contact: ${error instanceof Error ? error.message : String(error)}`
3043
+ }],
3044
+ isError: true
3045
+ };
3046
+ }
3047
+ }
3048
+ //#endregion
3049
+ //#region ../mcp-core/src/tools/get-contact.ts
3050
+ 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.") };
3051
+ async function handleGetContact(api, args) {
3052
+ try {
3053
+ const c = await api.getContact(args.contactCode);
3054
+ const lines = [];
3055
+ const name = [
3056
+ c.title,
3057
+ c.firstName,
3058
+ c.middleName,
3059
+ c.lastName
3060
+ ].filter(Boolean).join(" ") || "(no name)";
3061
+ lines.push(`Contact: ${name} (${c.code ?? args.contactCode})`);
3062
+ if (c.email) lines.push(`Email: ${c.email}`);
3063
+ if (c.phone) lines.push(`Phone: ${c.phone}`);
3064
+ if (c.mobile) lines.push(`Mobile: ${c.mobile}`);
3065
+ if (c.address) lines.push(`Address: ${c.address}`);
3066
+ if (c.dateOfBirth) lines.push(`Date of birth: ${c.dateOfBirth}`);
3067
+ if (c.nationality) lines.push(`Nationality: ${c.nationality}`);
3068
+ if (c.maritalStatus) lines.push(`Marital status: ${c.maritalStatus}`);
3069
+ if (c.utr) lines.push(`UTR: ${c.utr}`);
3070
+ if (c.niNumber) lines.push(`NI number: ${c.niNumber}`);
3071
+ if (c.personalCode) lines.push(`Companies House person code: ${c.personalCode}`);
3072
+ if (c.isDeceased) lines.push(`Deceased: yes${c.deceasedDate ? ` (${c.deceasedDate})` : ""}`);
3073
+ if (c.clientCount !== void 0) lines.push(`Linked to ${c.clientCount} client(s)`);
3074
+ if (c.client) lines.push(`Primary client: ${c.client.name} (${c.client.code})`);
3075
+ if (c.individualClient) lines.push(`Individual client record: ${c.individualClient.name} (${c.individualClient.code})`);
3076
+ if (c.portalUser) lines.push(`Portal user: ${c.portalUser.name} (${c.portalUser.code})`);
3077
+ return { content: [{
3078
+ type: "text",
3079
+ text: lines.join("\n")
3080
+ }] };
3081
+ } catch (error) {
3082
+ return {
3083
+ content: [{
3084
+ type: "text",
3085
+ text: error instanceof SodiumApiError ? `Error getting contact: ${error.message} (correlation: ${error.correlationId})` : `Error getting contact: ${error instanceof Error ? error.message : String(error)}`
3086
+ }],
3087
+ isError: true
3088
+ };
3089
+ }
3090
+ }
3091
+ //#endregion
2974
3092
  //#region ../mcp-core/src/tools/list-client-notes.ts
2975
3093
  const sortFieldEnum$1 = z.enum(["Date", "UpdatedDate"]);
2976
3094
  const ListClientNotesInputSchema = {
@@ -3350,6 +3468,16 @@ async function buildServer(config) {
3350
3468
  openWorldHint: true
3351
3469
  }
3352
3470
  }, (args) => handleListContacts(api, args));
3471
+ server.registerTool("get_contact", {
3472
+ title: "Get full details of a contact",
3473
+ 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.",
3474
+ inputSchema: GetContactInputSchema,
3475
+ annotations: {
3476
+ readOnlyHint: true,
3477
+ idempotentHint: true,
3478
+ openWorldHint: true
3479
+ }
3480
+ }, (args) => handleGetContact(api, args));
3353
3481
  registerWriteTool(server, config.context, "add_task_note", {
3354
3482
  title: "Add a note to a task",
3355
3483
  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.",
@@ -3405,6 +3533,17 @@ async function buildServer(config) {
3405
3533
  openWorldHint: true
3406
3534
  }
3407
3535
  }, (args) => handleUpdateContact(api, args));
3536
+ registerWriteTool(server, config.context, "create_contact", {
3537
+ title: "Create a new contact",
3538
+ 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.",
3539
+ inputSchema: CreateContactInputSchema,
3540
+ annotations: {
3541
+ readOnlyHint: false,
3542
+ destructiveHint: false,
3543
+ idempotentHint: false,
3544
+ openWorldHint: true
3545
+ }
3546
+ }, (args) => handleCreateContact(api, args));
3408
3547
  return server;
3409
3548
  }
3410
3549
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sodiumhq/mcp-pm",
3
- "version": "0.1.0-beta.2771",
3
+ "version": "0.1.0-beta.2774",
4
4
  "description": "Sodium Practice Management MCP server — lets AI assistants interact with your Sodium tenant",
5
5
  "type": "module",
6
6
  "bin": {