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