@shortcut/mcp 0.2.1 → 0.4.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.
Files changed (3) hide show
  1. package/README.md +30 -0
  2. package/dist/index.js +114 -2
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -51,8 +51,38 @@ See the [official Cursor docs](https://docs.cursor.com/context/model-context-pro
51
51
  }
52
52
  ```
53
53
 
54
+ ### Claude Code
55
+
56
+ See the [official Claude Code docs](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/tutorials#set-up-model-context-protocol-mcp) for more information.
57
+
58
+ _You can add a new MCP server from the Claude Code CLI. But modifying the json file directly is simpler!_
59
+
60
+ 1. Open the Claude Code configuration file (it should be in `~/.claude.json`).
61
+ 2. Find the `projects` > `mcpServers` section and add the following details and save the file:
62
+
63
+ ```json
64
+ {
65
+ "projects": {
66
+ "mcpServers": {
67
+ "shortcut": {
68
+ "command": "npx",
69
+ "args": [
70
+ "-y",
71
+ "@shortcut/mcp"
72
+ ],
73
+ "env": {
74
+ "SHORTCUT_API_TOKEN": "<YOUR_SHORTCUT_API_TOKEN>"
75
+ }
76
+ }
77
+ }
78
+ }
79
+ }
80
+ ```
81
+
54
82
  ## Issues and Troubleshooting
55
83
 
84
+ Before doing anything else, please make sure you are running the latest version!
85
+
56
86
  If you run into problems using this MCP server, you have a couple of options:
57
87
 
58
88
  - Open an issue on [GitHub](https://github.com/useshortcut/mcp-server-shortcut/issues)
package/dist/index.js CHANGED
@@ -14761,6 +14761,27 @@ class ShortcutClientWrapper {
14761
14761
  return { stories: null, total: null };
14762
14762
  return { stories, total: stories.length };
14763
14763
  }
14764
+ async createStoryComment(storyPublicId, params) {
14765
+ const response = await this.client.createStoryComment(storyPublicId, params);
14766
+ const storyComment = response?.data ?? null;
14767
+ if (!storyComment)
14768
+ throw new Error(`Failed to create the comment: ${response.status}`);
14769
+ return storyComment;
14770
+ }
14771
+ async createIteration(params) {
14772
+ const response = await this.client.createIteration(params);
14773
+ const iteration = response?.data ?? null;
14774
+ if (!iteration)
14775
+ throw new Error(`Failed to create the iteration: ${response.status}`);
14776
+ return iteration;
14777
+ }
14778
+ async createEpic(params) {
14779
+ const response = await this.client.createEpic(params);
14780
+ const epic = response?.data ?? null;
14781
+ if (!epic)
14782
+ throw new Error(`Failed to create the epic: ${response.status}`);
14783
+ return epic;
14784
+ }
14764
14785
  }
14765
14786
 
14766
14787
  // node_modules/zod/lib/index.mjs
@@ -21304,7 +21325,7 @@ var import_client = __toESM(require_lib(), 1);
21304
21325
 
21305
21326
  // package.json
21306
21327
  var name = "@shortcut/mcp";
21307
- var version = "0.2.1";
21328
+ var version = "0.4.0";
21308
21329
 
21309
21330
  // src/tools/base.ts
21310
21331
  class BaseTools {
@@ -21344,6 +21365,23 @@ var formatPullRequestList = (branches) => {
21344
21365
  var formatTaskList = (tasks) => {
21345
21366
  return formatAsUnorderedList((tasks || []).map((task) => `[${task.complete ? "X" : " "}] ${task.description}`), "Tasks");
21346
21367
  };
21368
+ var formatStats = (stats, showPoints) => {
21369
+ const { num_stories_backlog, num_stories_unstarted, num_stories_started, num_stories_done } = stats;
21370
+ const { num_points_backlog, num_points_unstarted, num_points_started, num_points_done } = stats;
21371
+ const totalCount = num_stories_backlog + num_stories_unstarted + num_stories_started + num_stories_done;
21372
+ const totalUnstarted = num_stories_backlog + num_stories_unstarted;
21373
+ const totalPoints = (num_points_backlog || 0) + (num_points_unstarted || 0) + (num_points_started || 0) + (num_points_done || 0);
21374
+ const totalUnstartedPoints = (num_points_backlog || 0) + (num_points_unstarted || 0);
21375
+ const statsString = `Stats:
21376
+ - Total stories: ${totalCount}${showPoints ? ` (${totalPoints} points)` : ""}
21377
+ - Unstarted stories: ${totalUnstarted}${showPoints ? ` (${totalUnstartedPoints} points)` : ""}
21378
+ - Stories in progress: ${num_stories_started}${showPoints ? ` (${num_points_started || 0} points)` : ""}
21379
+ - Completed stories: ${num_stories_done}${showPoints ? ` (${num_points_done || 0} points)` : ""}`;
21380
+ if (showPoints && stats.num_stories_unestimated)
21381
+ return `${statsString}
21382
+ - (${stats.num_stories_unestimated} of the stories are unestimated)`;
21383
+ return statsString;
21384
+ };
21347
21385
 
21348
21386
  // src/tools/utils/search.ts
21349
21387
  var getKey = (prop) => {
@@ -21425,6 +21463,11 @@ class EpicTools extends BaseTools {
21425
21463
  completed: date2,
21426
21464
  due: date2
21427
21465
  }, async (params) => await tools.searchEpics(params));
21466
+ server.tool("create-epic", "Create a new Shortcut epic.", {
21467
+ name: z.string().describe("The name of the epic"),
21468
+ teamId: z.string().optional().describe("The ID of the team to assign the epic to"),
21469
+ description: z.string().optional().describe("The description of the epic")
21470
+ }, async (params) => await tools.createEpic(params));
21428
21471
  return tools;
21429
21472
  }
21430
21473
  async searchEpics(params) {
@@ -21442,6 +21485,8 @@ ${formatAsUnorderedList(epics.map((epic) => `${epic.id}: ${epic.name}`))}`);
21442
21485
  const epic = await this.client.getEpic(epicPublicId);
