@sodiumhq/mcp-pm 0.1.0-beta.6299 → 0.1.0-beta.6353

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-b0BBk3Y8.mjs";
2
+ import { t as buildServer } from "./src-C2ETa30c.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-b0BBk3Y8.mjs";
2
+ import { t as buildServer } from "./src-C2ETa30c.mjs";
3
3
  import { createRequire } from "node:module";
4
4
  import { serveStdio } from "@modelcontextprotocol/server/stdio";
5
5
  import { parseArgs } from "node:util";
@@ -2491,6 +2491,16 @@ var SodiumApiClient = class {
2491
2491
  if (error !== void 0 || !data) throw this.toError(response, error, correlationId, "get current user");
2492
2492
  return data;
2493
2493
  }
2494
+ async getCurrentTenantUserCode() {
2495
+ const me = await this.getCurrentUser();
2496
+ if (!me.code || !me.email) throw new Error("Could not work out who you are signed in as.");
2497
+ const tenantUser = (await this.listUsers({
2498
+ search: me.email,
2499
+ limit: 50
2500
+ })).data?.find((u) => u.userCode === me.code);
2501
+ if (!tenantUser?.code) throw new Error(`You are not a user of practice '${this.tenant}'.`);
2502
+ return tenantUser.code;
2503
+ }
2494
2504
  async listClients(query = {}) {
2495
2505
  const correlationId = randomUUID();
2496
2506
  const { data, error, response } = await listClients({
@@ -3809,8 +3819,9 @@ var SodiumApiClient = class {
3809
3819
  //#region ../mcp-core/src/context/instructions.ts
3810
3820
  const ROSTER_CAP = 20;
3811
3821
  async function buildInstructions(api, ctx, selection) {
3812
- const [user, tenant, practice, team] = await Promise.allSettled([
3822
+ const [user, userCode, tenant, practice, team] = await Promise.allSettled([
3813
3823
  api.getCurrentUser(),
3824
+ api.getCurrentTenantUserCode(),
3814
3825
  api.getTenantDetails(),
3815
3826
  api.getPracticeDetails(),
3816
3827
  api.listUsers({
@@ -3845,13 +3856,13 @@ async function buildInstructions(api, ctx, selection) {
3845
3856
  `Current UTC time: ${now.toISOString()}`
3846
3857
  ];
3847
3858
  if (user.status === "fulfilled") {
3848
- const name = user.value.fullName ?? user.value.email ?? user.value.code ?? "unknown";
3849
- lines.push(`Current user: ${name}${user.value.code ? ` (${user.value.code})` : ""}`);
3859
+ const name = user.value.fullName ?? user.value.email ?? "unknown";
3860
+ lines.push(`Current user: ${name}${userCode.status === "fulfilled" ? ` (${userCode.value})` : ""}`);
3850
3861
  }
3851
3862
  if (tenant.status === "fulfilled") lines.push(`Tenant: ${tenant.value.name} (${tenant.value.code})`);
3852
3863
  if (practice.status === "fulfilled") lines.push(`Practice: ${practice.value.name}`);
3853
3864
  if (selection.kind === "choice") {
3854
- lines.push("", "PRACTICE SELECTION — this user belongs to more than one practice.", "Data tools take an optional 'practice' argument (a practice code). Ask the user which practice to work in, then pass its code on EVERY tool call for the rest of the conversation. Calls without it are refused with a reminder.");
3865
+ lines.push("", "PRACTICE SELECTION — this user belongs to more than one practice.", "Data tools take an optional 'practice' argument (a practice code). Ask the user which practice to work in, then pass its code on EVERY tool call for the rest of the conversation. Calls without it are refused with a reminder.", "The user's own code differs in each practice. For 'my tasks' / 'my time' style filters, call whoami with the chosen practice and use the user code it shows.");
3855
3866
  if (selection.practices.length > 0) {
3856
3867
  lines.push("Practices available:");
3857
3868
  for (const p of selection.practices) lines.push(`- ${p.name} (${p.code})`);
@@ -5122,11 +5133,11 @@ const AddTaskNoteInputSchema = {
5122
5133
  };
5123
5134
  async function handleAddTaskNote(api, args) {
5124
5135
  try {
5125
- const user = await api.getCurrentUser();
5136
+ const authorCode = await api.getCurrentTenantUserCode();
5126
5137
  const note = await api.createTaskNote(args.taskCode, {
5127
5138
  text: args.text,
5128
5139
  date: (/* @__PURE__ */ new Date()).toISOString(),
5129
- noteFromUserCode: user.code ?? "",
5140
+ noteFromUserCode: authorCode,
5130
5141
  pinnedLevel: args.pinnedLevel ?? 0
5131
5142
  });
5132
5143
  return { content: [{
@@ -6015,8 +6026,9 @@ async function handleUpdateContact(api, args) {
6015
6026
  //#region ../mcp-core/src/tools/whoami.ts
6016
6027
  async function handleWhoami(api) {
6017
6028
  try {
6018
- const [userResult, tenantResult, practiceResult] = await Promise.allSettled([
6029
+ const [userResult, userCodeResult, tenantResult, practiceResult] = await Promise.allSettled([
6019
6030
  api.getCurrentUser(),
6031
+ api.getCurrentTenantUserCode(),
6020
6032
  api.getTenantDetails(),
6021
6033
  api.getPracticeDetails()
6022
6034
  ]);
@@ -6026,7 +6038,8 @@ async function handleWhoami(api) {
6026
6038
  lines.push("--- User ---");
6027
6039
  lines.push(`Name: ${u.fullName ?? "(not set)"}`);
6028
6040
  lines.push(`Email: ${u.email ?? "(not set)"}`);
6029
- lines.push(`Code: ${u.code ?? "(not set)"}`);
6041
+ if (userCodeResult.status === "fulfilled") lines.push(`Practice user code: ${userCodeResult.value}`);
6042
+ lines.push(`Platform user code (not accepted by practice filters): ${u.code ?? "(not set)"}`);
6030
6043
  lines.push(`Email verified: ${u.isEmailVerified ? "yes" : "no"}`);
6031
6044
  lines.push(`Auth provider: ${u.authProvider ?? "(not set)"}`);
6032
6045
  if (u.isSuperAdmin) lines.push("Super admin: yes");
@@ -9376,15 +9389,25 @@ function captureClientInfo(server, api) {
9376
9389
  version: info.version
9377
9390
  });
9378
9391
  }
9392
+ function withAnnotationTitle(config) {
9393
+ if (!config.title) return config;
9394
+ return {
9395
+ ...config,
9396
+ annotations: {
9397
+ title: config.title,
9398
+ ...config.annotations
9399
+ }
9400
+ };
9401
+ }
9379
9402
  function registerTenantTool(server, api, name, config, cb) {
9380
9403
  const inputSchema = {
9381
9404
  ...config.inputSchema ?? {},
9382
9405
  practice: practiceField
9383
9406
  };
9384
- server.registerTool(name, {
9407
+ server.registerTool(name, withAnnotationTitle({
9385
9408
  ...config,
9386
9409
  inputSchema
9387
- }, async (args, extra) => {
9410
+ }), async (args, extra) => {
9388
9411
  const started = Date.now();
9389
9412
  captureClientInfo(server, api);
9390
9413
  const { practice, ...rest } = args;
@@ -9464,21 +9487,21 @@ async function buildServer(config) {
9464
9487
  openWorldHint: true
9465
9488
  }
9466
9489
  }, () => handleGetPracticeDetails(api));
9467
- server.registerTool("whoami", {
9490
+ server.registerTool("whoami", withAnnotationTitle({
9468
9491
  title: "Show current user and tenant info",
9469
- description: "Returns the identity of the authenticated user (name, email, code), the tenant/practice the call runs against (name, code, status), and the practice name. Use this when the user asks 'who am I?', 'which tenant am I connected to?', 'what account is this?', or when debugging authentication/permission issues. The optional 'practice' argument picks the practice section shown; without it the user's lone practice is used, and the identity section still answers when no practice can be resolved.",
9492
+ description: "Returns the identity of the authenticated user (name, email, and their user code in the practice, which is the code to use for 'my tasks' / 'my time' filters and 'assign to me'), the tenant/practice the call runs against (name, code, status), and the practice name. Use this when the user asks 'who am I?', 'which tenant am I connected to?', 'what account is this?', or when debugging authentication/permission issues. The optional 'practice' argument picks the practice section shown; without it the user's lone practice is used, and the identity section still answers when no practice can be resolved.",
9470
9493
  inputSchema: { practice: practiceField },
9471
9494
  annotations: {
9472
9495
  readOnlyHint: true,
9473
9496
  idempotentHint: true,
9474
9497
  openWorldHint: true
9475
9498
  }
9476
- }, async (args) => {
9499
+ }), async (args) => {
9477
9500
  captureClientInfo(server, api);
9478
9501
  await resolveTenantForCall(api, args.practice);
9479
9502
  return handleWhoami(api);
9480
9503
  });
9481
- server.registerTool("list_practices", {
9504
+ server.registerTool("list_practices", withAnnotationTitle({
9482
9505
  title: "List the practices you can work in",
9483
9506
  description: "List the practices (tenants) the signed-in user belongs to. Use this when the user belongs to more than one practice and you need the codes to pass in other tools' 'practice' argument, or when the user asks 'which practices can I access?'. Practices flagged 'unavailable' cannot be worked in.",
9484
9507
  inputSchema: {},
@@ -9487,7 +9510,7 @@ async function buildServer(config) {
9487
9510
  idempotentHint: true,
9488
9511
  openWorldHint: true
9489
9512
  }
9490
- }, () => {
9513
+ }), () => {
9491
9514
  captureClientInfo(server, api);
9492
9515
  return handleListPractices(api);
9493
9516
  });
@@ -9694,7 +9717,7 @@ async function buildServer(config) {
9694
9717
  inputSchema: ListWorkflowOccurrencesInputSchema,
9695
9718
  annotations: reportAnnotations
9696
9719
  }, (args) => handleListWorkflowOccurrences(api, args));
9697
- server.registerTool("get_usage_examples", {
9720
+ server.registerTool("get_usage_examples", withAnnotationTitle({
9698
9721
  title: "Show example prompts for this integration",
9699
9722
  description: "Returns a curated cookbook of example prompts this Sodium integration answers well, grouped by topic (daily-workflow, clients, tasks-and-deadlines, client-requests, time-tracking, reports, companies-house, proposals-and-engagements, making-changes). Use when the user asks 'what can you do?', 'what can I ask about my Sodium data?', 'give me some examples', or seems unsure how to get value from the integration. Also useful onboarding a new user: show the overview, then drill into the topic nearest their role. Static content — makes no API call.",
9700
9723
  inputSchema: GetUsageExamplesInputSchema,
@@ -9703,7 +9726,7 @@ async function buildServer(config) {
9703
9726
  idempotentHint: true,
9704
9727
  openWorldHint: false
9705
9728
  }
9706
- }, (args) => handleGetUsageExamples(args));
9729
+ }), (args) => handleGetUsageExamples(args));
9707
9730
  const lookupAnnotations = {
9708
9731
  readOnlyHint: true,
9709
9732
  idempotentHint: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sodiumhq/mcp-pm",
3
- "version": "0.1.0-beta.6299",
3
+ "version": "0.1.0-beta.6353",
4
4
  "description": "Sodium Practice Management MCP server — lets AI assistants interact with your Sodium tenant",
5
5
  "type": "module",
6
6
  "bin": {