@shortcut/mcp 0.10.2 → 0.11.0

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.
Files changed (2) hide show
  1. package/dist/index.js +94 -39
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -61,6 +61,14 @@ var ShortcutClientWrapper = class {
61
61
  this.workflowCache = new Cache();
62
62
  this.customFieldCache = new Cache();
63
63
  }
64
+ getNextPageToken(next) {
65
+ let next_page_token = null;
66
+ if (next) try {
67
+ const [, t] = /next=(.+)(?:&|$)/.exec(next) || [];
68
+ if (t) next_page_token = t;
69
+ } catch {}
70
+ return next_page_token;
71
+ }
64
72
  async loadMembers() {
65
73
  if (this.userCache.isStale) {
66
74
  const response = await this.client.listMembers({});
@@ -181,38 +189,46 @@ var ShortcutClientWrapper = class {
181
189
  if (!milestone) return null;
182
190
  return milestone;
183
191
  }
184
- async searchStories(query) {
192
+ async searchStories(query, nextToken) {
185
193
  const response = await this.client.searchStories({
186
194
  query,
187
195
  page_size: 25,
188
- detail: "full"
196
+ detail: "full",
197
+ next: nextToken
189
198
  });
190
199
  const stories = response?.data?.data;
191
200
  const total = response?.data?.total;
201
+ const next = response?.data?.next;
192
202
  if (!stories) return {
193
203
  stories: null,
194
- total: null
204
+ total: null,
205
+ next_page_token: null
195
206
  };
196
207
  return {
197
208
  stories,
198
- total
209
+ total,
210
+ next_page_token: this.getNextPageToken(next)
199
211
  };
200
212
  }
201
- async searchIterations(query) {
213
+ async searchIterations(query, nextToken) {
202
214
  const response = await this.client.searchIterations({
203
215
  query,
204
216
  page_size: 25,
205
- detail: "full"
217
+ detail: "full",
218
+ next: nextToken
206
219
  });
207
220
  const iterations = response?.data?.data;
208
221
  const total = response?.data?.total;
222
+ const next = response?.data?.next;
209
223
  if (!iterations) return {
210
224
  iterations: null,
211
- total: null
225
+ total: null,
226
+ next_page_token: null
212
227
  };
213
228
  return {
214
229
  iterations,
215
- total
230
+ total,
231
+ next_page_token: this.getNextPageToken(next)
216
232
  };
217
233
  }
218
234
  async getActiveIteration(teamIds) {
@@ -259,38 +275,46 @@ var ShortcutClientWrapper = class {
259
275
  }, /* @__PURE__ */ new Map());
260
276
  return upcomingIterationByTeam;
261
277
  }
262
- async searchEpics(query) {
278
+ async searchEpics(query, nextToken) {
263
279
  const response = await this.client.searchEpics({
264
280
  query,
265
281
  page_size: 25,
266
- detail: "full"
282
+ detail: "full",
283
+ next: nextToken
267
284
  });
268
285
  const epics = response?.data?.data;
269
286
  const total = response?.data?.total;
287
+ const next = response?.data?.next;
270
288
  if (!epics) return {
271
289
  epics: null,
272
- total: null
290
+ total: null,
291
+ next_page_token: null
273
292
  };
274
293
  return {
275
294
  epics,
276
- total
295
+ total,
296
+ next_page_token: this.getNextPageToken(next)
277
297
  };
278
298
  }
279
- async searchMilestones(query) {
299
+ async searchMilestones(query, nextToken) {
280
300
  const response = await this.client.searchMilestones({
281
301
  query,
282
302
  page_size: 25,
283
- detail: "full"
303
+ detail: "full",
304
+ next: nextToken
284
305
  });
285
306
  const milestones = response?.data?.data;
286
307
  const total = response?.data?.total;
308
+ const next = response?.data?.next;
287
309
  if (!milestones) return {
288
310
  milestones: null,
289
- total: null
311
+ total: null,
312
+ next_page_token: null
290
313
  };
291
314
  return {
292
315
  milestones,
293
- total
316
+ total,
317
+ next_page_token: this.getNextPageToken(next)
294
318
  };
295
319
  }
296
320
  async listIterationStories(iterationPublicId, includeDescription = false) {
@@ -411,7 +435,7 @@ var ShortcutClientWrapper = class {
411
435
  //#endregion
412
436
  //#region package.json
413
437
  var name = "@shortcut/mcp";
414
- var version = "0.10.2";
438
+ var version = "0.11.0";
415
439
 
416
440
  //#endregion
417
441
  //#region src/tools/base.ts
@@ -524,14 +548,15 @@ var BaseTools = class {
524
548
  }
525
549
  getSimplifiedTeam(entity) {
526
550
  if (!entity) return null;
527
- const { archived, id, name: name$1, mention_name, member_ids, workflow_ids } = entity;
551
+ const { archived, id, name: name$1, mention_name, member_ids, workflow_ids, default_workflow_id } = entity;
528
552
  return {
529
553
  id,
530
554
  name: name$1,
531
555
  archived,
532
556
  mention_name,
533
557
  member_ids,
534
- workflow_ids
558
+ workflow_ids,
559
+ default_workflow_id: default_workflow_id ?? null
535
560
  };
536
561
  }
537
562
  getSimplifiedObjective(entity) {
@@ -675,10 +700,11 @@ var BaseTools = class {
675
700
  workflowsForEpic,
676
701
  workflowForStory ? { [workflowForStory.id]: workflowForStory } : {}
677
702
  ]);
703
+ const simplifiedStoryTeam = this.getSimplifiedTeam(teamForStory);
678
704
  const teams = this.mergeRelatedEntities([
679
705
  teamsForIteration,
680
706
  teamsForEpic,
681
- teamForStory ? { [teamForStory.id]: teamForStory } : {}
707
+ simplifiedStoryTeam ? { [simplifiedStoryTeam.id]: simplifiedStoryTeam } : {}
682
708
  ]);
683
709
  const epics = simplifiedEpic ? { [simplifiedEpic.id]: simplifiedEpic } : {};
684
710
  const iterations = simplifiedIteration ? { [simplifiedIteration.id]: simplifiedIteration } : {};
@@ -739,10 +765,10 @@ var BaseTools = class {
739
765
  relatedEntities: this.mergeRelatedEntities(relatedEntities)
740
766
  };
741
767
  }
742
- toResult(message, data) {
768
+ toResult(message, data, paginationToken) {
743
769
  return { content: [{
744
770
  type: "text",
745
- text: `${message}${data !== void 0 ? `\n\n<json>\n${JSON.stringify(data, null, 2)}\n</json>` : ""}`
771
+ text: `${message}${data !== void 0 ? `\n\n<json>\n${JSON.stringify(data, null, 2)}\n</json>${paginationToken ? `\n\n<next-page-token>${paginationToken}</next-page-token>` : ""}` : ""}`
746
772
  }] };
747
773
  }
748
774
  };
@@ -840,6 +866,7 @@ var EpicTools = class EpicTools extends BaseTools {
840
866
  full: z.boolean().optional().default(false).describe("True to return all epic fields from the API. False to return a slim version that excludes uncommon fields")
841
867
  }, async ({ epicPublicId, full }) => await tools.getEpic(epicPublicId, full));
842
868
  server$1.tool("search-epics", "Find Shortcut epics.", {
869
+ nextPageToken: z.string().optional().describe("If a next_page_token was returned from the search result, pass it in to get the next page of results. Should be combined with the original search parameters."),
843
870
  id: z.number().optional().describe("Find only epics with the specified public ID"),
844
871
  name: z.string().optional().describe("Find only epics matching the specified name"),
845
872
  description: z.string().optional().describe("Find only epics matching the specified description"),
@@ -866,7 +893,7 @@ var EpicTools = class EpicTools extends BaseTools {
866
893
  updated: date(),
867
894
  completed: date(),
868
895
  due: date()
869
- }, async (params) => await tools.searchEpics(params));
896
+ }, async ({ nextPageToken,...params }) => await tools.searchEpics(params, nextPageToken));
870
897
  server$1.tool("create-epic", "Create a new Shortcut epic.", {
871
898
  name: z.string().describe("The name of the epic"),
872
899
  owner: z.string().optional().describe("The user ID of the owner of the epic"),
@@ -875,13 +902,13 @@ var EpicTools = class EpicTools extends BaseTools {
875
902
  }, async (params) => await tools.createEpic(params));
876
903
  return tools;
877
904
  }
878
- async searchEpics(params) {
905
+ async searchEpics(params, nextToken) {
879
906
  const currentUser = await this.client.getCurrentUser();
880
907
  const query = await buildSearchQuery(params, currentUser);
881
- const { epics, total } = await this.client.searchEpics(query);
908
+ const { epics, total, next_page_token } = await this.client.searchEpics(query, nextToken);
882
909
  if (!epics) throw new Error(`Failed to search for epics matching your query: "${query}"`);
883
910
  if (!epics.length) return this.toResult(`Result: No epics found.`);
884
- return this.toResult(`Result (first ${epics.length} shown of ${total} total epics found):`, await this.entitiesWithRelatedEntities(epics, "epics"));
911
+ return this.toResult(`Result (${epics.length} shown of ${total} total epics found):`, await this.entitiesWithRelatedEntities(epics, "epics"), next_page_token);
885
912
  }
886
913
  async getEpic(epicPublicId, full = false) {
887
914
  const epic = await this.client.getEpic(epicPublicId);
@@ -913,6 +940,7 @@ var IterationTools = class IterationTools extends BaseTools {
913
940
  full: z.boolean().optional().default(false).describe("True to return all iteration fields from the API. False to return a slim version that excludes uncommon fields")
914
941
  }, async ({ iterationPublicId, full }) => await tools.getIteration(iterationPublicId, full));
915
942
  server$1.tool("search-iterations", "Find Shortcut iterations.", {
943
+ nextPageToken: z.string().optional().describe("If a next_page_token was returned from the search result, pass it in to get the next page of results. Should be combined with the original search parameters."),
916
944
  id: z.number().optional().describe("Find only iterations with the specified public ID"),
917
945
  name: z.string().optional().describe("Find only iterations matching the specified name"),
918
946
  description: z.string().optional().describe("Find only iterations matching the specified description"),
@@ -926,7 +954,7 @@ var IterationTools = class IterationTools extends BaseTools {
926
954
  updated: date(),
927
955
  startDate: date(),
928
956
  endDate: date()
929
- }, async (params) => await tools.searchIterations(params));
957
+ }, async ({ nextPageToken,...params }) => await tools.searchIterations(params, nextPageToken));
930
958
  server$1.tool("create-iteration", "Create a new Shortcut iteration", {
931
959
  name: z.string().describe("The name of the iteration"),
932
960
  startDate: z.string().describe("The start date of the iteration in YYYY-MM-DD format"),
@@ -943,13 +971,13 @@ var IterationTools = class IterationTools extends BaseTools {
943
971
  if (!stories) throw new Error(`Failed to retrieve Shortcut stories in iteration with public ID: ${iterationPublicId}.`);
944
972
  return this.toResult(`Result (${stories.length} stories found):`, await this.entitiesWithRelatedEntities(stories, "stories"));
945
973
  }
946
- async searchIterations(params) {
974
+ async searchIterations(params, nextToken) {
947
975
  const currentUser = await this.client.getCurrentUser();
948
976
  const query = await buildSearchQuery(params, currentUser);
949
- const { iterations, total } = await this.client.searchIterations(query);
977
+ const { iterations, total, next_page_token } = await this.client.searchIterations(query, nextToken);
950
978
  if (!iterations) throw new Error(`Failed to search for iterations matching your query: "${query}".`);
951
979
  if (!iterations.length) return this.toResult(`Result: No iterations found.`);
952
- return this.toResult(`Result (first ${iterations.length} shown of ${total} total iterations found):`, await this.entitiesWithRelatedEntities(iterations, "iterations"));
980
+ return this.toResult(`Result (${iterations.length} shown of ${total} total iterations found):`, await this.entitiesWithRelatedEntities(iterations, "iterations"), next_page_token);
953
981
  }
954
982
  async getIteration(iterationPublicId, full = false) {
955
983
  const iteration = await this.client.getIteration(iterationPublicId);
@@ -1019,6 +1047,7 @@ var ObjectiveTools = class ObjectiveTools extends BaseTools {
1019
1047
  full: z.boolean().optional().default(false).describe("True to return all objective fields from the API. False to return a slim version that excludes uncommon fields")
1020
1048
  }, async ({ objectivePublicId, full }) => await tools.getObjective(objectivePublicId, full));
1021
1049
  server$1.tool("search-objectives", "Find Shortcut objectives.", {
1050
+ nextPageToken: z.string().optional().describe("If a next_page_token was returned from the search result, pass it in to get the next page of results. Should be combined with the original search parameters."),
1022
1051
  id: z.number().optional().describe("Find objectives matching the specified id"),
1023
1052
  name: z.string().optional().describe("Find objectives matching the specified name"),
1024
1053
  description: z.string().optional().describe("Find objectives matching the specified description"),
@@ -1038,16 +1067,16 @@ var ObjectiveTools = class ObjectiveTools extends BaseTools {
1038
1067
  created: date(),
1039
1068
  updated: date(),
1040
1069
  completed: date()
1041
- }, async (params) => await tools.searchObjectives(params));
1070
+ }, async ({ nextPageToken,...params }) => await tools.searchObjectives(params, nextPageToken));
1042
1071
  return tools;
1043
1072
  }
1044
- async searchObjectives(params) {
1073
+ async searchObjectives(params, nextToken) {
1045
1074
  const currentUser = await this.client.getCurrentUser();
1046
1075
  const query = await buildSearchQuery(params, currentUser);
1047
- const { milestones, total } = await this.client.searchMilestones(query);
1076
+ const { milestones, total, next_page_token } = await this.client.searchMilestones(query, nextToken);
1048
1077
  if (!milestones) throw new Error(`Failed to search for milestones matching your query: "${query}"`);
1049
1078
  if (!milestones.length) return this.toResult(`Result: No milestones found.`);
1050
- return this.toResult(`Result (first ${milestones.length} shown of ${total} total milestones found):`, await this.entitiesWithRelatedEntities(milestones, "objectives"));
1079
+ return this.toResult(`Result (${milestones.length} shown of ${total} total milestones found):`, await this.entitiesWithRelatedEntities(milestones, "objectives"), next_page_token);
1051
1080
  }
1052
1081
  async getObjective(objectivePublicId, full = false) {
1053
1082
  const objective = await this.client.getMilestone(objectivePublicId);
@@ -1061,12 +1090,12 @@ var ObjectiveTools = class ObjectiveTools extends BaseTools {
1061
1090
  var StoryTools = class StoryTools extends BaseTools {
1062
1091
  static create(client$1, server$1) {
1063
1092
  const tools = new StoryTools(client$1);
1064
- server$1.tool("get-story-branch-name", "Get a valid branch name for a specific story.", { storyPublicId: z.number().positive().describe("The public Id of the story") }, async ({ storyPublicId }) => await tools.getStoryBranchName(storyPublicId));
1065
1093
  server$1.tool("get-story", "Get a Shortcut story by public ID", {
1066
1094
  storyPublicId: z.number().positive().describe("The public ID of the story to get"),
1067
1095
  full: z.boolean().optional().default(false).describe("True to return all story fields from the API. False to return a slim version that excludes uncommon fields")
1068
1096
  }, async ({ storyPublicId, full }) => await tools.getStory(storyPublicId, full));
1069
1097
  server$1.tool("search-stories", "Find Shortcut stories.", {
1098
+ nextPageToken: z.string().optional().describe("If a next_page_token was returned from the search result, pass it in to get the next page of results. Should be combined with the original search parameters."),
1070
1099
  id: z.number().optional().describe("Find only stories with the specified public ID"),
1071
1100
  name: z.string().optional().describe("Find only stories matching the specified name"),
1072
1101
  description: z.string().optional().describe("Find only stories matching the specified description"),
@@ -1115,7 +1144,8 @@ var StoryTools = class StoryTools extends BaseTools {
1115
1144
  updated: date(),
1116
1145
  completed: date(),
1117
1146
  due: date()
1118
- }, async (params) => await tools.searchStories(params));
1147
+ }, async ({ nextPageToken,...params }) => await tools.searchStories(params, nextPageToken));
1148
+ server$1.tool("get-story-branch-name", "Get a valid branch name for a specific story.", { storyPublicId: z.number().positive().describe("The public Id of the story") }, async ({ storyPublicId }) => await tools.getStoryBranchName(storyPublicId));
1119
1149
  server$1.tool("create-story", `Create a new Shortcut story.
1120
1150
  Name is required, and either a Team or Workflow must be specified:
1121
1151
  - If only Team is specified, we will use the default workflow for that team.
@@ -1258,13 +1288,13 @@ The story will be added to the default state for the workflow.
1258
1288
  });
1259
1289
  return this.toResult(`Created story: ${story.id}`);
1260
1290
  }
1261
- async searchStories(params) {
1291
+ async searchStories(params, nextToken) {
1262
1292
  const currentUser = await this.client.getCurrentUser();
1263
1293
  const query = await buildSearchQuery(params, currentUser);
1264
- const { stories, total } = await this.client.searchStories(query);
1294
+ const { stories, total, next_page_token } = await this.client.searchStories(query, nextToken);
1265
1295
  if (!stories) throw new Error(`Failed to search for stories matching your query: "${query}".`);
1266
1296
  if (!stories.length) return this.toResult(`Result: No stories found.`);
1267
- return this.toResult(`Result (first ${stories.length} shown of ${total} total stories found):`, await this.entitiesWithRelatedEntities(stories, "stories"));
1297
+ return this.toResult(`Result (${stories.length} shown of ${total} total stories found):`, await this.entitiesWithRelatedEntities(stories, "stories"), next_page_token);
1268
1298
  }
1269
1299
  async getStory(storyPublicId, full = false) {
1270
1300
  const story = await this.client.getStory(storyPublicId);
@@ -1402,6 +1432,7 @@ var UserTools = class UserTools extends BaseTools {
1402
1432
  static create(client$1, server$1) {
1403
1433
  const tools = new UserTools(client$1);
1404
1434
  server$1.tool("get-current-user", "Get the current user", async () => await tools.getCurrentUser());
1435
+ server$1.tool("get-current-user-teams", "Get a list of teams where the current user is a member", async () => await tools.getCurrentUserTeams());
1405
1436
  server$1.tool("list-members", "Get all members", async () => await tools.listMembers());
1406
1437
  return tools;
1407
1438
  }
@@ -1410,6 +1441,14 @@ var UserTools = class UserTools extends BaseTools {
1410
1441
  if (!user$1) throw new Error("Failed to retrieve current user.");
1411
1442
  return this.toResult(`Current user:`, user$1);
1412
1443
  }
1444
+ async getCurrentUserTeams() {
1445
+ const teams = await this.client.getTeams();
1446
+ const currentUser = await this.client.getCurrentUser();
1447
+ if (!currentUser) throw new Error("Failed to get current user.");
1448
+ const userTeams = teams.filter((team) => team.member_ids.includes(currentUser.id));
1449
+ if (!userTeams.length) return this.toResult(`Current user is not a member of any teams.`);
1450
+ return this.toResult(`Current user is a member of ${userTeams.length} teams:`, await this.entitiesWithRelatedEntities(userTeams, "teams"));
1451
+ }
1413
1452
  async listMembers() {
1414
1453
  const members = await this.client.listMembers();
1415
1454
  return this.toResult(`Found ${members.length} members:`, members);
@@ -1421,6 +1460,7 @@ var UserTools = class UserTools extends BaseTools {
1421
1460
  var WorkflowTools = class WorkflowTools extends BaseTools {
1422
1461
  static create(client$1, server$1) {
1423
1462
  const tools = new WorkflowTools(client$1);
1463
+ server$1.tool("get-default-workflow", "Get the default workflow for a specific team or the global default if no team is specified.", { teamPublicId: z.string().optional().describe("The public ID of the team to get the default workflow for.") }, async ({ teamPublicId }) => await tools.getDefaultWorkflow(teamPublicId));
1424
1464
  server$1.tool("get-workflow", "Get a Shortcut workflow by public ID", {
1425
1465
  workflowPublicId: z.number().positive().describe("The public ID of the workflow to get"),
1426
1466
  full: z.boolean().optional().default(false).describe("True to return all workflow fields from the API. False to return a slim version that excludes uncommon fields")
@@ -1428,6 +1468,21 @@ var WorkflowTools = class WorkflowTools extends BaseTools {
1428
1468
  server$1.tool("list-workflows", "List all Shortcut workflows", async () => await tools.listWorkflows());
1429
1469
  return tools;
1430
1470
  }
1471
+ async getDefaultWorkflow(teamPublicId) {
1472
+ if (teamPublicId) try {
1473
+ const teamDefaultWorkflowId = await this.client.getTeam(teamPublicId).then((t) => t?.default_workflow_id);
1474
+ if (teamDefaultWorkflowId) {
1475
+ const teamDefaultWorkflow = await this.client.getWorkflow(teamDefaultWorkflowId);
1476
+ if (teamDefaultWorkflow) return this.toResult(`Default workflow for team "${teamPublicId}" has id ${teamDefaultWorkflow.id}.`, await this.entityWithRelatedEntities(teamDefaultWorkflow, "workflow"));
1477
+ }
1478
+ } catch {}
1479
+ const currentUser = await this.client.getCurrentUser();
1480
+ if (!currentUser) throw new Error("Failed to retrieve current user.");
1481
+ const workspaceDefaultWorkflowId = currentUser.workspace2.default_workflow_id;
1482
+ const workspaceDefaultWorkflow = await this.client.getWorkflow(workspaceDefaultWorkflowId);
1483
+ if (workspaceDefaultWorkflow) return this.toResult(`${teamPublicId ? `No default workflow found for team with public ID "${teamPublicId}". The general default workflow has id ` : "Default workflow has id "}${workspaceDefaultWorkflow.id}.`, await this.entityWithRelatedEntities(workspaceDefaultWorkflow, "workflow"));
1484
+ return this.toResult("No default workflow found.");
1485
+ }
1431
1486
  async getWorkflow(workflowPublicId, full = false) {
1432
1487
  const workflow = await this.client.getWorkflow(workflowPublicId);
1433
1488
  if (!workflow) return this.toResult(`Workflow with public ID: ${workflowPublicId} not found.`);
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "modelcontextprotocol"
13
13
  ],
14
14
  "license": "MIT",
15
- "version": "0.10.2",
15
+ "version": "0.11.0",
16
16
  "type": "module",
17
17
  "main": "dist/index.js",
18
18
  "bin": {