@shortcut/mcp 0.19.0 → 0.20.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
|
@@ -14,12 +14,70 @@ The MCP server for [Shortcut](https://shortcut.com).
|
|
|
14
14
|
|
|
15
15
|
## Usage
|
|
16
16
|
|
|
17
|
+
The only required input is your Shortcut API token. You can find it in your [Shortcut account settings](https://app.shortcut.com/settings/account/api-tokens).
|
|
18
|
+
|
|
19
|
+
Once you have a valid token, you can pass it to the MCP server as an environement variable or a CLI argument.
|
|
20
|
+
|
|
21
|
+
Examples:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
"shortcut": {
|
|
25
|
+
"command": "npx",
|
|
26
|
+
"args": [
|
|
27
|
+
"-y",
|
|
28
|
+
"@shortcut/mcp@latest"
|
|
29
|
+
],
|
|
30
|
+
"env": {
|
|
31
|
+
"SHORTCUT_API_TOKEN": "<YOUR_SHORTCUT_API_TOKEN>"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
"shortcut": {
|
|
38
|
+
"command": "npx",
|
|
39
|
+
"args": [
|
|
40
|
+
"-y",
|
|
41
|
+
"@shortcut/mcp@latest",
|
|
42
|
+
"SHORTCUT_API_TOKEN=<YOUR_SHORTCUT_API_TOKEN>"
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Due to an issue in `gemini-cli` that redacts environment variables that contain the word "token", you can also use the alternative name `SHORTCUT_API_TKN` instead of `SHORTCUT_API_TOKEN`. This works for both the environment variable and the CLI argument:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
"shortcut": {
|
|
51
|
+
"command": "npx",
|
|
52
|
+
"args": [
|
|
53
|
+
"-y",
|
|
54
|
+
"@shortcut/mcp@latest"
|
|
55
|
+
],
|
|
56
|
+
"env": {
|
|
57
|
+
"SHORTCUT_API_TKN": "<YOUR_SHORTCUT_API_TOKEN>"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
"shortcut": {
|
|
64
|
+
"command": "npx",
|
|
65
|
+
"args": [
|
|
66
|
+
"-y",
|
|
67
|
+
"@shortcut/mcp@latest",
|
|
68
|
+
"SHORTCUT_API_TKN=<YOUR_SHORTCUT_API_TOKEN>"
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
For more information on how to setup the MCP for your tool of choice, see below.
|
|
74
|
+
|
|
17
75
|
### Windsurf
|
|
18
76
|
|
|
19
77
|
See the [official Windsurf docs](https://docs.windsurf.com/windsurf/cascade/mcp) for more information.
|
|
20
78
|
|
|
21
|
-
1. Open the `Windsurf
|
|
22
|
-
2. Click `Add
|
|
79
|
+
1. Open the MCP configuration by clicking the `MCPs` icon in the Cascade panel, or navigate to `Windsurf Settings` > `Cascade` > `MCP Servers`.
|
|
80
|
+
2. Click `Add Custom Server` to edit the raw `mcp_config.json` file (located at `~/.codeium/windsurf/mcp_config.json`).
|
|
23
81
|
3. Add the following details and save the file:
|
|
24
82
|
|
|
25
83
|
```json
|
|
@@ -66,35 +124,27 @@ See the [official Cursor docs](https://docs.cursor.com/context/model-context-pro
|
|
|
66
124
|
|
|
67
125
|
### Claude Code
|
|
68
126
|
|
|
69
|
-
See the [official Claude Code docs](https://docs.anthropic.com/en/docs/
|
|
70
|
-
|
|
71
|
-
_You can add a new MCP server from the Claude Code CLI. But modifying the json file directly is simpler!_
|
|
127
|
+
See the [official Claude Code docs](https://docs.anthropic.com/en/docs/claude-code/mcp) for more information.
|
|
72
128
|
|
|
73
|
-
|
|
129
|
+
Add the MCP server from the command line:
|
|
74
130
|
|
|
75
131
|
```shell
|
|
76
|
-
|
|
77
|
-
claude mcp add shortcut --transport=stdio -e SHORTCUT_API_TOKEN=$SHORTCUT_API_TOKEN -- npx -y @shortcut/mcp@latest
|
|
132
|
+
claude mcp add shortcut --transport stdio -e SHORTCUT_API_TOKEN=$SHORTCUT_API_TOKEN -- npx -y @shortcut/mcp@latest
|
|
78
133
|
```
|
|
79
134
|
|
|
80
|
-
Or you can
|
|
81
|
-
|
|
82
|
-
1. Open the Claude Code configuration file (it should be in `~/.claude.json`).
|
|
83
|
-
2. Find the `projects` > `mcpServers` section and add the following details and save the file:
|
|
135
|
+
Or you can create a `.mcp.json` file in your project root to share with your team:
|
|
84
136
|
|
|
85
137
|
```json
|
|
86
138
|
{
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
|
|
91
|
-
"
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
"
|
|
96
|
-
"SHORTCUT_API_TOKEN": "<YOUR_SHORTCUT_API_TOKEN>"
|
|
97
|
-
}
|
|
139
|
+
"mcpServers": {
|
|
140
|
+
"shortcut": {
|
|
141
|
+
"command": "npx",
|
|
142
|
+
"args": [
|
|
143
|
+
"-y",
|
|
144
|
+
"@shortcut/mcp@latest"
|
|
145
|
+
],
|
|
146
|
+
"env": {
|
|
147
|
+
"SHORTCUT_API_TOKEN": "<YOUR_SHORTCUT_API_TOKEN>"
|
|
98
148
|
}
|
|
99
149
|
}
|
|
100
150
|
}
|
|
@@ -102,26 +152,72 @@ Or you can edit the local JSON file directly:
|
|
|
102
152
|
```
|
|
103
153
|
|
|
104
154
|
### Zed
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
155
|
+
|
|
156
|
+
See the [official Zed MCP docs](https://zed.dev/docs/ai/mcp) for more information.
|
|
157
|
+
|
|
158
|
+
1. Open your `settings.json` file. Instructions [here](https://zed.dev/docs/configuring-zed#settings-files).
|
|
159
|
+
2. Add the following to the `context_servers` section and save the file:
|
|
108
160
|
|
|
109
161
|
```json
|
|
162
|
+
{
|
|
110
163
|
"context_servers": {
|
|
111
164
|
"shortcut": {
|
|
112
|
-
"
|
|
113
|
-
"
|
|
114
|
-
"
|
|
115
|
-
"
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
165
|
+
"command": "npx",
|
|
166
|
+
"args": [
|
|
167
|
+
"-y",
|
|
168
|
+
"@shortcut/mcp@latest"
|
|
169
|
+
],
|
|
170
|
+
"env": {
|
|
171
|
+
"SHORTCUT_API_TOKEN": "<YOUR_SHORTCUT_API_TOKEN>"
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### VS Code
|
|
179
|
+
|
|
180
|
+
See the [official VS Code MCP docs](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) for more information.
|
|
181
|
+
|
|
182
|
+
1. Create (or open) the `.vscode/mcp.json` file in your workspace.
|
|
183
|
+
2. Add the following details and save the file:
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
{
|
|
187
|
+
"servers": {
|
|
188
|
+
"shortcut": {
|
|
189
|
+
"command": "npx",
|
|
190
|
+
"args": [
|
|
191
|
+
"-y",
|
|
192
|
+
"@shortcut/mcp@latest"
|
|
193
|
+
],
|
|
194
|
+
"env": {
|
|
195
|
+
"SHORTCUT_API_TOKEN": "<YOUR_SHORTCUT_API_TOKEN>"
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### OpenCode
|
|
203
|
+
|
|
204
|
+
See the [official OpenCode MCP docs](https://opencode.ai/docs/mcp-servers/) for more information.
|
|
205
|
+
|
|
206
|
+
Add the following to your `opencode.json` configuration file:
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"$schema": "https://opencode.ai/config.json",
|
|
211
|
+
"mcp": {
|
|
212
|
+
"shortcut": {
|
|
213
|
+
"type": "local",
|
|
214
|
+
"command": ["npx", "-y", "@shortcut/mcp@latest"],
|
|
215
|
+
"environment": {
|
|
216
|
+
"SHORTCUT_API_TOKEN": "<YOUR_SHORTCUT_API_TOKEN>"
|
|
122
217
|
}
|
|
123
218
|
}
|
|
124
219
|
}
|
|
220
|
+
}
|
|
125
221
|
```
|
|
126
222
|
|
|
127
223
|
## Available Tools
|
|
@@ -191,7 +287,7 @@ Or you can edit the local JSON file directly:
|
|
|
191
287
|
|
|
192
288
|
### Documents
|
|
193
289
|
|
|
194
|
-
- **documents-create** - Create a new document in Shortcut with
|
|
290
|
+
- **documents-create** - Create a new document in Shortcut with Markdown content
|
|
195
291
|
- **documents-update** - Update content of an existing document by its ID
|
|
196
292
|
- **documents-list** - List all documents in Shortcut
|
|
197
293
|
- **documents-search** - Search for documents
|
|
@@ -204,7 +300,8 @@ You can limit the tools available to the LLM by setting the `SHORTCUT_TOOLS` env
|
|
|
204
300
|
- Tools can be limited by entity type by just adding the entity, eg `stories` or `epics`.
|
|
205
301
|
- Individual tools can also be limitied by their full name, eg `stories-get-by-id` or `epics-search`.
|
|
206
302
|
|
|
207
|
-
|
|
303
|
+
> [!NOTE]
|
|
304
|
+
> By default, all tools are enabled.
|
|
208
305
|
|
|
209
306
|
Example:
|
|
210
307
|
|
|
@@ -242,7 +339,8 @@ The following values are accepted in addition to the full tool names listed abov
|
|
|
242
339
|
|
|
243
340
|
You can run the MCP server in read-only mode by setting the `SHORTCUT_READONLY` environment variable to `true`. This will disable all tools that modify data in Shortcut.
|
|
244
341
|
|
|
245
|
-
|
|
342
|
+
> [!TIP]
|
|
343
|
+
> Shortcut supports **read-only API tokens**, which you can use to ensure that the MCP server is limited to read-only operations at the API level. This provides an additional layer of security since the restriction is enforced by the Shortcut API itself, not just the MCP server. You can create a read-only token from your [Shortcut API tokens settings](https://app.shortcut.com/settings/account/api-tokens).
|
|
246
344
|
|
|
247
345
|
Example:
|
|
248
346
|
|
|
@@ -266,7 +364,8 @@ Example:
|
|
|
266
364
|
|
|
267
365
|
## Issues and Troubleshooting
|
|
268
366
|
|
|
269
|
-
|
|
367
|
+
> [!IMPORTANT]
|
|
368
|
+
> Before doing anything else, please make sure you are running the latest version!
|
|
270
369
|
|
|
271
370
|
If you run into problems using this MCP server, you have a couple of options:
|
|
272
371
|
|
|
@@ -277,6 +376,10 @@ You can also check the list of [common issues](#common-issues) below to see if t
|
|
|
277
376
|
|
|
278
377
|
### Common Issues and Solutions
|
|
279
378
|
|
|
379
|
+
#### MCP fails on startup in Gemini CLI
|
|
380
|
+
|
|
381
|
+
If you are using the Gemini CLI and the MCP fails with the following error: `✕ Error during discovery for MCP server 'shortcut': MCP error -32000: Connection closed`, it might be due to an issue in Gemini where it redacts environment variables that contain the word `token`. You can either pass the Shortcut token as a CLI argument, or use the alternative name `SHORTCUT_API_TKN` instead of `SHORTCUT_API_TOKEN`. See the [Usage section](#usage) for more information.
|
|
382
|
+
|
|
280
383
|
#### NPX command not working when using MISE for version management
|
|
281
384
|
|
|
282
385
|
If you are using MISE for managing Node and NPM versions, you may encounter a "Client closed" error when trying to run the MCP server. Installing this extension into your IDE might help: https://github.com/hverlin/mise-vscode/.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as ObjectiveTools, c as DocumentTools, d as ShortcutClientWrapper, i as StoryTools, l as BaseTools, n as UserTools, o as IterationTools, r as TeamTools, s as EpicTools, t as WorkflowTools, u as CustomMcpServer } from "./workflows-
|
|
2
|
+
import { a as ObjectiveTools, c as DocumentTools, d as ShortcutClientWrapper, i as StoryTools, l as BaseTools, n as UserTools, o as IterationTools, r as TeamTools, s as EpicTools, t as WorkflowTools, u as CustomMcpServer } from "./workflows-GwezThPA.mjs";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { ShortcutClient } from "@shortcut/client";
|
|
5
5
|
import { z } from "zod";
|
|
@@ -9,10 +9,10 @@ import { z } from "zod";
|
|
|
9
9
|
* Tools for managing Shortcut labels.
|
|
10
10
|
*/
|
|
11
11
|
var LabelTools = class LabelTools extends BaseTools {
|
|
12
|
-
static create(client
|
|
13
|
-
const tools = new LabelTools(client
|
|
14
|
-
server
|
|
15
|
-
server
|
|
12
|
+
static create(client, server) {
|
|
13
|
+
const tools = new LabelTools(client);
|
|
14
|
+
server.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.addToolWithWriteAccess("labels-create", "Create a new label in Shortcut.", {
|
|
16
16
|
name: z.string().min(1).max(128).describe("The name of the new label. Required."),
|
|
17
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
18
|
description: z.string().max(1024).optional().describe("A description of the label.")
|
|
@@ -47,16 +47,17 @@ var LabelTools = class LabelTools extends BaseTools {
|
|
|
47
47
|
|
|
48
48
|
//#endregion
|
|
49
49
|
//#region src/server.ts
|
|
50
|
-
let apiToken = process.env.SHORTCUT_API_TOKEN;
|
|
50
|
+
let apiToken = process.env.SHORTCUT_API_TKN || process.env.SHORTCUT_API_TOKEN;
|
|
51
51
|
let isReadonly = process.env.SHORTCUT_READONLY === "true";
|
|
52
52
|
let enabledTools = (process.env.SHORTCUT_TOOLS || "").split(",").map((tool) => tool.trim()).filter(Boolean);
|
|
53
53
|
if (process.argv.length >= 3) process.argv.slice(2).map((arg) => arg.split("=")).forEach(([name, value]) => {
|
|
54
|
+
if (name === "SHORTCUT_API_TKN") apiToken = value;
|
|
54
55
|
if (name === "SHORTCUT_API_TOKEN") apiToken = value;
|
|
55
56
|
if (name === "SHORTCUT_READONLY") isReadonly = value === "true";
|
|
56
57
|
if (name === "SHORTCUT_TOOLS") enabledTools = value.split(",").map((tool) => tool.trim()).filter(Boolean);
|
|
57
58
|
});
|
|
58
59
|
if (!apiToken) {
|
|
59
|
-
console.error("
|
|
60
|
+
console.error("A Shortcut api token is required.");
|
|
60
61
|
process.exit(1);
|
|
61
62
|
}
|
|
62
63
|
const server = new CustomMcpServer({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ObjectiveTools, c as DocumentTools, d as ShortcutClientWrapper, i as StoryTools, n as UserTools, o as IterationTools, r as TeamTools, s as EpicTools, t as WorkflowTools, u as CustomMcpServer } from "./workflows-
|
|
1
|
+
import { a as ObjectiveTools, c as DocumentTools, d as ShortcutClientWrapper, i as StoryTools, n as UserTools, o as IterationTools, r as TeamTools, s as EpicTools, t as WorkflowTools, u as CustomMcpServer } from "./workflows-GwezThPA.mjs";
|
|
2
2
|
import { ShortcutClient } from "@shortcut/client";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
@@ -95,23 +95,23 @@ var ShortcutClientWrapper = class {
|
|
|
95
95
|
}
|
|
96
96
|
async getCurrentUser() {
|
|
97
97
|
if (this.currentUser) return this.currentUser;
|
|
98
|
-
const user
|
|
99
|
-
if (!user
|
|
100
|
-
this.currentUser = user
|
|
101
|
-
return user
|
|
98
|
+
const user = (await this.client.getCurrentMemberInfo())?.data;
|
|
99
|
+
if (!user) return null;
|
|
100
|
+
this.currentUser = user;
|
|
101
|
+
return user;
|
|
102
102
|
}
|
|
103
103
|
async getUser(userId) {
|
|
104
|
-
const user
|
|
105
|
-
if (!user
|
|
106
|
-
return user
|
|
104
|
+
const user = (await this.client.getMember(userId, {}))?.data;
|
|
105
|
+
if (!user) return null;
|
|
106
|
+
return user;
|
|
107
107
|
}
|
|
108
108
|
async getUserMap(userIds) {
|
|
109
109
|
await this.loadMembers();
|
|
110
|
-
return new Map(userIds.map((id) => [id, this.userCache.get(id)]).filter((user
|
|
110
|
+
return new Map(userIds.map((id) => [id, this.userCache.get(id)]).filter((user) => user[1] !== null));
|
|
111
111
|
}
|
|
112
112
|
async getUsers(userIds) {
|
|
113
113
|
await this.loadMembers();
|
|
114
|
-
return userIds.map((id) => this.userCache.get(id)).filter((user
|
|
114
|
+
return userIds.map((id) => this.userCache.get(id)).filter((user) => user !== null);
|
|
115
115
|
}
|
|
116
116
|
async listMembers() {
|
|
117
117
|
await this.loadMembers();
|
|
@@ -477,7 +477,7 @@ var ShortcutClientWrapper = class {
|
|
|
477
477
|
//#endregion
|
|
478
478
|
//#region package.json
|
|
479
479
|
var name = "@shortcut/mcp";
|
|
480
|
-
var version = "0.
|
|
480
|
+
var version = "0.20.0";
|
|
481
481
|
|
|
482
482
|
//#endregion
|
|
483
483
|
//#region src/mcp/CustomMcpServer.ts
|
|
@@ -492,10 +492,10 @@ var CustomMcpServer = class extends McpServer {
|
|
|
492
492
|
this.readonly = readonly;
|
|
493
493
|
this.tools = new Set(tools || []);
|
|
494
494
|
}
|
|
495
|
-
shouldAddTool(name
|
|
495
|
+
shouldAddTool(name) {
|
|
496
496
|
if (!this.tools.size) return true;
|
|
497
|
-
const [entityType] = name
|
|
498
|
-
if (this.tools.has(entityType) || this.tools.has(name
|
|
497
|
+
const [entityType] = name.split("-");
|
|
498
|
+
if (this.tools.has(entityType) || this.tools.has(name)) return true;
|
|
499
499
|
return false;
|
|
500
500
|
}
|
|
501
501
|
addToolWithWriteAccess(...args) {
|
|
@@ -552,10 +552,10 @@ var BaseTools = class {
|
|
|
552
552
|
}
|
|
553
553
|
getSimplifiedMember(entity) {
|
|
554
554
|
if (!entity) return null;
|
|
555
|
-
const { id, disabled, role, profile: { is_owner, name
|
|
555
|
+
const { id, disabled, role, profile: { is_owner, name, email_address, mention_name } } = entity;
|
|
556
556
|
return {
|
|
557
557
|
id,
|
|
558
|
-
name
|
|
558
|
+
name,
|
|
559
559
|
email_address,
|
|
560
560
|
mention_name,
|
|
561
561
|
role,
|
|
@@ -565,13 +565,13 @@ var BaseTools = class {
|
|
|
565
565
|
}
|
|
566
566
|
getSimplifiedStory(entity, kind) {
|
|
567
567
|
if (!entity) return null;
|
|
568
|
-
const { id, name
|
|
568
|
+
const { id, name, app_url, archived, group_id, epic_id, iteration_id, workflow_id, workflow_state_id, owner_ids, requested_by_id, estimate, labels, comments, description, external_links, story_links, pull_requests, formatted_vcs_branch_name, branches, parent_story_id, sub_task_story_ids, tasks, custom_fields, blocked, blocker } = entity;
|
|
569
569
|
const additionalFields = {};
|
|
570
570
|
if (kind === "simple") {
|
|
571
571
|
additionalFields.description = description;
|
|
572
572
|
additionalFields.estimate = estimate ?? null;
|
|
573
|
-
additionalFields.comments = (comments || []).filter((c) => !c.deleted).map(({ id
|
|
574
|
-
id
|
|
573
|
+
additionalFields.comments = (comments || []).filter((c) => !c.deleted).map(({ id, author_id, text }) => ({
|
|
574
|
+
id,
|
|
575
575
|
author_id: author_id ?? null,
|
|
576
576
|
text: text ?? ""
|
|
577
577
|
}));
|
|
@@ -592,7 +592,7 @@ var BaseTools = class {
|
|
|
592
592
|
}
|
|
593
593
|
return {
|
|
594
594
|
id,
|
|
595
|
-
name
|
|
595
|
+
name,
|
|
596
596
|
app_url,
|
|
597
597
|
archived,
|
|
598
598
|
team_id: group_id || null,
|
|
@@ -609,10 +609,10 @@ var BaseTools = class {
|
|
|
609
609
|
}
|
|
610
610
|
getSimplifiedWorkflow(entity) {
|
|
611
611
|
if (!entity) return null;
|
|
612
|
-
const { id, name
|
|
612
|
+
const { id, name, states } = entity;
|
|
613
613
|
return {
|
|
614
614
|
id,
|
|
615
|
-
name
|
|
615
|
+
name,
|
|
616
616
|
states: states.map((state) => ({
|
|
617
617
|
id: state.id,
|
|
618
618
|
name: state.name,
|
|
@@ -620,26 +620,30 @@ var BaseTools = class {
|
|
|
620
620
|
}))
|
|
621
621
|
};
|
|
622
622
|
}
|
|
623
|
-
getSimplifiedTeam(entity) {
|
|
623
|
+
getSimplifiedTeam(entity, kind) {
|
|
624
624
|
if (!entity) return null;
|
|
625
|
-
const { archived, id, name
|
|
625
|
+
const { archived, id, name, mention_name, member_ids, workflow_ids, default_workflow_id } = entity;
|
|
626
|
+
const additionalFields = {};
|
|
627
|
+
if (kind === "simple") {
|
|
628
|
+
additionalFields.member_ids = member_ids;
|
|
629
|
+
additionalFields.workflow_ids = workflow_ids;
|
|
630
|
+
}
|
|
626
631
|
return {
|
|
627
632
|
id,
|
|
628
|
-
name
|
|
629
|
-
archived,
|
|
633
|
+
name,
|
|
630
634
|
mention_name,
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
635
|
+
archived,
|
|
636
|
+
default_workflow_id: default_workflow_id ?? null,
|
|
637
|
+
...additionalFields
|
|
634
638
|
};
|
|
635
639
|
}
|
|
636
640
|
getSimplifiedObjective(entity) {
|
|
637
641
|
if (!entity) return null;
|
|
638
|
-
const { app_url, id, name
|
|
642
|
+
const { app_url, id, name, archived, state, categories } = entity;
|
|
639
643
|
return {
|
|
640
644
|
app_url,
|
|
641
645
|
id,
|
|
642
|
-
name
|
|
646
|
+
name,
|
|
643
647
|
archived,
|
|
644
648
|
state,
|
|
645
649
|
categories: categories.map((cat) => cat.name)
|
|
@@ -647,11 +651,11 @@ var BaseTools = class {
|
|
|
647
651
|
}
|
|
648
652
|
getSimplifiedEpic(entity, kind) {
|
|
649
653
|
if (!entity) return null;
|
|
650
|
-
const { id, name
|
|
654
|
+
const { id, name, app_url, archived, group_id, state, milestone_id, comments, description, deadline, owner_ids } = entity;
|
|
651
655
|
const additionalFields = {};
|
|
652
656
|
if (kind === "simple") {
|
|
653
|
-
additionalFields.comments = (comments || []).filter((comment) => !comment.deleted).map(({ id
|
|
654
|
-
id
|
|
657
|
+
additionalFields.comments = (comments || []).filter((comment) => !comment.deleted).map(({ id, author_id, text }) => ({
|
|
658
|
+
id,
|
|
655
659
|
author_id,
|
|
656
660
|
text
|
|
657
661
|
}));
|
|
@@ -660,7 +664,7 @@ var BaseTools = class {
|
|
|
660
664
|
}
|
|
661
665
|
return {
|
|
662
666
|
id,
|
|
663
|
-
name
|
|
667
|
+
name,
|
|
664
668
|
app_url,
|
|
665
669
|
archived,
|
|
666
670
|
state,
|
|
@@ -672,10 +676,10 @@ var BaseTools = class {
|
|
|
672
676
|
}
|
|
673
677
|
getSimplifiedIteration(entity) {
|
|
674
678
|
if (!entity) return null;
|
|
675
|
-
const { id, name
|
|
679
|
+
const { id, name, app_url, group_ids, status, start_date, end_date } = entity;
|
|
676
680
|
return {
|
|
677
681
|
id,
|
|
678
|
-
name
|
|
682
|
+
name,
|
|
679
683
|
app_url,
|
|
680
684
|
team_ids: group_ids,
|
|
681
685
|
status,
|
|
@@ -683,19 +687,35 @@ var BaseTools = class {
|
|
|
683
687
|
end_date
|
|
684
688
|
};
|
|
685
689
|
}
|
|
686
|
-
async getRelatedEntitiesForTeam(entity) {
|
|
690
|
+
async getRelatedEntitiesForTeam(entity, kind) {
|
|
687
691
|
if (!entity) return {
|
|
688
692
|
users: {},
|
|
689
693
|
workflows: {}
|
|
690
694
|
};
|
|
691
|
-
const { member_ids, workflow_ids } = entity;
|
|
695
|
+
const { member_ids, workflow_ids, default_workflow_id } = entity;
|
|
696
|
+
if (kind === "list") {
|
|
697
|
+
if (!default_workflow_id) return {
|
|
698
|
+
users: {},
|
|
699
|
+
workflows: {}
|
|
700
|
+
};
|
|
701
|
+
const workflows = await this.client.getWorkflowMap([default_workflow_id]);
|
|
702
|
+
const simplifiedWorkflow = this.getSimplifiedWorkflow(workflows.get(default_workflow_id));
|
|
703
|
+
if (!simplifiedWorkflow) return {
|
|
704
|
+
users: {},
|
|
705
|
+
workflows: {}
|
|
706
|
+
};
|
|
707
|
+
return {
|
|
708
|
+
users: {},
|
|
709
|
+
workflows: simplifiedWorkflow ? { [default_workflow_id]: simplifiedWorkflow } : {}
|
|
710
|
+
};
|
|
711
|
+
}
|
|
692
712
|
const [users, workflows] = await Promise.all([this.client.getUserMap(member_ids), this.client.getWorkflowMap(workflow_ids)]);
|
|
693
713
|
return {
|
|
694
714
|
users: Object.fromEntries(member_ids.map((id) => this.getSimplifiedMember(users.get(id))).filter((member) => member !== null).map((member) => [member.id, member])),
|
|
695
715
|
workflows: Object.fromEntries(workflow_ids.map((id) => this.getSimplifiedWorkflow(workflows.get(id))).filter((workflow) => workflow !== null).map((workflow) => [workflow.id, workflow]))
|
|
696
716
|
};
|
|
697
717
|
}
|
|
698
|
-
async getRelatedEntitiesForIteration(entity) {
|
|
718
|
+
async getRelatedEntitiesForIteration(entity, kind) {
|
|
699
719
|
if (!entity) return {
|
|
700
720
|
teams: {},
|
|
701
721
|
users: {},
|
|
@@ -703,15 +723,15 @@ var BaseTools = class {
|
|
|
703
723
|
};
|
|
704
724
|
const { group_ids } = entity;
|
|
705
725
|
const teams = await this.client.getTeamMap(group_ids || []);
|
|
706
|
-
const relatedEntitiesForTeams = await Promise.all(Array.from(teams.values()).map((team) => this.getRelatedEntitiesForTeam(team)));
|
|
726
|
+
const relatedEntitiesForTeams = await Promise.all(Array.from(teams.values()).map((team) => this.getRelatedEntitiesForTeam(team, kind)));
|
|
707
727
|
const { users, workflows } = this.mergeRelatedEntities(relatedEntitiesForTeams);
|
|
708
728
|
return {
|
|
709
|
-
teams: Object.fromEntries([...teams.entries()].map(([id, team]) => [id, this.getSimplifiedTeam(team)]).filter(([_, team]) => !!team)),
|
|
729
|
+
teams: Object.fromEntries([...teams.entries()].map(([id, team]) => [id, this.getSimplifiedTeam(team, kind)]).filter(([_, team]) => !!team)),
|
|
710
730
|
users,
|
|
711
731
|
workflows
|
|
712
732
|
};
|
|
713
733
|
}
|
|
714
|
-
async getRelatedEntitiesForEpic(entity) {
|
|
734
|
+
async getRelatedEntitiesForEpic(entity, kind) {
|
|
715
735
|
if (!entity) return {
|
|
716
736
|
users: {},
|
|
717
737
|
workflows: {},
|
|
@@ -729,9 +749,9 @@ var BaseTools = class {
|
|
|
729
749
|
this.client.getTeamMap(group_id ? [group_id] : []),
|
|
730
750
|
milestone_id ? this.client.getMilestone(milestone_id) : null
|
|
731
751
|
]);
|
|
732
|
-
const usersForEpic = Object.fromEntries([...usersForEpicMap.entries()].filter(([_, user
|
|
733
|
-
const team = this.getSimplifiedTeam(teams.get(group_id || ""));
|
|
734
|
-
const { users, workflows } = await this.getRelatedEntitiesForTeam(teams.get(group_id || ""));
|
|
752
|
+
const usersForEpic = Object.fromEntries([...usersForEpicMap.entries()].filter(([_, user]) => !!user).map(([id, user]) => [id, this.getSimplifiedMember(user)]));
|
|
753
|
+
const team = this.getSimplifiedTeam(teams.get(group_id || ""), kind);
|
|
754
|
+
const { users, workflows } = await this.getRelatedEntitiesForTeam(teams.get(group_id || ""), kind);
|
|
735
755
|
const milestone = this.getSimplifiedObjective(fullMilestone);
|
|
736
756
|
return {
|
|
737
757
|
users: this.mergeRelatedEntities([usersForEpic, users]),
|
|
@@ -754,14 +774,14 @@ var BaseTools = class {
|
|
|
754
774
|
iteration_id ? this.client.getIteration(iteration_id) : null,
|
|
755
775
|
epic_id ? this.client.getEpic(epic_id) : null
|
|
756
776
|
]);
|
|
757
|
-
const usersForStory = Object.fromEntries([...fullUsersForStory.entries()].filter(([_, user
|
|
777
|
+
const usersForStory = Object.fromEntries([...fullUsersForStory.entries()].filter(([_, user]) => !!user).map(([id, user]) => [id, this.getSimplifiedMember(user)]));
|
|
758
778
|
const simplifiedIteration = this.getSimplifiedIteration(iteration);
|
|
759
779
|
const simplifiedEpic = this.getSimplifiedEpic(epic, kind);
|
|
760
780
|
const teamForStory = teamsForStory.get(group_id || "");
|
|
761
781
|
const workflowForStory = this.getSimplifiedWorkflow(workflowsForStory.get(workflow_id));
|
|
762
|
-
const { users: usersForTeam, workflows: workflowsForTeam } = await this.getRelatedEntitiesForTeam(teamForStory);
|
|
763
|
-
const { users: usersForIteration, workflows: workflowsForIteration, teams: teamsForIteration } = await this.getRelatedEntitiesForIteration(iteration);
|
|
764
|
-
const { users: usersForEpic, workflows: workflowsForEpic, teams: teamsForEpic, objectives } = await this.getRelatedEntitiesForEpic(epic);
|
|
782
|
+
const { users: usersForTeam, workflows: workflowsForTeam } = await this.getRelatedEntitiesForTeam(teamForStory, kind);
|
|
783
|
+
const { users: usersForIteration, workflows: workflowsForIteration, teams: teamsForIteration } = await this.getRelatedEntitiesForIteration(iteration, kind);
|
|
784
|
+
const { users: usersForEpic, workflows: workflowsForEpic, teams: teamsForEpic, objectives } = await this.getRelatedEntitiesForEpic(epic, kind);
|
|
765
785
|
const users = this.mergeRelatedEntities([
|
|
766
786
|
usersForTeam,
|
|
767
787
|
usersForStory,
|
|
@@ -774,7 +794,7 @@ var BaseTools = class {
|
|
|
774
794
|
workflowsForEpic,
|
|
775
795
|
workflowForStory ? { [workflowForStory.id]: workflowForStory } : {}
|
|
776
796
|
]);
|
|
777
|
-
const simplifiedStoryTeam = this.getSimplifiedTeam(teamForStory);
|
|
797
|
+
const simplifiedStoryTeam = this.getSimplifiedTeam(teamForStory, kind);
|
|
778
798
|
const teams = this.mergeRelatedEntities([
|
|
779
799
|
teamsForIteration,
|
|
780
800
|
teamsForEpic,
|
|
@@ -809,14 +829,14 @@ var BaseTools = class {
|
|
|
809
829
|
};
|
|
810
830
|
}
|
|
811
831
|
async getRelatedEntities(entity, kind) {
|
|
812
|
-
if (entity.entity_type === "group") return this.getRelatedEntitiesForTeam(entity);
|
|
813
|
-
if (entity.entity_type === "iteration") return this.getRelatedEntitiesForIteration(entity);
|
|
814
|
-
if (entity.entity_type === "epic") return this.getRelatedEntitiesForEpic(entity);
|
|
832
|
+
if (entity.entity_type === "group") return this.getRelatedEntitiesForTeam(entity, kind);
|
|
833
|
+
if (entity.entity_type === "iteration") return this.getRelatedEntitiesForIteration(entity, kind);
|
|
834
|
+
if (entity.entity_type === "epic") return this.getRelatedEntitiesForEpic(entity, kind);
|
|
815
835
|
if (entity.entity_type === "story") return this.getRelatedEntitiesForStory(entity, kind);
|
|
816
836
|
return {};
|
|
817
837
|
}
|
|
818
838
|
getSimplifiedEntity(entity, kind) {
|
|
819
|
-
if (entity.entity_type === "group") return this.getSimplifiedTeam(entity);
|
|
839
|
+
if (entity.entity_type === "group") return this.getSimplifiedTeam(entity, kind);
|
|
820
840
|
if (entity.entity_type === "iteration") return this.getSimplifiedIteration(entity);
|
|
821
841
|
if (entity.entity_type === "epic") return this.getSimplifiedEpic(entity, kind);
|
|
822
842
|
if (entity.entity_type === "story") return this.getSimplifiedStory(entity, kind);
|
|
@@ -998,11 +1018,11 @@ const variations = [
|
|
|
998
1018
|
t: dateformat
|
|
999
1019
|
})
|
|
1000
1020
|
];
|
|
1001
|
-
const DATE_REGEXP =
|
|
1021
|
+
const DATE_REGEXP = new RegExp(`^(${variations.join("|")})$`);
|
|
1002
1022
|
const date = () => z.string().regex(DATE_REGEXP).optional().describe("The date in \"YYYY-MM-DD\" format, or one of the keywords: \"yesterday\", \"today\", \"tomorrow\", or a date range in the format \"YYYY-MM-DD..YYYY-MM-DD\". The date range can also be open ended by using \"*\" for one of the bounds. Examples: \"2023-01-01\", \"today\", \"2023-01-01..*\" (from Jan 1, 2023 to any future date), \"*.2023-01-31\" (any date up to Jan 31, 2023), \"today..*\" (from today onwards), \"*.yesterday\" (any date up to yesterday). The keywords cannot be used to calculate relative dates (e.g. the following are not valid: \"today-1\" or \"tomorrow+1\").");
|
|
1003
1023
|
const is = (field) => z.boolean().optional().describe(`Find only entities that are ${field} when true, or only entities that are not ${field} when false.`);
|
|
1004
1024
|
const has = (field) => z.boolean().optional().describe(`Find only entities that have ${field} when true, or only entities that do not have ${field} when false. Example: hasOwner: true will find stories with an owner, hasOwner: false will find stories without an owner.`);
|
|
1005
|
-
const user = (field) => z.string().optional().describe(`Find entities where the ${field} match the specified user. This must either be the user
|
|
1025
|
+
const user = (field) => z.string().optional().describe(`Find entities where the ${field} match the specified user. This must either be the user's mention name or the keyword "me" for the current user.`);
|
|
1006
1026
|
|
|
1007
1027
|
//#endregion
|
|
1008
1028
|
//#region src/tools/epics.ts
|
|
@@ -1062,9 +1082,9 @@ var EpicTools = class EpicTools extends BaseTools {
|
|
|
1062
1082
|
if (!epic) throw new Error(`Failed to retrieve Shortcut epic with public ID: ${epicPublicId}`);
|
|
1063
1083
|
return this.toResult(`Epic: ${epicPublicId}`, await this.entityWithRelatedEntities(epic, "epic", full));
|
|
1064
1084
|
}
|
|
1065
|
-
async createEpic({ name
|
|
1085
|
+
async createEpic({ name, owner, teamId: group_id, description }) {
|
|
1066
1086
|
const epic = await this.client.createEpic({
|
|
1067
|
-
name
|
|
1087
|
+
name,
|
|
1068
1088
|
group_id,
|
|
1069
1089
|
owner_ids: owner ? [owner] : void 0,
|
|
1070
1090
|
description
|
|
@@ -1130,9 +1150,9 @@ var IterationTools = class IterationTools extends BaseTools {
|
|
|
1130
1150
|
if (!iteration) throw new Error(`Failed to retrieve Shortcut iteration with public ID: ${iterationPublicId}.`);
|
|
1131
1151
|
return this.toResult(`Iteration: ${iterationPublicId}`, await this.entityWithRelatedEntities(iteration, "iteration", full));
|
|
1132
1152
|
}
|
|
1133
|
-
async createIteration({ name
|
|
1153
|
+
async createIteration({ name, startDate, endDate, teamId, description }) {
|
|
1134
1154
|
const iteration = await this.client.createIteration({
|
|
1135
|
-
name
|
|
1155
|
+
name,
|
|
1136
1156
|
start_date: startDate,
|
|
1137
1157
|
end_date: endDate,
|
|
1138
1158
|
group_ids: teamId ? [teamId] : void 0,
|
|
@@ -1301,8 +1321,8 @@ The story will be added to the default state for the workflow.
|
|
|
1301
1321
|
iteration: z.number().optional().describe("The iteration id of the iteration the story belongs to"),
|
|
1302
1322
|
team: z.string().optional().describe("The team ID or mention name of the team the story belongs to. Required unless a workflow is specified."),
|
|
1303
1323
|
workflow: z.number().optional().describe("The workflow ID to add the story to. Required unless a team is specified.")
|
|
1304
|
-
}, async ({ name
|
|
1305
|
-
name
|
|
1324
|
+
}, async ({ name, description, type, owner, epic, iteration, team, workflow }) => await tools.createStory({
|
|
1325
|
+
name,
|
|
1306
1326
|
description,
|
|
1307
1327
|
type,
|
|
1308
1328
|
owner,
|
|
@@ -1418,14 +1438,14 @@ The story will be added to the default state for the workflow.
|
|
|
1418
1438
|
const branchName = story.formatted_vcs_branch_name || this.createBranchName(currentUser, story);
|
|
1419
1439
|
return this.toResult(`Branch name for story sc-${storyPublicId}: ${branchName}`);
|
|
1420
1440
|
}
|
|
1421
|
-
async createStory({ name
|
|
1441
|
+
async createStory({ name, description, type, owner, epic, iteration, team, workflow }) {
|
|
1422
1442
|
if (!workflow && !team) throw new Error("Team or Workflow has to be specified");
|
|
1423
1443
|
if (!workflow && team) workflow = (await this.client.getTeam(team))?.workflow_ids?.[0];
|
|
1424
1444
|
if (!workflow) throw new Error("Failed to find workflow for team");
|
|
1425
1445
|
const fullWorkflow = await this.client.getWorkflow(workflow);
|
|
1426
1446
|
if (!fullWorkflow) throw new Error("Failed to find workflow");
|
|
1427
1447
|
const story = await this.client.createStory({
|
|
1428
|
-
name
|
|
1448
|
+
name,
|
|
1429
1449
|
description,
|
|
1430
1450
|
story_type: type,
|
|
1431
1451
|
owner_ids: owner ? [owner] : [],
|
|
@@ -1436,9 +1456,9 @@ The story will be added to the default state for the workflow.
|
|
|
1436
1456
|
});
|
|
1437
1457
|
return this.toResult(`Created story: sc-${story.id}`);
|
|
1438
1458
|
}
|
|
1439
|
-
async createSubTask({ parentStoryPublicId, name
|
|
1459
|
+
async createSubTask({ parentStoryPublicId, name, description }) {
|
|
1440
1460
|
if (!parentStoryPublicId) throw new Error("ID of parent story is required");
|
|
1441
|
-
if (!name
|
|
1461
|
+
if (!name) throw new Error("Sub-task name is required");
|
|
1442
1462
|
const parentStory = await this.client.getStory(parentStoryPublicId);
|
|
1443
1463
|
if (!parentStory) throw new Error(`Failed to retrieve parent story with public ID: ${parentStoryPublicId}`);
|
|
1444
1464
|
const workflow = await this.client.getWorkflow(parentStory.workflow_id);
|
|
@@ -1446,7 +1466,7 @@ The story will be added to the default state for the workflow.
|
|
|
1446
1466
|
const workflowState = workflow.states[0];
|
|
1447
1467
|
if (!workflowState) throw new Error("Failed to determine default state for sub-task");
|
|
1448
1468
|
const subTask = await this.client.createStory({
|
|
1449
|
-
name
|
|
1469
|
+
name,
|
|
1450
1470
|
description,
|
|
1451
1471
|
story_type: parentStory.story_type,
|
|
1452
1472
|
epic_id: parentStory.epic_id,
|
|
@@ -1592,7 +1612,7 @@ var TeamTools = class TeamTools extends BaseTools {
|
|
|
1592
1612
|
teamPublicId: z.string().describe("The public ID of the team to get"),
|
|
1593
1613
|
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")
|
|
1594
1614
|
}, async ({ teamPublicId, full }) => await tools.getTeam(teamPublicId, full));
|
|
1595
|
-
server.addToolWithReadAccess("teams-list", "List
|
|
1615
|
+
server.addToolWithReadAccess("teams-list", "List Shortcut teams", { includeArchived: z.boolean().describe("True to include archived teams.").optional().default(false) }, async ({ includeArchived }) => await tools.getTeams(includeArchived));
|
|
1596
1616
|
return tools;
|
|
1597
1617
|
}
|
|
1598
1618
|
async getTeam(teamPublicId, full = false) {
|
|
@@ -1600,10 +1620,11 @@ var TeamTools = class TeamTools extends BaseTools {
|
|
|
1600
1620
|
if (!team) return this.toResult(`Team with public ID: ${teamPublicId} not found.`);
|
|
1601
1621
|
return this.toResult(`Team: ${team.id}`, await this.entityWithRelatedEntities(team, "team", full));
|
|
1602
1622
|
}
|
|
1603
|
-
async getTeams() {
|
|
1623
|
+
async getTeams(includeArchived) {
|
|
1604
1624
|
const teams = await this.client.getTeams();
|
|
1605
1625
|
if (!teams.length) return this.toResult(`No teams found.`);
|
|
1606
|
-
|
|
1626
|
+
const filteredTeams = includeArchived ? teams : teams.filter((team) => !team.archived);
|
|
1627
|
+
return this.toResult(`Result (first ${filteredTeams.length} shown of ${filteredTeams.length} total teams found):`, await this.entitiesWithRelatedEntities(filteredTeams, "teams"));
|
|
1607
1628
|
}
|
|
1608
1629
|
};
|
|
1609
1630
|
|
|
@@ -1618,9 +1639,9 @@ var UserTools = class UserTools extends BaseTools {
|
|
|
1618
1639
|
return tools;
|
|
1619
1640
|
}
|
|
1620
1641
|
async getCurrentUser() {
|
|
1621
|
-
const user
|
|
1622
|
-
if (!user
|
|
1623
|
-
return this.toResult(`Current user:`, user
|
|
1642
|
+
const user = await this.client.getCurrentUser();
|
|
1643
|
+
if (!user) throw new Error("Failed to retrieve current user.");
|
|
1644
|
+
return this.toResult(`Current user:`, user);
|
|
1624
1645
|
}
|
|
1625
1646
|
async getCurrentUserTeams() {
|
|
1626
1647
|
const teams = await this.client.getTeams();
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shortcut/mcp",
|
|
3
|
-
"
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "Shortcut MCP Server",
|
|
5
|
-
"repository": {
|
|
6
|
-
"type": "git",
|
|
7
|
-
"url": "https://github.com/useshortcut/mcp-server-shortcut.git"
|
|
8
|
-
},
|
|
9
5
|
"keywords": [
|
|
10
6
|
"shortcut",
|
|
11
7
|
"mcp",
|
|
12
8
|
"modelcontextprotocol"
|
|
13
9
|
],
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/useshortcut/mcp-server-shortcut.git"
|
|
13
|
+
},
|
|
14
14
|
"license": "MIT",
|
|
15
|
-
"
|
|
15
|
+
"author": "Shortcut (https://www.shortcut.com)",
|
|
16
16
|
"type": "module",
|
|
17
17
|
"main": "dist/index.js",
|
|
18
18
|
"bin": {
|
|
@@ -21,17 +21,16 @@
|
|
|
21
21
|
"files": [
|
|
22
22
|
"dist"
|
|
23
23
|
],
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"typescript": "^5"
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsdown",
|
|
26
|
+
"dev:http": "bun run src/server-http.ts",
|
|
27
|
+
"format": "biome check --write ./",
|
|
28
|
+
"lint": "biome check ./",
|
|
29
|
+
"prepare": "husky",
|
|
30
|
+
"prod:http": "node dist/server-http.js",
|
|
31
|
+
"prepublish": "bun run build",
|
|
32
|
+
"test": "bun test",
|
|
33
|
+
"ts": "tsc -b"
|
|
35
34
|
},
|
|
36
35
|
"dependencies": {
|
|
37
36
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
@@ -41,15 +40,16 @@
|
|
|
41
40
|
"pino-http": "^10.3.0",
|
|
42
41
|
"zod": "^3.24.4"
|
|
43
42
|
},
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@biomejs/biome": "2.3.14",
|
|
45
|
+
"@types/bun": "latest",
|
|
46
|
+
"@types/express": "^4.17.21",
|
|
47
|
+
"bun": "1.3.9",
|
|
48
|
+
"husky": "9.1.7",
|
|
49
|
+
"pino-pretty": "^13.0.0",
|
|
50
|
+
"tsdown": "0.20.3"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"typescript": "^5"
|
|
54
54
|
}
|
|
55
55
|
}
|