spacepal-mcp 0.1.0 → 0.2.0
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.
- package/README.md +24 -4
- package/index.js +211 -16
- package/package.json +29 -6
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Lets Claude, Cursor and other MCP clients work with your macOS Spaces through
|
|
|
4
4
|
[Spacepal](https://getspacepal.com): "switch to my Mail Space", "what's on Desktop 3?",
|
|
5
5
|
"name this desktop Research".
|
|
6
6
|
|
|
7
|
+

|
|
8
|
+
|
|
7
9
|
It runs locally over stdio. It reads the Space list Spacepal writes to
|
|
8
10
|
`~/Library/Application Support/Spacepal/state.json` and sends commands through the
|
|
9
11
|
`spacepal://` URL scheme. It makes no network calls.
|
|
@@ -17,17 +19,35 @@ It runs locally over stdio. It reads the Space list Spacepal writes to
|
|
|
17
19
|
|
|
18
20
|
| Tool | What it does |
|
|
19
21
|
|---|---|
|
|
20
|
-
| `list_spaces` | Every Space with its number, name, display, apps, and which one is active |
|
|
22
|
+
| `list_spaces` | Every Space with its number, name, display, apps, note, and which one is active |
|
|
21
23
|
| `current_space` | The Space you are on |
|
|
22
24
|
| `switch_space` | Switch by number or name, or step with `next`, `previous`, `back` |
|
|
23
25
|
| `move_window_to_space` | Send the frontmost window to a Space by number or name, or to `next` or `previous` |
|
|
24
26
|
| `rename_space` | Name a Space; an empty name restores "Desktop N" |
|
|
25
|
-
| `open_overview` | Open Spacepal's overview grid |
|
|
26
27
|
| `set_space_note` | Attach a short note to a Space |
|
|
27
|
-
| `
|
|
28
|
+
| `open_overview` | Open Spacepal's overview grid |
|
|
29
|
+
| `list_profiles` | Names of your saved profiles |
|
|
30
|
+
| `apply_profile` | Apply a saved profile (names, colours, notes and automations) |
|
|
31
|
+
| `apply_template` | Name the Spaces from a starter template: Developer, Designer, Freelancer, Student, Streamer |
|
|
32
|
+
| `set_up_space` | Switch to a Space and run its automations: open its apps, URLs and files |
|
|
33
|
+
| `save_window_layout` | Save which Space and position every window is in |
|
|
34
|
+
| `restore_window_layout` | Put windows back from the last saved layout |
|
|
35
|
+
| `save_sessions` | Save terminal sessions and Chrome windows on one Space or all of them |
|
|
36
|
+
| `restore_sessions` | Reopen saved sessions on their Spaces |
|
|
37
|
+
| `add_desktop` | Add a desktop on the current display |
|
|
38
|
+
| `remove_desktop` | Remove a desktop; its windows move to another one |
|
|
39
|
+
|
|
40
|
+
Every tool is marked with MCP hints, so clients can tell the read-only tools apart and ask
|
|
41
|
+
before `remove_desktop` or `apply_template`, which change your setup.
|
|
28
42
|
|
|
29
43
|
`move_window_to_space` acts on the frontmost window. In Claude Desktop that is usually
|
|
30
|
-
Claude's own window.
|
|
44
|
+
Claude's own window. It moves the window directly on macOS 14.0–14.4 and 26.4+, and by
|
|
45
|
+
dragging it on other versions. `restore_window_layout` can only move windows between
|
|
46
|
+
Spaces on 14.0–14.4 and 26.4+.
|
|
47
|
+
|
|
48
|
+
The MCP server is free. The tools follow the app's own limits: after Spacepal's 14-day
|
|
49
|
+
trial, names and notes stay free, while the overview, sessions and other Pro features need a
|
|
50
|
+
license.
|
|
31
51
|
|
|
32
52
|
## Install
|
|
33
53
|
|
package/index.js
CHANGED
|
@@ -13,13 +13,17 @@ import { join } from "node:path";
|
|
|
13
13
|
import { promisify } from "node:util";
|
|
14
14
|
|
|
15
15
|
const run = promisify(execFile);
|
|
16
|
-
const VERSION = "0.
|
|
16
|
+
const VERSION = "0.2.0";
|
|
17
17
|
|
|
18
18
|
// Direct build writes to Application Support; the App Store build is sandboxed.
|
|
19
|
-
const
|
|
20
|
-
join(homedir(), "Library/Application Support/Spacepal
|
|
21
|
-
join(homedir(), "Library/Containers/com.halberd.spacepal/Data/Library/Application Support/Spacepal
|
|
19
|
+
const SUPPORT_DIRS = [
|
|
20
|
+
join(homedir(), "Library/Application Support/Spacepal"),
|
|
21
|
+
join(homedir(), "Library/Containers/com.halberd.spacepal/Data/Library/Application Support/Spacepal"),
|
|
22
22
|
];
|
|
23
|
+
const STATE_FILES = SUPPORT_DIRS.map((d) => join(d, "state.json"));
|
|
24
|
+
const PROFILE_FILES = SUPPORT_DIRS.map((d) => join(d, "profiles.json"));
|
|
25
|
+
// Built into the app (Profiles.swift, SpaceTemplate.all()).
|
|
26
|
+
const TEMPLATES = ["Developer", "Designer", "Freelancer", "Student", "Streamer"];
|
|
23
27
|
|
|
24
28
|
const NOT_RUNNING = "Spacepal is not running. Open Spacepal (getspacepal.com) and try again.";
|
|
25
29
|
|
|
@@ -32,16 +36,30 @@ async function isRunning() {
|
|
|
32
36
|
}
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
async function
|
|
39
|
+
async function newestFile(paths) {
|
|
36
40
|
let newest = null;
|
|
37
|
-
for (const path of
|
|
41
|
+
for (const path of paths) {
|
|
38
42
|
try {
|
|
39
43
|
const { mtimeMs } = await stat(path);
|
|
40
44
|
if (!newest || mtimeMs > newest.mtimeMs) newest = { path, mtimeMs };
|
|
41
45
|
} catch {}
|
|
42
46
|
}
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
return newest?.path ?? null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function readState() {
|
|
51
|
+
const path = await newestFile(STATE_FILES);
|
|
52
|
+
return path ? JSON.parse(await readFile(path, "utf8")) : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function readProfiles() {
|
|
56
|
+
const path = await newestFile(PROFILE_FILES);
|
|
57
|
+
if (!path) return [];
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(await readFile(path, "utf8")).map((p) => p.name);
|
|
60
|
+
} catch {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
45
63
|
}
|
|
46
64
|
|
|
47
65
|
async function send(path, query = {}) {
|
|
@@ -104,9 +122,9 @@ server.registerTool(
|
|
|
104
122
|
"list_spaces",
|
|
105
123
|
{
|
|
106
124
|
title: "List Spaces",
|
|
107
|
-
description: "List every macOS Space (desktop) with its number, name, display, apps and which one is active.",
|
|
125
|
+
description: "List every macOS Space (desktop) with its number, name, display, the apps that have windows there, its note, and which one is active.",
|
|
108
126
|
inputSchema: {},
|
|
109
|
-
annotations: { readOnlyHint: true },
|
|
127
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
110
128
|
},
|
|
111
129
|
guarded(async () => {
|
|
112
130
|
if (!(await isRunning())) return fail(NOT_RUNNING);
|
|
@@ -115,7 +133,8 @@ server.registerTool(
|
|
|
115
133
|
const lines = state.spaces.map((s) => {
|
|
116
134
|
const label = s.isFullscreen ? "full screen" : `Desktop ${s.number ?? "?"}`;
|
|
117
135
|
const apps = s.apps?.length ? ` — ${s.apps.join(", ")}` : "";
|
|
118
|
-
|
|
136
|
+
const note = s.note ? ` (note: ${s.note})` : "";
|
|
137
|
+
return `${s.isActive ? "*" : " "} ${label}: ${s.name}${apps}${note}`;
|
|
119
138
|
});
|
|
120
139
|
const displays = new Set(state.spaces.map((s) => s.display)).size;
|
|
121
140
|
return {
|
|
@@ -133,7 +152,7 @@ server.registerTool(
|
|
|
133
152
|
title: "Current Space",
|
|
134
153
|
description: "Name and number of the Space you are on.",
|
|
135
154
|
inputSchema: {},
|
|
136
|
-
annotations: { readOnlyHint: true },
|
|
155
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
137
156
|
},
|
|
138
157
|
guarded(async () => {
|
|
139
158
|
if (!(await isRunning())) return fail(NOT_RUNNING);
|
|
@@ -150,6 +169,7 @@ server.registerTool(
|
|
|
150
169
|
title: "Switch Space",
|
|
151
170
|
description: "Switch to a Space by desktop number or name. Use \"next\", \"previous\" or \"back\" to step.",
|
|
152
171
|
inputSchema: { space: spaceArg },
|
|
172
|
+
annotations: { openWorldHint: false },
|
|
153
173
|
},
|
|
154
174
|
guarded(async ({ space }) => {
|
|
155
175
|
const before = (await readState())?.current;
|
|
@@ -175,8 +195,11 @@ server.registerTool(
|
|
|
175
195
|
title: "Move window to Space",
|
|
176
196
|
description:
|
|
177
197
|
"Send the frontmost window to another Space (by number or name, or \"next\"/\"previous\"). " +
|
|
178
|
-
"
|
|
198
|
+
"On macOS 14.0-14.4 and 26.4+ the window moves directly; on other versions Spacepal drags it, " +
|
|
199
|
+
"which takes a moment longer. The frontmost window is usually the one the user worked in last, " +
|
|
200
|
+
"not the MCP client's own window when it runs in a terminal.",
|
|
179
201
|
inputSchema: { space: spaceArg },
|
|
202
|
+
annotations: { openWorldHint: false },
|
|
180
203
|
},
|
|
181
204
|
guarded(async ({ space }) => {
|
|
182
205
|
const s = String(space).toLowerCase();
|
|
@@ -199,6 +222,7 @@ server.registerTool(
|
|
|
199
222
|
space: spaceArg.optional(),
|
|
200
223
|
name: z.string().max(60).describe("New name. Empty text restores \"Desktop N\"."),
|
|
201
224
|
},
|
|
225
|
+
annotations: { openWorldHint: false },
|
|
202
226
|
},
|
|
203
227
|
guarded(async ({ space, name }) => {
|
|
204
228
|
if (space !== undefined && !(await findSpace(space))) return fail(`No Space called "${space}".`);
|
|
@@ -214,10 +238,11 @@ server.registerTool(
|
|
|
214
238
|
title: "Open overview",
|
|
215
239
|
description: "Open Spacepal's overview grid of all Spaces on screen.",
|
|
216
240
|
inputSchema: {},
|
|
241
|
+
annotations: { openWorldHint: false },
|
|
217
242
|
},
|
|
218
243
|
guarded(async () => {
|
|
219
244
|
await send("overview");
|
|
220
|
-
return text("
|
|
245
|
+
return text("Asked Spacepal to open the overview. It opens if the trial is running or Spacepal is licensed; otherwise Spacepal shows its license window.");
|
|
221
246
|
}),
|
|
222
247
|
);
|
|
223
248
|
|
|
@@ -227,8 +252,10 @@ server.registerTool(
|
|
|
227
252
|
title: "Set Space note",
|
|
228
253
|
description: "Attach a short note to a Space (what it is for).",
|
|
229
254
|
inputSchema: { space: spaceArg.optional(), text: z.string().max(500) },
|
|
255
|
+
annotations: { openWorldHint: false },
|
|
230
256
|
},
|
|
231
257
|
guarded(async ({ space, text: note }) => {
|
|
258
|
+
if (space !== undefined && !(await findSpace(space))) return fail(`No Space called "${space}".`);
|
|
232
259
|
await send(`note/${ref(space)}`, { text: note });
|
|
233
260
|
return text("Note saved.");
|
|
234
261
|
}),
|
|
@@ -240,10 +267,178 @@ server.registerTool(
|
|
|
240
267
|
title: "Apply profile",
|
|
241
268
|
description: "Apply a saved Spacepal profile by name (names, layouts and rules).",
|
|
242
269
|
inputSchema: { name: z.string().min(1) },
|
|
270
|
+
annotations: { openWorldHint: false },
|
|
271
|
+
},
|
|
272
|
+
guarded(async ({ name }) => {
|
|
273
|
+
const names = await readProfiles();
|
|
274
|
+
const match = names.find((n) => n.toLowerCase() === name.toLowerCase());
|
|
275
|
+
if (!match) {
|
|
276
|
+
return fail(names.length ? `No profile called "${name}". Profiles: ${names.join(", ")}.` : "No saved profiles yet. Save one in Spacepal Settings, Profiles.");
|
|
277
|
+
}
|
|
278
|
+
await send(`profile/${encodeURIComponent(match)}`);
|
|
279
|
+
return text(`Applied profile "${match}".`);
|
|
280
|
+
}),
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
server.registerTool(
|
|
284
|
+
"list_profiles",
|
|
285
|
+
{
|
|
286
|
+
title: "List profiles",
|
|
287
|
+
description: "Names of the saved Spacepal profiles (sets of Space names, colours, notes and automations).",
|
|
288
|
+
inputSchema: {},
|
|
289
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
290
|
+
},
|
|
291
|
+
guarded(async () => {
|
|
292
|
+
const names = await readProfiles();
|
|
293
|
+
return text(names.length ? names.join("\n") : "No saved profiles yet.");
|
|
294
|
+
}),
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
server.registerTool(
|
|
298
|
+
"apply_template",
|
|
299
|
+
{
|
|
300
|
+
title: "Apply template",
|
|
301
|
+
description:
|
|
302
|
+
`Name and colour the Spaces from a built-in starter template: ${TEMPLATES.join(", ")}. ` +
|
|
303
|
+
"This replaces the current Space names; the user can save them first as a profile.",
|
|
304
|
+
inputSchema: { name: z.enum(TEMPLATES) },
|
|
305
|
+
annotations: { destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
243
306
|
},
|
|
244
307
|
guarded(async ({ name }) => {
|
|
245
|
-
await send(`
|
|
246
|
-
|
|
308
|
+
await send(`template/${encodeURIComponent(name)}`);
|
|
309
|
+
await settle();
|
|
310
|
+
return text(`Applied the ${name} template.`);
|
|
311
|
+
}),
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
server.registerTool(
|
|
315
|
+
"set_up_space",
|
|
316
|
+
{
|
|
317
|
+
title: "Set up Space",
|
|
318
|
+
description: "Switch to a Space and run its automations now: open its apps, URLs and files, run its Shortcut or script.",
|
|
319
|
+
inputSchema: { space: spaceArg.optional() },
|
|
320
|
+
annotations: { openWorldHint: false },
|
|
321
|
+
},
|
|
322
|
+
guarded(async ({ space }) => {
|
|
323
|
+
const target = space === undefined ? null : await findSpace(space);
|
|
324
|
+
if (space !== undefined && !target) return fail(`No Space called "${space}".`);
|
|
325
|
+
await send(`setup/${ref(space)}`);
|
|
326
|
+
return text(`Setting up ${target?.name ?? "the current Space"}.`);
|
|
327
|
+
}),
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
server.registerTool(
|
|
331
|
+
"save_window_layout",
|
|
332
|
+
{
|
|
333
|
+
title: "Save window layout",
|
|
334
|
+
description: "Save which Space and position every window is in, for this display setup. Replaces the previous manual save.",
|
|
335
|
+
inputSchema: {},
|
|
336
|
+
annotations: { idempotentHint: true, openWorldHint: false },
|
|
337
|
+
},
|
|
338
|
+
guarded(async () => {
|
|
339
|
+
await send("save-layout");
|
|
340
|
+
return text("Saved the window layout.");
|
|
341
|
+
}),
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
server.registerTool(
|
|
345
|
+
"restore_window_layout",
|
|
346
|
+
{
|
|
347
|
+
title: "Restore window layout",
|
|
348
|
+
description:
|
|
349
|
+
"Put windows back on the Spaces and positions from the last saved layout for this display setup. " +
|
|
350
|
+
"Moving windows between Spaces needs macOS 14.0-14.4 or 26.4+.",
|
|
351
|
+
inputSchema: {},
|
|
352
|
+
annotations: { openWorldHint: false },
|
|
353
|
+
},
|
|
354
|
+
guarded(async () => {
|
|
355
|
+
await send("restore-layout");
|
|
356
|
+
return text("Asked Spacepal to restore the saved layout. It shows how many windows it moved.");
|
|
357
|
+
}),
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
server.registerTool(
|
|
361
|
+
"save_sessions",
|
|
362
|
+
{
|
|
363
|
+
title: "Save sessions",
|
|
364
|
+
description:
|
|
365
|
+
"Save the terminal sessions and Chrome windows on a Space (or on every Space with all=true), " +
|
|
366
|
+
"so Spacepal can reopen them after a restart. Needs the Spacepal trial or a license.",
|
|
367
|
+
inputSchema: {
|
|
368
|
+
space: spaceArg.optional(),
|
|
369
|
+
all: z.boolean().optional().describe("Save every Space instead of one."),
|
|
370
|
+
},
|
|
371
|
+
annotations: { idempotentHint: true, openWorldHint: false },
|
|
372
|
+
},
|
|
373
|
+
guarded(async ({ space, all }) => {
|
|
374
|
+
if (all) {
|
|
375
|
+
await send("session-save/all");
|
|
376
|
+
return text("Asked Spacepal to save sessions on every Space. It shows how many windows it saved.");
|
|
377
|
+
}
|
|
378
|
+
if (space !== undefined && !(await findSpace(space))) return fail(`No Space called "${space}".`);
|
|
379
|
+
await send(`session-save/${ref(space)}`);
|
|
380
|
+
return text("Asked Spacepal to save the sessions. It shows how many windows it saved.");
|
|
381
|
+
}),
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
server.registerTool(
|
|
385
|
+
"restore_sessions",
|
|
386
|
+
{
|
|
387
|
+
title: "Restore sessions",
|
|
388
|
+
description: "Reopen the saved terminal sessions and Chrome windows on their Spaces. Needs the Spacepal trial or a license.",
|
|
389
|
+
inputSchema: {},
|
|
390
|
+
annotations: { openWorldHint: false },
|
|
391
|
+
},
|
|
392
|
+
guarded(async () => {
|
|
393
|
+
await send("session-restore");
|
|
394
|
+
return text("Asked Spacepal to reopen the saved sessions.");
|
|
395
|
+
}),
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
server.registerTool(
|
|
399
|
+
"add_desktop",
|
|
400
|
+
{
|
|
401
|
+
title: "Add desktop",
|
|
402
|
+
description: "Add a new desktop (Space) on the current display.",
|
|
403
|
+
inputSchema: {},
|
|
404
|
+
annotations: { openWorldHint: false },
|
|
405
|
+
},
|
|
406
|
+
guarded(async () => {
|
|
407
|
+
const before = (await readState())?.spaces.length ?? 0;
|
|
408
|
+
await send("add-desktop");
|
|
409
|
+
const end = Date.now() + 4000;
|
|
410
|
+
while (Date.now() < end) {
|
|
411
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
412
|
+
const after = (await readState())?.spaces.length ?? 0;
|
|
413
|
+
if (after > before) return text(`Added a desktop. ${after} Spaces now.`);
|
|
414
|
+
}
|
|
415
|
+
return text("Asked Spacepal to add a desktop; it has not confirmed it yet.");
|
|
416
|
+
}),
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
server.registerTool(
|
|
420
|
+
"remove_desktop",
|
|
421
|
+
{
|
|
422
|
+
title: "Remove desktop",
|
|
423
|
+
description:
|
|
424
|
+
"Remove a desktop by number or name. Its windows move to another desktop, as when you close it in Mission Control. " +
|
|
425
|
+
"Full-screen Spaces cannot be removed this way. Ask the user before calling this.",
|
|
426
|
+
inputSchema: { space: spaceArg },
|
|
427
|
+
annotations: { destructiveHint: true, openWorldHint: false },
|
|
428
|
+
},
|
|
429
|
+
guarded(async ({ space }) => {
|
|
430
|
+
const target = await findSpace(space);
|
|
431
|
+
if (!target) return fail(`No Space called "${space}". Call list_spaces to see the names.`);
|
|
432
|
+
if (target.isFullscreen) return fail(`${target.name} is a full-screen Space. Leave full screen in that app instead.`);
|
|
433
|
+
const before = (await readState())?.spaces.length ?? 0;
|
|
434
|
+
await send(`remove-desktop/${ref(space)}`);
|
|
435
|
+
const end = Date.now() + 4000;
|
|
436
|
+
while (Date.now() < end) {
|
|
437
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
438
|
+
const after = (await readState())?.spaces.length ?? 0;
|
|
439
|
+
if (after < before) return text(`Removed ${target.name}. ${after} Spaces now.`);
|
|
440
|
+
}
|
|
441
|
+
return text(`Asked Spacepal to remove ${target.name}; it has not confirmed it yet.`);
|
|
247
442
|
}),
|
|
248
443
|
);
|
|
249
444
|
|
package/package.json
CHANGED
|
@@ -1,15 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spacepal-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MCP server for Spacepal: list, switch, rename macOS Spaces and move windows between them from Claude, Cursor or any MCP client.",
|
|
5
5
|
"mcpName": "com.getspacepal/spacepal",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"bin": {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"
|
|
11
|
-
|
|
7
|
+
"bin": {
|
|
8
|
+
"spacepal-mcp": "index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"index.js",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE",
|
|
14
|
+
"server.json"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"os": [
|
|
20
|
+
"darwin"
|
|
21
|
+
],
|
|
22
|
+
"keywords": [
|
|
23
|
+
"mcp",
|
|
24
|
+
"model-context-protocol",
|
|
25
|
+
"macos",
|
|
26
|
+
"spaces",
|
|
27
|
+
"mission-control",
|
|
28
|
+
"spacepal",
|
|
29
|
+
"claude"
|
|
30
|
+
],
|
|
12
31
|
"homepage": "https://getspacepal.com/integrations/mcp/",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/TanKucukhas/spacepal-mcp.git"
|
|
35
|
+
},
|
|
13
36
|
"author": "Halberd LLC",
|
|
14
37
|
"license": "MIT",
|
|
15
38
|
"dependencies": {
|
package/server.json
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
"name": "com.getspacepal/spacepal",
|
|
4
4
|
"title": "Spacepal",
|
|
5
5
|
"description": "List, switch and rename macOS Spaces and move windows between them. Needs the Spacepal app.",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.2.0",
|
|
7
7
|
"websiteUrl": "https://getspacepal.com/integrations/mcp/",
|
|
8
8
|
"packages": [
|
|
9
9
|
{
|
|
10
10
|
"registryType": "npm",
|
|
11
11
|
"identifier": "spacepal-mcp",
|
|
12
|
-
"version": "0.
|
|
12
|
+
"version": "0.2.0",
|
|
13
13
|
"transport": { "type": "stdio" }
|
|
14
14
|
}
|
|
15
15
|
]
|