@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.
- package/dist/index.js +386 -75
- 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.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,
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|