# Directory Structure ``` ├── .gitignore ├── LICENSE ├── package-lock.json ├── package.json ├── README.md ├── src │ └── index.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` build/ archive/ *.log .env* node_modules/ ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # Apple Shortcuts MCP Server 🤖 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. <a href="https://www.npmjs.com/package/mcp-server-apple-shortcuts"><img src="https://img.shields.io/npm/v/mcp-server-apple-shortcuts"/></a> <a href="https://glama.ai/mcp/servers/15z6abk6p2"><img width="380" height="200" src="https://glama.ai/mcp/servers/15z6abk6p2/badge" /></a> ## What is MCP? 🤔 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. ## What does this server do? 🚀 The Apple Shortcuts MCP server: - Enables AI assistants to list available shortcuts - Allows running shortcuts by name with optional input parameters - Provides a simple interface for automation control ## Prerequisites 📋 Before you begin, ensure you have: - [Node.js](https://nodejs.org/) (v18 or higher) - [Claude Desktop](https://claude.ai/download) installed - macOS with Shortcuts app configured ## Configuration to use Apple Shortcuts Server ⚙️ Here's the Claude Desktop configuration to use the Apple Shortcuts server: ```json { "mcpServers": { "apple-shortcuts": { "command": "npx", "args": ["-y", "mcp-server-apple-shortcuts"] } } } ``` ## Build Apple Shortcuts Server and run locally 🛠️ 1. Clone this repository: ```sh git clone [email protected]:recursechat/mcp-server-apple-shortcuts.git ``` 2. Install dependencies: ```sh npm install ``` 3. Build project ```sh npm run build ``` Here's the Claude Desktop configuration to use the Apple Shortcuts server with a local build: ```json { "mcpServers": { "apple-shortcuts": { "command": "npx", "args": ["/path/to/mcp-server-apple-shortcuts/build/index.js"], } } } ``` <!-- ```json { "mcpServers": { "apple-shortcuts": { "command": "npx", "args": ["-y", "mcp-server-apple-shortcuts"] } } } ``` --> ## Usage 🎯 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". ## License ⚖️ Apache-2.0 ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json { "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "outDir": "./build" }, "include": ["src/**/*"], "exclude": ["node_modules"] } ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json { "name": "mcp-server-apple-shortcuts", "version": "1.0.1", "description": "MCP server for automation using Apple Shortcuts", "main": "index.js", "type": "module", "keywords": [], "author": "Recurse Chat (https://recurse.chat)", "homepage": "https://recurse.chat", "license": "Apache-2.0", "dependencies": { "@modelcontextprotocol/sdk": "^1.0.4", "shx": "^0.3.4", "zod": "^3.24.1" }, "bin": { "mcp-server-apple-shortcuts": "./build/index.js" }, "workspaces": [ "src/*" ], "files": [ "build" ], "scripts": { "build": "tsc && shx chmod +x build/*.js", "prepare": "npm run build", "watch": "tsc --watch", "inspector": "npx @modelcontextprotocol/inspector build/index.js" }, "devDependencies": { "@types/node": "^22.10.1", "typescript": "^5.7.2" } } ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript #!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, CallToolResult, Tool, } from "@modelcontextprotocol/sdk/types.js"; import { execSync } from "child_process"; // Define the tools for Shortcuts interaction const TOOLS: Tool[] = [ { name: "run_shortcut", description: "Run a Shortcuts automation by name", inputSchema: { type: "object", properties: { name: { type: "string", description: "Name of the shortcut to run" }, input: { type: "string", description: "Optional input to pass to the shortcut", }, }, required: ["name"], }, }, { name: "list_shortcuts", description: "List all available shortcuts", inputSchema: { type: "object", properties: {}, }, }, ]; // Global state to track shortcuts let availableShortcuts: string[] = []; function updateShortcutsList() { try { const stdout = execSync("shortcuts list").toString(); availableShortcuts = stdout .split("\n") .map((line) => line.trim()) .filter((line) => line.length > 0); } catch (error) { console.error("Failed to list shortcuts:", error); availableShortcuts = []; } } async function handleToolCall( name: string, args: any ): Promise<CallToolResult> { switch (name) { case "list_shortcuts": { updateShortcutsList(); console.error("MCP shortcuts: Listing shortcuts"); return { content: [ { type: "text", text: `Available shortcuts:\n${availableShortcuts.join("\n")}`, }, ], isError: false, }; } case "run_shortcut": { try { const command = args.input ? `shortcuts run "${args.name}" -i "${args.input}"` : `shortcuts run "${args.name}"`; console.error("MCP shortcuts: Running command:", command); const stdout = execSync(command).toString(); return { content: [ { type: "text", text: stdout || "Shortcut executed successfully", }, ], isError: false, }; } catch (error) { return { content: [ { type: "text", text: `Failed to run shortcut: ${(error as Error).message}`, }, ], isError: true, }; } } default: return { content: [ { type: "text", text: `Unknown tool: ${name}`, }, ], isError: true, }; } } const server = new Server( { name: "recursechat/shortcuts", version: "1.0.1", }, { capabilities: { resources: {}, tools: {}, }, } ); // Setup request handlers server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [ { uri: "shortcuts://list", mimeType: "text/plain", name: "Available Shortcuts", }, ], })); server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const uri = request.params.uri.toString(); if (uri === "shortcuts://list") { updateShortcutsList(); return { contents: [ { uri, mimeType: "text/plain", text: availableShortcuts.join("\n"), }, ], }; } throw new Error(`Resource not found: ${uri}`); }); server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS, })); server.setRequestHandler(CallToolRequestSchema, async (request) => handleToolCall(request.params.name, request.params.arguments ?? {}) ); async function runServer() { const transport = new StdioServerTransport(); await server.connect(transport); // Initial shortcuts list update updateShortcutsList(); } runServer().catch(console.error); ```