@shortcut/mcp 0.7.2 → 0.8.1

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 +434 -74
  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;
@@ -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.2";
21672
+ var version = "0.8.1";
21582
21673
 
21583
21674
  // src/tools/base.ts
21584
21675
  class BaseTools {
@@ -21586,99 +21677,264 @@ 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
+ getSimplifiedStory(entity) {
21601
21723
  if (!entity)
21602
21724
  return null;
21603
- const { team_id, ...withoutTeam } = entity;
21604
- return { ...withoutTeam };
21725
+ const {
21726
+ id,
21727
+ name: name2,
21728
+ app_url,
21729
+ archived,
21730
+ group_id,
21731
+ epic_id,
21732
+ iteration_id,
21733
+ workflow_id,
21734
+ workflow_state_id,
21735
+ owner_ids,
21736
+ requested_by_id
21737
+ } = entity;
21738
+ return {
21739
+ id,
21740
+ name: name2,
21741
+ app_url,
21742
+ archived,
21743
+ team_id: group_id || null,
21744
+ epic_id: epic_id || null,
21745
+ iteration_id: iteration_id || null,
21746
+ workflow_id,
21747
+ workflow_state_id,
21748
+ owner_ids,
21749
+ requested_by_id
21750
+ };
21605
21751
  }
21606
- async correctTeam(entity) {
21752
+ getSimplifiedWorkflow(entity) {
21607
21753
  if (!entity)
21608
21754
  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))
21755
+ const { id, name: name2, states } = entity;
21756
+ return {
21757
+ id,
21758
+ name: name2,
21759
+ states: states.map((state) => ({ id: state.id, name: state.name, type: state.type }))
21616
21760
  };
21617
- return correctedEntity;
21618
21761
  }
21619
- async correctIteration(entity) {
21762
+ getSimplifiedTeam(entity) {
21620
21763
  if (!entity)
21621
21764
  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) ?? [])
21765
+ const { archived, id, name: name2, mention_name, member_ids, workflow_ids } = entity;
21766
+ return { id, name: name2, archived, mention_name, member_ids, workflow_ids };
21767
+ }
21768
+ getSimplifiedObjective(entity) {
21769
+ if (!entity)
21770
+ return null;
21771
+ const { app_url, id, name: name2, archived, state, categories } = entity;
21772
+ return { app_url, id, name: name2, archived, state, categories: categories.map((cat) => cat.name) };
21773
+ }
21774
+ getSimplifiedEpic(entity) {
21775
+ if (!entity)
21776
+ return null;
21777
+ const { id, name: name2, app_url, archived, group_id, state, milestone_id } = entity;
21778
+ return {
21779
+ id,
21780
+ name: name2,
21781
+ app_url,
21782
+ archived,
21783
+ state,
21784
+ team_id: group_id || null,
21785
+ objective_id: milestone_id || null
21627
21786
  };
21628
- return correctedEntity;
21629
21787
  }
