# 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 |
```