@sodiumhq/mcp-pm 0.1.0-beta.5330 → 0.1.0-beta.5443
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/README.md +1 -1
- package/dist/http.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{src-BY8wr-4D.mjs → src-CEXQpQbI.mjs} +59 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -125,7 +125,7 @@ Only enable write mode with an AI client you trust — it hands the client the a
|
|
|
125
125
|
**Proposals and engagements**
|
|
126
126
|
- **`get_engagement_pipeline_summary`** — pipeline counts and values by status in one call. "What's our proposal pipeline worth?"
|
|
127
127
|
- **`list_proposals`** and **`list_engagement_letters`** — aliased tools for the same underlying engagement pipeline (pre-acceptance vs signed). Filter by status, client, date range.
|
|
128
|
-
- **`get_proposal_summary`** and **`get_engagement_letter_summary`** — aliased drill-ins: identity + client + value breakdown + services with pricing + PDFs + email history + acceptance record
|
|
128
|
+
- **`get_proposal_summary`** and **`get_engagement_letter_summary`** — aliased drill-ins: identity + client + value breakdown (including catch-up fees, under the practice's own label) + services with pricing + PDFs + email history + acceptance record
|
|
129
129
|
|
|
130
130
|
**Services**
|
|
131
131
|
- **`list_services`** — find and count services in the practice's catalogue by search, category, applicable client type, or archived status. Answers "what do we offer?", "list our tax services", "what do we offer limited companies?", "how many active services do we have?"
|
package/dist/http.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as buildServer } from "./src-
|
|
2
|
+
import { t as buildServer } from "./src-CEXQpQbI.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { SUPPORTED_PROTOCOL_VERSIONS, createMcpHandler } from "@modelcontextprotocol/server";
|
|
5
5
|
import { createHash, randomUUID } from "node:crypto";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as buildServer } from "./src-
|
|
2
|
+
import { t as buildServer } from "./src-CEXQpQbI.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
5
5
|
import { parseArgs } from "node:util";
|
|
@@ -1412,6 +1412,22 @@ const getMailboxConversation = (options) => (options.client ?? client).get({
|
|
|
1412
1412
|
...options
|
|
1413
1413
|
});
|
|
1414
1414
|
/**
|
|
1415
|
+
* Get engagement settings
|
|
1416
|
+
*
|
|
1417
|
+
* Returns the engagement settings for the specified tenant
|
|
1418
|
+
*/
|
|
1419
|
+
const getEngagementSettings = (options) => (options.client ?? client).get({
|
|
1420
|
+
security: [{
|
|
1421
|
+
name: "x-api-key",
|
|
1422
|
+
type: "apiKey"
|
|
1423
|
+
}, {
|
|
1424
|
+
scheme: "bearer",
|
|
1425
|
+
type: "http"
|
|
1426
|
+
}],
|
|
1427
|
+
url: "/tenants/{tenant}/practice/engagement-settings",
|
|
1428
|
+
...options
|
|
1429
|
+
});
|
|
1430
|
+
/**
|
|
1415
1431
|
* List Engagements
|
|
1416
1432
|
*
|
|
1417
1433
|
* Lists all Engagements for the given tenant.
|
|
@@ -2678,6 +2694,16 @@ var SodiumApiClient = class {
|
|
|
2678
2694
|
if (error !== void 0 || !data) throw this.toError(response, error, correlationId, `get emails for engagement ${code}`);
|
|
2679
2695
|
return data;
|
|
2680
2696
|
}
|
|
2697
|
+
async getEngagementSettings() {
|
|
2698
|
+
const correlationId = randomUUID();
|
|
2699
|
+
const { data, error, response } = await getEngagementSettings({
|
|
2700
|
+
path: { tenant: this.tenant },
|
|
2701
|
+
headers: { "X-Correlation-Id": correlationId },
|
|
2702
|
+
client: this.http
|
|
2703
|
+
});
|
|
2704
|
+
if (error !== void 0 || !data) throw this.toError(response, error, correlationId, "get engagement settings");
|
|
2705
|
+
return data;
|
|
2706
|
+
}
|
|
2681
2707
|
async getTaskWorkflowGroups(taskCode) {
|
|
2682
2708
|
const correlationId = randomUUID();
|
|
2683
2709
|
const { data, error, response } = await getTaskWorkflowGroups({
|
|
@@ -4248,9 +4274,17 @@ function humanizeDateType(type) {
|
|
|
4248
4274
|
}
|
|
4249
4275
|
//#endregion
|
|
4250
4276
|
//#region ../mcp-core/src/tools/get-engagement-summary.ts
|
|
4277
|
+
const DEFAULT_CATCH_UP_FEE_LABEL = "Catch-up fee";
|
|
4278
|
+
function labelInSentence(label) {
|
|
4279
|
+
return /^[A-Z][A-Z]/.test(label) ? label : label.charAt(0).toLowerCase() + label.slice(1);
|
|
4280
|
+
}
|
|
4251
4281
|
const GetEngagementSummaryInputSchema = { code: z.string().min(1, "Engagement code is required").describe("REQUIRED: The engagement code (identifier, e.g. 'ENG-123'). This is the unique code of a specific engagement — NOT a description like 'the unsent proposal' or 'ACME's proposal'. If the user describes an engagement by attribute (status, client, date) rather than by code, you MUST call list_proposals or list_engagement_letters first (with the appropriate filters) to find the engagement's code, then pass that code here.") };
|
|
4252
4282
|
async function handleGetEngagementSummary(api, { code }) {
|
|
4253
|
-
const [practice, [engagementResult, emailsResult]] = await Promise.all([api.getPracticeLocale(), Promise.allSettled([
|
|
4283
|
+
const [practice, [engagementResult, emailsResult, settingsResult]] = await Promise.all([api.getPracticeLocale(), Promise.allSettled([
|
|
4284
|
+
api.getEngagement(code),
|
|
4285
|
+
api.getEngagementEmails(code),
|
|
4286
|
+
api.getEngagementSettings()
|
|
4287
|
+
])]);
|
|
4254
4288
|
if (engagementResult.status === "rejected") {
|
|
4255
4289
|
const err = engagementResult.reason;
|
|
4256
4290
|
return {
|
|
@@ -4262,18 +4296,24 @@ async function handleGetEngagementSummary(api, { code }) {
|
|
|
4262
4296
|
};
|
|
4263
4297
|
}
|
|
4264
4298
|
const engagement = engagementResult.value;
|
|
4299
|
+
const emails = emailsResult.status === "fulfilled" ? emailsResult.value : [];
|
|
4300
|
+
const catchUpFeeLabel = settingsResult.status === "fulfilled" && settingsResult.value.catchUpFeeLabel?.trim() || DEFAULT_CATCH_UP_FEE_LABEL;
|
|
4301
|
+
const gaps = [];
|
|
4302
|
+
if (emailsResult.status === "rejected") gaps.push("email history");
|
|
4303
|
+
if (settingsResult.status === "rejected") gaps.push("the practice's catch-up fee label");
|
|
4265
4304
|
return { content: [{
|
|
4266
4305
|
type: "text",
|
|
4267
4306
|
text: format$2({
|
|
4268
4307
|
engagement,
|
|
4269
|
-
emails
|
|
4270
|
-
gaps
|
|
4271
|
-
practice
|
|
4308
|
+
emails,
|
|
4309
|
+
gaps,
|
|
4310
|
+
practice,
|
|
4311
|
+
catchUpFeeLabel
|
|
4272
4312
|
})
|
|
4273
4313
|
}] };
|
|
4274
4314
|
}
|
|
4275
4315
|
function format$2(input) {
|
|
4276
|
-
const { engagement: e, emails, gaps, practice } = input;
|
|
4316
|
+
const { engagement: e, emails, gaps, practice, catchUpFeeLabel } = input;
|
|
4277
4317
|
const lines = [];
|
|
4278
4318
|
const code = e.code ?? "(no code)";
|
|
4279
4319
|
const typeLabel = e.typeName ?? e.type ?? "";
|
|
@@ -4287,12 +4327,12 @@ function format$2(input) {
|
|
|
4287
4327
|
lines.push(`Recipient: ${recipientName || "(no name)"}${email}`);
|
|
4288
4328
|
}
|
|
4289
4329
|
if (e.lastViewed) lines.push(`Last viewed by client: ${e.lastViewed}`);
|
|
4290
|
-
const valueLines = formatValues(e, practice);
|
|
4330
|
+
const valueLines = formatValues(e, practice, catchUpFeeLabel);
|
|
4291
4331
|
if (valueLines.length > 0) lines.push("", "--- Value ---", ...valueLines);
|
|
4292
4332
|
const services = e.proposalServices ?? [];
|
|
4293
4333
|
if (services.length > 0) {
|
|
4294
4334
|
lines.push("", `--- Services (${services.length}) ---`);
|
|
4295
|
-
for (const s of services) lines.push(`- ${formatService$1(s, practice)}`);
|
|
4335
|
+
for (const s of services) lines.push(`- ${formatService$1(s, practice, catchUpFeeLabel)}`);
|
|
4296
4336
|
}
|
|
4297
4337
|
if (e.acceptance && e.status === "Accepted") {
|
|
4298
4338
|
lines.push("", "--- Acceptance ---");
|
|
@@ -4318,21 +4358,23 @@ function format$2(input) {
|
|
|
4318
4358
|
if (gaps.length > 0) lines.push("", `Note: could not load ${gaps.join(", ")} (partial failure). Retry for a complete picture.`);
|
|
4319
4359
|
return lines.join("\n");
|
|
4320
4360
|
}
|
|
4321
|
-
function formatValues(e, practice) {
|
|
4361
|
+
function formatValues(e, practice, catchUpFeeLabel) {
|
|
4322
4362
|
const out = [];
|
|
4323
4363
|
if (e.annualTotal && e.annualTotal > 0) out.push(`Annual: ${money(e.annualTotal, practice)}`);
|
|
4324
4364
|
if (e.quarterlyTotal && e.quarterlyTotal > 0) out.push(`Quarterly: ${money(e.quarterlyTotal, practice)}`);
|
|
4325
4365
|
if (e.monthlyTotal && e.monthlyTotal > 0) out.push(`Monthly: ${money(e.monthlyTotal, practice)}`);
|
|
4326
4366
|
if (e.weeklyTotal && e.weeklyTotal > 0) out.push(`Weekly: ${money(e.weeklyTotal, practice)}`);
|
|
4327
4367
|
if (e.oneOffTotal && e.oneOffTotal > 0) out.push(`One-off: ${money(e.oneOffTotal, practice)}`);
|
|
4368
|
+
if (e.catchUpTotal) out.push(`${catchUpFeeLabel}: ${money(e.catchUpTotal, practice)}`);
|
|
4328
4369
|
if (e.totalValue && e.totalValue > 0) out.push(`Total contracted value: ${money(e.totalValue, practice)}`);
|
|
4329
4370
|
return out;
|
|
4330
4371
|
}
|
|
4331
|
-
function formatService$1(s, practice) {
|
|
4372
|
+
function formatService$1(s, practice, catchUpFeeLabel) {
|
|
4332
4373
|
const name = s.billableService?.name ?? "(unnamed)";
|
|
4333
4374
|
const freq = s.billingFrequency ? ` ${s.billingFrequency}` : "";
|
|
4334
|
-
|
|
4335
|
-
|
|
4375
|
+
const catchUp = s.catchUp?.fee ? ` + ${money(s.catchUp.fee, practice)} ${labelInSentence(catchUpFeeLabel)} (${s.catchUp.months ?? "?"} months)` : "";
|
|
4376
|
+
if (s.effectivePrice !== void 0 && s.effectivePrice !== null) return `${name} — ${money(s.effectivePrice, practice)}${freq}${catchUp}`;
|
|
4377
|
+
return freq ? `${name} —${freq}${catchUp}` : `${name}${catchUp}`;
|
|
4336
4378
|
}
|
|
4337
4379
|
function formatEmail$1(em) {
|
|
4338
4380
|
return `- ${em.sentDate ?? "(no date)"}: ${em.subject ?? "(no subject)"}${em.status ? ` · ${em.status}` : ""}${em.toRecipients?.length ? ` → ${em.toRecipients.join(", ")}` : ""}`;
|
|
@@ -8816,8 +8858,7 @@ const AddClientServiceInputSchema = {
|
|
|
8816
8858
|
overrideSetupFee: z.boolean().optional().describe("true = use the custom setupFee instead of the service default."),
|
|
8817
8859
|
managedByUserCode: z.string().optional().describe("User who manages this service for the client. Resolve names via the roster or list_users."),
|
|
8818
8860
|
stageCode: z.string().optional().describe("Initial workflow stage for the service, if the service uses stages."),
|
|
8819
|
-
autoInvoice: z.boolean().optional().describe("Whether the service auto-generates billing line items. Omit to use the default; only set when the user is explicit.")
|
|
8820
|
-
nextBillingDate: z.string().optional().describe("First billing date (YYYY-MM-DD) when autoInvoice is on. Omit to default from startDate.")
|
|
8861
|
+
autoInvoice: z.boolean().optional().describe("Whether the service auto-generates billing line items. Omit to use the default; only set when the user is explicit.")
|
|
8821
8862
|
};
|
|
8822
8863
|
async function handleAddClientService(api, args) {
|
|
8823
8864
|
try {
|
|
@@ -8835,8 +8876,7 @@ async function handleAddClientService(api, args) {
|
|
|
8835
8876
|
...rest.setupFee !== void 0 ? { setupFee: rest.setupFee } : {},
|
|
8836
8877
|
managedByUserCode: rest.managedByUserCode ?? null,
|
|
8837
8878
|
stageCode: rest.stageCode ?? null,
|
|
8838
|
-
...rest.autoInvoice !== void 0 ? { autoInvoice: rest.autoInvoice } : {}
|
|
8839
|
-
nextBillingDate: rest.nextBillingDate ?? null
|
|
8879
|
+
...rest.autoInvoice !== void 0 ? { autoInvoice: rest.autoInvoice } : {}
|
|
8840
8880
|
};
|
|
8841
8881
|
const [practice, created] = await Promise.all([api.getPracticeLocale(), api.createClientService(clientCode, body)]);
|
|
8842
8882
|
const lines = [
|
|
@@ -8846,7 +8886,7 @@ async function handleAddClientService(api, args) {
|
|
|
8846
8886
|
];
|
|
8847
8887
|
if (created.managedByUser?.name) lines.push(`Managed by: ${created.managedByUser.name}`);
|
|
8848
8888
|
if (created.stage?.name) lines.push(`Stage: ${created.stage.name}`);
|
|
8849
|
-
if (created.autoInvoice) lines.push(
|
|
8889
|
+
if (created.autoInvoice) lines.push("Auto-invoicing on");
|
|
8850
8890
|
return { content: [{
|
|
8851
8891
|
type: "text",
|
|
8852
8892
|
text: lines.join("\n")
|
|
@@ -9507,7 +9547,7 @@ async function buildServer(config) {
|
|
|
9507
9547
|
}, (args) => handleListEngagements(api, args));
|
|
9508
9548
|
registerTenantTool(server, api, "get_proposal_summary", {
|
|
9509
9549
|
title: "Get full detail of one proposal",
|
|
9510
|
-
description: "Get a consolidated view of a single proposal by engagement code: identity (code, status, type, date), client, recipient, value breakdown (annual / quarterly / monthly / one-off / total), the proposed services with pricing, acceptance record (if accepted), PDF availability (proposal + letter of engagement), email history, and the acceptance link. REQUIRES a concrete engagement code as input — do NOT call this tool unless you already have the code. If the user describes an engagement by attribute — e.g. 'the unsent proposal', 'the proposal for ACME', 'our most recent proposal', 'the rejected one' — you MUST call list_proposals FIRST with the matching filters to look up the code, then call this tool with that code. Returns identical data to get_engagement_letter_summary — both aliases back the same Engagement entity. Tolerates partial failures (email history may be missing with a note); only fails hard if the engagement itself can't be fetched.",
|
|
9550
|
+
description: "Get a consolidated view of a single proposal by engagement code: identity (code, status, type, date), client, recipient, value breakdown (annual / quarterly / monthly / one-off incl. catch-up fees / total), the proposed services with pricing (and each service's catch-up fee share), acceptance record (if accepted), PDF availability (proposal + letter of engagement), email history, and the acceptance link. REQUIRES a concrete engagement code as input — do NOT call this tool unless you already have the code. If the user describes an engagement by attribute — e.g. 'the unsent proposal', 'the proposal for ACME', 'our most recent proposal', 'the rejected one' — you MUST call list_proposals FIRST with the matching filters to look up the code, then call this tool with that code. Returns identical data to get_engagement_letter_summary — both aliases back the same Engagement entity. Tolerates partial failures (email history or the practice's catch-up fee label may be missing with a note); only fails hard if the engagement itself can't be fetched.",
|
|
9511
9551
|
inputSchema: GetEngagementSummaryInputSchema,
|
|
9512
9552
|
annotations: {
|
|
9513
9553
|
readOnlyHint: true,
|
|
@@ -9517,7 +9557,7 @@ async function buildServer(config) {
|
|
|
9517
9557
|
}, (args) => handleGetEngagementSummary(api, args));
|
|
9518
9558
|
registerTenantTool(server, api, "get_engagement_letter_summary", {
|
|
9519
9559
|
title: "Get full detail of one letter of engagement",
|
|
9520
|
-
description: "Get a consolidated view of a single letter of engagement by code: identity (code, status, type, date), client, recipient, value breakdown (annual / quarterly / monthly / one-off / total), services included with pricing, acceptance record (accepted date, whether accepted via portal or recorded manually), PDF availability (proposal + letter of engagement), email history, and the acceptance link. REQUIRES a concrete engagement code as input — do NOT call this tool unless you already have the code. If the user describes the engagement by attribute — e.g. 'ACME's letter of engagement', 'the most recent signed engagement', 'the live engagement for Smith & Co' — you MUST call list_engagement_letters FIRST with the matching filters to look up the code, then call this tool with that code. Returns identical data to get_proposal_summary — both aliases back the same Engagement entity. Tolerates partial failures (email history may be missing with a note); only fails hard if the engagement itself can't be fetched.",
|
|
9560
|
+
description: "Get a consolidated view of a single letter of engagement by code: identity (code, status, type, date), client, recipient, value breakdown (annual / quarterly / monthly / one-off incl. catch-up fees / total), services included with pricing (and each service's catch-up fee share), acceptance record (accepted date, whether accepted via portal or recorded manually), PDF availability (proposal + letter of engagement), email history, and the acceptance link. REQUIRES a concrete engagement code as input — do NOT call this tool unless you already have the code. If the user describes the engagement by attribute — e.g. 'ACME's letter of engagement', 'the most recent signed engagement', 'the live engagement for Smith & Co' — you MUST call list_engagement_letters FIRST with the matching filters to look up the code, then call this tool with that code. Returns identical data to get_proposal_summary — both aliases back the same Engagement entity. Tolerates partial failures (email history or the practice's catch-up fee label may be missing with a note); only fails hard if the engagement itself can't be fetched.",
|
|
9521
9561
|
inputSchema: GetEngagementSummaryInputSchema,
|
|
9522
9562
|
annotations: {
|
|
9523
9563
|
readOnlyHint: true,
|
|
@@ -9867,7 +9907,7 @@ async function buildServer(config) {
|
|
|
9867
9907
|
}, (args) => handleListClientLinks(api, args));
|
|
9868
9908
|
registerWriteTool(server, api, config.context, "add_client_service", {
|
|
9869
9909
|
title: "Assign a catalogue service to a client",
|
|
9870
|
-
description: "Put a client on one of the practice's services: requires billableServiceCode (from list_services), billingFrequency (must match one of the service's configured pricing options — check get_service_details first), and startDate. Optional: status (default Active; use 'Proposed' when quoting), custom price (with overridePricing=true), pricing tier (REQUIRED for CustomTiers-priced services), setup fee, managing user, initial stage
|
|
9910
|
+
description: "Put a client on one of the practice's services: requires billableServiceCode (from list_services), billingFrequency (must match one of the service's configured pricing options — check get_service_details first), and startDate. Optional: status (default Active; use 'Proposed' when quoting), custom price (with overridePricing=true), pricing tier (REQUIRED for CustomTiers-priced services), setup fee, managing user, initial stage and auto-invoicing (billing dates come from the client's billing schedule). Use for 'put ACME on monthly bookkeeping', 'add VAT returns to Greggs from next month'. NOT exposed: pricing-factor questionnaires — services priced by factors should be configured in the Sodium UI; if the API rejects the call asking for pricing answers, say so and point the user to the UI. Check get_client_summary first to avoid assigning a duplicate service.",
|
|
9871
9911
|
inputSchema: AddClientServiceInputSchema,
|
|
9872
9912
|
annotations: {
|
|
9873
9913
|
readOnlyHint: false,
|