#
tokens: 2926/50000 5/5 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── src
│   └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | build/
2 | archive/
3 | *.log
4 | .env*
5 | node_modules/
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Apple Shortcuts MCP Server 🤖
 2 | 
 3 | A Model Context Protocol (MCP) server that lets AI assistants like Claude control Apple Shortcuts automations. This enables AI models to trigger shortcuts and automate tasks on macOS in a safe and controlled way.
 4 | 
 5 | <a href="https://www.npmjs.com/package/mcp-server-apple-shortcuts"><img src="https://img.shields.io/npm/v/mcp-server-apple-shortcuts"/></a>
 6 | 
 7 | <a href="https://glama.ai/mcp/servers/15z6abk6p2"><img width="380" height="200" src="https://glama.ai/mcp/servers/15z6abk6p2/badge" /></a>
 8 | 
 9 | ## What is MCP? 🤔
10 | 
11 | The Model Context Protocol (MCP) is a system that lets AI apps, like Claude Desktop, connect to external tools and data sources. It gives a clear and safe way for AI assistants to work with local services and APIs while keeping the user in control.
12 | 
13 | ## What does this server do? 🚀
14 | 
15 | The Apple Shortcuts MCP server:
16 | - Enables AI assistants to list available shortcuts
17 | - Allows running shortcuts by name with optional input parameters 
18 | - Provides a simple interface for automation control
19 | 
20 | ## Prerequisites 📋
21 | 
22 | Before you begin, ensure you have:
23 | 
24 | - [Node.js](https://nodejs.org/) (v18 or higher)
25 | - [Claude Desktop](https://claude.ai/download) installed
26 | - macOS with Shortcuts app configured
27 | 
28 | ## Configuration to use Apple Shortcuts Server ⚙️
29 | 
30 | Here's the Claude Desktop configuration to use the Apple Shortcuts server:
31 | ```json
32 | {
33 |   "mcpServers": {
34 |     "apple-shortcuts": {
35 |       "command": "npx",
36 |       "args": ["-y", "mcp-server-apple-shortcuts"]
37 |     }
38 |   }
39 | }
40 | ```
41 | 
42 | ## Build Apple Shortcuts Server and run locally 🛠️
43 | 
44 | 1. Clone this repository:
45 | 
46 | ```sh
47 | git clone [email protected]:recursechat/mcp-server-apple-shortcuts.git
48 | ```
49 | 
50 | 2. Install dependencies:
51 | ```sh
52 | npm install
53 | ```
54 | 
55 | 3. Build project
56 | ```sh
57 | npm run build
58 | ```
59 | 
60 | Here's the Claude Desktop configuration to use the Apple Shortcuts server with a local build:
61 | ```json
62 | {
63 |   "mcpServers": {
64 |     "apple-shortcuts": {
65 |       "command": "npx",
66 |       "args": ["/path/to/mcp-server-apple-shortcuts/build/index.js"],
67 |     }
68 |   }
69 | }
70 | ```
71 | 
72 | <!--
73 | ```json
74 | {
75 |   "mcpServers": {
76 |     "apple-shortcuts": {
77 |       "command": "npx",
78 |       "args": ["-y", "mcp-server-apple-shortcuts"]
79 |     }
80 |   }
81 | }
82 | ```
83 | -->
84 | 
85 | ## Usage 🎯
86 | 
87 | You can ask Claude "list shortcuts" or run a specific shortcut with the shortcut name, for example "get word of the day" or "play a song".
88 | 
89 | ## License ⚖️
90 | 
91 | Apache-2.0
92 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "Node16",
 5 |     "moduleResolution": "Node16",
 6 |     "strict": true,
 7 |     "esModuleInterop": true,
 8 |     "skipLibCheck": true,
 9 |     "forceConsistentCasingInFileNames": true,
10 |     "resolveJsonModule": true,
11 |     "outDir": "./build"
12 |   },
13 |   "include": ["src/**/*"],
14 |   "exclude": ["node_modules"]
15 | }
16 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "mcp-server-apple-shortcuts",
 3 |   "version": "1.0.1",
 4 |   "description": "MCP server for automation using Apple Shortcuts",
 5 |   "main": "index.js",
 6 |   "type": "module",
 7 |   "keywords": [],
 8 |   "author": "Recurse Chat (https://recurse.chat)",
 9 |   "homepage": "https://recurse.chat",