21630
- async correctMilestone(entity) {
21631
- return entity;
21788
+ getSimplifiedIteration(entity) {
21789
+ if (!entity)
21790
+ return null;
21791
+ const { id, name: name2, app_url, group_ids, status } = entity;
21792
+ return { id, name: name2, app_url, team_ids: group_ids, status };
21632
21793
  }
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])
21794
+ async getRelatedEntitiesForTeam(entity) {
21795
+ if (!entity)
21796
+ return { users: {}, workflows: {} };
21797
+ const { member_ids, workflow_ids } = entity;
21798
+ const users = await this.client.getUserMap(member_ids);
21799
+ const workflows = await this.client.getWorkflowMap(workflow_ids);
21800
+ return {
21801
+ users: Object.fromEntries(member_ids.map((id) => this.getSimplifiedMember(users.get(id))).filter((member) => member !== null).map((member) => [member.id, member])),
21802
+ workflows: Object.fromEntries(workflow_ids.map((id) => this.getSimplifiedWorkflow(workflows.get(id))).filter((workflow) => workflow !== null).map((workflow) => [workflow.id, workflow]))
21803
+ };
21804
+ }
21805
+ async getRelatedEntitiesForIteration(entity) {
21806
+ if (!entity)
21807
+ return { teams: {}, users: {}, workflows: {} };
21808
+ const { group_ids } = entity;
21809
+ const teams = await this.client.getTeamMap(group_ids || []);
21810
+ const relatedEntitiesForTeams = await Promise.all(Array.from(teams.values()).map((team) => this.getRelatedEntitiesForTeam(team)));
21811
+ const { users, workflows } = this.mergeRelatedEntities(relatedEntitiesForTeams);
21812
+ return {
21813
+ teams: Object.fromEntries(teams.entries().map(([id, team]) => [id, this.getSimplifiedTeam(team)]).filter(([_, team]) => !!team)),
21814
+ users,
21815
+ workflows
21816
+ };
21817
+ }
21818
+ async getRelatedEntitiesForEpic(entity) {
21819
+ if (!entity)
21820
+ return { users: {}, workflows: {}, teams: {}, objectives: {} };
21821
+ const { group_id, owner_ids, milestone_id, requested_by_id, follower_ids } = entity;
21822
+ const usersForEpicMap = await this.client.getUserMap([
21823
+ ...new Set([...owner_ids || [], requested_by_id, ...follower_ids || []].filter(Boolean))
21637
21824
  ]);
21825
+ const usersForEpic = Object.fromEntries(usersForEpicMap.entries().filter(([_, user]) => !!user).map(([id, user]) => [id, this.getSimplifiedMember(user)]));
21638
21826
  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
21827
+ const team = this.getSimplifiedTeam(teams.get(group_id || ""));
21828
+ const { users, workflows } = await this.getRelatedEntitiesForTeam(teams.get(group_id || ""));
21829
+ const milestone = this.getSimplifiedObjective(milestone_id ? await this.client.getMilestone(milestone_id) : null);
21830
+ return {
21831
+ users: this.mergeRelatedEntities([usersForEpic, users]),
21832
+ teams: team ? { [team.id]: team } : {},
21833
+ objectives: milestone ? { [milestone.id]: milestone } : {},
21834
+ workflows
21645
21835
  };
21646
- return correctedEntity;
21647
21836
  }
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])
21837
+ async getRelatedEntitiesForStory(entity) {
21838
+ const {
21839
+ group_id,
21840
+ iteration_id,
21841
+ epic_id,
21842
+ owner_ids,
21843
+ requested_by_id,
21844
+ follower_ids,
21845
+ workflow_id
21846
+ } = entity;
21847
+ const fullUsersForStory = await this.client.getUserMap([
21848
+ ...new Set([...owner_ids || [], requested_by_id, ...follower_ids || []].filter(Boolean))
21652
21849
  ]);
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
21850
+ const usersForStory = Object.fromEntries(fullUsersForStory.entries().filter(([_, user]) => !!user).map(([id, user]) => [id, this.getSimplifiedMember(user)]));
21851
+ const teamsForStory = await this.client.getTeamMap(group_id ? [group_id] : []);
21852
+ const workflowsForStory = await this.client.getWorkflowMap(workflow_id ? [workflow_id] : []);
21853
+ const iteration = iteration_id ? await this.client.getIteration(iteration_id) : null;
21854
+ const simplifiedIteration = this.getSimplifiedIteration(iteration);
21855
+ const epic = epic_id ? await this.client.getEpic(epic_id) : null;
21856
+ const simplifiedEpic = this.getSimplifiedEpic(epic);
21857
+ const teamForStory = teamsForStory.get(group_id || "");
21858
+ const workflowForStory = this.getSimplifiedWorkflow(workflowsForStory.get(workflow_id));
21859
+ const { users: usersForTeam, workflows: workflowsForTeam } = await this.getRelatedEntitiesForTeam(teamForStory);
21860
+ const {
21861
+ users: usersForIteration,
21862
+ workflows: workflowsForIteration,
21863
+ teams: teamsForIteration
21864
+ } = await this.getRelatedEntitiesForIteration(iteration);
21865
+ const {
21866
+ users: usersForEpic,
21867
+ workflows: workflowsForEpic,
21868
+ teams: teamsForEpic,
21869
+ objectives
21870
+ } = await this.getRelatedEntitiesForEpic(epic);
21871
+ const users = this.mergeRelatedEntities([
21872
+ usersForTeam,
21873
+ usersForStory,
21874
+ usersForIteration,
21875
+ usersForEpic
21876
+ ]);
21877
+ const workflows = this.mergeRelatedEntities([
21878
+ workflowsForTeam,
21879
+ workflowsForIteration,
21880
+ workflowsForEpic,
21881
+ workflowForStory ? { [workflowForStory.id]: workflowForStory } : {}
21882
+ ]);
21883
+ const teams = this.mergeRelatedEntities([
21884
+ teamsForIteration,
21885
+ teamsForEpic,
21886
+ teamForStory ? { [teamForStory.id]: teamForStory } : {}
21887
+ ]);
21888
+ const epics = simplifiedEpic ? { [simplifiedEpic.id]: simplifiedEpic } : {};
21889
+ const iterations = simplifiedIteration ? { [simplifiedIteration.id]: simplifiedIteration } : {};
21890
+ return {
21891
+ users,
21892
+ epics,
21893
+ iterations,
21894
+ workflows,
21895
+ teams,
21896
+ objectives
21662
21897
  };
21663
- return correctedEntity;
21664
21898
  }
