@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-ChA_XcfF.js";
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();
@@ -1,4 +1,4 @@
1
- import { CustomMcpServer, DocumentTools, EpicTools, IterationTools, ObjectiveTools, ShortcutClientWrapper, StoryTools, TeamTools, UserTools, WorkflowTools } from "./workflows-ChA_XcfF.js";
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.16.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.16.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": "^2.2.0",
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",