@shortcut/mcp 0.13.0 → 0.15.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/README.md +76 -36
- package/dist/index.js +178 -145
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -129,63 +129,103 @@ Or you can edit the local JSON file directly:
|
|
|
129
129
|
|
|
130
130
|
### Stories
|
|
131
131
|
|
|
132
|
-
- **get-
|
|
133
|
-
- **search
|
|
134
|
-
- **get-
|
|
135
|
-
- **create
|
|
136
|
-
- **update
|
|
137
|
-
- **upload-file
|
|
138
|
-
- **assign-current-user
|
|
139
|
-
- **unassign-current-user
|
|
140
|
-
- **create-
|
|
141
|
-
- **add-task
|
|
142
|
-
- **update-task** - Update a task in a story
|
|
143
|
-
- **add-relation
|
|
144
|
-
- **add-external-link
|
|
145
|
-
- **remove-external-link
|
|
146
|
-
- **set-
|
|
147
|
-
- **get-
|
|
132
|
+
- **stories-get-by-id** - Get a single Shortcut story by ID
|
|
133
|
+
- **stories-search** - Find Shortcut stories with filtering and search options
|
|
134
|
+
- **stories-get-branch-name** - Get the recommended branch name (based on workspace settings) for a specific story.
|
|
135
|
+
- **stories-create** - Create a new Shortcut story
|
|
136
|
+
- **stories-update** - Update an existing Shortcut story
|
|
137
|
+
- **stories-upload-file** - Upload a file and link it to a story
|
|
138
|
+
- **stories-assign-current-user** - Assign the current user as the owner of a story
|
|
139
|
+
- **stories-unassign-current-user** - Unassign the current user as the owner of a story
|
|
140
|
+
- **stories-create-comment** - Create a comment on a story
|
|
141
|
+
- **stories-add-task** - Add a task to a story
|
|
142
|
+
- **stories-update-task** - Update a task in a story
|
|
143
|
+
- **stories-add-relation** - Add a story relationship (relates to, blocks, duplicates, etc.)
|
|
144
|
+
- **stories-add-external-link** - Add an external link to a Shortcut story
|
|
145
|
+
- **stories-remove-external-link** - Remove an external link from a Shortcut story
|
|
146
|
+
- **stories-set-external-links** - Replace all external links on a story with a new set of links
|
|
147
|
+
- **stories-get-by-external-link** - Find all stories that contain a specific external link
|
|
148
148
|
|
|
149
149
|
### Epics
|
|
150
150
|
|
|
151
|
-
- **get-
|
|
152
|
-
- **search
|
|
153
|
-
- **create
|
|
151
|
+
- **epics-get-by-id** - Get a Shortcut epic by ID
|
|
152
|
+
- **epics-search** - Find Shortcut epics with filtering and search options
|
|
153
|
+
- **epics-create** - Create a new Shortcut epic
|
|
154
154
|
|
|
155
155
|
### Iterations
|
|
156
156
|
|
|
157
|
-
- **get-
|
|
158
|
-
- **get-
|
|
159
|
-
- **search
|
|
160
|
-
- **create
|
|
161
|
-
- **get-active
|
|
162
|
-
- **get-upcoming
|
|
157
|
+
- **iterations-get-stories** - Get stories in a specific iteration by iteration ID
|
|
158
|
+
- **iterations-get-by-id** - Get a Shortcut iteration by ID
|
|
159
|
+
- **iterations-search** - Find Shortcut iterations with filtering and search options
|
|
160
|
+
- **iterations-create** - Create a new Shortcut iteration with start/end dates
|
|
161
|
+
- **iterations-get-active** - Get active iterations for the current user based on team memberships
|
|
162
|
+
- **iterations-get-upcoming** - Get upcoming iterations for the current user based on team memberships
|
|
163
163
|
|
|
164
164
|
### Objectives
|
|
165
165
|
|
|
166
|
-
- **get-
|
|
167
|
-
- **search
|
|
166
|
+
- **objectives-get-by-id** - Get a Shortcut objective by ID
|
|
167
|
+
- **objectives-search** - Find Shortcut objectives with filtering and search options
|
|
168
168
|
|
|
169
169
|
### Teams
|
|
170
170
|
|
|
171
|
-
- **get-
|
|
172
|
-
- **list
|
|
171
|
+
- **teams-get-by-id** - Get a Shortcut team by ID
|
|
172
|
+
- **teams-list** - List all Shortcut teams
|
|
173
173
|
|
|
174
174
|
### Workflows
|
|
175
175
|
|
|
176
|
-
- **get-default
|
|
177
|
-
- **get-
|
|
178
|
-
- **list
|
|
176
|
+
- **workflows-get-default** - Get the default workflow for a specific team or the workspace default
|
|
177
|
+
- **workflows-get-by-id** - Get a Shortcut workflow by ID
|
|
178
|
+
- **workflows-list** - List all Shortcut workflows
|
|
179
179
|
|
|
180
180
|
### Users
|
|
181
181
|
|
|
182
|
-
- **get-current
|
|
183
|
-
- **get-current-
|
|
184
|
-
- **list
|
|
182
|
+
- **users-get-current** - Get the current user information
|
|
183
|
+
- **users-get-current-teams** - Get a list of teams where the current user is a member
|
|
184
|
+
- **users-list** - Get all workspace users
|
|
185
185
|
|
|
186
186
|
### Documents
|
|
187
187
|
|
|
188
|
-
- **create
|
|
188
|
+
- **documents-create** - Create a new document in Shortcut with HTML content
|
|
189
|
+
|
|
190
|
+
## Limit tools
|
|
191
|
+
|
|
192
|
+
You can limit the tools available to the LLM by setting the `SHORTCUT_TOOLS` environment variable to a comma-separated list.
|
|
193
|
+
|
|
194
|
+
- Tools can be limited by entity type by just adding the entity, eg `stories` or `epics`.
|
|
195
|
+
- Individual tools can also be limitied by their full name, eg `stories-get-by-id` or `epics-search`.
|
|
196
|
+
|
|
197
|
+
By default, all tools are enabled.
|
|
198
|
+
|
|
199
|
+
Example:
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{
|
|
203
|
+
"mcpServers": {
|
|
204
|
+
"shortcut": {
|
|
205
|
+
"command": "npx",
|
|
206
|
+
"args": [
|
|
207
|
+
"-y",
|
|
208
|
+
"@shortcut/mcp@latest"
|
|
209
|
+
],
|
|
210
|
+
"env": {
|
|
211
|
+
"SHORTCUT_API_TOKEN": "<YOUR_SHORTCUT_API_TOKEN>",
|
|
212
|
+
"SHORTCUT_TOOLS": "stories,epics,iterations-create"
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The following values are accepted in addition to the full tool names listed above under [Available Tools](#available-tools):
|
|
220
|
+
|
|
221
|
+
- `users`
|
|
222
|
+
- `stories`
|
|
223
|
+
- `epics`
|
|
224
|
+
- `iterations`
|
|
225
|
+
- `objectives`
|
|
226
|
+
- `teams`
|
|
227
|
+
- `workflows`
|
|
228
|
+
- `documents`
|
|
189
229
|
|
|
190
230
|
## Read-only mode
|
|
191
231
|
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
3
|
import { ShortcutClient } from "@shortcut/client";
|
|
5
4
|
import { File } from "node:buffer";
|
|
6
5
|
import { readFileSync } from "node:fs";
|
|
7
6
|
import { basename } from "node:path";
|
|
7
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
|
|
10
10
|
//#region src/client/cache.ts
|
|
@@ -450,7 +450,41 @@ var ShortcutClientWrapper = class {
|
|
|
450
450
|
//#endregion
|
|
451
451
|
//#region package.json
|
|
452
452
|
var name = "@shortcut/mcp";
|
|
453
|
-
var version = "0.
|
|
453
|
+
var version = "0.15.1";
|
|
454
|
+
|
|
455
|
+
//#endregion
|
|
456
|
+
//#region src/mcp/CustomMcpServer.ts
|
|
457
|
+
var CustomMcpServer = class extends McpServer {
|
|
458
|
+
readonly;
|
|
459
|
+
tools;
|
|
460
|
+
constructor({ readonly, tools }) {
|
|
461
|
+
super({
|
|
462
|
+
name,
|
|
463
|
+
version
|
|
464
|
+
});
|
|
465
|
+
this.readonly = readonly;
|
|
466
|
+
this.tools = new Set(tools || []);
|
|
467
|
+
}
|
|
468
|
+
shouldAddTool(name$1) {
|
|
469
|
+
console.log("Checking tool:", name$1, this.tools.size);
|
|
470
|
+
if (!this.tools.size) return true;
|
|
471
|
+
const [entityType] = name$1.split("-");
|
|
472
|
+
if (this.tools.has(entityType) || this.tools.has(name$1)) return true;
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
addToolWithWriteAccess(...args) {
|
|
476
|
+
if (this.readonly) return null;
|
|
477
|
+
if (!this.shouldAddTool(args[0])) return null;
|
|
478
|
+
return super.tool(...args);
|
|
479
|
+
}
|
|
480
|
+
addToolWithReadAccess(...args) {
|
|
481
|
+
if (!this.shouldAddTool(args[0])) return null;
|
|
482
|
+
return super.tool(...args);
|
|
483
|
+
}
|
|
484
|
+
tool() {
|
|
485
|
+
throw new Error("Call addToolWithReadAccess or addToolWithWriteAccess instead.");
|
|
486
|
+
}
|
|
487
|
+
};
|
|
454
488
|
|
|
455
489
|
//#endregion
|
|
456
490
|
//#region src/tools/base.ts
|
|
@@ -458,9 +492,8 @@ var version = "0.13.0";
|
|
|
458
492
|
* Base class for all tools.
|
|
459
493
|
*/
|
|
460
494
|
var BaseTools = class {
|
|
461
|
-
constructor(client$1
|
|
495
|
+
constructor(client$1) {
|
|
462
496
|
this.client = client$1;
|
|
463
|
-
this.isReadonly = isReadonly$1;
|
|
464
497
|
}
|
|
465
498
|
renameEntityProps(entity) {
|
|
466
499
|
if (!entity || typeof entity !== "object") return entity;
|
|
@@ -792,9 +825,9 @@ var BaseTools = class {
|
|
|
792
825
|
//#endregion
|
|
793
826
|
//#region src/tools/documents.ts
|
|
794
827
|
var DocumentTools = class DocumentTools extends BaseTools {
|
|
795
|
-
static create(client$1, server$1
|
|
796
|
-
const tools = new DocumentTools(client$1
|
|
797
|
-
|
|
828
|
+
static create(client$1, server$1) {
|
|
829
|
+
const tools = new DocumentTools(client$1);
|
|
830
|
+
server$1.addToolWithWriteAccess("documents-create", "Create a new document in Shortcut with a title and content. Returns the document's id, title, and app_url. Note: Use HTML markup for the content (e.g., <p>, <h1>, <ul>, <strong>) rather than Markdown.", {
|
|
798
831
|
title: z.string().max(256).describe("The title for the new document (max 256 characters)"),
|
|
799
832
|
content: z.string().describe("The content for the new document in HTML format (e.g., <p>Hello</p>, <h1>Title</h1>, <ul><li>Item</li></ul>)")
|
|
800
833
|
}, async ({ title, content }) => await tools.createDocument(title, content));
|
|
@@ -876,13 +909,13 @@ const user = (field) => z.string().optional().describe(`Find entities where the
|
|
|
876
909
|
//#endregion
|
|
877
910
|
//#region src/tools/epics.ts
|
|
878
911
|
var EpicTools = class EpicTools extends BaseTools {
|
|
879
|
-
static create(client$1, server$1
|
|
880
|
-
const tools = new EpicTools(client$1
|
|
881
|
-
server$1.
|
|
912
|
+
static create(client$1, server$1) {
|
|
913
|
+
const tools = new EpicTools(client$1);
|
|
914
|
+
server$1.addToolWithReadAccess("epics-get-by-id", "Get a Shortcut epic by public ID", {
|
|
882
915
|
epicPublicId: z.number().positive().describe("The public ID of the epic to get"),
|
|
883
916
|
full: z.boolean().optional().default(false).describe("True to return all epic fields from the API. False to return a slim version that excludes uncommon fields")
|
|
884
917
|
}, async ({ epicPublicId, full }) => await tools.getEpic(epicPublicId, full));
|
|
885
|
-
server$1.
|
|
918
|
+
server$1.addToolWithReadAccess("epics-search", "Find Shortcut epics.", {
|
|
886
919
|
nextPageToken: z.string().optional().describe("If a next_page_token was returned from the search result, pass it in to get the next page of results. Should be combined with the original search parameters."),
|
|
887
920
|
id: z.number().optional().describe("Find only epics with the specified public ID"),
|
|
888
921
|
name: z.string().optional().describe("Find only epics matching the specified name"),
|
|
@@ -911,7 +944,7 @@ var EpicTools = class EpicTools extends BaseTools {
|
|
|
911
944
|
completed: date(),
|
|
912
945
|
due: date()
|
|
913
946
|
}, async ({ nextPageToken,...params }) => await tools.searchEpics(params, nextPageToken));
|
|
914
|
-
|
|
947
|
+
server$1.addToolWithWriteAccess("epics-create", "Create a new Shortcut epic.", {
|
|
915
948
|
name: z.string().describe("The name of the epic"),
|
|
916
949
|
owner: z.string().optional().describe("The user ID of the owner of the epic"),
|
|
917
950
|
description: z.string().optional().describe("A description of the epic"),
|
|
@@ -946,17 +979,17 @@ var EpicTools = class EpicTools extends BaseTools {
|
|
|
946
979
|
//#endregion
|
|
947
980
|
//#region src/tools/iterations.ts
|
|
948
981
|
var IterationTools = class IterationTools extends BaseTools {
|
|
949
|
-
static create(client$1, server$1
|
|
950
|
-
const tools = new IterationTools(client$1
|
|
951
|
-
server$1.
|
|
982
|
+
static create(client$1, server$1) {
|
|
983
|
+
const tools = new IterationTools(client$1);
|
|
984
|
+
server$1.addToolWithReadAccess("iterations-get-stories", "Get stories in a specific iteration by iteration public ID", {
|
|
952
985
|
iterationPublicId: z.number().positive().describe("The public ID of the iteration"),
|
|
953
986
|
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.")
|
|
954
987
|
}, async ({ iterationPublicId, includeStoryDescriptions }) => await tools.getIterationStories(iterationPublicId, includeStoryDescriptions));
|
|
955
|
-
server$1.
|
|
988
|
+
server$1.addToolWithReadAccess("iterations-get-by-id", "Get a Shortcut iteration by public ID", {
|
|
956
989
|
iterationPublicId: z.number().positive().describe("The public ID of the iteration to get"),
|
|
957
990
|
full: z.boolean().optional().default(false).describe("True to return all iteration fields from the API. False to return a slim version that excludes uncommon fields")
|
|
958
991
|
}, async ({ iterationPublicId, full }) => await tools.getIteration(iterationPublicId, full));
|
|
959
|
-
server$1.
|
|
992
|
+
server$1.addToolWithReadAccess("iterations-search", "Find Shortcut iterations.", {
|
|
960
993
|
nextPageToken: z.string().optional().describe("If a next_page_token was returned from the search result, pass it in to get the next page of results. Should be combined with the original search parameters."),
|
|
961
994
|
id: z.number().optional().describe("Find only iterations with the specified public ID"),
|
|
962
995
|
name: z.string().optional().describe("Find only iterations matching the specified name"),
|
|
@@ -972,15 +1005,15 @@ var IterationTools = class IterationTools extends BaseTools {
|
|
|
972
1005
|
startDate: date(),
|
|
973
1006
|
endDate: date()
|
|
974
1007
|
}, async ({ nextPageToken,...params }) => await tools.searchIterations(params, nextPageToken));
|
|
975
|
-
|
|
1008
|
+
server$1.addToolWithWriteAccess("iterations-create", "Create a new Shortcut iteration", {
|
|
976
1009
|
name: z.string().describe("The name of the iteration"),
|
|
977
1010
|
startDate: z.string().describe("The start date of the iteration in YYYY-MM-DD format"),
|
|
978
1011
|
endDate: z.string().describe("The end date of the iteration in YYYY-MM-DD format"),
|
|
979
1012
|
teamId: z.string().optional().describe("The ID of a team to assign the iteration to"),
|
|
980
1013
|
description: z.string().optional().describe("A description of the iteration")
|
|
981
1014
|
}, async (params) => await tools.createIteration(params));
|
|
982
|
-
server$1.
|
|
983
|
-
server$1.
|
|
1015
|
+
server$1.addToolWithReadAccess("iterations-get-active", "Get the active Shortcut iterations for the current user based on their team memberships", { teamId: z.string().optional().describe("The ID of a team to filter iterations by") }, async ({ teamId }) => await tools.getActiveIterations(teamId));
|
|
1016
|
+
server$1.addToolWithReadAccess("iterations-get-upcoming", "Get the upcoming Shortcut iterations for the current user based on their team memberships", { teamId: z.string().optional().describe("The ID of a team to filter iterations by") }, async ({ teamId }) => await tools.getUpcomingIterations(teamId));
|
|
984
1017
|
return tools;
|
|
985
1018
|
}
|
|
986
1019
|
async getIterationStories(iterationPublicId, includeDescription) {
|
|
@@ -1057,13 +1090,13 @@ var IterationTools = class IterationTools extends BaseTools {
|
|
|
1057
1090
|
//#endregion
|
|
1058
1091
|
//#region src/tools/objectives.ts
|
|
1059
1092
|
var ObjectiveTools = class ObjectiveTools extends BaseTools {
|
|
1060
|
-
static create(client$1, server$1
|
|
1061
|
-
const tools = new ObjectiveTools(client$1
|
|
1062
|
-
server$1.
|
|
1093
|
+
static create(client$1, server$1) {
|
|
1094
|
+
const tools = new ObjectiveTools(client$1);
|
|
1095
|
+
server$1.addToolWithReadAccess("objectives-get-by-id", "Get a Shortcut objective by public ID", {
|
|
1063
1096
|
objectivePublicId: z.number().positive().describe("The public ID of the objective to get"),
|
|
1064
1097
|
full: z.boolean().optional().default(false).describe("True to return all objective fields from the API. False to return a slim version that excludes uncommon fields")
|
|
1065
1098
|
}, async ({ objectivePublicId, full }) => await tools.getObjective(objectivePublicId, full));
|
|
1066
|
-
server$1.
|
|
1099
|
+
server$1.addToolWithReadAccess("objectives-search", "Find Shortcut objectives.", {
|
|
1067
1100
|
nextPageToken: z.string().optional().describe("If a next_page_token was returned from the search result, pass it in to get the next page of results. Should be combined with the original search parameters."),
|
|
1068
1101
|
id: z.number().optional().describe("Find objectives matching the specified id"),
|
|
1069
1102
|
name: z.string().optional().describe("Find objectives matching the specified name"),
|
|
@@ -1105,13 +1138,13 @@ var ObjectiveTools = class ObjectiveTools extends BaseTools {
|
|
|
1105
1138
|
//#endregion
|
|
1106
1139
|
//#region src/tools/stories.ts
|
|
1107
1140
|
var StoryTools = class StoryTools extends BaseTools {
|
|
1108
|
-
static create(client$1, server$1
|
|
1109
|
-
const tools = new StoryTools(client$1
|
|
1110
|
-
server$1.
|
|
1141
|
+
static create(client$1, server$1) {
|
|
1142
|
+
const tools = new StoryTools(client$1);
|
|
1143
|
+
server$1.addToolWithReadAccess("stories-get-by-id", "Get a Shortcut story by public ID", {
|
|
1111
1144
|
storyPublicId: z.number().positive().describe("The public ID of the story to get"),
|
|
1112
1145
|
full: z.boolean().optional().default(false).describe("True to return all story fields from the API. False to return a slim version that excludes uncommon fields")
|
|
1113
1146
|
}, async ({ storyPublicId, full }) => await tools.getStory(storyPublicId, full));
|
|
1114
|
-
server$1.
|
|
1147
|
+
server$1.addToolWithReadAccess("stories-search", "Find Shortcut stories.", {
|
|
1115
1148
|
nextPageToken: z.string().optional().describe("If a next_page_token was returned from the search result, pass it in to get the next page of results. Should be combined with the original search parameters."),
|
|
1116
1149
|
id: z.number().optional().describe("Find only stories with the specified public ID"),
|
|
1117
1150
|
name: z.string().optional().describe("Find only stories matching the specified name"),
|
|
@@ -1162,103 +1195,101 @@ var StoryTools = class StoryTools extends BaseTools {
|
|
|
1162
1195
|
completed: date(),
|
|
1163
1196
|
due: date()
|
|
1164
1197
|
}, async ({ nextPageToken,...params }) => await tools.searchStories(params, nextPageToken));
|
|
1165
|
-
server$1.
|
|
1166
|
-
|
|
1167
|
-
server$1.tool("create-story", `Create a new Shortcut story.
|
|
1198
|
+
server$1.addToolWithReadAccess("stories-get-branch-name", "Get a valid branch name for a specific story.", { storyPublicId: z.number().positive().describe("The public Id of the story") }, async ({ storyPublicId }) => await tools.getStoryBranchName(storyPublicId));
|
|
1199
|
+
server$1.addToolWithWriteAccess("stories-create", `Create a new Shortcut story.
|
|
1168
1200
|
Name is required, and either a Team or Workflow must be specified:
|
|
1169
1201
|
- If only Team is specified, we will use the default workflow for that team.
|
|
1170
1202
|
- If Workflow is specified, it will be used regardless of Team.
|
|
1171
1203
|
The story will be added to the default state for the workflow.
|
|
1172
1204
|
`, {
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
}
|
|
1261
|
-
server$1.tool("get-stories-by-external-link", "Find all stories that contain a specific external link", { externalLink: z.string().url().max(2048).describe("The external link URL to search for") }, async ({ externalLink }) => await tools.getStoriesByExternalLink(externalLink));
|
|
1205
|
+
name: z.string().min(1).max(512).describe("The name of the story. Required."),
|
|
1206
|
+
description: z.string().max(1e4).optional().describe("The description of the story"),
|
|
1207
|
+
type: z.enum([
|
|
1208
|
+
"feature",
|
|
1209
|
+
"bug",
|
|
1210
|
+
"chore"
|
|
1211
|
+
]).default("feature").describe("The type of the story"),
|
|
1212
|
+
owner: z.string().optional().describe("The user id of the owner of the story"),
|
|
1213
|
+
epic: z.number().optional().describe("The epic id of the epic the story belongs to"),
|
|
1214
|
+
iteration: z.number().optional().describe("The iteration id of the iteration the story belongs to"),
|
|
1215
|
+
team: z.string().optional().describe("The team ID or mention name of the team the story belongs to. Required unless a workflow is specified."),
|
|
1216
|
+
workflow: z.number().optional().describe("The workflow ID to add the story to. Required unless a team is specified.")
|
|
1217
|
+
}, async ({ name: name$1, description, type, owner, epic, iteration, team, workflow }) => await tools.createStory({
|
|
1218
|
+
name: name$1,
|
|
1219
|
+
description,
|
|
1220
|
+
type,
|
|
1221
|
+
owner,
|
|
1222
|
+
epic,
|
|
1223
|
+
iteration,
|
|
1224
|
+
team,
|
|
1225
|
+
workflow
|
|
1226
|
+
}));
|
|
1227
|
+
server$1.addToolWithWriteAccess("stories-update", "Update an existing Shortcut story. Only provide fields you want to update. The story public ID will always be included in updates.", {
|
|
1228
|
+
storyPublicId: z.number().positive().describe("The public ID of the story to update"),
|
|
1229
|
+
name: z.string().max(512).optional().describe("The name of the story"),
|
|
1230
|
+
description: z.string().max(1e4).optional().describe("The description of the story"),
|
|
1231
|
+
type: z.enum([
|
|
1232
|
+
"feature",
|
|
1233
|
+
"bug",
|
|
1234
|
+
"chore"
|
|
1235
|
+
]).optional().describe("The type of the story"),
|
|
1236
|
+
epic: z.number().nullable().optional().describe("The epic id of the epic the story belongs to, or null to unset"),
|
|
1237
|
+
estimate: z.number().nullable().optional().describe("The point estimate of the story, or null to unset"),
|
|
1238
|
+
iteration: z.number().nullable().optional().describe("The iteration id of the iteration the story belongs to, or null to unset"),
|
|
1239
|
+
owner_ids: z.array(z.string()).optional().describe("Array of user UUIDs to assign as owners of the story"),
|
|
1240
|
+
workflow_state_id: z.number().optional().describe("The workflow state ID to move the story to"),
|
|
1241
|
+
labels: z.array(z.object({
|
|
1242
|
+
name: z.string().describe("The name of the label"),
|
|
1243
|
+
color: z.string().optional().describe("The color of the label"),
|
|
1244
|
+
description: z.string().optional().describe("The description of the label")
|
|
1245
|
+
})).optional().describe("Labels to assign to the story")
|
|
1246
|
+
}, async (params) => await tools.updateStory(params));
|
|
1247
|
+
server$1.addToolWithWriteAccess("stories-upload-file", "Upload a file and link it to a story.", {
|
|
1248
|
+
storyPublicId: z.number().positive().describe("The public ID of the story"),
|
|
1249
|
+
filePath: z.string().describe("The path to the file to upload")
|
|
1250
|
+
}, async ({ storyPublicId, filePath }) => await tools.uploadFileToStory(storyPublicId, filePath));
|
|
1251
|
+
server$1.addToolWithWriteAccess("stories-assign-current-user", "Assign the current user as the owner of a story", { storyPublicId: z.number().positive().describe("The public ID of the story") }, async ({ storyPublicId }) => await tools.assignCurrentUserAsOwner(storyPublicId));
|
|
1252
|
+
server$1.addToolWithWriteAccess("stories-unassign-current-user", "Unassign the current user as the owner of a story", { storyPublicId: z.number().positive().describe("The public ID of the story") }, async ({ storyPublicId }) => await tools.unassignCurrentUserAsOwner(storyPublicId));
|
|
1253
|
+
server$1.addToolWithWriteAccess("stories-create-comment", "Create a comment on a story", {
|
|
1254
|
+
storyPublicId: z.number().positive().describe("The public ID of the story"),
|
|
1255
|
+
text: z.string().min(1).describe("The text of the comment")
|
|
1256
|
+
}, async (params) => await tools.createStoryComment(params));
|
|
1257
|
+
server$1.addToolWithWriteAccess("stories-add-task", "Add a task to a story", {
|
|
1258
|
+
storyPublicId: z.number().positive().describe("The public ID of the story"),
|
|
1259
|
+
taskDescription: z.string().min(1).describe("The description of the task"),
|
|
1260
|
+
taskOwnerIds: z.array(z.string()).optional().describe("Array of user IDs to assign as owners of the task")
|
|
1261
|
+
}, async (params) => await tools.addTaskToStory(params));
|
|
1262
|
+
server$1.addToolWithWriteAccess("stories-update-task", "Update a task in a story", {
|
|
1263
|
+
storyPublicId: z.number().positive().describe("The public ID of the story"),
|
|
1264
|
+
taskPublicId: z.number().positive().describe("The public ID of the task"),
|
|
1265
|
+
taskDescription: z.string().optional().describe("The description of the task"),
|
|
1266
|
+
taskOwnerIds: z.array(z.string()).optional().describe("Array of user IDs to assign as owners of the task"),
|
|
1267
|
+
isCompleted: z.boolean().optional().describe("Whether the task is completed or not")
|
|
1268
|
+
}, async (params) => await tools.updateTask(params));
|
|
1269
|
+
server$1.addToolWithWriteAccess("stories-add-relation", "Add a story relationship to a story", {
|
|
1270
|
+
storyPublicId: z.number().positive().describe("The public ID of the story"),
|
|
1271
|
+
relatedStoryPublicId: z.number().positive().describe("The public ID of the related story"),
|
|
1272
|
+
relationshipType: z.enum([
|
|
1273
|
+
"relates to",
|
|
1274
|
+
"blocks",
|
|
1275
|
+
"blocked by",
|
|
1276
|
+
"duplicates",
|
|
1277
|
+
"duplicated by"
|
|
1278
|
+
]).optional().default("relates to").describe("The type of relationship")
|
|
1279
|
+
}, async (params) => await tools.addRelationToStory(params));
|
|
1280
|
+
server$1.addToolWithWriteAccess("stories-add-external-link", "Add an external link to a Shortcut story", {
|
|
1281
|
+
storyPublicId: z.number().positive().describe("The public ID of the story"),
|
|
1282
|
+
externalLink: z.string().url().max(2048).describe("The external link URL to add")
|
|
1283
|
+
}, async ({ storyPublicId, externalLink }) => await tools.addExternalLinkToStory(storyPublicId, externalLink));
|
|
1284
|
+
server$1.addToolWithWriteAccess("stories-remove-external-link", "Remove an external link from a Shortcut story", {
|
|
1285
|
+
storyPublicId: z.number().positive().describe("The public ID of the story"),
|
|
1286
|
+
externalLink: z.string().url().max(2048).describe("The external link URL to remove")
|
|
1287
|
+
}, async ({ storyPublicId, externalLink }) => await tools.removeExternalLinkFromStory(storyPublicId, externalLink));
|
|
1288
|
+
server$1.addToolWithWriteAccess("stories-set-external-links", "Replace all external links on a story with a new set of links", {
|
|
1289
|
+
storyPublicId: z.number().positive().describe("The public ID of the story"),
|
|
1290
|
+
externalLinks: z.array(z.string().url().max(2048)).describe("Array of external link URLs to set (replaces all existing links)")
|
|
1291
|
+
}, async ({ storyPublicId, externalLinks }) => await tools.setStoryExternalLinks(storyPublicId, externalLinks));
|
|
1292
|
+
server$1.addToolWithReadAccess("stories-get-by-external-link", "Find all stories that contain a specific external link", { externalLink: z.string().url().max(2048).describe("The external link URL to search for") }, async ({ externalLink }) => await tools.getStoriesByExternalLink(externalLink));
|
|
1262
1293
|
return tools;
|
|
1263
1294
|
}
|
|
1264
1295
|
async assignCurrentUserAsOwner(storyPublicId) {
|
|
@@ -1437,13 +1468,13 @@ The story will be added to the default state for the workflow.
|
|
|
1437
1468
|
//#endregion
|
|
1438
1469
|
//#region src/tools/teams.ts
|
|
1439
1470
|
var TeamTools = class TeamTools extends BaseTools {
|
|
1440
|
-
static create(client$1, server$1
|
|
1441
|
-
const tools = new TeamTools(client$1
|
|
1442
|
-
server$1.
|
|
1471
|
+
static create(client$1, server$1) {
|
|
1472
|
+
const tools = new TeamTools(client$1);
|
|
1473
|
+
server$1.addToolWithReadAccess("teams-get-by-id", "Get a Shortcut team by public ID", {
|
|
1443
1474
|
teamPublicId: z.string().describe("The public ID of the team to get"),
|
|
1444
1475
|
full: z.boolean().optional().default(false).describe("True to return all team fields from the API. False to return a slim version that excludes uncommon fields")
|
|
1445
1476
|
}, async ({ teamPublicId, full }) => await tools.getTeam(teamPublicId, full));
|
|
1446
|
-
server$1.
|
|
1477
|
+
server$1.addToolWithReadAccess("teams-list", "List all Shortcut teams", async () => await tools.getTeams());
|
|
1447
1478
|
return tools;
|
|
1448
1479
|
}
|
|
1449
1480
|
async getTeam(teamPublicId, full = false) {
|
|
@@ -1461,11 +1492,11 @@ var TeamTools = class TeamTools extends BaseTools {
|
|
|
1461
1492
|
//#endregion
|
|
1462
1493
|
//#region src/tools/user.ts
|
|
1463
1494
|
var UserTools = class UserTools extends BaseTools {
|
|
1464
|
-
static create(client$1, server$1
|
|
1465
|
-
const tools = new UserTools(client$1
|
|
1466
|
-
server$1.
|
|
1467
|
-
server$1.
|
|
1468
|
-
server$1.
|
|
1495
|
+
static create(client$1, server$1) {
|
|
1496
|
+
const tools = new UserTools(client$1);
|
|
1497
|
+
server$1.addToolWithReadAccess("users-get-current", "Get the current user", async () => await tools.getCurrentUser());
|
|
1498
|
+
server$1.addToolWithReadAccess("users-get-current-teams", "Get a list of teams where the current user is a member", async () => await tools.getCurrentUserTeams());
|
|
1499
|
+
server$1.addToolWithReadAccess("users-list", "Get all users", async () => await tools.listMembers());
|
|
1469
1500
|
return tools;
|
|
1470
1501
|
}
|
|
1471
1502
|
async getCurrentUser() {
|
|
@@ -1494,14 +1525,14 @@ var UserTools = class UserTools extends BaseTools {
|
|
|
1494
1525
|
//#endregion
|
|
1495
1526
|
//#region src/tools/workflows.ts
|
|
1496
1527
|
var WorkflowTools = class WorkflowTools extends BaseTools {
|
|
1497
|
-
static create(client$1, server$1
|
|
1498
|
-
const tools = new WorkflowTools(client$1
|
|
1499
|
-
server$1.
|
|
1500
|
-
server$1.
|
|
1528
|
+
static create(client$1, server$1) {
|
|
1529
|
+
const tools = new WorkflowTools(client$1);
|
|
1530
|
+
server$1.addToolWithReadAccess("workflows-get-default", "Get the default workflow for a specific team or the global default if no team is specified.", { teamPublicId: z.string().optional().describe("The public ID of the team to get the default workflow for.") }, async ({ teamPublicId }) => await tools.getDefaultWorkflow(teamPublicId));
|
|
1531
|
+
server$1.addToolWithReadAccess("workflows-get-by-id", "Get a Shortcut workflow by public ID", {
|
|
1501
1532
|
workflowPublicId: z.number().positive().describe("The public ID of the workflow to get"),
|
|
1502
1533
|
full: z.boolean().optional().default(false).describe("True to return all workflow fields from the API. False to return a slim version that excludes uncommon fields")
|
|
1503
1534
|
}, async ({ workflowPublicId, full }) => await tools.getWorkflow(workflowPublicId, full));
|
|
1504
|
-
server$1.
|
|
1535
|
+
server$1.addToolWithReadAccess("workflows-list", "List all Shortcut workflows", async () => await tools.listWorkflows());
|
|
1505
1536
|
return tools;
|
|
1506
1537
|
}
|
|
1507
1538
|
async getDefaultWorkflow(teamPublicId) {
|
|
@@ -1535,27 +1566,29 @@ var WorkflowTools = class WorkflowTools extends BaseTools {
|
|
|
1535
1566
|
//#region src/server.ts
|
|
1536
1567
|
let apiToken = process.env.SHORTCUT_API_TOKEN;
|
|
1537
1568
|
let isReadonly = process.env.SHORTCUT_READONLY === "true";
|
|
1569
|
+
let enabledTools = (process.env.SHORTCUT_TOOLS || "").split(",").map((tool) => tool.trim()).filter(Boolean);
|
|
1538
1570
|
if (process.argv.length >= 3) process.argv.slice(2).map((arg) => arg.split("=")).forEach(([name$1, value]) => {
|
|
1539
1571
|
if (name$1 === "SHORTCUT_API_TOKEN") apiToken = value;
|
|
1540
1572
|
if (name$1 === "SHORTCUT_READONLY") isReadonly = value === "true";
|
|
1573
|
+
if (name$1 === "SHORTCUT_TOOLS") enabledTools = value.split(",").map((tool) => tool.trim()).filter(Boolean);
|
|
1541
1574
|
});
|
|
1542
1575
|
if (!apiToken) {
|
|
1543
1576
|
console.error("SHORTCUT_API_TOKEN is required");
|
|
1544
1577
|
process.exit(1);
|
|
1545
1578
|
}
|
|
1546
|
-
const server = new
|
|
1547
|
-
|
|
1548
|
-
|
|
1579
|
+
const server = new CustomMcpServer({
|
|
1580
|
+
readonly: isReadonly,
|
|
1581
|
+
tools: enabledTools
|
|
1549
1582
|
});
|
|
1550
1583
|
const client = new ShortcutClientWrapper(new ShortcutClient(apiToken));
|
|
1551
|
-
UserTools.create(client, server
|
|
1552
|
-
StoryTools.create(client, server
|
|
1553
|
-
IterationTools.create(client, server
|
|
1554
|
-
EpicTools.create(client, server
|
|
1555
|
-
ObjectiveTools.create(client, server
|
|
1556
|
-
TeamTools.create(client, server
|
|
1557
|
-
WorkflowTools.create(client, server
|
|
1558
|
-
DocumentTools.create(client, server
|
|
1584
|
+
UserTools.create(client, server);
|
|
1585
|
+
StoryTools.create(client, server);
|
|
1586
|
+
IterationTools.create(client, server);
|
|
1587
|
+
EpicTools.create(client, server);
|
|
1588
|
+
ObjectiveTools.create(client, server);
|
|
1589
|
+
TeamTools.create(client, server);
|
|
1590
|
+
WorkflowTools.create(client, server);
|
|
1591
|
+
DocumentTools.create(client, server);
|
|
1559
1592
|
async function startServer() {
|
|
1560
1593
|
try {
|
|
1561
1594
|
const transport = new StdioServerTransport();
|