21665
- async toCorrectedEntity(entity) {
21666
- if (entity.entity_type === "workflow")
21667
- return this.correctWorkflow(entity);
21899
+ async getRelatedEntities(entity) {
21668
21900
  if (entity.entity_type === "group")
21669
- return this.correctTeam(entity);
21901
+ return this.getRelatedEntitiesForTeam(entity);
21670
21902
  if (entity.entity_type === "iteration")
21671
- return this.correctIteration(entity);
21672
- if (entity.entity_type === "milestone")
21673
- return this.correctMilestone(entity);
21903
+ return this.getRelatedEntitiesForIteration(entity);
21904
+ if (entity.entity_type === "epic")
21905
+ return this.getRelatedEntitiesForEpic(entity);
21906
+ if (entity.entity_type === "story")
21907
+ return this.getRelatedEntitiesForStory(entity);
21908
+ return {};
21909
+ }
21910
+ getSimplifiedEntity(entity) {
21911
+ if (entity.entity_type === "group")
21912
+ return this.getSimplifiedTeam(entity);
21913
+ if (entity.entity_type === "iteration")
21914
+ return this.getSimplifiedIteration(entity);
21674
21915
  if (entity.entity_type === "epic")
21675
- return this.correctEpic(entity);
21916
+ return this.getSimplifiedEpic(entity);
21676
21917
  if (entity.entity_type === "story")
21677
- return this.correctStory(entity);
21918
+ return this.getSimplifiedStory(entity);
21919
+ if (entity.entity_type === "milestone")
21920
+ return this.getSimplifiedObjective(entity);
21921
+ if (entity.entity_type === "workflow")
21922
+ return this.getSimplifiedWorkflow(entity);
21678
21923
  return entity;
21679
21924
  }
21680
- async toCorrectedEntities(entities) {
21681
- return Promise.all(entities.map((entity) => this.toCorrectedEntity(entity)));
21925
+ async entityWithRelatedEntities(entity, entityType = "entity") {
21926
+ const relatedEntities = await this.getRelatedEntities(entity);
21927
+ return {
21928
+ [entityType]: this.renameEntityProps(entity),
21929
+ relatedEntities
21930
+ };
21931
+ }
21932
+ async entitiesWithRelatedEntities(entities, entityType = "entities") {
21933
+ const relatedEntities = await Promise.all(entities.map((entity) => this.getRelatedEntities(entity)));
21934
+ return {
21935
+ [entityType]: entities.map((entity) => this.getSimplifiedEntity(entity)),
21936
+ relatedEntities: this.mergeRelatedEntities(relatedEntities)
21937
+ };
21682
21938
  }
21683
21939
  toResult(message, data) {
21684
21940
  return {
@@ -21798,13 +22054,13 @@ class EpicTools extends BaseTools {
21798
22054
  throw new Error(`Failed to search for epics matching your query: "${query}"`);
21799
22055
  if (!epics.length)
21800
22056
  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));
22057
+ return this.toResult(`Result (first ${epics.length} shown of ${total} total epics found):`, await this.entitiesWithRelatedEntities(epics, "epics"));
21802
22058
  }
21803
22059
  async getEpic(epicPublicId) {
21804
22060
  const epic = await this.client.getEpic(epicPublicId);
21805
22061
  if (!epic)
21806
22062
  throw new Error(`Failed to retrieve Shortcut epic with public ID: ${epicPublicId}`);
21807
- return this.toResult(`Epic: ${epicPublicId}`, await this.toCorrectedEntity(epic));
22063
+ return this.toResult(`Epic: ${epicPublicId}`, await this.entityWithRelatedEntities(epic, "epic"));
21808
22064
  }
21809
22065
  async createEpic({
21810
22066
  name: name2,
@@ -21851,13 +22107,19 @@ class IterationTools extends BaseTools {
21851
22107
  teamId: z.string().optional().describe("The ID of a team to assign the iteration to"),
21852
22108
  description: z.string().optional().describe("A description of the iteration")
21853
22109
  }, async (params) => await tools.createIteration(params));
22110
+ server.tool("get-active-iterations", "Get the active Shortcut iterations for the current user based on their team memberships", {
22111
+ teamId: z.string().optional().describe("The ID of a team to filter iterations by")
22112
+ }, async ({ teamId }) => await tools.getActiveIterations(teamId));
22113
+ server.tool("get-upcoming-iterations", "Get the upcoming Shortcut iterations for the current user based on their team memberships", {
22114
+ teamId: z.string().optional().describe("The ID of a team to filter iterations by")
22115
+ }, async ({ teamId }) => await tools.getUpcomingIterations(teamId));
21854
22116
  return tools;
21855
22117
  }
21856
22118
  async getIterationStories(iterationPublicId, includeDescription) {
21857
22119
  const { stories } = await this.client.listIterationStories(iterationPublicId, includeDescription);
21858
22120
  if (!stories)
21859
22121
  throw new Error(`Failed to retrieve Shortcut stories in iteration with public ID: ${iterationPublicId}.`);
21860
- return this.toResult(`Result (${stories.length} stories found):`, await this.toCorrectedEntities(stories));
22122
+ return this.toResult(`Result (${stories.length} stories found):`, await this.entitiesWithRelatedEntities(stories, "stories"));
21861
22123
  }
21862
22124
  async searchIterations(params) {
21863
22125
  const currentUser = await this.client.getCurrentUser();
@@ -21867,13 +22129,13 @@ class IterationTools extends BaseTools {
21867
22129
  throw new Error(`Failed to search for iterations matching your query: "${query}".`);
21868
22130
  if (!iterations.length)
21869
22131
  return this.toResult(`Result: No iterations found.`);
21870
- return this.toResult(`Result (first ${iterations.length} shown of ${total} total iterations found):`, await this.toCorrectedEntities(iterations));
22132
+ return this.toResult(`Result (first ${iterations.length} shown of ${total} total iterations found):`, await this.entitiesWithRelatedEntities(iterations, "iterations"));
21871
22133
  }
21872
22134
  async getIteration(iterationPublicId) {
21873
22135
  const iteration = await this.client.getIteration(iterationPublicId);
21874
22136
  if (!iteration)
21875
22137
  throw new Error(`Failed to retrieve Shortcut iteration with public ID: ${iterationPublicId}.`);
21876
- return this.toResult(`Iteration: ${iterationPublicId}`, await this.toCorrectedEntity(iteration));
22138
+ return this.toResult(`Iteration: ${iterationPublicId}`, await this.entityWithRelatedEntities(iteration, "iteration"));
21877
22139
  }
21878
22140
  async createIteration({
21879
22141
  name: name2,
@@ -21893,6 +22155,54 @@ class IterationTools extends BaseTools {
21893
22155
  throw new Error(`Failed to create the iteration.`);
21894
22156
  return this.toResult(`Iteration created with ID: ${iteration.id}.`);
21895
22157
  }
22158
+ async getActiveIterations(teamId) {
22159
+ if (teamId) {
22160
+ const team = await this.client.getTeam(teamId);
22161
+ if (!team)
22162
+ throw new Error(`No team found matching id: "${teamId}"`);
22163
+ const result = await this.client.getActiveIteration([teamId]);
22164
+ const iteration = result.get(teamId);
22165
+ if (!iteration)
22166
+ return this.toResult(`Result: No active iterations found for team.`);
22167
+ return this.toResult("The active iteration for the team is:", await this.entityWithRelatedEntities(iteration, "iteration"));
22168
+ }
22169
+ const currentUser = await this.client.getCurrentUser();
22170
+ if (!currentUser)
22171
+ throw new Error("Failed to retrieve current user.");
22172
+ const teams = await this.client.getTeams();
22173
+ const teamIds = teams.filter((team) => team.member_ids.includes(currentUser.id)).map((team) => team.id);
22174
+ if (!teamIds.length)
22175
+ throw new Error("Current user does not belong to any teams.");
22176
+ const resultsByTeam = await this.client.getActiveIteration(teamIds);
22177
+ const allActiveIterations = [...resultsByTeam.values()];
22178
+ if (!allActiveIterations.length)
22179
+ return this.toResult("Result: No active iterations found for any of your teams.");
22180
+ return this.toResult(`You have ${allActiveIterations.length} active iterations for your teams:`, await this.entitiesWithRelatedEntities(allActiveIterations, "iterations"));
22181
+ }
22182
+ async getUpcomingIterations(teamId) {
22183
+ if (teamId) {
22184
+ const team = await this.client.getTeam(teamId);
22185
+ if (!team)
22186
+ throw new Error(`No team found matching id: "${teamId}"`);
22187
+ const result = await this.client.getUpcomingIteration([teamId]);
22188
+ const iteration = result.get(teamId);
22189
+ if (!iteration)
22190
+ return this.toResult(`Result: No upcoming iterations found for team.`);
22191
+ return this.toResult("The next upcoming iteration for the team is:", await this.entityWithRelatedEntities(iteration, "iteration"));
22192
+ }
22193
+ const currentUser = await this.client.getCurrentUser();
22194
+ if (!currentUser)
22195
+ throw new Error("Failed to retrieve current user.");
22196
+ const teams = await this.client.getTeams();
22197
+ const teamIds = teams.filter((team) => team.member_ids.includes(currentUser.id)).map((team) => team.id);
22198
+ if (!teamIds.length)
22199
+ throw new Error("Current user does not belong to any teams.");
22200
+ const resultsByTeam = await this.client.getUpcomingIteration(teamIds);
22201
+ const allUpcomingIterations = [...resultsByTeam.values()];
22202
+ if (!allUpcomingIterations.length)
22203
+ return this.toResult("Result: No upcoming iterations found for any of your teams.");
22204
+ return this.toResult("The upcoming iterations for all your teams are:", await this.entitiesWithRelatedEntities(allUpcomingIterations, "iterations"));
22205
+ }
21896
22206
  }
21897
22207
 
21898
22208
  // src/tools/objectives.ts
@@ -21929,13 +22239,13 @@ class ObjectiveTools extends BaseTools {
21929
22239
  throw new Error(`Failed to search for milestones matching your query: "${query}"`);
21930
22240
  if (!milestones.length)
21931
22241
  return this.toResult(`Result: No milestones found.`);
21932
- return this.toResult(`Result (first ${milestones.length} shown of ${total} total milestones found):`, await this.toCorrectedEntities(milestones));
22242
+ return this.toResult(`Result (first ${milestones.length} shown of ${total} total milestones found):`, await this.entitiesWithRelatedEntities(milestones, "objectives"));
21933
22243
  }
21934
22244
  async getObjective(objectivePublicId) {
21935
22245
  const objective = await this.client.getMilestone(objectivePublicId);
21936
22246
  if (!objective)
21937
22247
  throw new Error(`Failed to retrieve Shortcut objective with public ID: ${objectivePublicId}`);
21938
- return this.toResult(`Objective: ${objectivePublicId}`, await this.toCorrectedEntity(objective));
22248
+ return this.toResult(`Objective: ${objectivePublicId}`, await this.entityWithRelatedEntities(objective, "objective"));
21939
22249
  }
21940
22250
  }
21941
22251
 
@@ -22059,6 +22369,21 @@ The story will be added to the default state for the workflow.
22059
22369
  taskOwnerIds: z.array(z.string()).optional().describe("Array of user IDs to assign as owners of the task"),
22060
22370
  isCompleted: z.boolean().optional().describe("Whether the task is completed or not")
22061
22371
  }, async (params) => await tools.updateTask(params));
22372
+ server.tool("add-external-link-to-story", "Add an external link to a Shortcut story", {
22373
+ storyPublicId: z.number().positive().describe("The public ID of the story"),
22374
+ externalLink: z.string().url().max(2048).describe("The external link URL to add")
22375
+ }, async ({ storyPublicId, externalLink }) => await tools.addExternalLinkToStory(storyPublicId, externalLink));
22376
+ server.tool("remove-external-link-from-story", "Remove an external link from a Shortcut story", {
22377
+ storyPublicId: z.number().positive().describe("The public ID of the story"),
22378
+ externalLink: z.string().url().max(2048).describe("The external link URL to remove")
22379
+ }, async ({ storyPublicId, externalLink }) => await tools.removeExternalLinkFromStory(storyPublicId, externalLink));
22380
+ server.tool("get-stories-by-external-link", "Find all stories that contain a specific external link", {
22381
+ externalLink: z.string().url().max(2048).describe("The external link URL to search for")
22382
+ }, async ({ externalLink }) => await tools.getStoriesByExternalLink(externalLink));
22383
+ server.tool("set-story-external-links", "Replace all external links on a story with a new set of links", {
22384
+ storyPublicId: z.number().positive().describe("The public ID of the story"),
22385
+ externalLinks: z.array(z.string().url().max(2048)).describe("Array of external link URLs to set (replaces all existing links)")
22386
+ }, async ({ storyPublicId, externalLinks }) => await tools.setStoryExternalLinks(storyPublicId, externalLinks));
22062
22387
  return tools;
22063
22388
  }
