supergravity-mcp 0.1.2 → 0.1.5

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 CHANGED
@@ -1,5 +1,7 @@
1
1
  # supergravity-mcp
2
2
 
3
+ ![supergravity](https://raw.githubusercontent.com/presidentrice/supergravity-mcp/main/assets/hero.jpg)
4
+
3
5
  An MCP server that lets Claude Code (or any MCP client) delegate tasks to the
4
6
  **Google Antigravity** desktop app — by driving its real, already-logged-in UI.
5
7
  No reverse engineering, no extracted credentials, no unofficial API calls.
@@ -27,33 +29,36 @@ same way a human would use it, so it works anywhere the app itself works.
27
29
 
28
30
  ## Setup
29
31
 
30
- ```bash
31
- npm install
32
- npm run build
33
- ```
34
-
35
32
  Antigravity must be installed and already signed in. By default the tool
36
33
  looks for it at `/Applications/Antigravity.app` or `~/Desktop/Antigravity.app`;
37
34
  set `ANTIGRAVITY_APP_PATH` if it lives somewhere else.
38
35
 
39
- Register it with Claude Code:
36
+ ### Easiest: install the plugin (MCP server + skill together)
37
+
38
+ ```bash
39
+ claude plugin marketplace add presidentrice/supergravity-mcp
40
+ claude plugin install supergravity@supergravity-mcp
41
+ ```
42
+
43
+ This gets you both the `delegate_to_antigravity` / `get_antigravity_quota`
44
+ tools and the `supergravity:delegate` skill, which teaches Claude how to
45
+ route natural requests ("use gemini", "ask antigravity", "check antigravity's
46
+ quota") to the right tool call and model.
47
+
48
+ ### Or: just the MCP server, no skill
40
49
 
41
50
  ```bash
42
51
  claude mcp add supergravity -- npx -y supergravity-mcp
43
52
  ```
44
53
 
45
- ### Optional: the skill
54
+ Works fine without the skill — Claude still has the tools, just less
55
+ built-in guidance on picking a model or handling a quota-exhausted error.
46
56
 
47
- `skills/supergravity/SKILL.md` teaches Claude how to route natural requests
48
- ("use gemini", "ask antigravity", "check antigravity's quota") to the right
49
- tool call and model — without it, Claude still has the tools, just less
50
- guidance on picking a model or handling a quota-exhausted error. Install it
51
- globally so it applies everywhere:
57
+ ### Building from source
52
58
 
53
59
  ```bash
54
- mkdir -p ~/.claude/skills/supergravity
55
- curl -o ~/.claude/skills/supergravity/SKILL.md \
56
- https://raw.githubusercontent.com/presidentrice/supergravity-mcp/main/skills/supergravity/SKILL.md
60
+ npm install
61
+ npm run build
57
62
  ```
58
63
 
59
64
  ## The tools
@@ -32,34 +32,53 @@ GPT-OSS 120B (~$0.03-0.09/$0.10-0.40 — far cheaper than the rest) — open-wei
32
32
 
33
33
  Gemini models and Claude+GPT models draw from SEPARATE Antigravity quota pools — call getAntigravityQuota() first if unsure which pool has room.
34
34
  `.trim();
35
+ // Antigravity is a single window shared by every call. Two delegations
36
+ // running at once would type into and read from the same conversation,
37
+ // corrupting both. Every entry point below is serialized through this lock
38
+ // so calls queue up instead of racing each other.
39
+ let lock = Promise.resolve();
40
+ function withLock(fn) {
41
+ const run = lock.then(fn, fn);
42
+ lock = run.then(() => undefined, () => undefined);
43
+ return run;
44
+ }
35
45
  export async function delegateToAntigravity(opts) {
36
- const { task, model, timeoutMs = 120_000 } = opts;
37
- await ensureAntigravityReady();
38
- const target = await getMainPageTarget();
39
- const session = new CdpSession(target.webSocketDebuggerUrl);
40
- try {
41
- await startNewConversation(session);
42
- if (model)
43
- await selectModel(session, model);
44
- await pasteAndSend(session, task);
45
- return await waitForReply(session, task, timeoutMs);
46
- }
47
- finally {
48
- session.close();
49
- }
46
+ return withLock(async () => {
47
+ const { task, model, timeoutMs = 120_000 } = opts;
48
+ await ensureAntigravityReady();
49
+ const target = await getMainPageTarget();
50
+ const session = new CdpSession(target.webSocketDebuggerUrl);
51
+ try {
52
+ // The CDP debug port opens before Antigravity's own UI finishes mounting
53
+ // (its window loads content from a local server that itself needs a
54
+ // moment to boot). On a cold start this can leave the chat input
55
+ // missing for several seconds after we've already connected.
56
+ await waitForChatReady(session);
57
+ await startNewConversation(session);
58
+ if (model)
59
+ await selectModel(session, model);
60
+ await pasteAndSend(session, task);
61
+ return await waitForReply(session, task, timeoutMs);
62
+ }
63
+ finally {
64
+ session.close();
65
+ }
66
+ });
50
67
  }
51
68
  export async function getAntigravityQuota() {
52
- await ensureAntigravityReady();
53
- const target = await getMainPageTarget();
54
- const session = new CdpSession(target.webSocketDebuggerUrl);
55
- try {
56
- const raw = await openSettingsModelsTab(session);
57
- return parseQuotaText(raw);
58
- }
59
- finally {
60
- await closeSettings(session);
61
- session.close();
62
- }
69
+ return withLock(async () => {
70
+ await ensureAntigravityReady();
71
+ const target = await getMainPageTarget();
72
+ const session = new CdpSession(target.webSocketDebuggerUrl);
73
+ try {
74
+ const raw = await openSettingsModelsTab(session);
75
+ return parseQuotaText(raw);
76
+ }
77
+ finally {
78
+ await closeSettings(session);
79
+ session.close();
80
+ }
81
+ });
63
82
  }
64
83
  async function openSettingsModelsTab(session) {
65
84
  await session.evaluate(`
@@ -258,6 +277,26 @@ class CdpSession {
258
277
  }
259
278
  }
260
279
  // ---------- UI actions (selectors verified against Antigravity 2.2.1) ----------
280
+ async function waitForChatReady(session, timeoutMs = 45_000) {
281
+ const start = Date.now();
282
+ while (Date.now() - start < timeoutMs) {
283
+ const state = await session.evaluate(`
284
+ (() => {
285
+ if (document.querySelector('[contenteditable="true"][aria-label="Message input"]')) return 'ready';
286
+ const text = document.body.innerText || '';
287
+ if (text.includes('Continue with Google') || text.includes('Welcome to Antigravity')) return 'needs-signin';
288
+ return 'loading';
289
+ })()
290
+ `);
291
+ if (state === "ready")
292
+ return;
293
+ if (state === "needs-signin") {
294
+ throw new Error("Antigravity needs you to sign in. Open the app and click \"Continue with Google\", then try again — this can't be automated (it's your Google login).");
295
+ }
296
+ await sleep(500);
297
+ }
298
+ throw new Error(`Antigravity's chat UI didn't finish loading within ${timeoutMs}ms of launch. It may still be starting up — try again in a few seconds.`);
299
+ }
261
300
  async function startNewConversation(session) {
262
301
  await session.evaluate(`
263
302
  (() => {
@@ -377,10 +416,15 @@ async function waitForReply(session, taskText, timeoutMs) {
377
416
  continue;
378
417
  if (threadText === lastText) {
379
418
  stableCount++;
380
- // Require the "Thought for Ns" marker so we don't return early on the
381
- // brief window right after sending, before Antigravity starts replying.
382
- if (stableCount >= 2 && /Thought for/.test(threadText)) {
383
- return extractReply(threadText, taskText);
419
+ // Two consecutive stable reads plus actual reply content (not just our
420
+ // own sent message sitting there) means it's done. "Thought for Ns" is
421
+ // NOT a reliable signal Antigravity skips it entirely for fast/simple
422
+ // answers, so requiring it caused real replies to be missed until
423
+ // timeout even though the answer was already on screen.
424
+ if (stableCount >= 2) {
425
+ const candidate = extractReply(threadText, taskText);
426
+ if (candidate)
427
+ return candidate;
384
428
  }
385
429
  }
386
430
  else {
@@ -391,13 +435,26 @@ async function waitForReply(session, taskText, timeoutMs) {
391
435
  throw new Error(`Timed out after ${timeoutMs}ms waiting for Antigravity's reply.`);
392
436
  }
393
437
  function extractReply(threadText, taskText) {
438
+ // We only search on the first 200 chars (waitForReply's DOM lookup does the
439
+ // same, to keep every poll's evaluate() call cheap on huge tasks) - but
440
+ // slicing by that same truncated length left a tail of our own message
441
+ // behind for anything longer than 200 chars. Instead, find where OUR
442
+ // message actually ends by locating the timestamp Antigravity renders
443
+ // immediately after it, which appears regardless of message length.
394
444
  const needle = taskText.slice(0, 200);
395
445
  const idx = threadText.lastIndexOf(needle);
396
- const after = idx >= 0 ? threadText.slice(idx) : threadText;
397
- const thoughtMatch = after.match(/Thought for[^\n]*\n+/);
398
- let rest = thoughtMatch ? after.slice(after.indexOf(thoughtMatch[0]) + thoughtMatch[0].length) : after;
399
- rest = rest.replace(/\n+\d{1,2}:\d{2}\s*$/, "").trim();
400
- return rest;
446
+ const searchFrom = idx >= 0 ? idx + needle.length : 0;
447
+ const closingTimestamp = threadText.slice(searchFrom).match(/\d{1,2}:\d{2}/);
448
+ let after = closingTimestamp
449
+ ? threadText.slice(searchFrom + closingTimestamp.index + closingTimestamp[0].length)
450
+ : threadText.slice(searchFrom);
451
+ // strip the "Thought for Ns" line when present - not always there
452
+ const thoughtMatch = after.match(/^\n*Thought for[^\n]*\n+/);
453
+ if (thoughtMatch)
454
+ after = after.slice(thoughtMatch[0].length);
455
+ // strip the trailing timestamp under the reply itself
456
+ after = after.replace(/\n+\d{1,2}:\d{2}\s*$/, "");
457
+ return after.trim();
401
458
  }
402
459
  function sleep(ms) {
403
460
  return new Promise((resolve) => setTimeout(resolve, ms));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supergravity-mcp",
3
- "version": "0.1.2",
3
+ "version": "0.1.5",
4
4
  "description": "🚀 MCP server that lets Claude (or any MCP client) delegate tasks to Google's Antigravity desktop app 🌌. Built for Intel Macs 💻 that Antigravity's own CLI doesn't support.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,14 +1,15 @@
1
1
  ---
2
- name: supergravity
3
- description: Use when the user wants to delegate a task to Google's Antigravity desktop app — phrases like "use gemini", "ask antigravity", "have claude in antigravity do this", "offload this", or "check antigravity's quota". Explains which model to pick and how to handle a disabled-send-button error. Requires the supergravity-mcp MCP server (delegate_to_antigravity, get_antigravity_quota tools) to be connected.
2
+ name: delegate
3
+ description: Use when the user wants to delegate a task to Google's Antigravity desktop app — phrases like "use gemini", "ask antigravity", "have claude in antigravity do this", "offload this", or "check antigravity's quota". Explains which model to pick and how to handle a disabled-send-button error. Requires the supergravity MCP server (delegate_to_antigravity, get_antigravity_quota tools) to be connected.
4
4
  ---
5
5
 
6
- # supergravity
6
+ # delegate
7
7
 
8
8
  Antigravity is a separate AI desktop app (Google's). This skill runs tasks
9
- through it via the `supergravity-mcp` MCP server, instead of answering
10
- yourself useful when the user explicitly asks to use it, wants a second
11
- opinion from a different model family, or wants to offload work.
9
+ through it via the `supergravity` MCP server's `delegate_to_antigravity` /
10
+ `get_antigravity_quota` tools, instead of answering yourself useful when
11
+ the user explicitly asks to use it, wants a second opinion from a different
12
+ model family, or wants to offload work.
12
13
 
13
14
  ## When to trigger
14
15
 
@@ -18,6 +19,21 @@ Any explicit ask to use Antigravity or one of the models it hosts:
18
19
  generic requests that don't name Antigravity or one of its models — just
19
20
  answer normally.
20
21
 
22
+ Load this skill *before* calling either tool the first time in a
23
+ conversation, not after something already went wrong — the model-picking and
24
+ quota-pool guidance below changes which tool call you should even make.
25
+
26
+ ## You don't need to check if Antigravity is running
27
+
28
+ `delegate_to_antigravity` launches Antigravity itself if it isn't already
29
+ open, and waits for its chat UI to finish loading before typing anything —
30
+ you don't need to check first or tell the user to open it. Just call the
31
+ tool. The only implication: the very first call in a session can take
32
+ 15-30+ seconds longer than normal (cold app launch) before anything visible
33
+ happens. If a call fails specifically because the app couldn't be found at
34
+ all, the error names the exact env var (`ANTIGRAVITY_APP_PATH`) to fix it —
35
+ that's a real install problem, not something to retry.
36
+
21
37
  ## Picking a model
22
38
 
23
39
  If the user names a specific model ("use claude opus", "use gemini flash"),
@@ -44,6 +60,22 @@ until it refreshes — don't retry the same model. Instead:
44
60
  3. Otherwise, tell the user which pool is out and when it refreshes — don't
45
61
  silently fall back without saying so if they asked for a specific model.
46
62
 
63
+ ## Code-editing tasks: verify with the filesystem, not the reply text
64
+
65
+ If the task involves editing code, Antigravity works inside a real project
66
+ folder on disk (its "Projects" sidebar is literal directories, same
67
+ filesystem you already have access to) — it actually writes the files, it
68
+ doesn't just describe changes in chat. Its reply text is a narrative summary
69
+ written by the model, not the deliverable, and can be vague or incomplete
70
+ ("Updated the function as requested").
71
+
72
+ So after a code-editing delegation, don't parse the reply for specifics —
73
+ go check the real files yourself: `git diff`, `git status`, or just read the
74
+ file. You have direct filesystem access on this machine; use it instead of
75
+ trusting prose. Only fall back to the reply text for context Antigravity
76
+ wouldn't have written to disk (its reasoning, or a plain Q&A task with no
77
+ files involved).
78
+
47
79
  ## Other failure modes
48
80
 
49
81
  - Antigravity not installed / not found: the error will say so — tell the