@shortcut/mcp 0.7.2 → 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 +386 -75
  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.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,
@@ -21851,13 +22058,19 @@ class IterationTools extends BaseTools {
21851
22058
  teamId: z.string().optional().describe("The ID of a team to assign the iteration to"),
21852
22059
  description: z.string().optional().describe("A description of the iteration")
21853
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));
21854
22067
  return tools;
21855
22068
  }
21856
22069
  async getIterationStories(iterationPublicId, includeDescription) {
21857
22070
  const { stories } = await this.client.listIterationStories(iterationPublicId, includeDescription);
21858
22071
  if (!stories)
21859
22072
  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));
22073
+ return this.toResult(`Result (${stories.length} stories found):`, await this.entitiesWithRelatedEntities(stories, "stories"));
21861
22074
  }
21862
22075
  async searchIterations(params) {
21863
22076
  const currentUser = await this.client.getCurrentUser();
@@ -21867,13 +22080,13 @@ class IterationTools extends BaseTools {
21867
22080
  throw new Error(`Failed to search for iterations matching your query: "${query}".`);
21868
22081
  if (!iterations.length)
21869
22082
  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));
22083
+ return this.toResult(`Result (first ${iterations.length} shown of ${total} total iterations found):`, await this.entitiesWithRelatedEntities(iterations, "iterations"));
21871
22084
  }
21872
22085
  async getIteration(iterationPublicId) {
21873
22086
  const iteration = await this.client.getIteration(iterationPublicId);
21874
22087
  if (!iteration)
21875
22088
  throw new Error(`Failed to retrieve Shortcut iteration with public ID: ${iterationPublicId}.`);
21876
- return this.toResult(`Iteration: ${iterationPublicId}`, await this.toCorrectedEntity(iteration));
22089
+ return this.toResult(`Iteration: ${iterationPublicId}`, await this.entityWithRelatedEntities(iteration, "iteration"));
21877
22090
  }
21878
22091
  async createIteration({
21879
22092
  name: name2,
@@ -21893,6 +22106,54 @@ class IterationTools extends BaseTools {
21893
22106
  throw new Error(`Failed to create the iteration.`);
21894
22107
  return this.toResult(`Iteration created with ID: ${iteration.id}.`);
21895
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
+ }
21896
22157
  }
21897
22158
 
21898
22159
  // src/tools/objectives.ts
@@ -21929,13 +22190,13 @@ class ObjectiveTools extends BaseTools {
21929
22190
  throw new Error(`Failed to search for milestones matching your query: "${query}"`);
21930
22191
  if (!milestones.length)
21931
22192
  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));
22193
+ return this.toResult(`Result (first ${milestones.length} shown of ${total} total milestones found):`, await this.entitiesWithRelatedEntities(milestones, "objectives"));
21933
22194
  }
21934
22195
  async getObjective(objectivePublicId) {
21935
22196
  const objective = await this.client.getMilestone(objectivePublicId);
21936
22197
  if (!objective)
21937
22198
  throw new Error(`Failed to retrieve Shortcut objective with public ID: ${objectivePublicId}`);
21938
- return this.toResult(`Objective: ${objectivePublicId}`, await this.toCorrectedEntity(objective));
22199
+ return this.toResult(`Objective: ${objectivePublicId}`, await this.entityWithRelatedEntities(objective, "objective"));
21939
22200
  }
21940
22201
  }
21941
22202
 
@@ -22059,6 +22320,21 @@ The story will be added to the default state for the workflow.
22059
22320
  taskOwnerIds: z.array(z.string()).optional().describe("Array of user IDs to assign as owners of the task"),
22060
22321
  isCompleted: z.boolean().optional().describe("Whether the task is completed or not")
22061
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));
22062
22338
  return tools;
22063
22339
  }
22064
22340
  async assignCurrentUserAsOwner(storyPublicId) {
@@ -22141,13 +22417,13 @@ The story will be added to the default state for the workflow.
22141
22417
  throw new Error(`Failed to search for stories matching your query: "${query}".`);
22142
22418
  if (!stories.length)
22143
22419
  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));
22420
+ return this.toResult(`Result (first ${stories.length} shown of ${total} total stories found):`, await this.entitiesWithRelatedEntities(stories, "stories"));
22145
22421
  }
22146
22422
  async getStory(storyPublicId) {
22147
22423
  const story = await this.client.getStory(storyPublicId);
22148
22424
  if (!story)
22149
22425
  throw new Error(`Failed to retrieve Shortcut story with public ID: ${storyPublicId}.`);
22150
- return this.toResult(`Story: sc-${storyPublicId}`, await this.toCorrectedEntity(story));
22426
+ return this.toResult(`Story: sc-${storyPublicId}`, await this.entityWithRelatedEntities(story, "story"));
22151
22427
  }
22152
22428
  async createStoryComment({
22153
22429
  storyPublicId,
@@ -22268,6 +22544,41 @@ The story will be added to the default state for the workflow.
22268
22544
  await this.client.addRelationToStory(subjectStoryId, objectStoryId, relationshipType);
22269
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}.`);
22270
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
+ }
22271
22582
  }
22272
22583
 
22273
22584
  // src/tools/teams.ts
@@ -22282,13 +22593,13 @@ class TeamTools extends BaseTools {
22282
22593
  const team = await this.client.getTeam(teamPublicId);
22283
22594
  if (!team)
22284
22595
  return this.toResult(`Team with public ID: ${teamPublicId} not found.`);
22285
- return this.toResult(`Team: ${team.id}`, await this.toCorrectedEntity(team));
22596
+ return this.toResult(`Team: ${team.id}`, await this.entityWithRelatedEntities(team, "team"));
22286
22597
  }
22287
22598
  async getTeams() {
22288
22599
  const teams = await this.client.getTeams();
22289
22600
  if (!teams.length)
22290
22601
  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));
22602
+ return this.toResult(`Result (first ${teams.length} shown of ${teams.length} total teams found):`, await this.entitiesWithRelatedEntities(teams, "teams"));
22292
22603
  }
22293
22604
  }
22294
22605
 
@@ -22324,13 +22635,13 @@ class WorkflowTools extends BaseTools {
22324
22635
  const workflow = await this.client.getWorkflow(workflowPublicId);
22325
22636
  if (!workflow)
22326
22637
  return this.toResult(`Workflow with public ID: ${workflowPublicId} not found.`);
22327
- return this.toResult(`Workflow: ${workflow.id}`, await this.toCorrectedEntity(workflow));
22638
+ return this.toResult(`Workflow: ${workflow.id}`, await this.entityWithRelatedEntities(workflow, "workflow"));
22328
22639
  }
22329
22640
  async listWorkflows() {
22330
22641
  const workflows = await this.client.getWorkflows();
22331
22642
  if (!workflows.length)
22332
22643
  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));
22644
+ return this.toResult(`Result (first ${workflows.length} shown of ${workflows.length} total workflows found):`, await this.entitiesWithRelatedEntities(workflows, "workflows"));
22334
22645
  }
22335
22646
  }
22336
22647
 
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.0",
16
16
  "type": "module",
17
17
  "main": "dist/index.js",
18
18
  "bin": {