22064
22389
  async assignCurrentUserAsOwner(storyPublicId) {
@@ -22141,13 +22466,13 @@ The story will be added to the default state for the workflow.
22141
22466
  throw new Error(`Failed to search for stories matching your query: "${query}".`);
22142
22467
  if (!stories.length)
22143
22468
  return this.toResult(`Result: No stories found.`);
22144
- return this.toResult(`Result (first ${stories.length} shown of ${total} total stories found):`, await this.toCorrectedEntities(stories));
22469
+ return this.toResult(`Result (first ${stories.length} shown of ${total} total stories found):`, await this.entitiesWithRelatedEntities(stories, "stories"));
22145
22470
  }
22146
22471
  async getStory(storyPublicId) {
22147
22472
  const story = await this.client.getStory(storyPublicId);
22148
22473
  if (!story)
22149
22474
  throw new Error(`Failed to retrieve Shortcut story with public ID: ${storyPublicId}.`);
22150
- return this.toResult(`Story: sc-${storyPublicId}`, await this.toCorrectedEntity(story));
22475
+ return this.toResult(`Story: sc-${storyPublicId}`, await this.entityWithRelatedEntities(story, "story"));
22151
22476
  }
22152
22477
  async createStoryComment({
22153
22478
  storyPublicId,
@@ -22268,6 +22593,41 @@ The story will be added to the default state for the workflow.
22268
22593
  await this.client.addRelationToStory(subjectStoryId, objectStoryId, relationshipType);
22269
22594
  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}.`);
22270
22595
  }
22596
+ async addExternalLinkToStory(storyPublicId, externalLink) {
22597
+ if (!storyPublicId)
22598
+ throw new Error("Story public ID is required");
22599
+ if (!externalLink)
22600
+ throw new Error("External link is required");
22601
+ const updatedStory = await this.client.addExternalLinkToStory(storyPublicId, externalLink);
22602
+ return this.toResult(`Added external link to story sc-${storyPublicId}. Story URL: ${updatedStory.app_url}`);
22603
+ }
22604
+ async removeExternalLinkFromStory(storyPublicId, externalLink) {
22605
+ if (!storyPublicId)
22606
+ throw new Error("Story public ID is required");
22607
+ if (!externalLink)
22608
+ throw new Error("External link is required");
22609
+ const updatedStory = await this.client.removeExternalLinkFromStory(storyPublicId, externalLink);
22610
+ return this.toResult(`Removed external link from story sc-${storyPublicId}. Story URL: ${updatedStory.app_url}`);
22611
+ }
22612
+ async getStoriesByExternalLink(externalLink) {
22613
+ if (!externalLink)
22614
+ throw new Error("External link is required");
22615
+ const { stories, total } = await this.client.getStoriesByExternalLink(externalLink);
22616
+ if (!stories || !stories.length) {
22617
+ return this.toResult(`No stories found with external link: ${externalLink}`);
22618
+ }
22619
+ return this.toResult(`Found ${total} stories with external link: ${externalLink}`, await this.entitiesWithRelatedEntities(stories, "stories"));
22620
+ }
22621
+ async setStoryExternalLinks(storyPublicId, externalLinks) {
22622
+ if (!storyPublicId)
22623
+ throw new Error("Story public ID is required");
22624
+ if (!Array.isArray(externalLinks))
22625
+ throw new Error("External links must be an array");
22626
+ const updatedStory = await this.client.setStoryExternalLinks(storyPublicId, externalLinks);
22627
+ const linkCount = externalLinks.length;
22628
+ const message = linkCount === 0 ? `Removed all external links from story sc-${storyPublicId}` : `Set ${linkCount} external link${linkCount === 1 ? "" : "s"} on story sc-${storyPublicId}`;
22629
+ return this.toResult(`${message}. Story URL: ${updatedStory.app_url}`);
22630
+ }
22271
22631
  }
22272
22632
 
22273
22633
  // src/tools/teams.ts
@@ -22282,13 +22642,13 @@ class TeamTools extends BaseTools {
22282
22642
  const team = await this.client.getTeam(teamPublicId);
22283
22643
  if (!team)
22284
22644
  return this.toResult(`Team with public ID: ${teamPublicId} not found.`);
22285
- return this.toResult(`Team: ${team.id}`, await this.toCorrectedEntity(team));
22645
+ return this.toResult(`Team: ${team.id}`, await this.entityWithRelatedEntities(team, "team"));
22286
22646
  }
22287
22647
  async getTeams() {
22288
22648
  const teams = await this.client.getTeams();
22289
22649
  if (!teams.length)
22290
22650
  return this.toResult(`No teams found.`);
22291
- return this.toResult(`Result (first ${teams.length} shown of ${teams.length} total teams found):`, await this.toCorrectedEntities(teams));
22651
+ return this.toResult(`Result (first ${teams.length} shown of ${teams.length} total teams found):`, await this.entitiesWithRelatedEntities(teams, "teams"));
22292
22652
  }
22293
22653
  }
22294
22654
 
@@ -22324,13 +22684,13 @@ class WorkflowTools extends BaseTools {
22324
22684
  const workflow = await this.client.getWorkflow(workflowPublicId);
22325
22685
  if (!workflow)
22326
22686
  return this.toResult(`Workflow with public ID: ${workflowPublicId} not found.`);
22327
- return this.toResult(`Workflow: ${workflow.id}`, await this.toCorrectedEntity(workflow));
22687
+ return this.toResult(`Workflow: ${workflow.id}`, await this.entityWithRelatedEntities(workflow, "workflow"));
22328
22688
  }
22329
22689
  async listWorkflows() {
22330
22690
  const workflows = await this.client.getWorkflows();
22331
22691
  if (!workflows.length)
22332
22692
  return this.toResult(`No workflows found.`);
22333
- return this.toResult(`Result (first ${workflows.length} shown of ${workflows.length} total workflows found):`, await this.toCorrectedEntities(workflows));
22693
+ return this.toResult(`Result (first ${workflows.length} shown of ${workflows.length} total workflows found):`, await this.entitiesWithRelatedEntities(workflows, "workflows"));
22334
22694
  }
22335
22695
  }
22336
22696
 
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "modelcontextprotocol"
13
13
  ],
14
14
  "license": "MIT",
15
- "version": "0.7.2",
15
+ "version": "0.8.1",
16
16
  "type": "module",
17
17
  "main": "dist/index.js",
18
18
  "bin": {