granttap-mcp 0.8.37 → 0.8.39
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 +6 -1
- package/apps/bridge/src/mesh/admin/capsule.ts +2 -0
- package/apps/bridge/src/mesh/runtime/dependencies.ts +1 -1
- package/apps/bridge/src/mesh/runtime/handoff.ts +4 -1
- package/apps/bridge/src/mesh/runtime/index.ts +5 -5
- package/apps/mcp/src/connection-center/widget.html +1 -1
- package/apps/mcp/src/desktop/delivery/README.md +7 -1
- package/apps/mcp/src/desktop/delivery/task-handoff.ts +57 -0
- package/apps/mcp/src/desktop/engine-bridge.ts +2 -1
- package/apps/mcp/src/desktop/native-access/README.md +3 -0
- package/apps/mcp/src/desktop/native-access/consent-page.ts +43 -0
- package/apps/mcp/src/desktop/native-access/routes.ts +4 -4
- package/apps/mcp/src/desktop/operations/index.ts +2 -0
- package/cursor-plugin/README.md +1 -1
- package/cursor-plugin/mcp.json +1 -1
- package/cursor-plugin/stdio-bootstrap.cjs +1 -1
- package/package.json +1 -1
- package/packages/protocol/messages/mesh/events.ts +4 -0
package/README.md
CHANGED
|
@@ -170,7 +170,8 @@ decided by the runtime after receipt verification.
|
|
|
170
170
|
|
|
171
171
|
The first executable handoff path is Claude Code ↔ Codex across linked
|
|
172
172
|
computers. GrantTap builds a bounded Task Capsule from explicit task/git facts,
|
|
173
|
-
requires
|
|
173
|
+
requires authorization from the phone or the owning Mac's local desktop channel,
|
|
174
|
+
creates a separate target branch/worktree,
|
|
174
175
|
starts the target execution, and returns a receipt bound to the exact capsule.
|
|
175
176
|
Cursor uses the same provider-neutral schema and trusted caller attribution.
|
|
176
177
|
Grok Build remains observable where available, but does not
|
|
@@ -192,6 +193,10 @@ the commit fetches that branch once before refusing. A handoff can also stay on
|
|
|
192
193
|
one computer: the same Task continues with another agent there, in a worktree
|
|
193
194
|
of its own from the same commit, taken up at once by the computer that
|
|
194
195
|
prepared it.
|
|
196
|
+
The person can select an advertised destination model and add a bounded comment.
|
|
197
|
+
Both travel inside the encrypted capsule; the destination includes the comment
|
|
198
|
+
in the new execution's opening message and starts the selected model. A model
|
|
199
|
+
change on the same computer and provider is a new execution of the same Task.
|
|
195
200
|
|
|
196
201
|
### Pause and resume
|
|
197
202
|
|
|
@@ -70,6 +70,8 @@ export function buildTaskCapsule(
|
|
|
70
70
|
targetProvider: request.targetProvider,
|
|
71
71
|
targetActorId: request.targetActorId,
|
|
72
72
|
targetComputer: request.targetComputer,
|
|
73
|
+
targetModel: request.targetModel,
|
|
74
|
+
userComment: request.userComment,
|
|
73
75
|
repository: project.canonicalRepositoryId,
|
|
74
76
|
baseSha: head,
|
|
75
77
|
branch: session.branch ?? git(cwd, ["branch", "--show-current"]),
|
|
@@ -22,7 +22,7 @@ export type MeshRuntimeDependencies = {
|
|
|
22
22
|
now: () => number;
|
|
23
23
|
eventId: () => string;
|
|
24
24
|
providerEnabled: (provider: Exclude<MeshProvider, "grok_bot">) => boolean;
|
|
25
|
-
start: (provider: MeshProvider, prompt: string, cwd: string) => Promise<StartResult>;
|
|
25
|
+
start: (provider: MeshProvider, prompt: string, cwd: string, model?: string) => Promise<StartResult>;
|
|
26
26
|
deliver: typeof deliverToSession;
|
|
27
27
|
send: (
|
|
28
28
|
client: RelayClient,
|
|
@@ -19,6 +19,7 @@ function capsulePrompt(capsule: TaskCapsule): string {
|
|
|
19
19
|
`Base SHA: ${capsule.baseSha}`,
|
|
20
20
|
];
|
|
21
21
|
if (capsule.latestCommit) lines.push(`Latest commit: ${capsule.latestCommit}`);
|
|
22
|
+
if (capsule.userComment) lines.push(`User comment: ${capsule.userComment}`);
|
|
22
23
|
if (capsule.checkpoint) {
|
|
23
24
|
const { status, files, excluded } = capsule.checkpoint;
|
|
24
25
|
const line = status === "complete"
|
|
@@ -138,7 +139,9 @@ async function acceptHandoff(
|
|
|
138
139
|
revision,
|
|
139
140
|
);
|
|
140
141
|
if (!worktree) return rejectHandoff(deps, client, event, "A separate handoff worktree could not be created.");
|
|
141
|
-
const result = await deps.start(
|
|
142
|
+
const result = await deps.start(
|
|
143
|
+
capsule.targetProvider, capsulePrompt(capsule), worktree.path, capsule.targetModel,
|
|
144
|
+
);
|
|
142
145
|
if (!result.ok || !result.sessionId) {
|
|
143
146
|
const reason = result.ok ? "The target agent did not create a session." : result.error;
|
|
144
147
|
return rejectHandoff(deps, client, event, reason);
|
|
@@ -62,11 +62,11 @@ const defaultDependencies: MeshRuntimeDependencies = {
|
|
|
62
62
|
now: Date.now,
|
|
63
63
|
eventId: randomUUID,
|
|
64
64
|
providerEnabled: isProviderEnabled,
|
|
65
|
-
start: async (provider, prompt, cwd) => {
|
|
66
|
-
if (provider === "claude") return createClaudeSession(prompt, cwd);
|
|
67
|
-
if (provider === "codex") return createCodexSession(prompt, cwd);
|
|
68
|
-
if (provider === "cursor") return createCursorSession(prompt, cwd);
|
|
69
|
-
return createGrokSession(prompt, cwd);
|
|
65
|
+
start: async (provider, prompt, cwd, model) => {
|
|
66
|
+
if (provider === "claude") return createClaudeSession(prompt, cwd, 240_000, [], model);
|
|
67
|
+
if (provider === "codex") return createCodexSession(prompt, cwd, 240_000, [], model);
|
|
68
|
+
if (provider === "cursor") return createCursorSession(prompt, cwd, 240_000, [], model);
|
|
69
|
+
return createGrokSession(prompt, cwd, 240_000, [], model);
|
|
70
70
|
},
|
|
71
71
|
deliver: deliverToSession,
|
|
72
72
|
hasCommit: repositoryHasCommit,
|
|
@@ -37,7 +37,7 @@ details{border-top:1px solid #8884;padding-top:14px}summary{cursor:pointer;font-
|
|
|
37
37
|
<dt>Last encrypted message from iPhone</dt><dd id="phone">Not observed</dd></dl>
|
|
38
38
|
<h3>Provider readiness</h3><ul id="providers"></ul>
|
|
39
39
|
<p>QR pairs a phone directly. A passkey links this Mac to your GrantTap account so your signed-in phone can discover its computers. Provider sign-in is separate.</p>
|
|
40
|
-
<p>Missing hooks or background sync? Run <code>npm install -g granttap-mcp@0.8.
|
|
40
|
+
<p>Missing hooks or background sync? Run <code>npm install -g granttap-mcp@0.8.39</code> and <code>granttap setup</code> locally.
|
|
41
41
|
A saved pairing alone does not prove your phone or the background helper is online.</p>
|
|
42
42
|
<p><a href="https://granttap.com" target="_blank" rel="noopener noreferrer">Website</a> ·
|
|
43
43
|
<a href="https://granttap.com/privacy" target="_blank" rel="noopener noreferrer">Privacy</a> ·
|
|
@@ -10,7 +10,13 @@ same limits as encrypted phone messages. Invalid batches are rejected rather
|
|
|
10
10
|
than silently losing files. The existing provider attachment pipeline stages
|
|
11
11
|
images and arbitrary documents; the native client removes its batch after reply.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
`desktop.task_handoff` accepts a bounded handoff request from the authenticated
|
|
14
|
+
Mac desktop channel. It verifies the exact owning local execution, then asks the
|
|
15
|
+
canonical Mesh runtime to prepare the encrypted Task Capsule. A destination
|
|
16
|
+
model and user comment are carried with the Task, including for another computer.
|
|
17
|
+
|
|
18
|
+
Tests: `tests/desktop/desktop-task-send.test.ts`,
|
|
19
|
+
`tests/desktop/delivery/task-handoff.test.ts`, local `tests/attachments.test.ts`.
|
|
14
20
|
Parent: [Desktop local channel](../README.md).
|
|
15
21
|
|
|
16
22
|
This MCP component is covered by the repository [MIT License](../../../../../LICENSE).
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { RelayClient } from "../../../../../packages/core/relay-client";
|
|
3
|
+
import { MeshHandoffPrepare, type MeshHandoffPrepare as HandoffRequest } from "../../../../../packages/protocol/schema";
|
|
4
|
+
import { configDir, loadConfig, machineConfigPath } from "../../../../bridge/src/config";
|
|
5
|
+
import { applyNetworkRoute } from "../../../../bridge/src/device-network/settings";
|
|
6
|
+
import { observedComputerId } from "../../../../bridge/src/mesh/identity/computer";
|
|
7
|
+
import { prepareMeshHandoff } from "../../../../bridge/src/mesh/runtime";
|
|
8
|
+
import { readStoreState } from "../../../../bridge/src/mesh/store/state";
|
|
9
|
+
import { controllerPeerGate } from "../../../../bridge/src/pairing/controllers";
|
|
10
|
+
|
|
11
|
+
type Prepare = (request: HandoffRequest) => Promise<boolean>;
|
|
12
|
+
|
|
13
|
+
async function prepareThroughRelay(request: HandoffRequest): Promise<boolean> {
|
|
14
|
+
const config = applyNetworkRoute(loadConfig(machineConfigPath()));
|
|
15
|
+
const client = new RelayClient(config, { peerAllowed: controllerPeerGate(config) });
|
|
16
|
+
try {
|
|
17
|
+
await client.connect();
|
|
18
|
+
return await prepareMeshHandoff(client, request);
|
|
19
|
+
} finally {
|
|
20
|
+
client.close();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Same-user Mac request, limited to the exact Task owner on this computer. */
|
|
25
|
+
export async function desktopTaskHandoff(
|
|
26
|
+
input: unknown,
|
|
27
|
+
storePath = join(configDir(), "project-mesh.json"),
|
|
28
|
+
prepare: Prepare = prepareThroughRelay,
|
|
29
|
+
localComputer = observedComputerId(),
|
|
30
|
+
): Promise<unknown> {
|
|
31
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return undefined;
|
|
32
|
+
const row = input as Record<string, unknown>;
|
|
33
|
+
const request = MeshHandoffPrepare.safeParse({
|
|
34
|
+
type: "mesh.handoff.prepare",
|
|
35
|
+
sessionId: row.session_id, projectId: row.project_id, taskId: row.task_id,
|
|
36
|
+
targetProvider: row.target_provider, targetComputer: row.target_computer,
|
|
37
|
+
targetModel: row.target_model, userComment: row.user_comment,
|
|
38
|
+
checkpoint: row.checkpoint, push: row.push, createdAt: Date.now(),
|
|
39
|
+
});
|
|
40
|
+
if (!request.success) return undefined;
|
|
41
|
+
const loaded = readStoreState(storePath);
|
|
42
|
+
if (loaded.status !== "ok") return undefined;
|
|
43
|
+
const task = loaded.state.tasks.find((item) => item.taskId === request.data.taskId);
|
|
44
|
+
const execution = loaded.state.executions.find((item) =>
|
|
45
|
+
item.taskId === request.data.taskId && item.sessionId === request.data.sessionId
|
|
46
|
+
&& item.endedAt == null && item.computerId === localComputer);
|
|
47
|
+
if (task?.projectId !== request.data.projectId || task.ownerSessionId !== request.data.sessionId
|
|
48
|
+
|| !execution?.workspace) return undefined;
|
|
49
|
+
try {
|
|
50
|
+
const accepted = await prepare(request.data);
|
|
51
|
+
return { operation: "desktop.task_handoff", accepted,
|
|
52
|
+
error: accepted ? null : "The handoff was not ready. Check the Task and its repository." };
|
|
53
|
+
} catch {
|
|
54
|
+
return { operation: "desktop.task_handoff", accepted: false,
|
|
55
|
+
error: "The local relay or handoff service is unavailable." };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -22,6 +22,7 @@ export const DESKTOP_OPERATIONS = new Set([
|
|
|
22
22
|
"desktop.installed_skills", "desktop.policy_status", "desktop.policy_set", "desktop.project_auto_accept",
|
|
23
23
|
"desktop.mesh_snapshots",
|
|
24
24
|
"desktop.task_send",
|
|
25
|
+
"desktop.task_handoff",
|
|
25
26
|
"desktop.task_create",
|
|
26
27
|
"desktop.mesh_create",
|
|
27
28
|
"desktop.invocation_history",
|
|
@@ -48,7 +49,7 @@ export function desktopOperationTimeout(operation: unknown, input: unknown): num
|
|
|
48
49
|
|| operation === "desktop.mesh_snapshots") return 75_000;
|
|
49
50
|
if (operation === "desktop.task_activity"
|
|
50
51
|
|| operation === "desktop.task_image" || operation === "desktop.live_catalog") return 45_000;
|
|
51
|
-
if (operation === "desktop.task_send"
|
|
52
|
+
if (operation === "desktop.task_send" || operation === "desktop.task_handoff"
|
|
52
53
|
|| operation === "desktop.task_create") return 250_000;
|
|
53
54
|
if (operation === "desktop.mesh_snapshot"
|
|
54
55
|
&& (input as { enrich?: unknown } | undefined)?.enrich === "true") return 45_000;
|
|
@@ -10,6 +10,9 @@ may send an opaque Origin. The callback is fixed to
|
|
|
10
10
|
and codes expire after five minutes. A private file stores only token hashes,
|
|
11
11
|
up to eight active grants, expiring after 30 days. The app keeps its token in
|
|
12
12
|
its device-only Keychain and never shares it with a provider.
|
|
13
|
+
The fallback consent page is styled and localized by the loopback service;
|
|
14
|
+
the stylesheet has no script or external dependency. A Mac app with a verified
|
|
15
|
+
passkey account uses `/desktop/account/authorize` instead of opening that page.
|
|
13
16
|
|
|
14
17
|
`/desktop/invoke` requires that grant before parsing a bounded request. It can
|
|
15
18
|
invoke only the existing allowlisted desktop operations; inputs are still
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const copy = {
|
|
2
|
+
en: {
|
|
3
|
+
heading: 'Allow GrantTap on this Mac',
|
|
4
|
+
detail: 'GrantTap for Mac needs access to the local GrantTap service to show your tasks, send messages, handle approvals, and configure the relay.',
|
|
5
|
+
scope: 'This approval applies only to this Mac app on this computer. It does not authorize another computer or a coding provider.',
|
|
6
|
+
action: 'Allow this Mac app',
|
|
7
|
+
expires: 'The authorization request expires after five minutes.',
|
|
8
|
+
},
|
|
9
|
+
ru: {
|
|
10
|
+
heading: 'Разрешить GrantTap на этом Mac',
|
|
11
|
+
detail: 'Приложению GrantTap для Mac нужен доступ к локальному сервису, чтобы показывать задачи, отправлять сообщения, обрабатывать одобрения и настраивать relay.',
|
|
12
|
+
scope: 'Разрешение действует только для приложения на этом Mac. Оно не открывает доступ другому компьютеру или агенту.',
|
|
13
|
+
action: 'Разрешить на этом Mac',
|
|
14
|
+
expires: 'Запрос разрешения истечёт через пять минут.',
|
|
15
|
+
},
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
type Consent = { id: string; confirmation: string; state: string };
|
|
19
|
+
|
|
20
|
+
export function desktopConsentPage(row: Consent, language: string | undefined): string {
|
|
21
|
+
const locale = language?.toLowerCase().startsWith('ru') ? 'ru' : 'en';
|
|
22
|
+
const words = copy[locale];
|
|
23
|
+
return `<!doctype html><html lang="${locale}"><head><meta charset="utf-8">
|
|
24
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
25
|
+
<meta name="color-scheme" content="light dark"><title>GrantTap for Mac</title>
|
|
26
|
+
<link rel="stylesheet" href="/desktop/consent.css"></head><body>
|
|
27
|
+
<main class="consent-card"><div class="brand"><span class="brand-mark" aria-hidden="true">✓</span><span>GrantTap</span></div>
|
|
28
|
+
<h1>${words.heading}</h1><p>${words.detail}</p><p class="scope">${words.scope}</p>
|
|
29
|
+
<form action="/desktop/approve" method="post"><input type="hidden" name="id" value="${row.id}">
|
|
30
|
+
<input type="hidden" name="confirmation" value="${row.confirmation}">
|
|
31
|
+
<input type="hidden" name="state" value="${row.state}"><button type="submit">${words.action}</button></form>
|
|
32
|
+
<p class="footnote">${words.expires}</p></main></body></html>`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const desktopConsentStyles = `:root{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color-scheme:light dark}
|
|
36
|
+
*{box-sizing:border-box}body{margin:0;min-height:100vh;display:grid;place-items:center;padding:24px;background:#f4f1ed;color:#211f1d}
|
|
37
|
+
.consent-card{width:min(100%,480px);padding:32px;border:1px solid #dfd8d0;border-radius:24px;background:#fff;box-shadow:0 16px 48px #20170e18}
|
|
38
|
+
.brand{display:flex;align-items:center;gap:10px;font-size:19px;font-weight:750}.brand-mark{display:grid;place-items:center;width:38px;height:38px;border-radius:10px;background:#f17b39;color:#17110e;font-size:26px}
|
|
39
|
+
h1{margin:32px 0 14px;font-size:27px;line-height:1.2;letter-spacing:-.025em}p{margin:0 0 18px;line-height:1.5;color:#56504a}
|
|
40
|
+
.scope{padding:15px 16px;border-radius:12px;background:#f4f1ed;color:#38332e}button{display:block;width:100%;margin-top:24px;padding:14px 18px;border:0;border-radius:12px;background:#ca4e24;color:white;font:inherit;font-weight:700;cursor:pointer}
|
|
41
|
+
button:hover{background:#a93c1b}button:focus-visible{outline:3px solid #2171d1;outline-offset:3px}.footnote{margin:18px 0 0;text-align:center;font-size:13px;color:#817a73}
|
|
42
|
+
@media(prefers-color-scheme:dark){body{background:#101114;color:#f5f2ef}.consent-card{background:#22252a;border-color:#3b3e45;box-shadow:0 16px 48px #0007}p{color:#bdb8b2}.scope{background:#30343a;color:#e0dad3}.footnote{color:#a39e98}}
|
|
43
|
+
`;
|
|
@@ -5,23 +5,23 @@ import { invokeDesktop } from './invoke';
|
|
|
5
5
|
import { DESKTOP_OPERATIONS, desktopOperationTimeout } from '../engine-bridge';
|
|
6
6
|
import { enrollMacAccount } from '../../../../bridge/src/account-recovery/enroll';
|
|
7
7
|
import { websiteOrigin } from '../../oauth/consent/website-session';
|
|
8
|
+
import { desktopConsentPage, desktopConsentStyles } from './consent-page';
|
|
8
9
|
|
|
9
10
|
export function installDesktopNativeRoutes(app: Express, socketPath: string, origin: string,
|
|
10
11
|
access = new DesktopNativeAccess(), enroll = enrollMacAccount): void {
|
|
11
12
|
app.use('/desktop', (_req, res, next) => {
|
|
12
13
|
res.set({ 'Cache-Control': 'no-store', 'Referrer-Policy': 'no-referrer',
|
|
13
|
-
'X-Content-Type-Options': 'nosniff', 'Content-Security-Policy': "default-src 'none'; form-action 'self'; frame-ancestors 'none'" });
|
|
14
|
+
'X-Content-Type-Options': 'nosniff', 'Content-Security-Policy': "default-src 'none'; style-src 'self'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'" });
|
|
14
15
|
next();
|
|
15
16
|
});
|
|
17
|
+
app.get('/desktop/consent.css', (_req, res) => res.type('css').send(desktopConsentStyles));
|
|
16
18
|
app.get('/desktop/authorize', (req, res) => {
|
|
17
19
|
try {
|
|
18
20
|
const row = access.begin(String(req.query.challenge ?? ''), String(req.query.state ?? ''));
|
|
19
21
|
res.cookie('granttap_desktop_consent', row.confirmation, {
|
|
20
22
|
httpOnly: true, sameSite: 'strict', path: '/desktop/approve', maxAge: 300_000,
|
|
21
23
|
});
|
|
22
|
-
res.type('html').send(
|
|
23
|
-
<h1>Allow GrantTap for Mac on this computer</h1><p>The app can inspect tasks, send your messages, handle approvals and configure your local relay. This does not authorize another computer or a coding provider.</p>
|
|
24
|
-
<form action="/desktop/approve" method="post"><input type="hidden" name="id" value="${row.id}"><input type="hidden" name="confirmation" value="${row.confirmation}"><input type="hidden" name="state" value="${row.state}"><button type="submit">Allow this Mac app</button></form></html>`);
|
|
24
|
+
res.type('html').send(desktopConsentPage(row, req.headers['accept-language']));
|
|
25
25
|
} catch { res.status(400).json({ error: 'Invalid native authorization' }); }
|
|
26
26
|
});
|
|
27
27
|
app.post('/desktop/approve', (req, res) => {
|
|
@@ -10,6 +10,7 @@ import type { DesktopTaskActivityRunner } from "../task-activity-runner";
|
|
|
10
10
|
import type { EngineClient } from "../../../../bridge/src/engine/runtime/engine-client";
|
|
11
11
|
import type { EngineOperation } from "../../../../bridge/src/engine/protocol/engine-protocol";
|
|
12
12
|
import { desktopTaskSend } from "../delivery/task-send";
|
|
13
|
+
import { desktopTaskHandoff } from "../delivery/task-handoff";
|
|
13
14
|
import { desktopTaskCreate } from "../delivery/task-create";
|
|
14
15
|
import { desktopMeshCreate } from "../delivery/mesh-create";
|
|
15
16
|
import { desktopInvocationHistory } from "../invocation";
|
|
@@ -54,6 +55,7 @@ export function desktopReadOperation(input: {
|
|
|
54
55
|
if (operation === "desktop.task_activity") return activity.read(queryInput, storePath);
|
|
55
56
|
if (operation === "desktop.task_image") return activity.image(queryInput, storePath);
|
|
56
57
|
if (operation === "desktop.task_send") return desktopTaskSend(queryInput, storePath);
|
|
58
|
+
if (operation === "desktop.task_handoff") return desktopTaskHandoff(queryInput, storePath);
|
|
57
59
|
if (operation === "desktop.task_create") return desktopTaskCreate(queryInput, storePath);
|
|
58
60
|
if (operation === "desktop.mesh_create") return Promise.resolve(desktopMeshCreate(queryInput, storePath));
|
|
59
61
|
if (operation === "desktop.invocation_history") {
|
package/cursor-plugin/README.md
CHANGED
|
@@ -10,7 +10,7 @@ keeps each phone response tied to the exact originating chat and prompt.
|
|
|
10
10
|
Install the supported CLI on this computer, then run:
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
npm install -g granttap-mcp@0.8.
|
|
13
|
+
npm install -g granttap-mcp@0.8.39
|
|
14
14
|
granttap setup
|
|
15
15
|
granttap status
|
|
16
16
|
```
|
package/cursor-plugin/mcp.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"command": "node",
|
|
5
5
|
"args": [
|
|
6
6
|
"-e",
|
|
7
|
-
"\"use strict\";\n\n// Cursor may start this MCP from any cwd. Execute JavaScript entry points with\n// Node itself: Windows batch shims require shell quoting that differs by host.\nconst { spawn } = require(\"node:child_process\");\nconst { existsSync } = require(\"node:fs\");\nconst { delimiter, dirname, join } = require(\"node:path\");\n\nconst PACKAGE = \"granttap-mcp@0.8.
|
|
7
|
+
"\"use strict\";\n\n// Cursor may start this MCP from any cwd. Execute JavaScript entry points with\n// Node itself: Windows batch shims require shell quoting that differs by host.\nconst { spawn } = require(\"node:child_process\");\nconst { existsSync } = require(\"node:fs\");\nconst { delimiter, dirname, join } = require(\"node:path\");\n\nconst PACKAGE = \"granttap-mcp@0.8.39\";\nconst dryRun = process.env.GRANTTAP_BOOTSTRAP_DRY_RUN === \"1\";\nconst platform = dryRun && process.env.GRANTTAP_BOOTSTRAP_PLATFORM\n ? process.env.GRANTTAP_BOOTSTRAP_PLATFORM : process.platform;\nconst nodeRoot = dryRun && process.env.GRANTTAP_BOOTSTRAP_NODE_ROOT\n ? process.env.GRANTTAP_BOOTSTRAP_NODE_ROOT : dirname(process.execPath);\nconst pathRoots = (process.env.PATH || \"\").split(delimiter).filter(Boolean);\nconst appData = process.env.APPDATA;\nconst roots = [nodeRoot, ...pathRoots, ...(appData ? [join(appData, \"npm\")] : [])];\nconst first = (paths) => paths.find((path) => existsSync(path));\n\nfunction launch() {\n if (platform !== \"win32\") {\n const installed = first(roots.map((root) => join(root, \"granttap-mcp\")));\n if (installed) return { command: installed, args: [] };\n const npx = first(roots.map((root) => join(root, \"npx\"))) || \"npx\";\n return { command: npx, args: [\"-y\", PACKAGE] };\n }\n const installed = first(roots.map((root) =>\n join(root, \"node_modules\", \"granttap-mcp\", \"bin\", \"granttap-mcp.mjs\")));\n if (installed) return { command: process.execPath, args: [installed] };\n const npx = first(roots.map((root) =>\n join(root, \"node_modules\", \"npm\", \"bin\", \"npx-cli.js\")));\n if (npx) return { command: process.execPath, args: [npx, \"-y\", PACKAGE] };\n throw new Error(\"Node.js/npm 20+ is required on this Windows device. Install npm and retry GrantTap.\");\n}\n\nlet target;\ntry { target = launch(); }\ncatch (error) {\n process.stderr.write(`[granttap] ${error.message}\\n`);\n process.exit(1);\n}\n\nif (dryRun) {\n process.stdout.write(JSON.stringify(target));\n process.exit(0);\n}\n\nconst child = spawn(target.command, target.args, { stdio: \"inherit\", windowsHide: true });\nchild.on(\"error\", (error) => {\n process.stderr.write(`[granttap] ${error.message}\\n`);\n process.exit(1);\n});\nchild.on(\"exit\", (code, signal) => {\n if (signal && platform !== \"win32\") {\n try { process.kill(process.pid, signal); } catch { process.exit(code ?? 1); }\n return;\n }\n process.exit(code ?? 1);\n});\n"
|
|
8
8
|
]
|
|
9
9
|
}
|
|
10
10
|
}
|
|
@@ -7,7 +7,7 @@ const { spawn } = require("node:child_process");
|
|
|
7
7
|
const { existsSync } = require("node:fs");
|
|
8
8
|
const { delimiter, dirname, join } = require("node:path");
|
|
9
9
|
|
|
10
|
-
const PACKAGE = "granttap-mcp@0.8.
|
|
10
|
+
const PACKAGE = "granttap-mcp@0.8.39";
|
|
11
11
|
const dryRun = process.env.GRANTTAP_BOOTSTRAP_DRY_RUN === "1";
|
|
12
12
|
const platform = dryRun && process.env.GRANTTAP_BOOTSTRAP_PLATFORM
|
|
13
13
|
? process.env.GRANTTAP_BOOTSTRAP_PLATFORM : process.platform;
|
package/package.json
CHANGED
|
@@ -18,6 +18,8 @@ export const TaskCapsule = z.object({
|
|
|
18
18
|
targetProvider: MeshExecutionProvider,
|
|
19
19
|
targetActorId: Identifier.optional(),
|
|
20
20
|
targetComputer: Identifier,
|
|
21
|
+
targetModel: z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._:/-]{0,159}$/).optional(),
|
|
22
|
+
userComment: Detail.optional(),
|
|
21
23
|
repository: z.string().trim().min(1).max(1_024),
|
|
22
24
|
baseSha: GitSha,
|
|
23
25
|
branch: z.string().trim().min(1).max(512).optional(),
|
|
@@ -206,6 +208,8 @@ export const MeshHandoffPrepare = z.object({
|
|
|
206
208
|
targetProvider: MeshExecutionProvider,
|
|
207
209
|
targetActorId: Identifier.optional(),
|
|
208
210
|
targetComputer: Identifier,
|
|
211
|
+
targetModel: z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._:/-]{0,159}$/).optional(),
|
|
212
|
+
userComment: Detail.optional(),
|
|
209
213
|
createdAt: z.number().nonnegative(),
|
|
210
214
|
/**
|
|
211
215
|
* Commit uncommitted work to a checkpoint branch first, so the Task can
|