21443
21486
  if (!epic)
21444
21487
  throw new Error(`Failed to retrieve Shortcut epic with public ID: ${epicPublicId}`);
21488
+ const currentUser = await this.client.getCurrentUser();
21489
+ const showPoints = !!currentUser?.workspace2?.estimate_scale?.length;
21445
21490
  return this.toResult(`Epic: ${epicPublicId}
21446
21491
  URL: ${epic.app_url}
21447
21492
  Name: ${epic.name}
@@ -21452,9 +21497,24 @@ Due date: ${epic.deadline ? epic.deadline : "[Not set]"}
21452
21497
  Team: ${epic.group_id ? `${epic.group_id}` : "[None]"}
21453
21498
  Objective: ${epic.milestone_id ? `${epic.milestone_id}` : "[None]"}
21454
21499
 
21500
+ ${formatStats(epic.stats, showPoints)}
21501
+
21455
21502
  Description:
21456
21503
  ${epic.description}`);
21457
21504
  }
21505
+ async createEpic({
21506
+ name: name2,
21507
+ teamId,
21508
+ description
21509
+ }) {
21510
+ if (teamId) {
21511
+ const team = await this.client.getTeam(teamId);
21512
+ if (!team)
21513
+ throw new Error(`Team with ID ${teamId} not found`);
21514
+ }
21515
+ const epic = await this.client.createEpic({ name: name2, group_id: teamId, description });
21516
+ return this.toResult(`Epic created with ID: ${epic.id}.`);
21517
+ }
21458
21518
  }
21459
21519
 
21460
21520
  // src/tools/iterations.ts
@@ -21476,6 +21536,13 @@ class IterationTools extends BaseTools {
21476
21536
  startDate: date2,
21477
21537
  endDate: date2
21478
21538
  }, async (params) => await tools.searchIterations(params));
21539
+ server.tool("create-iteration", "Create a new Shortcut iteration", {
21540
+ name: z.string().describe("The name of the iteration"),
21541
+ description: z.string().optional().describe("The description of the iteration"),
21542
+ startDate: z.string().describe("The start date of the iteration"),
21543
+ endDate: z.string().describe("The end date of the iteration"),
21544
+ teamId: z.string().optional().describe("The ID of the team to assign the iteration to")
21545
+ }, async (params) => await tools.createIteration(params));
21479
21546
  return tools;
21480
21547
  }