10 |   "license": "Apache-2.0",
11 |   "dependencies": {
12 |     "@modelcontextprotocol/sdk": "^1.0.4",
13 |     "shx": "^0.3.4",
14 |     "zod": "^3.24.1"
15 |   },
16 |   "bin": {
17 |     "mcp-server-apple-shortcuts": "./build/index.js"
18 |   },
19 |   "workspaces": [
20 |     "src/*"
21 |   ],
22 |   "files": [
23 |     "build"
24 |   ],
25 |   "scripts": {
26 |     "build": "tsc && shx chmod +x build/*.js",
27 |     "prepare": "npm run build",
28 |     "watch": "tsc --watch",
29 |     "inspector": "npx @modelcontextprotocol/inspector build/index.js"
30 |   },
31 |   "devDependencies": {
32 |     "@types/node": "^22.10.1",
33 |     "typescript": "^5.7.2"
34 |   }
35 | }
36 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | 
  3 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
  4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  5 | import {
  6 |   CallToolRequestSchema,
  7 |   ListResourcesRequestSchema,
  8 |   ListToolsRequestSchema,
  9 |   ReadResourceRequestSchema,
 10 |   CallToolResult,
 11 |   Tool,
 12 | } from "@modelcontextprotocol/sdk/types.js";
 13 | import { execSync } from "child_process";
 14 | 
 15 | // Define the tools for Shortcuts interaction
 16 | const TOOLS: Tool[] = [
 17 |   {
 18 |     name: "run_shortcut",
 19 |     description: "Run a Shortcuts automation by name",
 20 |     inputSchema: {
 21 |       type: "object",
 22 |       properties: {
 23 |         name: { type: "string", description: "Name of the shortcut to run" },
 24 |         input: {
 25 |           type: "string",
 26 |           description: "Optional input to pass to the shortcut",
 27 |         },
 28 |       },
 29 |       required: ["name"],
 30 |     },
 31 |   },
 32 |   {
 33 |     name: "list_shortcuts",
 34 |     description: "List all available shortcuts",
 35 |     inputSchema: {
 36 |       type: "object",
 37 |       properties: {},
 38 |     },
 39 |   },
 40 | ];
 41 | 
 42 | // Global state to track shortcuts
 43 | let availableShortcuts: string[] = [];
 44 | 
 45 | function updateShortcutsList() {
 46 |   try {
 47 |     const stdout = execSync("shortcuts list").toString();
 48 |     availableShortcuts = stdout
 49 |       .split("\n")
 50 |       .map((line) => line.trim())
 51 |       .filter((line) => line.length > 0);
 52 |   } catch (error) {
 53 |     console.error("Failed to list shortcuts:", error);
 54 |     availableShortcuts = [];
 55 |   }
 56 | }
 57 | 
 58 | async function handleToolCall(
 59 |   name: string,
 60 |   args: any
 61 | ): Promise<CallToolResult> {
 62 |   switch (name) {
 63 |     case "list_shortcuts": {
 64 |       updateShortcutsList();
 65 |       console.error("MCP shortcuts: Listing shortcuts");
 66 |       return {
 67 |         content: [
 68 |           {
 69 |             type: "text",
 70 |             text: `Available shortcuts:\n${availableShortcuts.join("\n")}`,
 71 |           },
 72 |         ],
 73 |         isError: false,
 74 |       };
 75 |     }
 76 | 
 77 |     case "run_shortcut": {
 78 |       try {
 79 |         const command = args.input
 80 |           ? `shortcuts run "${args.name}" -i "${args.input}"`
 81 |           : `shortcuts run "${args.name}"`;
 82 | 
 83 |         console.error("MCP shortcuts: Running command:", command);
 84 |         const stdout = execSync(command).toString();
 85 | 
 86 |         return {
 87 |           content: [
 88 |             {
 89 |               type: "text",
 90 |               text: stdout || "Shortcut executed successfully",
 91 |             },
 92 |           ],
 93 |           isError: false,
 94 |         };
 95 |       } catch (error) {
 96 |         return {
 97 |           content: [
 98 |             {
 99 |               type: "text",
100 |               text: `Failed to run shortcut: ${(error as Error).message}`,
101 |             },
102 |           ],
103 |           isError: true,
104 |         };
105 |       }
106 |     }
107 | 
108 |     default:
109 |       return {
110 |         content: [
111 |           {
112 |             type: "text",
113 |             text: `Unknown tool: ${name}`,
114 |           },
115 |         ],
116 |         isError: true,
117 |       };
118 |   }
119 | }
120 | 
121 | const server = new Server(
122 |   {
123 |     name: "recursechat/shortcuts",
124 |     version: "1.0.1",
125 |   },
126 |   {
127 |     capabilities: {
128 |       resources: {},
129 |       tools: {},
130 |     },
131 |   }
132 | );
133 | 
134 | // Setup request handlers
135 | server.setRequestHandler(ListResourcesRequestSchema, async () => ({
136 |   resources: [
137 |     {
138 |       uri: "shortcuts://list",
139 |       mimeType: "text/plain",
140 |       name: "Available Shortcuts",
141 |     },
142 |   ],
143 | }));
144 | 
145 | server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
146 |   const uri = request.params.uri.toString();
147 | 
148 |   if (uri === "shortcuts://list") {
149 |     updateShortcutsList();
150 |     return {
151 |       contents: [
152 |         {
153 |           uri,
154 |           mimeType: "text/plain",
155 |           text: availableShortcuts.join("\n"),
156 |         },
157 |       ],
158 |     };
159 |   }
160 | 
161 |   throw new Error(`Resource not found: ${uri}`);
162 | });
163 | 
164 | server.setRequestHandler(ListToolsRequestSchema, async () => ({
165 |   tools: TOOLS,
166 | }));
167 | 
168 | server.setRequestHandler(CallToolRequestSchema, async (request) =>
169 |   handleToolCall(request.params.name, request.params.arguments ?? {})
170 | );
171 | 
172 | async function runServer() {
173 |   const transport = new StdioServerTransport();
174 |   await server.connect(transport);
175 | 
176 |   // Initial shortcuts list update
177 |   updateShortcutsList();
178 | }
179 | 
180 | runServer().catch(console.error);
181 | 
```