@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 +1 -0
- package/dist/index.js +89 -3
- package/package.json +1 -1
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"
|
|
1414
|
-
|
|
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 ?
|
|
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.",
|