@sodiumhq/mcp-pm 0.1.0-beta.4579 → 0.1.0-beta.4707

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/http.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as buildServer } from "./src-dwGvEajk.mjs";
2
+ import { t as buildServer } from "./src-DNCbW1Ge.mjs";
3
3
  import { createRequire } from "node:module";
4
4
  import { createHash, randomUUID } from "node:crypto";
5
5
  import { createServer } from "node:http";
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as buildServer } from "./src-dwGvEajk.mjs";
2
+ import { t as buildServer } from "./src-DNCbW1Ge.mjs";
3
3
  import { createRequire } from "node:module";
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
5
  import { parseArgs } from "node:util";
@@ -3577,7 +3577,7 @@ var SodiumApiClient = class {
3577
3577
  //#endregion
3578
3578
  //#region ../mcp-core/src/context/instructions.ts
3579
3579
  const ROSTER_CAP = 20;
3580
- async function buildInstructions(api, writesEnabled, selection) {
3580
+ async function buildInstructions(api, ctx, selection) {
3581
3581
  const [user, tenant, practice, team] = await Promise.allSettled([
3582
3582
  api.getCurrentUser(),
3583
3583
  api.getTenantDetails(),
@@ -3627,7 +3627,8 @@ async function buildInstructions(api, writesEnabled, selection) {
3627
3627
  } else lines.push("Call list_practices to see the available practices.");
3628
3628
  } else if (selection.kind === "none") lines.push("", "This user does not belong to any usable practice. Tell them their Sodium account has no active practice and there is nothing to show.");
3629
3629
  else if (selection.kind === "auto") lines.push("", `Working in ${selection.practice.name} (${selection.practice.code}) — the only practice this user belongs to. It is selected automatically; no 'practice' argument is needed.`);
3630
- lines.push("", writesEnabled ? "Write mode: ENABLED. Create/update tools are available; destructive and bulk operations are not." : "Write mode: DISABLED. Read-only — tell the user to relaunch with --enable-writes if they want changes made.");
3630
+ const isStdio = Boolean(ctx.apiKey) && !ctx.bearerToken;
3631
+ lines.push("", ctx.writesEnabled ? "Write mode: ENABLED. Create and update tools are available; delete and bulk operations are not exposed." : isStdio ? "Write mode: DISABLED. Read-only — tell the user to restart the server with --enable-writes (or SODIUM_ENABLE_WRITES=true) if they want changes made." : "Write mode: DISABLED. Read-only — this connection was authorised for read access only. Tell the user to reconnect the Sodium connector and approve write access if they want changes made.");
3631
3632
  if (team.status === "fulfilled") {
3632
3633
  const members = team.value.data ?? [];
3633
3634
  const total = team.value.totalCount ?? members.length;
@@ -7073,8 +7074,9 @@ function formatThread(t) {
7073
7074
  const from = t.latestFromName ?? t.latestFromEmail ?? "?";
7074
7075
  const counts = `${t.messageCount ?? 0} message${t.messageCount === 1 ? "" : "s"}${t.unreadCount ? `, ${t.unreadCount} unread` : ""}`;
7075
7076
  const attach = t.hasAttachments ? " · has attachments" : "";
7077
+ const ids = `\n configCode: ${t.configCode ?? "?"} · conversationId: ${t.conversationId ?? "?"}`;
7076
7078
  const snippet = t.snippet ? `\n ${t.snippet.length > 160 ? `${t.snippet.slice(0, 160)}…` : t.snippet}` : "";
7077
- return `- ${when} · "${t.subject ?? "(no subject)"}" · latest from ${from} · ${counts}${attach}${snippet}`;
7079
+ return `- ${when} · "${t.subject ?? "(no subject)"}" · latest from ${from} · ${counts}${attach}${ids}${snippet}`;
7078
7080
  }
7079
7081
  async function handleListClientEmails(api, args) {
7080
7082
  try {
@@ -7096,6 +7098,7 @@ async function handleListClientEmails(api, args) {
7096
7098
  ...items.map(formatThread)
7097
7099
  ];
7098
7100
  if (result.hasMore) lines.push("", `More results available — call again with offset: ${(args.offset ?? 0) + items.length}.`);
7101
+ lines.push("", "Use get_client_email_thread with a thread's configCode + conversationId to read the whole conversation.");
7099
7102
  return { content: [{
7100
7103
  type: "text",
7101
7104
  text: lines.join("\n")
@@ -7111,6 +7114,12 @@ async function handleListClientEmails(api, args) {
7111
7114
  }
7112
7115
  }
7113
7116
  //#endregion
7117
+ //#region ../mcp-core/src/tools/aml-common.ts
7118
+ function isAmlNotConfigured(error) {
7119
+ return error instanceof SodiumApiError && error.statusCode === 400 && /aml connection/i.test(error.message);
7120
+ }
7121
+ const AML_NOT_CONFIGURED = "This practice has no AML provider connected in Sodium, so there are no AML/KYC records to show. Records appear here once an AML connection is set up in Sodium.";
7122
+ //#endregion
7114
7123
  //#region ../mcp-core/src/tools/list-aml-clients.ts
7115
7124
  const ListAmlClientsInputSchema = {
7116
7125
  search: z.string().min(3, "Search must be at least 3 characters when provided").optional().describe("Search across AML client name, reference, or contact details. Minimum 3 characters. Use the PM client's name to find its AML record."),
@@ -7156,6 +7165,10 @@ async function handleListAmlClients(api, args) {
7156
7165
  text: lines.join("\n")
7157
7166
  }] };
7158
7167
  } catch (error) {
7168
+ if (isAmlNotConfigured(error)) return { content: [{
7169
+ type: "text",
7170
+ text: AML_NOT_CONFIGURED
7171
+ }] };
7159
7172
  return {
7160
7173
  content: [{
7161
7174
  type: "text",
@@ -7214,6 +7227,10 @@ async function handleGetAmlClient(api, args) {
7214
7227
  text: lines.join("\n")
7215
7228
  }] };
7216
7229
  } catch (error) {
7230
+ if (isAmlNotConfigured(error)) return { content: [{
7231
+ type: "text",
7232
+ text: AML_NOT_CONFIGURED
7233
+ }] };
7217
7234
  return {
7218
7235
  content: [{
7219
7236
  type: "text",
@@ -7444,13 +7461,14 @@ async function handleGetClientAgentAuthorisations(api, args) {
7444
7461
  type: "text",
7445
7462
  text: `No agent authorisation records for client ${args.clientCode}. (Authorisations appear once the client has services that require them.)`
7446
7463
  }] };
7447
- const missing = auths.filter((a) => a.isRequired && a.status !== "Granted");
7464
+ const required = auths.filter((a) => a.isRequired);
7465
+ const missing = required.filter((a) => a.status !== "Granted");
7448
7466
  const lines = [
7449
7467
  `HMRC agent authorisations for ${args.clientCode}:`,
7450
7468
  "",
7451
7469
  ...auths.map(formatAuth)
7452
7470
  ];
7453
- lines.push("", missing.length === 0 ? "All required authorisations are granted." : `⚠ ${missing.length} required authorisation${missing.length === 1 ? " is" : "s are"} not yet granted: ${missing.map((a) => a.authorisationType).join(", ")}. Requesting them is done from the client's page in Sodium.`);
7471
+ lines.push("", required.length === 0 ? "None of this client's current services require an HMRC agent authorisation." : missing.length === 0 ? "All required authorisations are granted." : `⚠ ${missing.length} required authorisation${missing.length === 1 ? " is" : "s are"} not yet granted: ${missing.map((a) => a.authorisationType).join(", ")}. Requesting them is done from the client's page in Sodium.`);
7454
7472
  return { content: [{
7455
7473
  type: "text",
7456
7474
  text: lines.join("\n")
@@ -8285,7 +8303,8 @@ async function handleGetOnboardingStatus(api, args) {
8285
8303
  }
8286
8304
  lines.push("(Name-matched — verify it's the right AML record.)");
8287
8305
  }
8288
- } else if (aml.status === "rejected") failures.push("AML");
8306
+ } else if (aml.status === "rejected") if (isAmlNotConfigured(aml.reason)) lines.push(AML_NOT_CONFIGURED);
8307
+ else failures.push("AML");
8289
8308
  else lines.push("(Client name too short to search the AML register.)");
8290
8309
  lines.push("", "## HMRC agent authorisations");
8291
8310
  if (auths.status === "fulfilled") {
@@ -8906,7 +8925,7 @@ async function buildServer(config) {
8906
8925
  let instructions;
8907
8926
  if (config.includeStartupContext !== false) {
8908
8927
  const selection = api.hasActiveTenant() ? { kind: "explicit" } : await resolveTenant(api);
8909
- instructions = await buildInstructions(api, config.context.writesEnabled, selection);
8928
+ instructions = await buildInstructions(api, config.context, selection);
8910
8929
  }
8911
8930
  const server = new McpServer({
8912
8931
  name: config.serverName,
@@ -9420,7 +9439,7 @@ async function buildServer(config) {
9420
9439
  inputSchema: UpdateClientServiceStageInputSchema,
9421
9440
  annotations: {
9422
9441
  readOnlyHint: false,
9423
- destructiveHint: false,
9442
+ destructiveHint: true,
9424
9443
  idempotentHint: true,
9425
9444
  openWorldHint: true
9426
9445
  }
@@ -9442,7 +9461,7 @@ async function buildServer(config) {
9442
9461
  inputSchema: SendDataFormInputSchema,
9443
9462
  annotations: {
9444
9463
  readOnlyHint: false,
9445
- destructiveHint: false,
9464
+ destructiveHint: true,
9446
9465
  idempotentHint: false,
9447
9466
  openWorldHint: true
9448
9467
  }
@@ -9464,7 +9483,7 @@ async function buildServer(config) {
9464
9483
  inputSchema: UpdateClientDatesInputSchema,
9465
9484
  annotations: {
9466
9485
  readOnlyHint: false,
9467
- destructiveHint: false,
9486
+ destructiveHint: true,
9468
9487
  idempotentHint: true,
9469
9488
  openWorldHint: true
9470
9489
  }
@@ -9475,7 +9494,7 @@ async function buildServer(config) {
9475
9494
  inputSchema: UpdateClientBusinessDetailsInputSchema,
9476
9495
  annotations: {
9477
9496
  readOnlyHint: false,
9478
- destructiveHint: false,
9497
+ destructiveHint: true,
9479
9498
  idempotentHint: true,
9480
9499
  openWorldHint: true
9481
9500
  }
@@ -9497,7 +9516,7 @@ async function buildServer(config) {
9497
9516
  inputSchema: UpdateWorkflowStepInputSchema,
9498
9517
  annotations: {
9499
9518
  readOnlyHint: false,
9500
- destructiveHint: false,
9519
+ destructiveHint: true,
9501
9520
  idempotentHint: true,
9502
9521
  openWorldHint: true
9503
9522
  }
@@ -9519,7 +9538,7 @@ async function buildServer(config) {
9519
9538
  inputSchema: UpdateTaskInputSchema,
9520
9539
  annotations: {
9521
9540
  readOnlyHint: false,
9522
- destructiveHint: false,
9541
+ destructiveHint: true,
9523
9542
  idempotentHint: true,
9524
9543
  openWorldHint: true
9525
9544
  }
@@ -9574,7 +9593,7 @@ async function buildServer(config) {
9574
9593
  inputSchema: SetClientCustomFieldsInputSchema,
9575
9594
  annotations: {
9576
9595
  readOnlyHint: false,
9577
- destructiveHint: false,
9596
+ destructiveHint: true,
9578
9597
  idempotentHint: true,
9579
9598
  openWorldHint: true
9580
9599
  }
@@ -9585,7 +9604,7 @@ async function buildServer(config) {
9585
9604
  inputSchema: UpdateContactInputSchema,
9586
9605
  annotations: {
9587
9606
  readOnlyHint: false,
9588
- destructiveHint: false,
9607
+ destructiveHint: true,
9589
9608
  idempotentHint: true,
9590
9609
  openWorldHint: true
9591
9610
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sodiumhq/mcp-pm",
3
- "version": "0.1.0-beta.4579",
3
+ "version": "0.1.0-beta.4707",
4
4
  "description": "Sodium Practice Management MCP server — lets AI assistants interact with your Sodium tenant",
5
5
  "type": "module",
6
6
  "bin": {