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