@sodiumhq/mcp-pm 0.1.0-beta.2749 → 0.1.0-beta.2765

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 CHANGED
@@ -60,6 +60,7 @@ Only enable write mode with an AI client you trust — it hands the client the a
60
60
 
61
61
  **Practice**
62
62
  - **`get_practice_details`** — consolidated practice overview (counts, connections, settings)
63
+ - **`whoami`** — show the authenticated user (name, email, code), tenant (name, code, status), and practice name. Useful for verifying which account is connected.
63
64
 
64
65
  **Clients**
65
66
  - **`list_clients`** — list and filter clients by search, status, type, assignee, services, saved filters
package/dist/index.js CHANGED
@@ -1410,8 +1410,11 @@ var SodiumApiClient = class {
1410
1410
  toError(response, error, correlationId, operation) {
1411
1411
  const status = response.status;
1412
1412
  let message = `Failed to ${operation} (HTTP ${status})`;
1413
- if (error && typeof error === "object" && "message" in error) message = String(error.message);
1414
- else if (typeof error === "string") message = error;
1413
+ if (error && typeof error === "object") {
1414
+ const obj = error;
1415
+ const detail = obj.detail ?? obj.message;
1416
+ if (typeof detail === "string") message = `${detail} (HTTP ${status}, ${operation})`;
1417
+ } else if (typeof error === "string") message = error;
1415
1418
  return new SodiumApiError(message, status, correlationId);
1416
1419
  }
1417
1420
  };
@@ -1678,7 +1681,7 @@ async function handleGetClientSummary(api, { code }) {
1678
1681
  datesResult.status === "rejected" ? "key dates" : null,
1679
1682
  overdueResult.status === "rejected" ? "overdue tasks" : null,
1680
1683
  upcomingResult.status === "rejected" ? "upcoming tasks" : null,
1681
- !customFieldsAvailable ? "custom fields" : null
1684
+ !customFieldsAvailable ? `custom fields (${gapReason(customFieldDefsResult, customFieldValsResult)})` : null
1682
1685
  ].filter((v) => v !== null)
1683
1686
  })
1684
1687
  }] };
