@shortcut/mcp 0.7.1 → 0.8.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 +394 -80
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -14756,6 +14756,66 @@ class ShortcutClientWrapper {
14756
14756
  return { iterations: null, total: null };
14757
14757
  return { iterations, total };
14758
14758
  }
14759
+ async getActiveIteration(teamIds) {
14760
+ const response = await this.client.listIterations();
14761
+ const iterations = response?.data;
14762
+ if (!iterations)
14763
+ return new Map;
14764
+ const [today] = new Date().toISOString().split("T");
14765
+ const activeIterationByTeam = iterations.reduce((acc, iteration) => {
14766
+ if (iteration.status !== "started")
14767
+ return acc;
14768
+ const [startDate] = new Date(iteration.start_date).toISOString().split("T");
14769
+ const [endDate] = new Date(iteration.end_date).toISOString().split("T");
14770
+ if (!startDate || !endDate)
14771
+ return acc;
14772
+ if (startDate > today || endDate < today)
14773
+ return acc;
14774
+ for (const groupId of iteration.group_ids) {
14775
+ if (!teamIds.includes(groupId))
14776
+ continue;
14777
+ const prevIteration = acc.get(groupId);
14778
+ if (prevIteration) {
14779
+ const [prevIterationEndDate] = new Date(prevIteration.end_date).toISOString().split("T");
14780
+ if (endDate < prevIterationEndDate)
14781
+ acc.set(groupId, iteration);
14782
+ } else
14783
+ acc.set(groupId, iteration);
14784
+ }
14785
+ return acc;
14786
+ }, new Map);
14787
+ return activeIterationByTeam;
14788
+ }
14789
+ async getUpcomingIteration(teamIds) {
14790
+ const response = await this.client.listIterations();
14791
+ const iterations = response?.data;
14792
+ if (!iterations)
14793
+ return new Map;
14794
+ const [today] = new Date().toISOString().split("T");
14795
+ const upcomingIterationByTeam = iterations.reduce((acc, iteration) => {
14796
+ if (iteration.status !== "unstarted")
14797
+ return acc;
14798
+ const [startDate] = new Date(iteration.start_date).toISOString().split("T");
14799
+ const [endDate] = new Date(iteration.end_date).toISOString().split("T");
14800
+ if (!startDate || !endDate)
14801
+ return acc;
14802
+ if (startDate < today)
14803
+ return acc;
14804
+ for (const groupId of iteration.group_ids) {
14805
+ if (!teamIds.includes(groupId))
14806
+ continue;
14807
+ const prevIteration = acc.get(groupId);
14808
+ if (prevIteration) {
14809
+ const [prevIterationEndDate] = new Date(prevIteration.end_date).toISOString().split("T");
14810
+ if (endDate < prevIterationEndDate)
14811
+ acc.set(groupId, iteration);
14812
+ } else
14813
+ acc.set(groupId, iteration);
14814
+ }
14815
+ return acc;
14816
+ }, new Map);
14817
+ return upcomingIterationByTeam;
14818
+ }
14759
14819
  async searchEpics(query) {
14760
14820
  const response = await this.client.searchEpics({ query, page_size: 25, detail: "full" });
14761
14821
  const epics = response?.data?.data;
@@ -14772,9 +14832,9 @@ class ShortcutClientWrapper {
14772
14832
  return { milestones: null, total: null };
14773
14833
  return { milestones, total };
14774
14834
  }
14775
- async listIterationStories(iterationPublicId) {
14835
+ async listIterationStories(iterationPublicId, includeDescription = false) {
14776
14836
  const response = await this.client.listIterationStories(iterationPublicId, {
14777
- includes_description: true
14837
+ includes_description: includeDescription
14778
14838
  });
14779
14839
  const stories = response?.data;
14780
14840
  if (!stories)
@@ -14845,6 +14905,37 @@ class ShortcutClientWrapper {
14845
14905
  throw new Error(`Failed to update the task: ${response.status}`);
14846
14906
  return task;
14847
14907
  }
14908
+ async addExternalLinkToStory(storyPublicId, externalLink) {
14909
+ const story = await this.getStory(storyPublicId);
14910
+ if (!story)
14911
+ throw new Error(`Story ${storyPublicId} not found`);
14912
+ const currentLinks = story.external_links || [];
14913
+ if (currentLinks.some((link) => link.toLowerCase() === externalLink.toLowerCase())) {
14914
+ return story;
14915
+ }
14916
+ const updatedLinks = [...currentLinks, externalLink];
14917
+ return await this.updateStory(storyPublicId, { external_links: updatedLinks });
14918
+ }
14919
+ async removeExternalLinkFromStory(storyPublicId, externalLink) {
14920
+ const story = await this.getStory(storyPublicId);
14921
+ if (!story)
14922
+ throw new Error(`Story ${storyPublicId} not found`);
14923
+ const currentLinks = story.external_links || [];
14924
+ const updatedLinks = currentLinks.filter((link) => link.toLowerCase() !== externalLink.toLowerCase());
14925
+ return await this.updateStory(storyPublicId, { external_links: updatedLinks });
14926
+ }
14927
+ async getStoriesByExternalLink(externalLink) {
14928
+ const response = await this.client.getExternalLinkStories({
14929
+ external_link: externalLink.toLowerCase()
14930
+ });
14931
+ const stories = response?.data;
14932
+ if (!stories)
14933
+ return { stories: null, total: null };
14934
+ return { stories, total: stories.length };
14935
+ }
14936
+ async setStoryExternalLinks(storyPublicId, externalLinks) {
14937
+ return await this.updateStory(storyPublicId, { external_links: externalLinks });
14938
+ }
14848
14939
  }
14849
14940
 
14850
14941
  // node_modules/zod/lib/index.mjs
@@ -21578,7 +21669,7 @@ var import_client = __toESM(require_lib(), 1);
21578
21669
 
21579
21670
  // package.json
21580
21671
  var name = "@shortcut/mcp";
21581
- var version = "0.7.1";
21672
+ var version = "0.8.0";
21582
21673
 
21583
21674
  // src/tools/base.ts
21584
21675
  class BaseTools {
@@ -21586,99 +21677,215 @@ class BaseTools {
21586
21677
  constructor(client) {
21587
21678
  this.client = client;
21588
21679
  }
21589
- async correctMember(entity) {
21680
+ renameEntityProps(entity) {
21681
+ if (!entity || typeof entity !== "object")
21682
+ return entity;
21683
+ const renames = [
21684
+ ["team_id", null],
21685
+ ["entity_type", null],
21686
+ ["group_id", "team_id"],
21687
+ ["group_ids", "team_ids"],
21688
+ ["milestone_id", "objective_id"],
21689
+ ["milestone_ids", "objective_ids"]
21690
+ ];
21691
+ for (const [from, to] of renames) {
21692
+ if (from in entity) {
21693
+ const value = entity[from];
21694
+ delete entity[from];
21695
+ if (to)
21696
+ entity = { ...entity, [to]: value };
21697
+ }
21698
+ }
21699
+ return entity;
21700
+ }
21701
+ mergeRelatedEntities(relatedEntities) {
21702
+ return relatedEntities.reduce((acc, obj) => {
21703
+ if (!obj)
21704
+ return acc;
21705
+ for (const [key, value] of Object.entries(obj)) {
21706
+ acc[key] = { ...acc[key] || {}, ...value };
21707
+ }
21708
+ return acc;
21709
+ }, {});
21710
+ }
21711
+ getSimplifiedMember(entity) {
21590
21712
  if (!entity)
21591
21713
  return null;
21592
21714
  const {
21593
21715
  id,
21594
21716
  disabled,
21595
21717
  role,
21596
- profile: { name: name2, email_address, mention_name }
21718
+ profile: { is_owner, name: name2, email_address, mention_name }
21597
21719
  } = entity;
21598
- return { id, name: name2, email_address, mention_name, role, disabled };
21720
+ return { id, name: name2, email_address, mention_name, role, disabled, is_owner };
21599
21721
  }
21600
- async correctWorkflow(entity) {
21722
+ getSimplifiedWorkflow(entity) {
21601
21723
  if (!entity)
21602
21724
  return null;
21603
- const { team_id, ...withoutTeam } = entity;
21604
- return { ...withoutTeam };
21725
+ const { id, name: name2, states } = entity;
21726
+ return { id, name: name2, states: states.map((state) => ({ id: state.id, name: state.name })) };
21605
21727
  }
21606
- async correctTeam(entity) {
21728
+ getSimplifiedTeam(entity) {
21607
21729
  if (!entity)
21608
21730
  return null;
21609
- const { member_ids, workflow_ids, ...withoutIds } = entity;
21610
- const users = await this.client.getUserMap(member_ids);
21611
- const workflows = await this.client.getWorkflowMap(workflow_ids);
21612
- const correctedEntity = {
21613
- ...withoutIds,
21614
- members: await Promise.all(member_ids.map((id) => this.correctMember(users.get(id))).filter(Boolean)),
21615
- workflows: await Promise.all(workflow_ids.map((id) => this.correctWorkflow(workflows.get(id))).filter(Boolean))
21731
+ const { archived, id, name: name2, mention_name, member_ids, workflow_ids } = entity;
21732
+ return { id, name: name2, archived, mention_name, member_ids, workflow_ids };
21733
+ }
21734
+ getSimplifiedObjective(entity) {
21735
+ if (!entity)
21736
+ return null;
21737
+ const { app_url, id, name: name2, archived, state, categories } = entity;
21738
+ return { app_url, id, name: name2, archived, state, categories: categories.map((cat) => cat.name) };
21739
+ }
21740
+ getSimplifiedEpic(entity) {
21741
+ if (!entity)
21742
+ return null;
21743
+ const { id, name: name2, app_url, archived, group_id, state, milestone_id } = entity;
21744
+ return {
21745
+ id,
21746
+ name: name2,
21747
+ app_url,
21748
+ archived,
21749
+ state,
21750
+ team_id: group_id || null,
21751
+ objective_id: milestone_id || null
21616
21752
  };
21617
- return correctedEntity;
21618
21753
  }
21619
- async correctIteration(entity) {
21754
+ getSimplifiedIteration(entity) {
21620
21755
  if (!entity)
21621
21756
  return null;
21622
- const { group_ids, ...withoutGroupIds } = entity;
21623
- const teams = await this.client.getTeamMap(group_ids?.filter(Boolean));
21624
- const correctedEntity = {
21625
- ...withoutGroupIds,
21626
- teams: await Promise.all(group_ids?.map((id) => this.correctTeam(teams.get(id)))?.filter(Boolean) ?? [])
21757
+ const { id, name: name2, app_url, group_ids, status } = entity;
21758
+ return { id, name: name2, app_url, team_ids: group_ids, status };
21759
+ }
21760
+ async getRelatedEntitiesForTeam(entity) {
21761
+ if (!entity)
21762
+ return { users: {}, workflows: {} };
21763
+ const { member_ids, workflow_ids } = entity;
21764
+ const users = await this.client.getUserMap(member_ids);
21765
+ const workflows = await this.client.getWorkflowMap(workflow_ids);
21766
+ return {
21767
+ users: Object.fromEntries(member_ids.map((id) => this.getSimplifiedMember(users.get(id))).filter((member) => member !== null).map((member) => [member.id, member])),
21768
+ workflows: Object.fromEntries(workflow_ids.map((id) => this.getSimplifiedWorkflow(workflows.get(id))).filter((workflow) => workflow !== null).map((workflow) => [workflow.id, workflow]))
21627
21769
  };
21628
- return correctedEntity;
21629
21770
  }
21630
- async correctMilestone(entity) {
21631
- return entity;
21771
+ async getRelatedEntitiesForIteration(entity) {
21772
+ if (!entity)
21773
+ return { teams: {}, users: {}, workflows: {} };
21774
+ const { group_ids } = entity;
21775
+ const teams = await this.client.getTeamMap(group_ids || []);
21776
+ const relatedEntitiesForTeams = await Promise.all(Array.from(teams.values()).map((team) => this.getRelatedEntitiesForTeam(team)));
21777
+ const { users, workflows } = this.mergeRelatedEntities(relatedEntitiesForTeams);
21778
+ return {
21779
+ teams: Object.fromEntries(teams.entries().map(([id, team]) => [id, this.getSimplifiedTeam(team)]).filter(([_, team]) => !!team)),
21780
+ users,
21781
+ workflows
21782
+ };
21632
21783
  }
21633
- async correctEpic(entity) {
21634
- const { group_id, owner_ids, requested_by_id, follower_ids, ...withoutIds } = entity;
21635
- const users = await this.client.getUserMap([
21636
- ...new Set([...owner_ids, requested_by_id, ...follower_ids])
21784
+ async getRelatedEntitiesForEpic(entity) {
21785
+ if (!entity)
21786
+ return { users: {}, workflows: {}, teams: {}, objectives: {} };
21787
+ const { group_id, owner_ids, milestone_id, requested_by_id, follower_ids } = entity;
21788
+ const usersForEpicMap = await this.client.getUserMap([
21789
+ ...new Set([...owner_ids || [], requested_by_id, ...follower_ids || []].filter(Boolean))
21637
21790
  ]);
21791
+ const usersForEpic = Object.fromEntries(usersForEpicMap.entries().filter(([_, user]) => !!user).map(([id, user]) => [id, this.getSimplifiedMember(user)]));
21638
21792
  const teams = await this.client.getTeamMap(group_id ? [group_id] : []);
21639
- const correctedEntity = {
21640
- ...withoutIds,
21641
- owners: (await Promise.all(owner_ids?.map((id) => this.correctMember(users.get(id))))).filter(Boolean) ?? [],
21642
- requested_by: requested_by_id ? await this.correctMember(users.get(requested_by_id)) : null,
21643
- followers: (await Promise.all(follower_ids?.map((id) => this.correctMember(users.get(id))))).filter(Boolean) ?? [],
21644
- team: group_id ? await this.correctTeam(teams.get(group_id)) : null
21793
+ const team = this.getSimplifiedTeam(teams.get(group_id || ""));
21794
+ const { users, workflows } = await this.getRelatedEntitiesForTeam(teams.get(group_id || ""));
21795
+ const milestone = this.getSimplifiedObjective(milestone_id ? await this.client.getMilestone(milestone_id) : null);
21796
+ return {
21797
+ users: this.mergeRelatedEntities([usersForEpic, users]),
21798
+ teams: team ? { [team.id]: team } : {},
21799
+ objectives: milestone ? { [milestone.id]: milestone } : {},
21800
+ workflows
21645
21801
  };
21646
- return correctedEntity;
21647
21802
  }
21648
- async correctStory(entity) {
21649
- const { group_id, owner_ids, requested_by_id, follower_ids, workflow_id, ...withoutIds } = entity;
21650
- const users = await this.client.getUserMap([
21651
- ...new Set([...owner_ids, requested_by_id, ...follower_ids])
21803
+ async getRelatedEntitiesForStory(entity) {
21804
+ const {
21805
+ group_id,
21806
+ iteration_id,
21807
+ epic_id,
21808
+ owner_ids,
21809
+ requested_by_id,
21810
+ follower_ids,
21811
+ workflow_id
21812
+ } = entity;
21813
+ const fullUsersForStory = await this.client.getUserMap([
21814
+ ...new Set([...owner_ids || [], requested_by_id, ...follower_ids || []].filter(Boolean))
21652
21815
  ]);
21653
- const teams = await this.client.getTeamMap(group_id ? [group_id] : []);
21654
- const workflows = await this.client.getWorkflowMap(workflow_id ? [workflow_id] : []);
21655
- const correctedEntity = {
21656
- ...withoutIds,
21657
- owners: (await Promise.all(owner_ids?.map((id) => this.correctMember(users.get(id)))))?.filter(Boolean) ?? [],
21658
- requested_by: requested_by_id ? await this.correctMember(users.get(requested_by_id)) : null,
21659
- followers: (await Promise.all(follower_ids?.map((id) => this.correctMember(users.get(id)))))?.filter(Boolean) ?? [],
21660
- team: group_id ? await this.correctTeam(teams.get(group_id)) : null,
21661
- workflow: workflow_id ? await this.correctWorkflow(workflows.get(workflow_id)) : null
21816
+ const usersForStory = Object.fromEntries(fullUsersForStory.entries().filter(([_, user]) => !!user).map(([id, user]) => [id, this.getSimplifiedMember(user)]));
21817
+ const teamsForStory = await this.client.getTeamMap(group_id ? [group_id] : []);
21818
+ const workflowsForStory = await this.client.getWorkflowMap(workflow_id ? [workflow_id] : []);
21819
+ const iteration = iteration_id ? await this.client.getIteration(iteration_id) : null;
21820
+ const simplifiedIteration = this.getSimplifiedIteration(iteration);
21821
+ const epic = epic_id ? await this.client.getEpic(epic_id) : null;
21822
+ const simplifiedEpic = this.getSimplifiedEpic(epic);
21823
+ const teamForStory = teamsForStory.get(group_id || "");
21824
+ const workflowForStory = this.getSimplifiedWorkflow(workflowsForStory.get(workflow_id));
21825
+ const { users: usersForTeam, workflows: workflowsForTeam } = await this.getRelatedEntitiesForTeam(teamForStory);
21826
+ const {
21827
+ users: usersForIteration,
21828
+ workflows: workflowsForIteration,
21829
+ teams: teamsForIteration
21830
+ } = await this.getRelatedEntitiesForIteration(iteration);
21831
+ const {
21832
+ users: usersForEpic,
21833
+ workflows: workflowsForEpic,
21834
+ teams: teamsForEpic,
21835
+ objectives
21836
+ } = await this.getRelatedEntitiesForEpic(epic);
21837
+ const users = this.mergeRelatedEntities([
21838
+ usersForTeam,
21839
+ usersForStory,
21840
+ usersForIteration,
21841
+ usersForEpic
21842
+ ]);
21843
+ const workflows = this.mergeRelatedEntities([
21844
+ workflowsForTeam,
21845
+ workflowsForIteration,
21846
+ workflowsForEpic,
21847
+ workflowForStory ? { [workflowForStory.id]: workflowForStory } : {}
21848
+ ]);
21849
+ const teams = this.mergeRelatedEntities([
21850
+ teamsForIteration,
21851
+ teamsForEpic,
21852
+ teamForStory ? { [teamForStory.id]: teamForStory } : {}
21853
+ ]);
21854
+ const epics = simplifiedEpic ? { [simplifiedEpic.id]: simplifiedEpic } : {};
21855
+ const iterations = simplifiedIteration ? { [simplifiedIteration.id]: simplifiedIteration } : {};
21856
+ return {
21857
+ users,
21858
+ epics,
21859
+ iterations,
21860
+ workflows,
21861
+ teams,
21862
+ objectives
21662
21863
  };
21663
- return correctedEntity;
21664
21864
  }
21665
- async toCorrectedEntity(entity) {
21666
- if (entity.entity_type === "workflow")
21667
- return this.correctWorkflow(entity);
21865
+ async getRelatedEntities(entity) {
21668
21866
  if (entity.entity_type === "group")
21669
- return this.correctTeam(entity);
21867
+ return this.getRelatedEntitiesForTeam(entity);
21670
21868
  if (entity.entity_type === "iteration")
21671
- return this.correctIteration(entity);
21672
- if (entity.entity_type === "milestone")
21673
- return this.correctMilestone(entity);
21869
+ return this.getRelatedEntitiesForIteration(entity);
21674
21870
  if (entity.entity_type === "epic")
21675
- return this.correctEpic(entity);
21871
+ return this.getRelatedEntitiesForEpic(entity);
21676
21872
  if (entity.entity_type === "story")
21677
- return this.correctStory(entity);
21678
- return entity;
21873
+ return this.getRelatedEntitiesForStory(entity);
21874
+ return {};
21679
21875
  }
21680
- async toCorrectedEntities(entities) {
21681
- return Promise.all(entities.map((entity) => this.toCorrectedEntity(entity)));
21876
+ async entityWithRelatedEntities(entity, entityType = "entity") {
21877
+ const relatedEntities = await this.getRelatedEntities(entity);
21878
+ return {
21879
+ [entityType]: this.renameEntityProps(entity),
21880
+ relatedEntities
21881
+ };
21882
+ }
21883
+ async entitiesWithRelatedEntities(entities, entityType = "entities") {
21884
+ const relatedEntities = await Promise.all(entities.map((entity) => this.getRelatedEntities(entity)));
21885
+ return {
21886
+ [entityType]: entities.map((entity) => this.renameEntityProps(entity)),
21887
+ relatedEntities: this.mergeRelatedEntities(relatedEntities)
21888
+ };
21682
21889
  }
21683
21890
  toResult(message, data) {
21684
21891
  return {
@@ -21798,13 +22005,13 @@ class EpicTools extends BaseTools {
21798
22005
  throw new Error(`Failed to search for epics matching your query: "${query}"`);
21799
22006
  if (!epics.length)
21800
22007
  return this.toResult(`Result: No epics found.`);
21801
- return this.toResult(`Result (first ${epics.length} shown of ${total} total epics found):`, await this.toCorrectedEntities(epics));
22008
+ return this.toResult(`Result (first ${epics.length} shown of ${total} total epics found):`, await this.entitiesWithRelatedEntities(epics, "epics"));
21802
22009
  }
21803
22010
  async getEpic(epicPublicId) {
21804
22011
  const epic = await this.client.getEpic(epicPublicId);
21805
22012
  if (!epic)
21806
22013
  throw new Error(`Failed to retrieve Shortcut epic with public ID: ${epicPublicId}`);
21807
- return this.toResult(`Epic: ${epicPublicId}`, await this.toCorrectedEntity(epic));
22014
+ return this.toResult(`Epic: ${epicPublicId}`, await this.entityWithRelatedEntities(epic, "epic"));
21808
22015
  }
21809
22016
  async createEpic({
21810
22017
  name: name2,
@@ -21826,7 +22033,10 @@ class EpicTools extends BaseTools {
21826
22033
  class IterationTools extends BaseTools {
21827
22034
  static create(client, server) {
21828
22035
  const tools = new IterationTools(client);
21829
- server.tool("get-iteration-stories", "Get stories in a specific iteration by iteration public ID", { iterationPublicId: z.number().positive().describe("The public ID of the iteration") }, async ({ iterationPublicId }) => await tools.getIterationStories(iterationPublicId));
22036
+ server.tool("get-iteration-stories", "Get stories in a specific iteration by iteration public ID", {
22037
+ iterationPublicId: z.number().positive().describe("The public ID of the iteration"),
22038
+ includeStoryDescriptions: z.boolean().optional().default(false).describe("Indicate whether story descriptions should be included. Including descriptions may take longer and will increase the size of the response.")
22039
+ }, async ({ iterationPublicId, includeStoryDescriptions }) => await tools.getIterationStories(iterationPublicId, includeStoryDescriptions));
21830
22040
  server.tool("get-iteration", "Get a Shortcut iteration by public ID", {
21831
22041
  iterationPublicId: z.number().positive().describe("The public ID of the iteration to get")
21832
22042
  }, async ({ iterationPublicId }) => await tools.getIteration(iterationPublicId));
@@ -21848,13 +22058,19 @@ class IterationTools extends BaseTools {
21848
22058
  teamId: z.string().optional().describe("The ID of a team to assign the iteration to"),
21849
22059
  description: z.string().optional().describe("A description of the iteration")
21850
22060
  }, async (params) => await tools.createIteration(params));
22061
+ server.tool("get-active-iterations", "Get the active Shortcut iterations for the current user based on their team memberships", {
22062
+ teamId: z.string().optional().describe("The ID of a team to filter iterations by")
22063
+ }, async ({ teamId }) => await tools.getActiveIterations(teamId));
22064
+ server.tool("get-upcoming-iterations", "Get the upcoming Shortcut iterations for the current user based on their team memberships", {
22065
+ teamId: z.string().optional().describe("The ID of a team to filter iterations by")
22066
+ }, async ({ teamId }) => await tools.getUpcomingIterations(teamId));
21851
22067
  return tools;
21852
22068
  }
21853
- async getIterationStories(iterationPublicId) {
21854
- const { stories } = await this.client.listIterationStories(iterationPublicId);
22069
+ async getIterationStories(iterationPublicId, includeDescription) {
22070
+ const { stories } = await this.client.listIterationStories(iterationPublicId, includeDescription);
21855
22071
  if (!stories)
21856
22072
  throw new Error(`Failed to retrieve Shortcut stories in iteration with public ID: ${iterationPublicId}.`);
21857
- return this.toResult(`Result (${stories.length} stories found):`, await this.toCorrectedEntities(stories));
22073
+ return this.toResult(`Result (${stories.length} stories found):`, await this.entitiesWithRelatedEntities(stories, "stories"));
21858
22074
  }
21859
22075
  async searchIterations(params) {
21860
22076
  const currentUser = await this.client.getCurrentUser();
@@ -21864,13 +22080,13 @@ class IterationTools extends BaseTools {
21864
22080
  throw new Error(`Failed to search for iterations matching your query: "${query}".`);
21865
22081
  if (!iterations.length)
21866
22082
  return this.toResult(`Result: No iterations found.`);
21867
- return this.toResult(`Result (first ${iterations.length} shown of ${total} total iterations found):`, await this.toCorrectedEntities(iterations));
22083
+ return this.toResult(`Result (first ${iterations.length} shown of ${total} total iterations found):`, await this.entitiesWithRelatedEntities(iterations, "iterations"));
21868
22084
  }
21869
22085
  async getIteration(iterationPublicId) {
21870
22086
  const iteration = await this.client.getIteration(iterationPublicId);
21871
22087
  if (!iteration)
21872
22088
  throw new Error(`Failed to retrieve Shortcut iteration with public ID: ${iterationPublicId}.`);
21873
- return this.toResult(`Iteration: ${iterationPublicId}`, await this.toCorrectedEntity(iteration));
22089
+ return this.toResult(`Iteration: ${iterationPublicId}`, await this.entityWithRelatedEntities(iteration, "iteration"));
21874
22090
  }
21875
22091
  async createIteration({
21876
22092
  name: name2,
@@ -21890,6 +22106,54 @@ class IterationTools extends BaseTools {
21890
22106
  throw new Error(`Failed to create the iteration.`);
21891
22107
  return this.toResult(`Iteration created with ID: ${iteration.id}.`);
21892
22108
  }
22109
+ async getActiveIterations(teamId) {
22110
+ if (teamId) {
22111
+ const team = await this.client.getTeam(teamId);
22112
+ if (!team)
22113
+ throw new Error(`No team found matching id: "${teamId}"`);
22114
+ const result = await this.client.getActiveIteration([teamId]);
22115
+ const iteration = result.get(teamId);
22116
+ if (!iteration)
22117
+ return this.toResult(`Result: No active iterations found for team.`);
22118
+ return this.toResult("The active iteration for the team is:", await this.entityWithRelatedEntities(iteration, "iteration"));
22119
+ }
22120
+ const currentUser = await this.client.getCurrentUser();
22121
+ if (!currentUser)
22122
+ throw new Error("Failed to retrieve current user.");
22123
+ const teams = await this.client.getTeams();
22124
+ const teamIds = teams.filter((team) => team.member_ids.includes(currentUser.id)).map((team) => team.id);
22125
+ if (!teamIds.length)
22126
+ throw new Error("Current user does not belong to any teams.");
22127
+ const resultsByTeam = await this.client.getActiveIteration(teamIds);
22128
+ const allActiveIterations = [...resultsByTeam.values()];
22129
+ if (!allActiveIterations.length)
22130
+ return this.toResult("Result: No active iterations found for any of your teams.");
22131
+ return this.toResult(`You have ${allActiveIterations.length} active iterations for your teams:`, await this.entitiesWithRelatedEntities(allActiveIterations, "iterations"));
22132
+ }
22133
+ async getUpcomingIterations(teamId) {
22134
+ if (teamId) {
22135
+ const team = await this.client.getTeam(teamId);
22136
+ if (!team)
22137
+ throw new Error(`No team found matching id: "${teamId}"`);
22138
+ const result = await this.client.getUpcomingIteration([teamId]);
22139
+ const iteration = result.get(teamId);
22140
+ if (!iteration)
22141
+ return this.toResult(`Result: No upcoming iterations found for team.`);
22142
+ return this.toResult("The next upcoming iteration for the team is:", await this.entityWithRelatedEntities(iteration, "iteration"));
22143
+ }
22144
+ const currentUser = await this.client.getCurrentUser();
22145
+ if (!currentUser)
22146
+ throw new Error("Failed to retrieve current user.");
22147
+ const teams = await this.client.getTeams();
22148
+ const teamIds = teams.filter((team) => team.member_ids.includes(currentUser.id)).map((team) => team.id);
22149
+ if (!teamIds.length)
22150
+ throw new Error("Current user does not belong to any teams.");
22151
+ const resultsByTeam = await this.client.getUpcomingIteration(teamIds);
22152
+ const allUpcomingIterations = [...resultsByTeam.values()];
22153
+ if (!allUpcomingIterations.length)
22154
+ return this.toResult("Result: No upcoming iterations found for any of your teams.");
22155
+ return this.toResult("The upcoming iterations for all your teams are:", await this.entitiesWithRelatedEntities(allUpcomingIterations, "iterations"));
22156
+ }
21893
22157
  }
21894
22158
 
21895
22159
  // src/tools/objectives.ts
@@ -21926,13 +22190,13 @@ class ObjectiveTools extends BaseTools {
21926
22190
  throw new Error(`Failed to search for milestones matching your query: "${query}"`);
21927
22191
  if (!milestones.length)
21928
22192
  return this.toResult(`Result: No milestones found.`);
21929
- return this.toResult(`Result (first ${milestones.length} shown of ${total} total milestones found):`, await this.toCorrectedEntities(milestones));
22193
+ return this.toResult(`Result (first ${milestones.length} shown of ${total} total milestones found):`, await this.entitiesWithRelatedEntities(milestones, "objectives"));
21930
22194
  }
21931
22195
  async getObjective(objectivePublicId) {
21932
22196
  const objective = await this.client.getMilestone(objectivePublicId);
21933
22197
  if (!objective)
21934
22198
  throw new Error(`Failed to retrieve Shortcut objective with public ID: ${objectivePublicId}`);
21935
- return this.toResult(`Objective: ${objectivePublicId}`, await this.toCorrectedEntity(objective));
22199
+ return this.toResult(`Objective: ${objectivePublicId}`, await this.entityWithRelatedEntities(objective, "objective"));
21936
22200
  }
21937
22201
  }
21938
22202
 
@@ -22056,6 +22320,21 @@ The story will be added to the default state for the workflow.
22056
22320
  taskOwnerIds: z.array(z.string()).optional().describe("Array of user IDs to assign as owners of the task"),
22057
22321
  isCompleted: z.boolean().optional().describe("Whether the task is completed or not")
22058
22322
  }, async (params) => await tools.updateTask(params));
22323
+ server.tool("add-external-link-to-story", "Add an external link to a Shortcut story", {
22324
+ storyPublicId: z.number().positive().describe("The public ID of the story"),
22325
+ externalLink: z.string().url().max(2048).describe("The external link URL to add")
22326
+ }, async ({ storyPublicId, externalLink }) => await tools.addExternalLinkToStory(storyPublicId, externalLink));
22327
+ server.tool("remove-external-link-from-story", "Remove an external link from a Shortcut story", {
22328
+ storyPublicId: z.number().positive().describe("The public ID of the story"),
22329
+ externalLink: z.string().url().max(2048).describe("The external link URL to remove")
22330
+ }, async ({ storyPublicId, externalLink }) => await tools.removeExternalLinkFromStory(storyPublicId, externalLink));
22331
+ server.tool("get-stories-by-external-link", "Find all stories that contain a specific external link", {
22332
+ externalLink: z.string().url().max(2048).describe("The external link URL to search for")
22333
+ }, async ({ externalLink }) => await tools.getStoriesByExternalLink(externalLink));
22334
+ server.tool("set-story-external-links", "Replace all external links on a story with a new set of links", {
22335
+ storyPublicId: z.number().positive().describe("The public ID of the story"),
22336
+ externalLinks: z.array(z.string().url().max(2048)).describe("Array of external link URLs to set (replaces all existing links)")
22337
+ }, async ({ storyPublicId, externalLinks }) => await tools.setStoryExternalLinks(storyPublicId, externalLinks));
22059
22338
  return tools;
22060
22339
  }
22061
22340
  async assignCurrentUserAsOwner(storyPublicId) {
@@ -22138,13 +22417,13 @@ The story will be added to the default state for the workflow.
22138
22417
  throw new Error(`Failed to search for stories matching your query: "${query}".`);
22139
22418
  if (!stories.length)
22140
22419
  return this.toResult(`Result: No stories found.`);
22141
- return this.toResult(`Result (first ${stories.length} shown of ${total} total stories found):`, await this.toCorrectedEntities(stories));
22420
+ return this.toResult(`Result (first ${stories.length} shown of ${total} total stories found):`, await this.entitiesWithRelatedEntities(stories, "stories"));
22142
22421
  }
22143
22422
  async getStory(storyPublicId) {
22144
22423
  const story = await this.client.getStory(storyPublicId);
22145
22424
  if (!story)
22146
22425
  throw new Error(`Failed to retrieve Shortcut story with public ID: ${storyPublicId}.`);
22147
- return this.toResult(`Story: sc-${storyPublicId}`, await this.toCorrectedEntity(story));
22426
+ return this.toResult(`Story: sc-${storyPublicId}`, await this.entityWithRelatedEntities(story, "story"));
22148
22427
  }
22149
22428
  async createStoryComment({
22150
22429
  storyPublicId,
@@ -22265,6 +22544,41 @@ The story will be added to the default state for the workflow.
22265
22544
  await this.client.addRelationToStory(subjectStoryId, objectStoryId, relationshipType);
22266
22545
  return this.toResult(relationshipType === "blocks" ? `Marked sc-${subjectStoryId} as a blocker to sc-${objectStoryId}.` : relationshipType === "duplicates" ? `Marked sc-${subjectStoryId} as a duplicate of sc-${objectStoryId}.` : `Added a relationship between sc-${subjectStoryId} and sc-${objectStoryId}.`);
22267
22546
  }
22547
+ async addExternalLinkToStory(storyPublicId, externalLink) {
22548
+ if (!storyPublicId)
22549
+ throw new Error("Story public ID is required");
22550
+ if (!externalLink)
22551
+ throw new Error("External link is required");
22552
+ const updatedStory = await this.client.addExternalLinkToStory(storyPublicId, externalLink);
22553
+ return this.toResult(`Added external link to story sc-${storyPublicId}. Story URL: ${updatedStory.app_url}`);
22554
+ }
22555
+ async removeExternalLinkFromStory(storyPublicId, externalLink) {
22556
+ if (!storyPublicId)
22557
+ throw new Error("Story public ID is required");
22558
+ if (!externalLink)
22559
+ throw new Error("External link is required");
22560
+ const updatedStory = await this.client.removeExternalLinkFromStory(storyPublicId, externalLink);
22561
+ return this.toResult(`Removed external link from story sc-${storyPublicId}. Story URL: ${updatedStory.app_url}`);
22562
+ }
22563
+ async getStoriesByExternalLink(externalLink) {
22564
+ if (!externalLink)
22565
+ throw new Error("External link is required");
22566
+ const { stories, total } = await this.client.getStoriesByExternalLink(externalLink);
22567
+ if (!stories || !stories.length) {
22568
+ return this.toResult(`No stories found with external link: ${externalLink}`);
22569
+ }
22570
+ return this.toResult(`Found ${total} stories with external link: ${externalLink}`, await this.entitiesWithRelatedEntities(stories, "stories"));
22571
+ }
22572
+ async setStoryExternalLinks(storyPublicId, externalLinks) {
22573
+ if (!storyPublicId)
22574
+ throw new Error("Story public ID is required");
22575
+ if (!Array.isArray(externalLinks))
22576
+ throw new Error("External links must be an array");
22577
+ const updatedStory = await this.client.setStoryExternalLinks(storyPublicId, externalLinks);
22578
+ const linkCount = externalLinks.length;
22579
+ const message = linkCount === 0 ? `Removed all external links from story sc-${storyPublicId}` : `Set ${linkCount} external link${linkCount === 1 ? "" : "s"} on story sc-${storyPublicId}`;
22580
+ return this.toResult(`${message}. Story URL: ${updatedStory.app_url}`);
22581
+ }
22268
22582
  }
22269
22583
 
22270
22584
  // src/tools/teams.ts
@@ -22279,13 +22593,13 @@ class TeamTools extends BaseTools {
22279
22593
  const team = await this.client.getTeam(teamPublicId);
22280
22594
  if (!team)
22281
22595
  return this.toResult(`Team with public ID: ${teamPublicId} not found.`);
22282
- return this.toResult(`Team: ${team.id}`, await this.toCorrectedEntity(team));
22596
+ return this.toResult(`Team: ${team.id}`, await this.entityWithRelatedEntities(team, "team"));
22283
22597
  }
22284
22598
  async getTeams() {
22285
22599
  const teams = await this.client.getTeams();
22286
22600
  if (!teams.length)
22287
22601
  return this.toResult(`No teams found.`);
22288
- return this.toResult(`Result (first ${teams.length} shown of ${teams.length} total teams found):`, await this.toCorrectedEntities(teams));
22602
+ return this.toResult(`Result (first ${teams.length} shown of ${teams.length} total teams found):`, await this.entitiesWithRelatedEntities(teams, "teams"));
22289
22603
  }
22290
22604
  }
22291
22605
 
@@ -22321,13 +22635,13 @@ class WorkflowTools extends BaseTools {
22321
22635
  const workflow = await this.client.getWorkflow(workflowPublicId);
22322
22636
  if (!workflow)
22323
22637
  return this.toResult(`Workflow with public ID: ${workflowPublicId} not found.`);
22324
- return this.toResult(`Workflow: ${workflow.id}`, await this.toCorrectedEntity(workflow));
22638
+ return this.toResult(`Workflow: ${workflow.id}`, await this.entityWithRelatedEntities(workflow, "workflow"));
22325
22639
  }
22326
22640
  async listWorkflows() {
22327
22641
  const workflows = await this.client.getWorkflows();
22328
22642
  if (!workflows.length)
22329
22643
  return this.toResult(`No workflows found.`);
22330
- return this.toResult(`Result (first ${workflows.length} shown of ${workflows.length} total workflows found):`, await this.toCorrectedEntities(workflows));
22644
+ return this.toResult(`Result (first ${workflows.length} shown of ${workflows.length} total workflows found):`, await this.entitiesWithRelatedEntities(workflows, "workflows"));
22331
22645
  }
22332
22646
  }
22333
22647
 
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "modelcontextprotocol"
13
13
  ],
14
14
  "license": "MIT",
15
- "version": "0.7.1",
15
+ "version": "0.8.0",
16
16
  "type": "module",
17
17
  "main": "dist/index.js",
18
18
  "bin": {