Shortcut MCP Server
# Shortcut MCP Server
MCP server for connecting Claude, Codex, or any MCP client to the [Shortcut](https://shortcut.com) project management API. Provides **73 tools** covering the full Shortcut API v3 surface.
## How it works
This project implements the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) — an open standard that lets AI assistants call external tools.
```
┌──────────────────┐ stdio ┌──────────────────┐ HTTPS ┌──────────────────┐
│ MCP Client │◄──────────────────►│ MCP Server │◄────────────────►│ Shortcut API │
│ (Claude Desktop, │ JSON-RPC over │ (this project) │ REST API v3 │ api.app. │
│ Claude Code, │ stdin/stdout │ │ with API token │ shortcut.com │
│ VS Code, etc.) │ │ src/index.ts │ │ │
└──────────────────┘ └──────────────────┘ └──────────────────┘
```
**Server** (`src/index.ts`): Registers 73 tools with the MCP SDK. When a client calls a tool, the server validates the input (via Zod schemas), makes the corresponding REST request to the Shortcut API, and returns the result. All write operations are protected by confirmation guards.
**Client** (`src/client.ts`): A standalone test client for development. It spawns the server as a child process, connects over stdio, and lets you call any tool from the command line:
```bash
# List available tools
npm run client
# Call a specific tool
npm run client -- list_epics
npm run client -- get_story '{"story_id": 123}'
npm run client -- create_story '{"name": "Test", "project_id": 101, "dry_run": true}'
```
In production, you don't use the test client — your MCP-compatible AI assistant (Claude Desktop, Claude Code, VS Code, etc.) acts as the client and calls tools automatically.
## Setup
1. Install dependencies:
```bash
npm install
```
2. Set your API token:
```bash
cp .env.example .env
# then edit .env and set SHORTCUT_API_TOKEN to your Shortcut API token
```
Or export directly:
```bash
export SHORTCUT_API_TOKEN="<your-token>"
```
You can generate a Shortcut API token at **Settings → API Tokens** in your Shortcut workspace.
3. Build:
```bash
npm run build
```
## Run
```bash
npm start
```
## Integration
### Claude Desktop
Add the following to your Claude Desktop config file:
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
- **Linux**: `~/.config/Claude/claude_desktop_config.json`
```json
{
"mcpServers": {
"shortcut": {
"command": "node",
"args": ["/absolute/path/to/shortcut-mcp-server/dist/index.js"],
"env": {
"SHORTCUT_API_TOKEN": "<your-shortcut-token>"
}
}
}
}
```
Restart Claude Desktop after saving.
### Claude Code (CLI)
Add to `~/.claude/settings.json`:
```json
{
"mcpServers": {
"shortcut": {
"command": "node",
"args": ["/absolute/path/to/shortcut-mcp-server/dist/index.js"],
"env": {
"SHORTCUT_API_TOKEN": "<your-shortcut-token>"
}
}
}
}
```
### VS Code (Claude Extension)
Add to your project's `.vscode/mcp.json` or workspace settings:
```json
{
"mcpServers": {
"shortcut": {
"command": "node",
"args": ["/absolute/path/to/shortcut-mcp-server/dist/index.js"],
"env": {
"SHORTCUT_API_TOKEN": "<your-shortcut-token>"
}
}
}
}
```
> **Note**: In all configs above, replace `/absolute/path/to/shortcut-mcp-server` with the actual path where you cloned this repo, and `<your-shortcut-token>` with your Shortcut API token.
## Guardrails for write tools
All mutating tools support safety guardrails:
- `confirm="CONFIRM_WRITE"` is required to execute any write/update/create.
- `dry_run=true` returns the exact request payload without changing Shortcut data.
- `confirm_archive=true` is required when archiving.
- `confirm_delete=true` is required for delete operations (in addition to `confirm`).
- Read tools do not support `dry_run`; passing it returns a validation error.
Recommended flow for writes:
1. Run with `dry_run=true` and validate the payload.
2. Re-run with `confirm="CONFIRM_WRITE"` (and `confirm_archive=true` / `confirm_delete=true` if applicable).
## Tools (73)
| # | Tool | Description | Type |
|---|------|-------------|------|
| | **Stories** | | |
| 1 | `search_stories` | Search stories with Shortcut query syntax | Read |
| 2 | `get_story` | Get a story by ID | Read |
| 3 | `create_story` | Create a story in a project | Write |
| 4 | `update_story` | Update fields on an existing story | Write |
| 5 | `delete_story` | Permanently delete a story | Delete |
| 6 | `archive_story` | Archive a story | Archive |
| 7 | `transition_story_state` | Move a story to a workflow state by ID or name | Write |
| 8 | `get_story_history` | Get change history of a story | Read |
| 9 | `bulk_update_stories` | Update multiple stories at once | Write |
| 10 | `bulk_move_stories` | Move multiple stories to a different project/state | Write |
| 11 | `bulk_delete_stories` | Permanently delete multiple stories | Delete |
| | **Story Comments** | | |
| 12 | `list_story_comments` | List comments for a story | Read |
| 13 | `create_story_comment` | Add a comment to a story | Write |
| 14 | `update_story_comment` | Update a comment on a story | Write |
| 15 | `delete_story_comment` | Delete a comment | Delete |
| | **Story Tasks (Checklists)** | | |
| 16 | `list_story_tasks` | List checklist tasks on a story | Read |
| 17 | `create_story_task` | Add a checklist task | Write |
| 18 | `update_story_task` | Update a checklist task (e.g. mark complete) | Write |
| 19 | `delete_story_task` | Delete a checklist task | Delete |
| | **Story Links** | | |
| 20 | `create_story_link` | Link two stories (blocks, relates to, duplicates) | Write |
| 21 | `delete_story_link` | Delete a story link | Delete |
| | **Story Labels** | | |
| 22 | `add_story_label` | Add a label to a story | Write |
| 23 | `remove_story_label` | Remove a label from a story | Write |
| | **Story VCS Integration** | | |
| 24 | `list_story_branches` | List Git branches associated with a story | Read |
| 25 | `list_story_commits` | List Git commits associated with a story | Read |
| 26 | `list_story_pull_requests` | List pull requests associated with a story | Read |
| 27 | `list_story_external_links` | List external links on a story | Read |
| | **Epics** | | |
| 28 | `list_epics` | List epics | Read |
| 29 | `get_epic` | Get an epic by ID | Read |
| 30 | `create_epic` | Create an epic | Write |
| 31 | `update_epic` | Update an epic | Write |
| 32 | `delete_epic` | Permanently delete an epic | Delete |
| 33 | `archive_epic` | Archive an epic | Archive |
| 34 | `search_epics` | Search epics with query syntax | Read |
| 35 | `list_epic_stories` | List stories in an epic | Read |
| 36 | `add_epic_label` | Add a label to an epic | Write |
| | **Epic Comments** | | |
| 37 | `list_epic_comments` | List comments on an epic | Read |
| 38 | `create_epic_comment` | Add a comment to an epic | Write |
| | **Iterations (Sprints)** | | |
| 39 | `list_iterations` | List iterations | Read |
| 40 | `get_iteration` | Get an iteration by ID | Read |
| 41 | `create_iteration` | Create an iteration | Write |
| 42 | `update_iteration` | Update an iteration | Write |
| 43 | `delete_iteration` | Permanently delete an iteration | Delete |
| 44 | `get_iteration_stories` | List stories in an iteration | Read |
| | **Milestones** | | |
| 45 | `list_milestones` | List milestones | Read |
| 46 | `get_milestone` | Get a milestone by ID | Read |
| 47 | `create_milestone` | Create a milestone | Write |
| 48 | `update_milestone` | Update a milestone | Write |
| 49 | `delete_milestone` | Permanently delete a milestone | Delete |
| 50 | `list_milestone_epics` | List epics in a milestone | Read |
| | **Projects** | | |
| 51 | `list_projects` | List projects | Read |
| 52 | `get_project` | Get a project by ID | Read |
| 53 | `create_project` | Create a project | Write |
| 54 | `update_project` | Update a project | Write |
| 55 | `delete_project` | Permanently delete a project | Delete |
| | **Labels** | | |
| 56 | `list_labels` | List all labels | Read |
| 57 | `get_label` | Get a label by ID | Read |
| 58 | `create_label` | Create a label | Write |
| 59 | `update_label` | Update a label | Write |
| 60 | `delete_label` | Permanently delete a label | Delete |
| | **Workflows** | | |
| 61 | `list_workflows` | List all workflows and their states | Read |
| | **Members & Groups** | | |
| 62 | `list_members` | List workspace members | Read |
| 63 | `get_member` | Get a member by UUID | Read |
| 64 | `list_groups` | List teams/groups | Read |
| 65 | `get_group` | Get a team/group by UUID | Read |
| | **Linked Files** | | |
| 66 | `list_linked_files` | List all linked files | Read |
| 67 | `get_linked_file` | Get a linked file by ID | Read |
| 68 | `create_linked_file` | Create a linked file reference (e.g. Google Doc, Figma) | Write |
| 69 | `update_linked_file` | Update a linked file | Write |
| 70 | `delete_linked_file` | Permanently delete a linked file | Delete |
| | **Other** | | |
| 71 | `list_custom_fields` | List all custom fields and their values | Read |
| 72 | `list_entity_templates` | List all story/epic templates | Read |
| 73 | `list_repositories` | List linked Git repositories | Read |
**Type legend**: Read = no confirmation needed | Write = requires `confirm="CONFIRM_WRITE"` | Delete = requires `confirm_delete=true` + `confirm="CONFIRM_WRITE"` | Archive = requires `confirm_archive=true` + `confirm="CONFIRM_WRITE"`
## Example tool calls
### Create a story (dry run first)
```json
{
"name": "MCP integration test story",
"project_id": 101,
"story_type": "feature",
"description": "Created from Shortcut MCP server",
"dry_run": true
}
```
### Create a story (execute)
```json
{
"name": "MCP integration test story",
"project_id": 101,
"story_type": "feature",
"description": "Created from Shortcut MCP server",
"confirm": "CONFIRM_WRITE"
}
```
### Transition a story to a new state
```json
{
"story_id": 12345,
"workflow_state_name": "In Progress",
"workflow_name": "Engineering Workflow",
"confirm": "CONFIRM_WRITE"
}
```
### Update an epic
```json
{
"epic_id": 14,
"state": "in progress",
"confirm": "CONFIRM_WRITE"
}
```
### Archive a story
```json
{
"story_id": 12345,
"confirm_archive": true,
"confirm": "CONFIRM_WRITE"
}
```
### Delete a story (double confirmation)
```json
{
"story_id": 12345,
"confirm_delete": true,
"confirm": "CONFIRM_WRITE"
}
```
### Bulk move stories
```json
{
"story_ids": [123, 456, 789],
"project_id": 202,
"workflow_state_id": 500000010,
"confirm": "CONFIRM_WRITE"
}
```
### Search stories
```json
{
"query": "owner:johndoe state:\"In Progress\" type:bug"
}
```
### Create an iteration
```json
{
"name": "Sprint 42",
"start_date": "2025-03-10",
"end_date": "2025-03-24",
"confirm": "CONFIRM_WRITE"
}
```
### Link two stories
```json
{
"subject_id": 123,
"object_id": 456,
"verb": "blocks",
"confirm": "CONFIRM_WRITE"
}
```
### Add a comment to a story
```json
{
"story_id": 12345,
"text": "Looks good, moving to review.",
"confirm": "CONFIRM_WRITE"
}
```
## Using the test client
The included test client (`src/client.ts`) lets you test tools from the command line without an AI assistant:
```bash
# Build first
npm run build
# List all available tools
npm run client
# Read operations (no confirmation needed)
npm run client -- list_epics
npm run client -- get_story '{"story_id": 123}'
npm run client -- search_stories '{"query": "state:\"In Progress\""}'
npm run client -- list_workflows
# Write operations (need confirmation)
npm run client -- create_story '{"name": "Test", "project_id": 101, "dry_run": true}'
npm run client -- create_story '{"name": "Test", "project_id": 101, "confirm": "CONFIRM_WRITE"}'
```
## Project structure
```
shortcut-mcp-server/
├── src/
│ ├── index.ts # MCP server — all 73 tool registrations and Shortcut API logic
│ └── client.ts # Standalone test client for development
├── dist/ # Compiled JavaScript (generated by npm run build)
├── package.json
├── tsconfig.json
├── .env.example # Template for API token
└── .gitignore
```
## License
MIT
TDQS
Scored across 73 tools
Most tools are clearly distinct by resource and action (e.g., list_story_comments vs list_epic_comments). However, generic update_story overlaps with transition_story_state and add_story_label, which could cause misselection.
All tools follow a strict snake_case verb_noun pattern: list/get for reads, create/update/delete for writes, and resource-specific nouns. Even bulk operations and transition_story_state fit the pattern, with no mixed conventions.
With 73 tools, this server far exceeds the 50+ extreme threshold. The massive surface is likely to overwhelm agents and create unnecessary selection overhead, despite each tool potentially having a specific purpose.
The tool set provides comprehensive CRUD coverage for stories, epics, projects, iterations, milestones, labels, linked files, and comments, plus search and bulk operations. Minor gaps include no update/delete for epic comments and no remove_epic_label.