#
tokens: 2083/50000 5/5 files
lines: off (toggle) GitHub
raw markdown copy
# 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);

```