@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.
- package/dist/index.js +170 -3
- 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
|
|
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
|