@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.
- package/dist/index.js +394 -80
- 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:
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
21722
|
+
getSimplifiedWorkflow(entity) {
|
|
21601
21723
|
if (!entity)
|
|
21602
21724
|
return null;
|
|
21603
|
-
const {
|
|
21604
|
-
return {
|
|
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
|
-
|
|
21728
|
+
getSimplifiedTeam(entity) {
|
|
21607
21729
|
if (!entity)
|
|
21608
21730
|
return null;
|
|
21609
|
-
const { member_ids, workflow_ids
|
|
21610
|
-
|
|
21611
|
-
|
|
21612
|
-
|
|
21613
|
-
|
|
21614
|
-
|
|
21615
|
-
|
|
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
|
-
|
|
21754
|
+
getSimplifiedIteration(entity) {
|
|
21620
21755
|
if (!entity)
|
|
21621
21756
|
return null;
|
|
21622
|
-
const { group_ids,
|
|
21623
|
-
|
|
21624
|
-
|
|
21625
|
-
|
|
21626
|
-
|
|
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
|
|
21631
|
-
|
|
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
|
|
21634
|
-
|
|
21635
|
-
|
|
21636
|
-
|
|
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
|
|
21640
|
-
|
|
21641
|
-
|
|
21642
|
-
|
|
21643
|
-
|
|
21644
|
-
|
|
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
|
|
21649
|
-
const {
|
|
21650
|
-
|
|
21651
|
-
|
|
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
|
|
21654
|
-
const
|
|
21655
|
-
const
|
|
21656
|
-
|
|
21657
|
-
|
|
21658
|
-
|
|
21659
|
-
|
|
21660
|
-
|
|
21661
|
-
|
|
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
|
|
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.
|
|
21867
|
+
return this.getRelatedEntitiesForTeam(entity);
|
|
21670
21868
|
if (entity.entity_type === "iteration")
|
|
21671
|
-
return this.
|
|
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.
|
|
21871
|
+
return this.getRelatedEntitiesForEpic(entity);
|
|
21676
21872
|
if (entity.entity_type === "story")
|
|
21677
|
-
return this.
|
|
21678
|
-
return
|
|
21873
|
+
return this.getRelatedEntitiesForStory(entity);
|
|
21874
|
+
return {};
|
|
21679
21875
|
}
|
|
21680
|
-
async
|
|
21681
|
-
|
|
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.
|
|
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.
|
|
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", {
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|