@shortcut/mcp 0.16.0 → 0.18.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/README.md
CHANGED
|
@@ -148,6 +148,10 @@ Or you can edit the local JSON file directly:
|
|
|
148
148
|
- **stories-set-external-links** - Replace all external links on a story with a new set of links
|
|
149
149
|
- **stories-get-by-external-link** - Find all stories that contain a specific external link
|
|
150
150
|
|
|
151
|
+
### Labels
|
|
152
|
+
- **labels-list** - List all labels in the Shortcut workspace.
|
|
153
|
+
- **labels-create** - Create a new label in Shortcut.
|
|
154
|
+
|
|
151
155
|
### Epics
|
|
152
156
|
|
|
153
157
|
- **epics-get-by-id** - Get a Shortcut epic by ID
|
|
@@ -188,6 +192,9 @@ Or you can edit the local JSON file directly:
|
|
|
188
192
|
### Documents
|
|
189
193
|
|
|
190
194
|
- **documents-create** - Create a new document in Shortcut with HTML content
|
|
195
|
+
- **documents-list** - List all documents in Shortcut
|
|
196
|
+
- **documents-search** - Search for documents
|
|
197
|
+
- **documents-get-by-id** - Retrieve a specific document in markdown format by its ID
|
|
191
198
|
|
|
192
199
|
## Limit tools
|
|
193
200
|
|
|
@@ -224,6 +231,7 @@ The following values are accepted in addition to the full tool names listed abov
|
|
|
224
231
|
- `stories`
|
|
225
232
|
- `epics`
|
|
226
233
|
- `iterations`
|
|
234
|
+
- `labels`
|
|
227
235
|
- `objectives`
|
|
228
236
|
- `teams`
|
|
229
237
|
- `workflows`
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,51 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { CustomMcpServer, DocumentTools, EpicTools, IterationTools, ObjectiveTools, ShortcutClientWrapper, StoryTools, TeamTools, UserTools, WorkflowTools } from "./workflows-
|
|
2
|
+
import { BaseTools, CustomMcpServer, DocumentTools, EpicTools, IterationTools, ObjectiveTools, ShortcutClientWrapper, StoryTools, TeamTools, UserTools, WorkflowTools } from "./workflows-Dko3ibgz.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { ShortcutClient } from "@shortcut/client";
|
|
5
|
+
import { z } from "zod";
|
|
5
6
|
|
|
7
|
+
//#region src/tools/labels.ts
|
|
8
|
+
/**
|
|
9
|
+
* Tools for managing Shortcut labels.
|
|
10
|
+
*/
|
|
11
|
+
var LabelTools = class LabelTools extends BaseTools {
|
|
12
|
+
static create(client$1, server$1) {
|
|
13
|
+
const tools = new LabelTools(client$1);
|
|
14
|
+
server$1.addToolWithReadAccess("labels-list", "List all labels in the Shortcut workspace.", { includeArchived: z.boolean().optional().describe("Whether to include archived labels in the list.").default(false) }, async (params) => await tools.listLabels(params));
|
|
15
|
+
server$1.addToolWithWriteAccess("labels-create", "Create a new label in Shortcut.", {
|
|
16
|
+
name: z.string().min(1).max(128).describe("The name of the new label. Required."),
|
|
17
|
+
color: z.string().regex(/^#[a-fA-F0-9]{6}$/).optional().describe("The hex color to be displayed with the label (e.g., \"#ff0000\")."),
|
|
18
|
+
description: z.string().max(1024).optional().describe("A description of the label.")
|
|
19
|
+
}, async (params) => await tools.createLabel(params));
|
|
20
|
+
return tools;
|
|
21
|
+
}
|
|
22
|
+
formatLabel(label, { includeDescription = false, includeArchived = false } = {}) {
|
|
23
|
+
return {
|
|
24
|
+
id: label.id,
|
|
25
|
+
name: label.name,
|
|
26
|
+
app_url: label.app_url,
|
|
27
|
+
...includeDescription ? { description: label.description ?? null } : {},
|
|
28
|
+
...includeArchived ? { archived: label.archived } : {},
|
|
29
|
+
stats: Object.fromEntries(Object.entries(label.stats || {}).filter(([key, value]) => !key.match(/(unestimated|total)$/) && !!value))
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
async listLabels({ includeArchived = false }) {
|
|
33
|
+
const labels = await this.client.listLabels({ includeArchived });
|
|
34
|
+
if (!labels.length) return this.toResult("Result: No labels found.");
|
|
35
|
+
const formattedLabels = labels.map((label) => this.formatLabel(label, { includeArchived }));
|
|
36
|
+
return this.toResult(`Result (${labels.length} labels found):`, { labels: formattedLabels });
|
|
37
|
+
}
|
|
38
|
+
async createLabel({ name, color, description }) {
|
|
39
|
+
const label = await this.client.createLabel({
|
|
40
|
+
name,
|
|
41
|
+
color,
|
|
42
|
+
description
|
|
43
|
+
});
|
|
44
|
+
return this.toResult(`Label created with ID: ${label.id}.`, { label: this.formatLabel(label, { includeDescription: true }) });
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
6
49
|
//#region src/server.ts
|
|
7
50
|
let apiToken = process.env.SHORTCUT_API_TOKEN;
|
|
8
51
|
let isReadonly = process.env.SHORTCUT_READONLY === "true";
|
|
@@ -29,6 +72,7 @@ ObjectiveTools.create(client, server);
|
|
|
29
72
|
TeamTools.create(client, server);
|
|
30
73
|
WorkflowTools.create(client, server);
|
|
31
74
|
DocumentTools.create(client, server);
|
|
75
|
+
LabelTools.create(client, server);
|
|
32
76
|
async function startServer() {
|
|
33
77
|
try {
|
|
34
78
|
const transport = new StdioServerTransport();
|
package/dist/server-http.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CustomMcpServer, DocumentTools, EpicTools, IterationTools, ObjectiveTools, ShortcutClientWrapper, StoryTools, TeamTools, UserTools, WorkflowTools } from "./workflows-
|
|
1
|
+
import { CustomMcpServer, DocumentTools, EpicTools, IterationTools, ObjectiveTools, ShortcutClientWrapper, StoryTools, TeamTools, UserTools, WorkflowTools } from "./workflows-Dko3ibgz.js";
|
|
2
2
|
import { ShortcutClient } from "@shortcut/client";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
@@ -422,6 +422,40 @@ var ShortcutClientWrapper = class {
|
|
|
422
422
|
if (!doc) throw new Error(`Failed to create the document: ${response.status}`);
|
|
423
423
|
return doc;
|
|
424
424
|
}
|
|
425
|
+
async listDocs() {
|
|
426
|
+
const response = await this.client.listDocs();
|
|
427
|
+
if (response.status === 403) throw new Error("Docs feature disabled for this workspace.");
|
|
428
|
+
return response?.data ?? null;
|
|
429
|
+
}
|
|
430
|
+
async searchDocuments({ title, archived, createdByCurrentUser, followedByCurrentUser, pageSize = 25, nextPageToken }) {
|
|
431
|
+
const response = await this.client.searchDocuments({
|
|
432
|
+
title,
|
|
433
|
+
archived,
|
|
434
|
+
created_by_me: createdByCurrentUser,
|
|
435
|
+
followed_by_me: followedByCurrentUser,
|
|
436
|
+
page_size: pageSize,
|
|
437
|
+
next: nextPageToken
|
|
438
|
+
});
|
|
439
|
+
if (response.status === 403) throw new Error("Docs feature disabled for this workspace.");
|
|
440
|
+
const documents = response?.data?.data;
|
|
441
|
+
const total = response?.data?.total;
|
|
442
|
+
const next = response?.data?.next;
|
|
443
|
+
if (!documents) return {
|
|
444
|
+
documents: null,
|
|
445
|
+
total: null,
|
|
446
|
+
next_page_token: null
|
|
447
|
+
};
|
|
448
|
+
return {
|
|
449
|
+
documents,
|
|
450
|
+
total,
|
|
451
|
+
next_page_token: this.getNextPageToken(next)
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
async getDocById(docId) {
|
|
455
|
+
const response = await this.client.getDoc(docId);
|
|
456
|
+
if (response.status === 403) throw new Error("Docs feature disabled for this workspace.");
|
|
457
|
+
return response?.data ?? null;
|
|
458
|
+
}
|
|
425
459
|
async uploadFile(storyId, filePath) {
|
|
426
460
|
const fileContent = readFileSync(filePath);
|
|
427
461
|
const fileName = basename(filePath);
|
|
@@ -442,12 +476,24 @@ var ShortcutClientWrapper = class {
|
|
|
442
476
|
await this.loadCustomFields();
|
|
443
477
|
return Array.from(this.customFieldCache.values());
|
|
444
478
|
}
|
|
479
|
+
async listLabels({ includeArchived = false }) {
|
|
480
|
+
const response = await this.client.listLabels({ slim: false });
|
|
481
|
+
const allLabels = response?.data ?? [];
|
|
482
|
+
if (includeArchived) return allLabels;
|
|
483
|
+
return allLabels.filter((label) => !label.archived);
|
|
484
|
+
}
|
|
485
|
+
async createLabel(params) {
|
|
486
|
+
const response = await this.client.createLabel(params);
|
|
487
|
+
const label = response?.data ?? null;
|
|
488
|
+
if (!label) throw new Error(`Failed to create the label: ${response.status}`);
|
|
489
|
+
return label;
|
|
490
|
+
}
|
|
445
491
|
};
|
|
446
492
|
|
|
447
493
|
//#endregion
|
|
448
494
|
//#region package.json
|
|
449
495
|
var name = "@shortcut/mcp";
|
|
450
|
-
var version = "0.
|
|
496
|
+
var version = "0.18.0";
|
|
451
497
|
|
|
452
498
|
//#endregion
|
|
453
499
|
//#region src/mcp/CustomMcpServer.ts
|
|
@@ -827,6 +873,20 @@ var DocumentTools = class DocumentTools extends BaseTools {
|
|
|
827
873
|
title: z.string().max(256).describe("The title for the new document (max 256 characters)"),
|
|
828
874
|
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>)")
|
|
829
875
|
}, async ({ title, content }) => await tools.createDocument(title, content));
|
|
876
|
+
server.addToolWithReadAccess("documents-list", "List all documents in Shortcut.", async () => await tools.listDocuments());
|
|
877
|
+
server.addToolWithReadAccess("documents-search", "Find documents.", {
|
|
878
|
+
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."),
|
|
879
|
+
title: z.string().describe("Find documents matching the specified name"),
|
|
880
|
+
archived: z.boolean().optional().describe("Find only documents matching the specified archived status"),
|
|
881
|
+
createdByCurrentUser: z.boolean().optional().describe("Find only documents created by current user"),
|
|
882
|
+
followedByCurrentUser: z.boolean().optional().describe("Find only documents followed by current user")
|
|
883
|
+
}, async ({ nextPageToken, title, archived, createdByCurrentUser, followedByCurrentUser }) => await tools.searchDocuments({
|
|
884
|
+
title,
|
|
885
|
+
archived,
|
|
886
|
+
createdByCurrentUser,
|
|
887
|
+
followedByCurrentUser
|
|
888
|
+
}, nextPageToken));
|
|
889
|
+
server.addToolWithReadAccess("documents-get-by-id", "Get a document as markdown by its ID", { docId: z.string().describe("The ID of the document to retrieve") }, async ({ docId }) => await tools.getDocumentById(docId));
|
|
830
890
|
return tools;
|
|
831
891
|
}
|
|
832
892
|
async createDocument(title, content) {
|
|
@@ -845,6 +905,40 @@ var DocumentTools = class DocumentTools extends BaseTools {
|
|
|
845
905
|
return this.toResult(`Failed to create document: ${errorMessage}`);
|
|
846
906
|
}
|
|
847
907
|
}
|
|
908
|
+
async listDocuments() {
|
|
909
|
+
try {
|
|
910
|
+
const docs = await this.client.listDocs();
|
|
911
|
+
if (!docs?.length) return this.toResult("No documents were found.");
|
|
912
|
+
return this.toResult(`Found ${docs.length} documents.`, docs);
|
|
913
|
+
} catch (error) {
|
|
914
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
915
|
+
return this.toResult(`Failed to list documents: ${errorMessage}`);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
async searchDocuments(params, nextPageToken) {
|
|
919
|
+
try {
|
|
920
|
+
const { documents, total, next_page_token } = await this.client.searchDocuments({
|
|
921
|
+
...params,
|
|
922
|
+
nextPageToken
|
|
923
|
+
});
|
|
924
|
+
if (!documents) throw new Error(`Failed to search for document matching your query.`);
|
|
925
|
+
if (!documents.length) return this.toResult(`Result: No documents found.`);
|
|
926
|
+
return this.toResult(`Result (${documents.length} shown of ${total} total documents found):`, documents, next_page_token);
|
|
927
|
+
} catch (error) {
|
|
928
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
929
|
+
return this.toResult(`Failed to search documents: ${errorMessage}`);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
async getDocumentById(docId) {
|
|
933
|
+
try {
|
|
934
|
+
const doc = await this.client.getDocById(docId);
|
|
935
|
+
if (!doc) return this.toResult(`Document with ID ${docId} not found.`);
|
|
936
|
+
return this.toResult(`Document with ID ${docId}`, doc);
|
|
937
|
+
} catch (error) {
|
|
938
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
939
|
+
return this.toResult(`Failed to get document: ${errorMessage}`);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
848
942
|
};
|
|
849
943
|
|
|
850
944
|
//#endregion
|
|
@@ -1606,4 +1700,4 @@ var WorkflowTools = class WorkflowTools extends BaseTools {
|
|
|
1606
1700
|
};
|
|
1607
1701
|
|
|
1608
1702
|
//#endregion
|
|
1609
|
-
export { CustomMcpServer, DocumentTools, EpicTools, IterationTools, ObjectiveTools, ShortcutClientWrapper, StoryTools, TeamTools, UserTools, WorkflowTools };
|
|
1703
|
+
export { BaseTools, CustomMcpServer, DocumentTools, EpicTools, IterationTools, ObjectiveTools, ShortcutClientWrapper, StoryTools, TeamTools, UserTools, WorkflowTools };
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"modelcontextprotocol"
|
|
13
13
|
],
|
|
14
14
|
"license": "MIT",
|
|
15
|
-
"version": "0.
|
|
15
|
+
"version": "0.18.0",
|
|
16
16
|
"type": "module",
|
|
17
17
|
"main": "dist/index.js",
|
|
18
18
|
"bin": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
38
|
-
"@shortcut/client": "^
|
|
38
|
+
"@shortcut/client": "^3.1.0",
|
|
39
39
|
"express": "^4.18.2",
|
|
40
40
|
"pino": "^9.5.0",
|
|
41
41
|
"pino-http": "^10.3.0",
|