scpl-updated-mcp-server 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/index.js +222 -102
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -6,7 +6,7 @@ Model Context Protocol server for creating macOS Shortcuts using ScPL (Shortcuts
6
6
 
7
7
  - **Create Shortcuts**: Convert ScPL code to .shortcut files
8
8
  - **Validate Syntax**: Check ScPL code without creating files
9
- - **Discover Actions**: Browse 294 available actions with descriptions
9
+ - **Discover Actions**: Browse 493 available actions with descriptions
10
10
  - **Documentation**: Access action reference and examples
11
11
 
12
12
  ## ⚠️ Shortcut Signing & Installation
package/index.js CHANGED
@@ -8,10 +8,167 @@ import {
8
8
  ListResourcesRequestSchema,
9
9
  ReadResourceRequestSchema,
10
10
  } from "@modelcontextprotocol/sdk/types.js";
11
- import { convert } from "scpl-macos-updated";
12
- import { writeFileSync, readFileSync } from "fs";
13
- import { join } from "path";
11
+ import pkg from "scpl-macos-updated";
12
+ const { convert } = pkg;
13
+ import { writeFileSync, readFileSync, existsSync, mkdirSync } from "fs";
14
+ import { join, dirname } from "path";
14
15
  import { homedir } from "os";
16
+ import { fileURLToPath } from "url";
17
+
18
+ const __filename = fileURLToPath(import.meta.url);
19
+ const __dirname = dirname(__filename);
20
+
21
+ // ============================================================================
22
+ // AUTO-SETUP: Run with --setup to install everything automatically
23
+ // ============================================================================
24
+ if (process.argv.includes("--setup")) {
25
+ console.log("🚀 Setting up ScPL Shortcuts for Claude Code...\n");
26
+
27
+ const claudeJsonPath = join(homedir(), ".claude.json");
28
+ const pluginsDir = join(homedir(), ".claude", "plugins", "local", "scpl-shortcuts");
29
+ const installedPluginsPath = join(homedir(), ".claude", "plugins", "installed_plugins.json");
30
+
31
+ // Step 1: Add MCP server to ~/.claude.json
32
+ console.log("📝 Step 1: Adding MCP server to ~/.claude.json...");
33
+ try {
34
+ let claudeConfig = {};
35
+ if (existsSync(claudeJsonPath)) {
36
+ claudeConfig = JSON.parse(readFileSync(claudeJsonPath, "utf-8"));
37
+ }
38
+
39
+ if (!claudeConfig.mcpServers) {
40
+ claudeConfig.mcpServers = {};
41
+ }
42
+
43
+ claudeConfig.mcpServers["scpl-shortcuts"] = {
44
+ type: "stdio",
45
+ command: "npx",
46
+ args: ["-y", "scpl-updated-mcp-server"]
47
+ };
48
+
49
+ writeFileSync(claudeJsonPath, JSON.stringify(claudeConfig, null, 2));
50
+ console.log(" ✅ MCP server added!\n");
51
+ } catch (error) {
52
+ console.error(" ❌ Failed to update ~/.claude.json:", error.message);
53
+ console.log(" You can manually add this to ~/.claude.json under mcpServers:");
54
+ console.log(` "scpl-shortcuts": { "type": "stdio", "command": "npx", "args": ["-y", "scpl-updated-mcp-server"] }\n`);
55
+ }
56
+
57
+ // Step 2: Create plugin directory and files
58
+ console.log("📁 Step 2: Installing plugin files...");
59
+ try {
60
+ mkdirSync(join(pluginsDir, "skills"), { recursive: true });
61
+
62
+ // Write plugin.json
63
+ const pluginJson = {
64
+ name: "scpl-shortcuts",
65
+ version: "1.0.0",
66
+ description: "Create macOS Shortcuts using natural language and ScPL",
67
+ skills: [
68
+ {
69
+ name: "create-shortcut",
70
+ description: "Create a macOS Shortcut using natural language. Guides you through the process and generates a .shortcut file.",
71
+ path: "skills/create-shortcut.md"
72
+ }
73
+ ]
74
+ };
75
+ writeFileSync(join(pluginsDir, "plugin.json"), JSON.stringify(pluginJson, null, 2));
76
+
77
+ // Write skill file
78
+ const skillContent = `---
79
+ description: Create macOS Shortcuts using natural language. Converts your request into ScPL code and generates a .shortcut file.
80
+ tags: [shortcuts, automation, macos, scpl]
81
+ ---
82
+
83
+ # Create Shortcut Skill
84
+
85
+ You have access to the ScPL MCP server with 493 actions for creating macOS Shortcuts.
86
+
87
+ ## Available Tools
88
+
89
+ 1. **create_shortcut** - Generate a .shortcut file from ScPL code
90
+ 2. **validate_scpl** - Check if ScPL code is valid before creating
91
+ 3. **list_actions** - Search available actions by category or keyword
92
+
93
+ ## Popular Action Categories
94
+
95
+ - **AI**: AskLLM (Apple Intelligence), AskChatGPT, AskClaude
96
+ - **Clock**: StartStopwatch, CreateAlarm, GetCurrentTime
97
+ - **Voice Memos**: CreateRecording, PlayRecording
98
+ - **System**: SetDarkMode, TakeScreenshot, LockScreen
99
+ - **Files**: GetFile, SaveFile, RenameFile, RevealInFinder
100
+ - **Scripting**: RunShellScript, RunAppleScript
101
+
102
+ ## ScPL Syntax Examples
103
+
104
+ \`\`\`scpl
105
+ # Simple notification
106
+ ShowResult "Hello World!"
107
+
108
+ # Use Apple Intelligence
109
+ Text "Summarize this for me"
110
+ AskLLM model="Apple Intelligence" prompt="Make it shorter"
111
+ ShowResult
112
+
113
+ # Shell script
114
+ RunShellScript shell="/bin/zsh" script="echo Hello"
115
+ ShowResult
116
+
117
+ # Variables
118
+ Text "Hello"
119
+ SetVariable v:greeting
120
+ ShowResult v:greeting
121
+ \`\`\`
122
+
123
+ ## Workflow
124
+
125
+ 1. User describes what they want
126
+ 2. Use \`list_actions\` if you need to find specific actions
127
+ 3. Write ScPL code for the shortcut
128
+ 4. Use \`validate_scpl\` to check syntax
129
+ 5. Use \`create_shortcut\` to generate the .shortcut file
130
+ 6. Tell user to drag the file onto Shortcut Source Helper to install
131
+ `;
132
+ writeFileSync(join(pluginsDir, "skills", "create-shortcut.md"), skillContent);
133
+ console.log(" ✅ Plugin files created!\n");
134
+ } catch (error) {
135
+ console.error(" ❌ Failed to create plugin files:", error.message, "\n");
136
+ }
137
+
138
+ // Step 3: Register plugin in installed_plugins.json
139
+ console.log("📋 Step 3: Registering plugin...");
140
+ try {
141
+ let installedPlugins = { version: 2, plugins: {} };
142
+ if (existsSync(installedPluginsPath)) {
143
+ installedPlugins = JSON.parse(readFileSync(installedPluginsPath, "utf-8"));
144
+ }
145
+
146
+ installedPlugins.plugins["scpl-shortcuts@local"] = [
147
+ {
148
+ scope: "user",
149
+ installPath: pluginsDir,
150
+ version: "1.0.0",
151
+ installedAt: new Date().toISOString(),
152
+ lastUpdated: new Date().toISOString(),
153
+ isLocal: true
154
+ }
155
+ ];
156
+
157
+ writeFileSync(installedPluginsPath, JSON.stringify(installedPlugins, null, 2));
158
+ console.log(" ✅ Plugin registered!\n");
159
+ } catch (error) {
160
+ console.error(" ❌ Failed to register plugin:", error.message, "\n");
161
+ }
162
+
163
+ console.log("🎉 Setup complete! Restart Claude Code to use the shortcuts tools.\n");
164
+ console.log("Usage: Just ask Claude to create a shortcut!");
165
+ console.log(' Example: "Create a shortcut that starts a timer and plays a sound"\n');
166
+ process.exit(0);
167
+ }
168
+
169
+ // ============================================================================
170
+ // MCP SERVER
171
+ // ============================================================================
15
172
 
16
173
  const server = new Server(
17
174
  {
@@ -46,7 +203,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
46
203
  },
47
204
  output_dir: {
48
205
  type: "string",
49
- description: "Optional output directory (defaults to ~/Downloads)",
206
+ description: "Optional output directory (defaults to ~/Documents)",
50
207
  },
51
208
  },
52
209
  required: ["scpl_code", "output_name"],
@@ -68,7 +225,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
68
225
  },
