@sodiumhq/mcp-pm 0.1.0-beta.2589 → 0.1.0-beta.2590
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/index.js +191 -23
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -775,6 +775,22 @@ const getCurrentUser = (options) => (options?.client ?? client).get({
|
|
|
775
775
|
url: "/users/me",
|
|
776
776
|
...options
|
|
777
777
|
});
|
|
778
|
+
/**
|
|
779
|
+
* List TenantUsers
|
|
780
|
+
*
|
|
781
|
+
* Lists all TenantUsers for the given tenant.
|
|
782
|
+
*/
|
|
783
|
+
const listTenantUsers = (options) => (options.client ?? client).get({
|
|
784
|
+
security: [{
|
|
785
|
+
name: "x-api-key",
|
|
786
|
+
type: "apiKey"
|
|
787
|
+
}, {
|
|
788
|
+
scheme: "bearer",
|
|
789
|
+
type: "http"
|
|
790
|
+
}],
|
|
791
|
+
url: "/tenants/{tenant}/users",
|
|
792
|
+
...options
|
|
793
|
+
});
|
|
778
794
|
//#endregion
|
|
779
795
|
//#region ../mcp-core/src/http/client.ts
|
|
780
796
|
var SodiumApiError = class extends Error {
|
|
@@ -875,6 +891,20 @@ var SodiumApiClient = class {
|
|
|
875
891
|
if (error !== void 0 || !data) throw this.toError(response, error, correlationId, `list services for client ${clientCode}`);
|
|
876
892
|
return data;
|
|
877
893
|
}
|
|
894
|
+
async listUsers(query = {}) {
|
|
895
|
+
const correlationId = randomUUID();
|
|
896
|
+
const { data, error, response } = await listTenantUsers({
|
|
897
|
+
path: { tenant: this.ctx.tenant },
|
|
898
|
+
query: {
|
|
899
|
+
...query,
|
|
900
|
+
limit: query.limit ?? 10,
|
|
901
|
+
offset: query.offset ?? 0
|
|
902
|
+
},
|
|
903
|
+
headers: { "X-Correlation-Id": correlationId }
|
|
904
|
+
});
|
|
905
|
+
if (error !== void 0 || !data) throw this.toError(response, error, correlationId, "list users");
|
|
906
|
+
return data;
|
|
907
|
+
}
|
|
878
908
|
async listTasks(query = {}) {
|
|
879
909
|
const correlationId = randomUUID();
|
|
880
910
|
const { data, error, response } = await listTaskItems({
|
|
@@ -899,11 +929,17 @@ var SodiumApiClient = class {
|
|
|
899
929
|
};
|
|
900
930
|
//#endregion
|
|
901
931
|
//#region ../mcp-core/src/context/instructions.ts
|
|
932
|
+
const ROSTER_CAP = 20;
|
|
902
933
|
async function buildInstructions(api) {
|
|
903
|
-
const [user, tenant, practice] = await Promise.allSettled([
|
|
934
|
+
const [user, tenant, practice, team] = await Promise.allSettled([
|
|
904
935
|
api.getCurrentUser(),
|
|
905
936
|
api.getTenantDetails(),
|
|
906
|
-
api.getPracticeDetails()
|
|
937
|
+
api.getPracticeDetails(),
|
|
938
|
+
api.listUsers({
|
|
939
|
+
status: "Active",
|
|
940
|
+
limit: ROSTER_CAP,
|
|
941
|
+
sortBy: "LastName"
|
|
942
|
+
})
|
|
907
943
|
]);
|
|
908
944
|
const now = /* @__PURE__ */ new Date();
|
|
909
945
|
const lines = [
|
|
@@ -918,7 +954,20 @@ async function buildInstructions(api) {
|
|
|
918
954
|
}
|
|
919
955
|
if (tenant.status === "fulfilled") lines.push(`Tenant: ${tenant.value.name} (${tenant.value.code})`);
|
|
920
956
|
if (practice.status === "fulfilled") lines.push(`Practice: ${practice.value.name}`);
|
|
921
|
-
|
|
957
|
+
if (team.status === "fulfilled") {
|
|
958
|
+
const members = team.value.data ?? [];
|
|
959
|
+
const total = team.value.totalCount ?? members.length;
|
|
960
|
+
if (members.length > 0) {
|
|
961
|
+
lines.push("", `Team members (${members.length}${total > members.length ? ` of ${total}` : ""} active):`);
|
|
962
|
+
for (const m of members) {
|
|
963
|
+
const code = m.code ?? "(no code)";
|
|
964
|
+
const display = m.displayName ?? [m.firstName, m.lastName].filter(Boolean).join(" ") ?? m.email ?? "(no name)";
|
|
965
|
+
lines.push(`- ${display} (${code})`);
|
|
966
|
+
}
|
|
967
|
+
if (total > members.length) lines.push(`... and ${total - members.length} more. Use list_users to see the rest or filter by role / status.`);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
lines.push("", "When interpreting relative dates (today, this week, next month), use the date above.", "When the user names a team member (e.g. 'Jane's tasks'), resolve to their code from the team roster above before calling other tools.", "All codes (client, task, engagement, user) are string identifiers, not numeric IDs.");
|
|
922
971
|
return lines.join("\n");
|
|
923
972
|
}
|
|
924
973
|
//#endregion
|
|
@@ -971,7 +1020,7 @@ async function handleGetPracticeDetails(api) {
|
|
|
971
1020
|
}
|
|
972
1021
|
//#endregion
|
|
973
1022
|
//#region ../mcp-core/src/tools/list-clients.ts
|
|
974
|
-
const statusEnum$
|
|
1023
|
+
const statusEnum$2 = z.enum([
|
|
975
1024
|
"Active",
|
|
976
1025
|
"Inactive",
|
|
977
1026
|
"Prospect",
|
|
@@ -987,19 +1036,19 @@ const typeEnum = z.enum([
|
|
|
987
1036
|
"Charity",
|
|
988
1037
|
"SoleTrader"
|
|
989
1038
|
]);
|
|
990
|
-
const sortByEnum$
|
|
1039
|
+
const sortByEnum$2 = z.enum(["Name", "InternalReference"]);
|
|
991
1040
|
const ListClientsInputSchema = {
|
|
992
1041
|
search: z.string().min(3, "Search must be at least 3 characters when provided").optional().describe("Free-text search across client code, name, and internal reference. Minimum 3 characters. Omit to browse by filter only."),
|
|
993
|
-
status: z.array(statusEnum$
|
|
1042
|
+
status: z.array(statusEnum$2).optional().describe("Filter by client status. Defaults to all statuses if omitted. Example: ['Active'] for active clients only."),
|
|
994
1043
|
type: z.array(typeEnum).optional().describe("Filter by organisation type. Use ['PrivateLimitedCompany', 'PublicLimitedCompany'] for 'limited companies'. Use ['LimitedLiabilityPartnership'] for LLPs. Defaults to all types if omitted."),
|
|
995
1044
|
managerCode: z.array(z.string()).optional().describe("Filter by assigned manager user codes."),
|
|
996
1045
|
partnerCode: z.array(z.string()).optional().describe("Filter by assigned partner user codes."),
|
|
997
1046
|
associateCode: z.array(z.string()).optional().describe("Filter by assigned associate user codes."),
|
|
998
1047
|
serviceCode: z.array(z.string()).optional().describe("Filter by billable service codes that clients have assigned."),
|
|
999
1048
|
savedFilter: z.string().optional().describe("Code of a user-saved filter to apply. Other filter parameters override fields from the saved filter."),
|
|
1000
|
-
sortBy: sortByEnum$
|
|
1049
|
+
sortBy: sortByEnum$2.optional().describe("Field to sort by. Defaults to Name."),
|
|
1001
1050
|
sortDesc: z.boolean().optional().describe("Sort in descending order. Defaults to ascending."),
|
|
1002
|
-
limit: z.number().int().min(
|
|
1051
|
+
limit: z.number().int().min(0).max(50).optional().describe("Maximum number of clients to return per page. Default 10, max 50. Pass 0 to return only the total count without any client data — use this for 'how many X?' questions so the API doesn't fetch a full page just to be counted."),
|
|
1003
1052
|
offset: z.number().int().min(0).optional().describe("Number of records to skip for pagination. Default 0.")
|
|
1004
1053
|
};
|
|
1005
1054
|
function formatClient(c) {
|
|
@@ -1013,15 +1062,22 @@ async function handleListClients(api, args) {
|
|
|
1013
1062
|
const query = args;
|
|
1014
1063
|
const result = await api.listClients(query);
|
|
1015
1064
|
const items = result.data ?? [];
|
|
1065
|
+
const total = result.totalCount ?? items.length;
|
|
1066
|
+
if (args.limit === 0) {
|
|
1067
|
+
const desc = describeFilters$2(args);
|
|
1068
|
+
return { content: [{
|
|
1069
|
+
type: "text",
|
|
1070
|
+
text: desc ? `Total: ${total} client${total === 1 ? "" : "s"} matching ${desc}.` : `Total: ${total} client${total === 1 ? "" : "s"}.`
|
|
1071
|
+
}] };
|
|
1072
|
+
}
|
|
1016
1073
|
if (items.length === 0) {
|
|
1017
|
-
const desc = describeFilters$
|
|
1074
|
+
const desc = describeFilters$2(args);
|
|
1018
1075
|
return { content: [{
|
|
1019
1076
|
type: "text",
|
|
1020
1077
|
text: desc ? `No clients match ${desc}.` : "No clients found."
|
|
1021
1078
|
}] };
|
|
1022
1079
|
}
|
|
1023
|
-
const
|
|
1024
|
-
const desc = describeFilters$1(args);
|
|
1080
|
+
const desc = describeFilters$2(args);
|
|
1025
1081
|
const lines = [
|
|
1026
1082
|
desc ? total > items.length ? `Found ${total} clients matching ${desc} (showing ${items.length}):` : `Found ${items.length} client${items.length === 1 ? "" : "s"} matching ${desc}:` : total > items.length ? `Showing ${items.length} of ${total} clients:` : `${items.length} client${items.length === 1 ? "" : "s"}:`,
|
|
1027
1083
|
"",
|
|
@@ -1045,7 +1101,7 @@ async function handleListClients(api, args) {
|
|
|
1045
1101
|
};
|
|
1046
1102
|
}
|
|
1047
1103
|
}
|
|
1048
|
-
function describeFilters$
|
|
1104
|
+
function describeFilters$2(args) {
|
|
1049
1105
|
const parts = [];
|
|
1050
1106
|
if (args.search) parts.push(`search "${args.search}"`);
|
|
1051
1107
|
if (args.status?.length) parts.push(`status ${args.status.join("/")}`);
|
|
@@ -1162,7 +1218,7 @@ function formatTask$1(t) {
|
|
|
1162
1218
|
}
|
|
1163
1219
|
//#endregion
|
|
1164
1220
|
//#region ../mcp-core/src/tools/list-tasks.ts
|
|
1165
|
-
const statusEnum = z.enum([
|
|
1221
|
+
const statusEnum$1 = z.enum([
|
|
1166
1222
|
"NotStarted",
|
|
1167
1223
|
"InProgress",
|
|
1168
1224
|
"Blocked",
|
|
@@ -1194,7 +1250,7 @@ const dateRangeEnum = z.enum([
|
|
|
1194
1250
|
"CustomDateRange"
|
|
1195
1251
|
]);
|
|
1196
1252
|
const dateBasisEnum = z.enum(["StartDate", "DueDate"]);
|
|
1197
|
-
const sortByEnum = z.enum([
|
|
1253
|
+
const sortByEnum$1 = z.enum([
|
|
1198
1254
|
"Name",
|
|
1199
1255
|
"DueDate",
|
|
1200
1256
|
"StartDate",
|
|
@@ -1205,7 +1261,7 @@ const sortByEnum = z.enum([
|
|
|
1205
1261
|
const ListTasksInputSchema = {
|
|
1206
1262
|
user: z.array(z.string()).optional().describe("Filter by assigned user codes. For 'my tasks' use the current user's code from the startup context. For another team member's tasks, pass their code. Omit to see tasks across all users (useful for practice managers)."),
|
|
1207
1263
|
client: z.array(z.string()).optional().describe("Filter by client codes — tasks belonging to these specific clients only."),
|
|
1208
|
-
status: z.array(statusEnum).optional().describe("Filter by task status. Typical: ['NotStarted', 'InProgress'] to exclude completed/skipped. Leave empty for all statuses. IMPORTANT: if the query includes NotStarted (the default for new tasks), you must ALSO provide one of: dateRange, isOverdue=true, or restrict status to non-NotStarted values only — otherwise the API rejects the call to prevent unbounded queries."),
|
|
1264
|
+
status: z.array(statusEnum$1).optional().describe("Filter by task status. Typical: ['NotStarted', 'InProgress'] to exclude completed/skipped. Leave empty for all statuses. IMPORTANT: if the query includes NotStarted (the default for new tasks), you must ALSO provide one of: dateRange, isOverdue=true, or restrict status to non-NotStarted values only — otherwise the API rejects the call to prevent unbounded queries."),
|
|
1209
1265
|
isOverdue: z.boolean().optional().describe("Set true to return only overdue tasks (DueDate before today, not completed/skipped). When true, no date range is required. Useful for sidestepping the NotStarted + date-range requirement."),
|
|
1210
1266
|
dateRange: dateRangeEnum.optional().describe("Preset date range. Use 'Today' for today's tasks, 'Next7Days' for 'due this week', 'Last30Days' for recent activity. If 'CustomDateRange', also provide startDate and/or endDate. REQUIRED when querying NotStarted tasks unless you pass isOverdue=true."),
|
|
1211
1267
|
startDate: z.string().optional().describe("Start of custom date range (YYYY-MM-DD). Used when dateRange='CustomDateRange'. Prefer specifying both startDate and endDate with a narrow window for performance — broad ranges strain the API. If startDate > endDate the API swaps them silently. Maximum allowed total range is 2 years."),
|
|
@@ -1217,9 +1273,9 @@ const ListTasksInputSchema = {
|
|
|
1217
1273
|
savedFilter: z.string().optional().describe("Apply a user-saved filter by its code. Other filter parameters override fields from the saved filter."),
|
|
1218
1274
|
includeProjected: z.boolean().optional().describe("Set true to include projected (virtual, not-yet-materialised) tasks in the results. Projected tasks are always NotStarted. Useful for 'what's coming up' queries."),
|
|
1219
1275
|
includeWorkflowSteps: z.boolean().optional().describe("Set true for Agenda Mode: returns both tasks AND their workflow steps as individual rows. Each step row has its parent task's properties plus step-specific details. Useful for 'what's the next action on X' or when workflow step granularity matters."),
|
|
1220
|
-
sortBy: sortByEnum.optional().describe("Field to sort by. Defaults to StartDate. Use 'DueDate' for 'what's due soonest' ordering. Combine with sortDesc for 'oldest/newest' ordering."),
|
|
1276
|
+
sortBy: sortByEnum$1.optional().describe("Field to sort by. Defaults to StartDate. Use 'DueDate' for 'what's due soonest' ordering. Combine with sortDesc for 'oldest/newest' ordering."),
|
|
1221
1277
|
sortDesc: z.boolean().optional().describe("Sort in descending order. Defaults to ascending."),
|
|
1222
|
-
limit: z.number().int().min(
|
|
1278
|
+
limit: z.number().int().min(0).max(50).optional().describe("Maximum number of tasks per page. Default 10, max 50. Pass 0 to return only the total count without any task data — use this for 'how many X?' questions so the API doesn't fetch a full page just to be counted."),
|
|
1223
1279
|
offset: z.number().int().min(0).optional().describe("Pagination offset. Default 0.")
|
|
1224
1280
|
};
|
|
1225
1281
|
function formatTask(t) {
|
|
@@ -1231,15 +1287,22 @@ async function handleListTasks(api, args) {
|
|
|
1231
1287
|
const query = args;
|
|
1232
1288
|
const result = await api.listTasks(query);
|
|
1233
1289
|
const items = result.data ?? [];
|
|
1290
|
+
const total = result.totalCount ?? items.length;
|
|
1291
|
+
if (args.limit === 0) {
|
|
1292
|
+
const desc = describeFilters$1(args);
|
|
1293
|
+
return { content: [{
|
|
1294
|
+
type: "text",
|
|
1295
|
+
text: desc ? `Total: ${total} task${total === 1 ? "" : "s"} matching ${desc}.` : `Total: ${total} task${total === 1 ? "" : "s"}.`
|
|
1296
|
+
}] };
|
|
1297
|
+
}
|
|
1234
1298
|
if (items.length === 0) {
|
|
1235
|
-
const desc = describeFilters(args);
|
|
1299
|
+
const desc = describeFilters$1(args);
|
|
1236
1300
|
return { content: [{
|
|
1237
1301
|
type: "text",
|
|
1238
1302
|
text: desc ? `No tasks match ${desc}.` : "No tasks found."
|
|
1239
1303
|
}] };
|
|
1240
1304
|
}
|
|
1241
|
-
const
|
|
1242
|
-
const desc = describeFilters(args);
|
|
1305
|
+
const desc = describeFilters$1(args);
|
|
1243
1306
|
const lines = [
|
|
1244
1307
|
desc ? total > items.length ? `Found ${total} tasks matching ${desc} (showing ${items.length}):` : `Found ${items.length} task${items.length === 1 ? "" : "s"} matching ${desc}:` : total > items.length ? `Showing ${items.length} of ${total} tasks:` : `${items.length} task${items.length === 1 ? "" : "s"}:`,
|
|
1245
1308
|
"",
|
|
@@ -1263,7 +1326,7 @@ async function handleListTasks(api, args) {
|
|
|
1263
1326
|
};
|
|
1264
1327
|
}
|
|
1265
1328
|
}
|
|
1266
|
-
function describeFilters(args) {
|
|
1329
|
+
function describeFilters$1(args) {
|
|
1267
1330
|
const parts = [];
|
|
1268
1331
|
if (args.user?.length) parts.push(`user ${args.user.join(",")}`);
|
|
1269
1332
|
if (args.client?.length) parts.push(`client ${args.client.join(",")}`);
|
|
@@ -1275,6 +1338,101 @@ function describeFilters(args) {
|
|
|
1275
1338
|
return parts.join(", ");
|
|
1276
1339
|
}
|
|
1277
1340
|
//#endregion
|
|
1341
|
+
//#region ../mcp-core/src/tools/list-users.ts
|
|
1342
|
+
const statusEnum = z.enum([
|
|
1343
|
+
"Created",
|
|
1344
|
+
"Invited",
|
|
1345
|
+
"Active",
|
|
1346
|
+
"Disabled",
|
|
1347
|
+
"Declined",
|
|
1348
|
+
"Deleted"
|
|
1349
|
+
]);
|
|
1350
|
+
const systemRoleEnum = z.enum([
|
|
1351
|
+
"Admin",
|
|
1352
|
+
"StandardUser",
|
|
1353
|
+
"Viewer"
|
|
1354
|
+
]);
|
|
1355
|
+
const sortByEnum = z.enum([
|
|
1356
|
+
"LastName",
|
|
1357
|
+
"FirstName",
|
|
1358
|
+
"Email"
|
|
1359
|
+
]);
|
|
1360
|
+
const ListUsersInputSchema = {
|
|
1361
|
+
search: z.string().min(3, "Search must be at least 3 characters when provided").optional().describe("Free-text search across user code, first name, last name, and email. Minimum 3 characters. Use for 'find Jane' when the name isn't in the startup roster (large teams, non-active users)."),
|
|
1362
|
+
status: statusEnum.optional().describe("Filter by a single user status. Use 'Active' for currently-working members, 'Invited' for pending invitations, 'Disabled' for offboarded users, 'Deleted' for removed users. Note: this is a single value, not an array."),
|
|
1363
|
+
systemRole: systemRoleEnum.optional().describe("Filter by system role. Admin = full access, StandardUser = normal access, Viewer = read-only. Single value."),
|
|
1364
|
+
isClientManager: z.boolean().optional().describe("Set true to return only users flagged as client managers; false to exclude them."),
|
|
1365
|
+
isPartner: z.boolean().optional().describe("Set true to return only users flagged as partners; false to exclude them. Use for 'list partners' queries."),
|
|
1366
|
+
isAssociate: z.boolean().optional().describe("Set true to return only users flagged as associates; false to exclude them."),
|
|
1367
|
+
sortBy: sortByEnum.optional().describe("Field to sort by. Defaults to LastName."),
|
|
1368
|
+
sortDesc: z.boolean().optional().describe("Sort in descending order. Defaults to ascending."),
|
|
1369
|
+
limit: z.number().int().min(0).max(50).optional().describe("Maximum number of users per page. Default 10, max 50. Pass 0 to return only the total count without any user data — use for 'how many users?' questions."),
|
|
1370
|
+
offset: z.number().int().min(0).optional().describe("Pagination offset. Default 0.")
|
|
1371
|
+
};
|
|
1372
|
+
function formatUser(u) {
|
|
1373
|
+
const code = u.code ?? "(no code)";
|
|
1374
|
+
const display = u.displayName ?? [u.firstName, u.lastName].filter(Boolean).join(" ") ?? u.email ?? "(no name)";
|
|
1375
|
+
const meta = [];
|
|
1376
|
+
if (u.status && u.status !== "Active") meta.push(u.status);
|
|
1377
|
+
if (u.email && u.email !== display) meta.push(u.email);
|
|
1378
|
+
if (u.isSuperAdmin) meta.push("SuperAdmin");
|
|
1379
|
+
return `- ${display} (${code})${meta.length > 0 ? ` — ${meta.join(" · ")}` : ""}`;
|
|
1380
|
+
}
|
|
1381
|
+
async function handleListUsers(api, args) {
|
|
1382
|
+
try {
|
|
1383
|
+
const query = args;
|
|
1384
|
+
const result = await api.listUsers(query);
|
|
1385
|
+
const items = result.data ?? [];
|
|
1386
|
+
const total = result.totalCount ?? items.length;
|
|
1387
|
+
if (args.limit === 0) {
|
|
1388
|
+
const desc = describeFilters(args);
|
|
1389
|
+
return { content: [{
|
|
1390
|
+
type: "text",
|
|
1391
|
+
text: desc ? `Total: ${total} user${total === 1 ? "" : "s"} matching ${desc}.` : `Total: ${total} user${total === 1 ? "" : "s"}.`
|
|
1392
|
+
}] };
|
|
1393
|
+
}
|
|
1394
|
+
if (items.length === 0) {
|
|
1395
|
+
const desc = describeFilters(args);
|
|
1396
|
+
return { content: [{
|
|
1397
|
+
type: "text",
|
|
1398
|
+
text: desc ? `No users match ${desc}.` : "No users found."
|
|
1399
|
+
}] };
|
|
1400
|
+
}
|
|
1401
|
+
const desc = describeFilters(args);
|
|
1402
|
+
const lines = [
|
|
1403
|
+
desc ? total > items.length ? `Found ${total} users matching ${desc} (showing ${items.length}):` : `Found ${items.length} user${items.length === 1 ? "" : "s"} matching ${desc}:` : total > items.length ? `Showing ${items.length} of ${total} users:` : `${items.length} user${items.length === 1 ? "" : "s"}:`,
|
|
1404
|
+
"",
|
|
1405
|
+
...items.map(formatUser)
|
|
1406
|
+
];
|
|
1407
|
+
if (result.hasMore) {
|
|
1408
|
+
const nextOffset = (args.offset ?? 0) + items.length;
|
|
1409
|
+
lines.push("", `More results available — call again with offset: ${nextOffset} to see the next page.`);
|
|
1410
|
+
}
|
|
1411
|
+
return { content: [{
|
|
1412
|
+
type: "text",
|
|
1413
|
+
text: lines.join("\n")
|
|
1414
|
+
}] };
|
|
1415
|
+
} catch (error) {
|
|
1416
|
+
return {
|
|
1417
|
+
content: [{
|
|
1418
|
+
type: "text",
|
|
1419
|
+
text: error instanceof SodiumApiError ? `Error listing users: ${error.message} (correlation: ${error.correlationId})` : `Error listing users: ${error instanceof Error ? error.message : String(error)}`
|
|
1420
|
+
}],
|
|
1421
|
+
isError: true
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
function describeFilters(args) {
|
|
1426
|
+
const parts = [];
|
|
1427
|
+
if (args.search) parts.push(`search "${args.search}"`);
|
|
1428
|
+
if (args.status) parts.push(`status ${args.status}`);
|
|
1429
|
+
if (args.systemRole) parts.push(`role ${args.systemRole}`);
|
|
1430
|
+
if (args.isClientManager !== void 0) parts.push(`isClientManager=${args.isClientManager}`);
|
|
1431
|
+
if (args.isPartner !== void 0) parts.push(`isPartner=${args.isPartner}`);
|
|
1432
|
+
if (args.isAssociate !== void 0) parts.push(`isAssociate=${args.isAssociate}`);
|
|
1433
|
+
return parts.join(", ");
|
|
1434
|
+
}
|
|
1435
|
+
//#endregion
|
|
1278
1436
|
//#region ../mcp-core/src/server.ts
|
|
1279
1437
|
async function buildServer(config) {
|
|
1280
1438
|
const api = new SodiumApiClient(config.context);
|
|
@@ -1298,7 +1456,7 @@ async function buildServer(config) {
|
|
|
1298
1456
|
}, () => handleGetPracticeDetails(api));
|
|
1299
1457
|
server.registerTool("list_clients", {
|
|
1300
1458
|
title: "List / search / filter clients",
|
|
1301
|
-
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. Follow up with get_client_summary for full detail on a specific client.",
|
|
1459
|
+
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.",
|
|
1302
1460
|
inputSchema: ListClientsInputSchema,
|
|
1303
1461
|
annotations: {
|
|
1304
1462
|
readOnlyHint: true,
|
|
@@ -1318,7 +1476,7 @@ async function buildServer(config) {
|
|
|
1318
1476
|
}, (args) => handleGetClientSummary(api, args));
|
|
1319
1477
|
server.registerTool("list_tasks", {
|
|
1320
1478
|
title: "List / filter tasks across the practice",
|
|
1321
|
-
description: "List tasks with any combination of filters: assigned user(s), client(s), status, overdue flag, preset date range (Today / ThisWeek / Next7Days / CustomDateRange etc), category, team, recurring task template, saved filter, include-projected, include-workflow-steps (Agenda Mode), sort, and pagination. Use for: 'my tasks' (pass current user's code from startup context), 'Jane's overdue tasks' (user + isOverdue), 'tasks for ACME due this week' (client + dateRange=Next7Days + dateBasis=DueDate), 'what is the team working on this month' (dateRange=ThisMonth, no user filter). Returns up to 50 tasks per page. IMPORTANT constraints to avoid API errors and keep queries efficient: (1) Querying NotStarted tasks requires one of — a dateRange, isOverdue=true, or restricting status to non-NotStarted values. (2) Prefer the narrowest date range that answers the question — broad ranges (quarterly/yearly) are expensive; prefer Today / ThisWeek / Next7Days / ThisMonth over larger windows unless explicitly asked. (3) For 'oldest incomplete tasks' prefer status=['InProgress','Blocked'] with sortBy=StartDate (no date range needed), or add isOverdue=true if 'oldest overdue' is meant.",
|
|
1479
|
+
description: "List tasks with any combination of filters: assigned user(s), client(s), status, overdue flag, preset date range (Today / ThisWeek / Next7Days / CustomDateRange etc), category, team, recurring task template, saved filter, include-projected, include-workflow-steps (Agenda Mode), sort, and pagination. Use for: 'my tasks' (pass current user's code from startup context), 'Jane's overdue tasks' (user + isOverdue), 'tasks for ACME due this week' (client + dateRange=Next7Days + dateBasis=DueDate), 'what is the team working on this month' (dateRange=ThisMonth, no user filter). Returns up to 50 tasks per page. IMPORTANT constraints to avoid API errors and keep queries efficient: (1) Querying NotStarted tasks requires one of — a dateRange, isOverdue=true, or restricting status to non-NotStarted values. (2) Prefer the narrowest date range that answers the question — broad ranges (quarterly/yearly) are expensive; prefer Today / ThisWeek / Next7Days / ThisMonth over larger windows unless explicitly asked. (3) For 'oldest incomplete tasks' prefer status=['InProgress','Blocked'] with sortBy=StartDate (no date range needed), or add isOverdue=true if 'oldest overdue' is meant. (4) For 'how many X?' questions, pass limit=0 to get just the total count without fetching any task data — much cheaper than fetching a full page and counting.",
|
|
1322
1480
|
inputSchema: ListTasksInputSchema,
|
|
1323
1481
|
annotations: {
|
|
1324
1482
|
readOnlyHint: true,
|
|
@@ -1326,6 +1484,16 @@ async function buildServer(config) {
|
|
|
1326
1484
|
openWorldHint: true
|
|
1327
1485
|
}
|
|
1328
1486
|
}, (args) => handleListTasks(api, args));
|
|
1487
|
+
server.registerTool("list_users", {
|
|
1488
|
+
title: "List / search / filter tenant users",
|
|
1489
|
+
description: "Find tenant users by name, email, role, or status. Use this when the user mentioned in a request isn't present in the startup roster (large teams have more than the top 20 active members shown there), or when filtering is needed beyond name resolution. Typical queries: 'find Jane' (search), 'list all partners' (isPartner=true), 'who's been invited but not joined yet?' (status=Invited), 'how many active users do we have?' (status=Active, limit=0). For 'how many X?' questions, pass limit=0 to get just the total count without fetching any user data.",
|
|
1490
|
+
inputSchema: ListUsersInputSchema,
|
|
1491
|
+
annotations: {
|
|
1492
|
+
readOnlyHint: true,
|
|
1493
|
+
idempotentHint: true,
|
|
1494
|
+
openWorldHint: true
|
|
1495
|
+
}
|
|
1496
|
+
}, (args) => handleListUsers(api, args));
|
|
1329
1497
|
return server;
|
|
1330
1498
|
}
|
|
1331
1499
|
//#endregion
|