21481
21548
  async getIterationStories(iterationPublicId) {
@@ -21501,6 +21568,8 @@ ${formatAsUnorderedList(iterations.map((iteration) => `${iteration.id}: ${iterat
21501
21568
  const iteration = await this.client.getIteration(iterationPublicId);
21502
21569
  if (!iteration)
21503
21570
  throw new Error(`Failed to retrieve Shortcut iteration with public ID: ${iterationPublicId}.`);
21571
+ const currentUser = await this.client.getCurrentUser();
21572
+ const showPoints = !!currentUser?.workspace2?.estimate_scale?.length;
21504
21573
  return this.toResult(`Iteration: ${iterationPublicId}
21505
21574
  Url: ${iteration.app_url}
21506
21575
  Name: ${iteration.name}
@@ -21510,9 +21579,34 @@ Completed: ${iteration.status === "completed" ? "Yes" : "No"}
21510
21579
  Started: ${iteration.status === "started" ? "Yes" : "No"}
21511
21580
  Team: ${iteration.group_ids?.length ? `${iteration.group_ids.join(", ")}` : "[None]"}
21512
21581
 
21582
+ ${formatStats(iteration.stats, showPoints)}
21583
+
21513
21584
  Description:
21514
21585
  ${iteration.description}`);
21515
21586
  }
21587
+ async createIteration({
21588
+ name: name2,
21589
+ startDate,
21590
+ endDate,
21591
+ teamId,
21592
+ description
21593
+ }) {
21594
+ if (teamId) {
21595
+ const team = await this.client.getTeam(teamId);
21596
+ if (!team)
21597
+ throw new Error(`Team with ID ${teamId} not found`);
21598
+ }
21599
+ const iteration = await this.client.createIteration({
21600
+ name: name2,
21601
+ start_date: startDate,
21602
+ end_date: endDate,
21603
+ group_ids: teamId ? [teamId] : undefined,
21604
+ description
21605
+ });
21606
+ if (!iteration)
21607
+ throw new Error(`Failed to create the iteration.`);
21608
+ return this.toResult(`Iteration created with ID: ${iteration.id}.`);
21609
+ }
21516
21610
  }
21517
21611
 
21518
21612
  // src/tools/objectives.ts
@@ -21650,6 +21744,10 @@ The story will be added to the default state for the workflow.
21650
21744
  server.tool("unassign-current-user-as-owner", "Unassign the current user as the owner of a story", {
21651
21745
  storyPublicId: z.number().positive().describe("The public ID of the story")
21652
21746
  }, async ({ storyPublicId }) => await tools.unassignCurrentUserAsOwner(storyPublicId));
21747
+ server.tool("create-story-comment", "Create a comment on a story", {
21748
+ storyPublicId: z.number().positive().describe("The public ID of the story"),
21749
+ text: z.string().min(1).describe("The text of the comment")
21750
+ }, async (params) => await tools.createStoryComment(params));
21653
21751
  return tools;
21654
21752
  }
21655
21753
  async assignCurrentUserAsOwner(storyPublicId) {
@@ -21778,6 +21876,20 @@ ${comment.text || ""}`;
21778
21876
 
21779
21877
  `)}`);
21780
21878
  }
21879
+ async createStoryComment({
21880
+ storyPublicId,
21881
+ text
21882
+ }) {
21883
+ if (!storyPublicId)
21884
+ throw new Error("Story public ID is required");
21885
+ if (!text)
21886
+ throw new Error("Story comment text is required");
21887
+ const story = await this.client.getStory(storyPublicId);
21888
+ if (!story)
21889
+ throw new Error(`Failed to retrieve Shortcut story with public ID: ${storyPublicId}`);
21890
+ const storyComment = await this.client.createStoryComment(storyPublicId, { text });
21891
+ return this.toResult(`Created comment on story sc-${storyPublicId}. Comment URL: ${storyComment.app_url}.`);
21892
+ }
21781
21893
  }
21782
21894
 
21783
21895
  // src/tools/teams.ts
@@ -21869,7 +21981,7 @@ Default State: ${workflow.states.find((state) => state.id === workflow.default_s
21869
21981
 
21870
21982
  // src/server.ts
21871
21983
  var apiToken = process.env.SHORTCUT_API_TOKEN;
21872
- if (process.argv.length > 2) {
21984
+ if (process.argv.length === 3) {
21873
21985
  const [name2, token] = String(process.argv[2]).split("=");
21874
21986
  if (name2 === "SHORTCUT_API_TOKEN")
21875
21987
  apiToken = token;
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "keywords": ["shortcut", "mcp", "modelcontextprotocol"],
10
10
  "license": "MIT",
11
- "version": "0.2.1",
11
+ "version": "0.4.0",
12
12
  "type": "module",
13
13
  "main": "dist/index.js",
14
14
  "bin": {