@@ -1783,6 +1786,15 @@ function formatBusinessDetails(bd) {
1783
1786
  function formatClientDate(d) {
1784
1787
  return `- ${humanizeDateType(d.dateType ?? "")}: ${d.date ?? "(no date)"}${d.description ? ` — ${d.description}` : ""}`;
1785
1788
  }
1789
+ function gapReason(...results) {
1790
+ for (const r of results) if (r.status === "rejected") {
1791
+ const err = r.reason;
1792
+ if (err instanceof SodiumApiError) return `${err.statusCode ?? "?"}: ${err.message}`;
1793
+ if (err instanceof Error) return err.message;
1794
+ return String(err);
1795
+ }
1796
+ return "unknown";
1797
+ }
1786
1798
  function humanizeDateType(type) {
1787
1799
  if (!type) return "(unknown)";
1788
1800
  const words = type.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").split(" ");
@@ -2683,6 +2695,70 @@ function validateFieldValue(def, value) {
2683
2695
  }
2684
2696
  }
2685
2697
  //#endregion
2698
+ //#region ../mcp-core/src/tools/whoami.ts
2699
+ async function handleWhoami(api) {
2700
+ try {
2701
+ const [userResult, tenantResult, practiceResult] = await Promise.allSettled([
2702
+ api.getCurrentUser(),
2703
+ api.getTenantDetails(),
2704
+ api.getPracticeDetails()
2705
+ ]);
2706
+ const lines = [];
2707
+ if (userResult.status === "fulfilled") {
2708
+ const u = userResult.value;
2709
+ lines.push("--- User ---");
2710
+ lines.push(`Name: ${u.fullName ?? "(not set)"}`);
2711
+ lines.push(`Email: ${u.email ?? "(not set)"}`);
2712
+ lines.push(`Code: ${u.code ?? "(not set)"}`);
2713
+ lines.push(`Email verified: ${u.isEmailVerified ? "yes" : "no"}`);
2714
+ lines.push(`Auth provider: ${u.authProvider ?? "(not set)"}`);
2715
+ if (u.isSuperAdmin) lines.push("Super admin: yes");
2716
+ if (u.isSupportAgent) lines.push("Support agent: yes");
2717
+ } else {
2718
+ lines.push("--- User ---");
2719
+ lines.push(`Error: ${formatError(userResult.reason)}`);
2720
+ }
2721
+ if (tenantResult.status === "fulfilled") {
2722
+ const t = tenantResult.value;
2723
+ lines.push("", "--- Tenant ---");
2724
+ lines.push(`Name: ${t.name ?? "(not set)"}`);
2725
+ lines.push(`Code: ${t.code ?? "(not set)"}`);
2726
+ lines.push(`Status: ${t.status ?? "(unknown)"}`);
2727
+ if (t.activeClientCount !== void 0) lines.push(`Active clients: ${t.activeClientCount}`);
2728
+ } else {
2729
+ lines.push("", "--- Tenant ---");
2730
+ lines.push(`Error: ${formatError(tenantResult.reason)}`);
2731
+ }
2732
+ if (practiceResult.status === "fulfilled") {
2733
+ const p = practiceResult.value;
2734
+ lines.push("", "--- Practice ---");
2735
+ lines.push(`Name: ${p.name ?? "(not set)"}`);
2736
+ if (p.email) lines.push(`Email: ${p.email}`);
2737
+ if (p.website) lines.push(`Website: ${p.website}`);
2738
+ } else {
2739
+ lines.push("", "--- Practice ---");
2740
+ lines.push(`Error: ${formatError(practiceResult.reason)}`);
2741
+ }
2742
+ return { content: [{
2743
+ type: "text",
2744
+ text: lines.join("\n")
2745
+ }] };
2746
+ } catch (error) {
2747
+ return {
2748
+ content: [{
2749
+ type: "text",
2750
+ text: error instanceof SodiumApiError ? `Error: ${error.message} (correlation: ${error.correlationId})` : `Error: ${error instanceof Error ? error.message : String(error)}`
2751
+ }],
2752
+ isError: true
2753
+ };
2754
+ }
2755
+ }
2756
+ function formatError(err) {
2757
+ if (err instanceof SodiumApiError) return `${err.statusCode ?? "?"}: ${err.message}`;
2758
+ if (err instanceof Error) return err.message;
2759
+ return String(err);
2760
+ }
2761
+ //#endregion
2686
2762
  //#region ../mcp-core/src/server.ts
2687
2763
  function registerWriteTool(server, ctx, name, config, cb) {
2688
2764
  if (!ctx.writesEnabled) return;
@@ -2724,6 +2800,16 @@ async function buildServer(config) {
2724
2800
  openWorldHint: true
2725
2801
  }
2726
2802
  }, () => handleGetPracticeDetails(api));
2803
+ server.registerTool("whoami", {
2804
+ title: "Show current user and tenant info",
2805
+ description: "Returns the identity of the authenticated API key user (name, email, code), the tenant they belong to (name, code, status, package), 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.",
2806
+ inputSchema: {},
2807
+ annotations: {
2808
+ readOnlyHint: true,
2809
+ idempotentHint: true,
2810
+ openWorldHint: true
2811
+ }
2812
+ }, () => handleWhoami(api));
2727
2813
  server.registerTool("list_clients", {
2728
2814
  title: "List / search / filter clients",
2729
2815
  description: "List clients with any combination of: search (code/name/internal reference, 3+ chars), status (Active/Inactive/Prospect/LostProspect), type (PrivateLimitedCompany/PublicLimitedCompany/LimitedLiabilityPartnership/Partnership/Individual/Trust/Charity/SoleTrader), manager/partner/associate user codes, service codes, a saved filter code, sort, and pagination. Use search for 'find ACME'-style queries. Use type for 'list limited companies' (pass PrivateLimitedCompany + PublicLimitedCompany). Use status: ['Active'] to exclude prospects/inactive. Returns up to 50 clients per page — paginate via offset for more. For 'how many X?' questions, pass limit=0 to get just the total count without fetching any client data. Follow up with get_client_summary for full detail on a specific client.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sodiumhq/mcp-pm",
3
- "version": "0.1.0-beta.2749",
3
+ "version": "0.1.0-beta.2765",
4
4
  "description": "Sodium Practice Management MCP server — lets AI assistants interact with your Sodium tenant",
5
5
  "type": "module",
6
6
  "bin": {