@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.
- package/dist/index.js +434 -74
- 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.
|
|
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
|
-
|
|
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
|
+
getSimplifiedStory(entity) {
|
|
21601
21723
|
if (!entity)
|
|
21602
21724
|
return null;
|
|
21603
|
-
const {
|
|
21604
|
-
|
|
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
|
-
|
|
21752
|
+
getSimplifiedWorkflow(entity) {
|
|
21607
21753
|
if (!entity)
|
|
21608
21754
|
return null;
|
|
21609
|
-
const {
|
|
21610
|
-
|
|
21611
|
-
|
|
21612
|
-
|
|
21613
|
-
|
|
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
|
-
|
|
21762
|
+
getSimplifiedTeam(entity) {
|
|
21620
21763
|
if (!entity)
|
|
21621
21764
|
return null;
|
|
21622
|
-
const {
|
|
21623
|
-
|
|
21624
|
-
|
|
21625
|
-
|
|
21626
|
-
|
|
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
|
-
|
|
21631
|
-
|
|
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
|
|
21634
|
-
|
|
21635
|
-
|
|
21636
|
-
|
|
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
|
|
21640
|
-
|
|
21641
|
-
|
|
21642
|
-
|
|
21643
|
-
|
|
21644
|
-
|
|
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
|
|
21649
|
-
const {
|
|
21650
|
-
|
|
21651
|
-
|
|
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
|
|
21654
|
-
const
|
|
21655
|
-
const
|
|
21656
|
-
|
|
21657
|
-
|
|
21658
|
-
|
|
21659
|
-
|
|
21660
|
-
|
|
21661
|
-
|
|
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
|
|
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.
|
|
21901
|
+
return this.getRelatedEntitiesForTeam(entity);
|
|
21670
21902
|
if (entity.entity_type === "iteration")
|
|
21671
|
-
return this.
|
|
21672
|
-
if (entity.entity_type === "
|
|
21673
|
-
return this.
|
|
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.
|
|
21916
|
+
return this.getSimplifiedEpic(entity);
|
|
21676
21917
|
if (entity.entity_type === "story")
|
|
21677
|
-
return this.
|
|
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
|
|
21681
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|