69
226
  {
70
227
  name: "list_actions",
71
- description: "List available ScPL actions with descriptions. Filter by category or search term.",
228
+ description: "List available ScPL actions (493 total). Filter by category or search term.",
72
229
  inputSchema: {
73
230
  type: "object",
74
231
  properties: {
@@ -95,7 +252,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
95
252
  const { scpl_code, output_name, output_dir } = args;
96
253
 
97
254
  // Convert ScPL to shortcut
98
- // Note: parse() returns a Buffer directly when makeShortcut: true
99
255
  const shortcutBuffer = convert(scpl_code, {
100
256
  makePlist: true,
101
257
  makeShortcut: true,
@@ -112,7 +268,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
112
268
  content: [
113
269
  {
114
270
  type: "text",
115
- text: `✅ Shortcut created successfully!\n\nPath: ${outputPath}\n\n📝 To install and sign it:\n\n1. Install Shortcut Source Helper from RoutineHub (if you haven't already)\n - Also install: Shortcut Source Tool, Tinycut Builder\n2. Add Shortcut Source Helper to your Dock\n3. Drag and drop ${output_name}.shortcut onto it\n4. Follow the prompts to sign and import\n\nThe shortcut will be added to your Shortcuts app and ready to use!`,
271
+ text: `✅ Shortcut created successfully!\n\nPath: ${outputPath}\n\n📝 To install:\n1. Download "Shortcut Source Helper" from RoutineHub\n2. Drag ${output_name}.shortcut onto it\n3. Follow the prompts to sign and import\n\nThe shortcut will be added to your Shortcuts app!`,
116
272
  },
117
273
  ],
118
274
  };
@@ -122,7 +278,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
122
278
  const { scpl_code } = args;
123
279
 
124
280
  try {
125
- // Just parse to validate, don't generate output
126
281
  convert(scpl_code, {
127
282
  makePlist: false,
128
283
  makeShortcut: false,
@@ -152,59 +307,71 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
152
307
  if (name === "list_actions") {
153
308
  const { category, search } = args || {};
154
309
 
155
- // Read the actions data
156
- const actionsPath = join(
157
- process.cwd(),
158
- "..",
159
- "src",
160
- "Data",
161
- "OutActions.json"
162
- );
163
- const actions = JSON.parse(readFileSync(actionsPath, "utf-8"));
310
+ // Embedded action list (top actions for quick reference)
311
+ const topActions = {
312
+ // AI
313
+ "is.workflow.actions.askllm": { Name: "Ask LLM", Category: "AI" },
314
+ "is.workflow.actions.askchatgpt": { Name: "Ask ChatGPT", Category: "AI" },
315
+ "com.anthropic.claudeforipad.AskClaudeIntentExtension": { Name: "Ask Claude", Category: "AI" },
316
+ // Clock
317
+ "com.apple.clock.StartStopwatchIntent": { Name: "Start Stopwatch", Category: "Clock" },
318
+ "com.apple.clock.StopStopwatchIntent": { Name: "Stop Stopwatch", Category: "Clock" },
319
+ "com.apple.clock.CreateAlarmIntent": { Name: "Create Alarm", Category: "Clock" },
320
+ // Voice Memos
321
+ "com.apple.VoiceMemos.CreateRecordingIntent": { Name: "Create Recording", Category: "Voice Memos" },
322
+ "com.apple.VoiceMemos.PlayRecordingIntent": { Name: "Play Recording", Category: "Voice Memos" },
323
+ // System
324
+ "is.workflow.actions.appearance": { Name: "Set Dark/Light Mode", Category: "System" },
325
+ "is.workflow.actions.takescreenshot": { Name: "Take Screenshot", Category: "System" },
326
+ "is.workflow.actions.lockscreen": { Name: "Lock Screen", Category: "System" },
327
+ // Scripting
328
+ "is.workflow.actions.runshellscript": { Name: "Run Shell Script", Category: "Scripting" },
329
+ "is.workflow.actions.runapplescript": { Name: "Run AppleScript", Category: "Scripting" },
330
+ "is.workflow.actions.runjavascriptforautomation": { Name: "Run JavaScript", Category: "Scripting" },
331
+ // Files
332
+ "is.workflow.actions.file.getfile": { Name: "Get File", Category: "Files" },
333
+ "is.workflow.actions.file.savefile": { Name: "Save File", Category: "Files" },
334
+ "is.workflow.actions.file.renamefile": { Name: "Rename File", Category: "Files" },
335
+ "is.workflow.actions.file.revealfile": { Name: "Reveal in Finder", Category: "Files" },
336
+ // Text
337
+ "is.workflow.actions.gettext": { Name: "Get Text", Category: "Text" },
338
+ "is.workflow.actions.showresult": { Name: "Show Result", Category: "Text" },
339
+ "is.workflow.actions.alert": { Name: "Show Alert", Category: "Text" },
340
+ // Variables
341
+ "is.workflow.actions.setvariable": { Name: "Set Variable", Category: "Variables" },
342
+ "is.workflow.actions.getvariable": { Name: "Get Variable", Category: "Variables" },
343
+ // Clipboard
344
+ "is.workflow.actions.getclipboard": { Name: "Get Clipboard", Category: "Clipboard" },
345
+ "is.workflow.actions.setclipboard": { Name: "Set Clipboard", Category: "Clipboard" },
346
+ };
164
347
 
165
- let filtered = Object.entries(actions);
348
+ let filtered = Object.entries(topActions);
166
349
 
167
- // Filter by category
168
350
  if (category) {
169
351
  filtered = filtered.filter(
170
- ([_, action]) =>
171
- action.Category?.toLowerCase() === category.toLowerCase()
352
+ ([_, action]) => action.Category?.toLowerCase() === category.toLowerCase()
172
353
  );
173
354
  }
174
355
 
175
- // Filter by search term
176
356
  if (search) {
177
357
  const searchLower = search.toLowerCase();
178
358
  filtered = filtered.filter(
179
359
  ([id, action]) =>
180
360
  id.toLowerCase().includes(searchLower) ||
181
361
  action.Name?.toLowerCase().includes(searchLower) ||
182
- action.Description?.DescriptionSummary?.toLowerCase().includes(
183
- searchLower
184
- )
362
+ action.Category?.toLowerCase().includes(searchLower)
185
363
  );
186
364
  }
187
365
 
188
- // Format the results
189
366
  const results = filtered
190
- .slice(0, 50) // Limit to 50 results
191
- .map(([id, action]) => {
192
- return `• **${action.Name || id}**\n ID: \`${id}\`\n ${
193
- action.Description?.DescriptionSummary || "No description"
194
- }\n Category: ${action.Category || "Unknown"}`;
195
- })
196
- .join("\n\n");
197
-
198
- const total = filtered.length;
199
- const showing = Math.min(50, total);
367
+ .map(([id, action]) => `• **${action.Name}** - \`${id}\` (${action.Category})`)
368
+ .join("\n");
200
369
 
201
370
  return {
202
371
  content: [
203
372
  {
204
373
  type: "text",
205
- text: `Found ${total} action${
206
- total !== 1 ? "s" : ""
207
- } (showing ${showing}):\n\n${results}`,
374
+ text: `Found ${filtered.length} actions:\n\n${results}\n\n💡 This is a subset of 493 total actions. For the full list, see: https://github.com/cavingraves/scpl-macos-updated`,
208
375
  },
209
376
  ],
210
377
  };
@@ -224,22 +391,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
224
391
  }
225
392
  });
226
393
 
227
- // Resource handlers (for documentation)
394
+ // Resource handlers
228
395
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
229
396
  return {
230
397
  resources: [
231
- {
232
- uri: "scpl://actions/all",
233
- name: "All Available Actions",
234
- description: "Complete list of all 294 ScPL actions",
235
- mimeType: "text/markdown",
236
- },
237
- {
238
- uri: "scpl://actions/tahoe",
239
- name: "macOS Tahoe New Actions",
240
- description: "22 new actions added for macOS Tahoe",
241
- mimeType: "text/markdown",
242
- },
243
398
  {
244
399
  uri: "scpl://examples",
245
400
  name: "ScPL Examples",
@@ -253,83 +408,48 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
253
408
  server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
254
409
  const { uri } = request.params;
255
410
 
256
- if (uri === "scpl://actions/tahoe") {
257
- const content = readFileSync(
258
- join(process.cwd(), "..", "MACOS_TAHOE_UPDATES.md"),
259
- "utf-8"
260
- );
261
- return {
262
- contents: [
263
- {
264
- uri,
265
- mimeType: "text/markdown",
266
- text: content,
267
- },
268
- ],
269
- };
270
- }
271
-
272
411
  if (uri === "scpl://examples") {
273
412
  const examples = `# ScPL Examples
274
413
 
275
- ## Apple Intelligence Example
414
+ ## Apple Intelligence
276
415
  \`\`\`scpl
277
- # Ask Apple Intelligence to summarize text
278
- Text "The quick brown fox jumps over the lazy dog. This is a sample text."
279
- AskLLM model="Apple Intelligence" prompt="Summarize this in 5 words"
416
+ Text "Summarize this document"
417
+ AskLLM model="Apple Intelligence" prompt="Make it concise"
280
418
  ShowResult
281
419
  \`\`\`
282
420
 
283
- ## Shell Script Example
284
- \`\`\`scpl
285
- # Run a shell script and show the result
286
- RunShellScript shell="/bin/zsh" script="sw_vers"
287
- ShowResult "macOS Version"
288
- \`\`\`
289
-
290
- ## File Operations Example
421
+ ## Timer with Sound
291
422
  \`\`\`scpl
292
- # Get files from Desktop and show count
293
- GetFile path="~/Desktop" errorIfNotFound=false
294
- Count
295
- ShowResult "Files on Desktop"
423
+ # Start a 5-minute timer
424
+ StartTimer minutes=5
425
+ ShowAlert title="Timer Started" message="5 minutes"
296
426
  \`\`\`
297
427
 
298
- ## ChatGPT Integration Example
428
+ ## Shell Script
299
429
  \`\`\`scpl
300
- # Ask ChatGPT a question
301
- Text "What is the meaning of life?"
302
- AskChatGPT
430
+ RunShellScript shell="/bin/zsh" script="echo Hello World"
303
431
  ShowResult
304
432
  \`\`\`
305
433
 
306
- ## Combined Workflow Example
434
+ ## Clipboard Workflow
307
435
  \`\`\`scpl
308
- # Get clipboard, ask AI to improve it, copy back
309
436
  GetClipboard
310
- SetVariable v:originalText
311
-
312
- AskLLM model="Apple Intelligence" prompt="Improve this text for clarity and grammar: \\(v:originalText)"
437
+ SetVariable v:text
438
+ AskChatGPT prompt="Improve this: \\(v:text)"
313
439
  SetClipboard
314
- ShowAlert title="Text Improved" message="The improved text has been copied to your clipboard"
440
+ ShowAlert title="Done" message="Improved text copied!"
315
441
  \`\`\`
316
442
  `;
317
443
 
318
444
  return {
319
- contents: [
320
- {
321
- uri,
322
- mimeType: "text/markdown",
323
- text: examples,
324
- },
325
- ],
445
+ contents: [{ uri, mimeType: "text/markdown", text: examples }],
326
446
  };
327
447
  }
328
448
 
329
449
  throw new Error(`Unknown resource: ${uri}`);
330
450
  });
331
451
 
332
- // Start the server
452
+ // Start server
333
453
  async function main() {
334
454
  const transport = new StdioServerTransport();
335
455
  await server.connect(transport);
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "scpl-updated-mcp-server",
3
- "version": "1.0.0",
4
- "description": "MCP server for creating macOS Shortcuts using ScPL (macOS Tahoe updated fork)",
3
+ "version": "1.0.1",
4
+ "description": "AI-powered Apple Shortcuts creation with Claude Code! Generate macOS shortcuts using natural language. 493 actions available. MCP server for text-based shortcut programming. Vibe code your automation workflows.",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "bin": {
8
- "scpl-updated-mcp-server": "./index.js"
8
+ "scpl-updated-mcp-server": "index.js"
9
9
  },
10
10
  "scripts": {
11
11
  "start": "node index.js"