granttap-mcp 0.8.87 → 0.8.89
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 +9 -1
- package/apps/bridge/src/capabilities/README.md +6 -0
- package/apps/bridge/src/capabilities/metadata.ts +4 -1
- package/apps/bridge/src/capabilities/types.ts +1 -1
- package/apps/bridge/src/mesh/catalog/project/capabilities.ts +6 -0
- package/apps/bridge/src/mesh/catalog/project/configured-mcp.ts +6 -0
- package/apps/bridge/src/monitor/handlers/user-message.ts +12 -9
- package/apps/bridge/src/reply/grok-response/README.md +20 -0
- package/apps/bridge/src/reply/grok-response/index.ts +48 -0
- package/apps/bridge/src/reply/process/index.ts +14 -1
- package/apps/bridge/src/reply/process/provider-headless.ts +3 -29
- 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/capabilities.ts +1 -0
- package/packages/protocol/messages/mesh/snapshot.ts +4 -0
package/README.md
CHANGED
|
@@ -155,7 +155,8 @@ device pairing without a passkey account.
|
|
|
155
155
|
|
|
156
156
|
- Claude Code and Codex: primary local control and continuation paths.
|
|
157
157
|
- Cursor (Beta): Task visibility and supported local controls, with narrower coverage.
|
|
158
|
-
- Grok Build:
|
|
158
|
+
- Grok Build: local history and headless follow-ups through an installed,
|
|
159
|
+
authenticated CLI; provider hook and scoped Mesh coverage remain narrower.
|
|
159
160
|
|
|
160
161
|
GrantTap reports the depth each provider actually exposes. Visibility does not
|
|
161
162
|
imply deterministic remote blocking or full mobile continuation.
|
|
@@ -477,6 +478,13 @@ grok plugin marketplace add sergii-ziborov/granttap-mcp
|
|
|
477
478
|
grok plugin install granttap --trust
|
|
478
479
|
```
|
|
479
480
|
|
|
481
|
+
In Grok, open `/mcps` and authenticate the GrantTap server, then run
|
|
482
|
+
`/granttap-connect`. GrantTap's phone pairing and Grok's permission to use that
|
|
483
|
+
MCP server are separate. An existing pairing does not authorize a new MCP client.
|
|
484
|
+
If a Grok session does not start responding within one minute, GrantTap asks you
|
|
485
|
+
to inspect the original task before retrying. A turn that has started can keep
|
|
486
|
+
running; its later failure does not mean that the message was never delivered.
|
|
487
|
+
|
|
480
488
|
For Cursor, use the reviewed **GrantTap** Marketplace listing when it is visible
|
|
481
489
|
to your account. Reload Cursor. On this computer, run `granttap setup`. Pair
|
|
482
490
|
and change settings on [granttap.com/connect](https://granttap.com/connect)
|
|
@@ -11,4 +11,10 @@ Catalog observations and Project requests do not install a skill or native MCP
|
|
|
11
11
|
configuration on another host. Availability requires a separate native apply
|
|
12
12
|
and independently observed initialization; that rollout is still unfinished.
|
|
13
13
|
|
|
14
|
+
MCP descriptions come from `initialize.serverInfo.description`, trimmed and
|
|
15
|
+
bounded to 4,000 characters. Descriptions, validated icons and website metadata
|
|
16
|
+
travel with both session and Project catalogs; conflicting native implementations
|
|
17
|
+
do not borrow an implementation's description or icon. These are display data,
|
|
18
|
+
not instructions or evidence that a capability was used.
|
|
19
|
+
|
|
14
20
|
License: this module is distributed under the GrantTap Commercial Source License in the repository-root `LICENSE` file.
|
|
@@ -117,9 +117,12 @@ function stringRecord(value: unknown): Record<string, string> {
|
|
|
117
117
|
async function normalizeServerMetadata(info: Implementation | undefined, descriptor: McpDescriptor): Promise<ServerMetadata | undefined> {
|
|
118
118
|
if (!info) return undefined;
|
|
119
119
|
const title = typeof info.title === "string" && info.title.trim() ? info.title.trim().slice(0, 160) : undefined;
|
|
120
|
+
const description = typeof info.description === "string" && info.description.trim()
|
|
121
|
+
? info.description.trim().slice(0, 4_000) : undefined;
|
|
120
122
|
const website = typeof info.websiteUrl === "string" ? safeHttpsUrl(info.websiteUrl) : undefined;
|
|
121
123
|
const version = typeof info.version === "string" && info.version.trim() ? info.version.trim().slice(0, 80) : undefined;
|
|
122
|
-
return { title,
|
|
124
|
+
return { title, ...(description ? { description } : {}), websiteUrl: website?.toString(),
|
|
125
|
+
version, icons: await normalizedIcons(info, descriptor), metadataSource: "mcp" };
|
|
123
126
|
}
|
|
124
127
|
|
|
125
128
|
async function normalizedIcons(info: Implementation, descriptor: McpDescriptor): Promise<NonNullable<McpServerInfo["icons"]> | undefined> {
|
|
@@ -30,10 +30,16 @@ export function projectMcpServers(
|
|
|
30
30
|
current.configuredEnabled &&= server.configuredEnabled;
|
|
31
31
|
current.allowed &&= server.allowed;
|
|
32
32
|
current.title ??= server.title;
|
|
33
|
+
if (server.description) current.description ??= server.description;
|
|
34
|
+
if (server.websiteUrl) current.websiteUrl ??= server.websiteUrl;
|
|
35
|
+
if (server.icons) current.icons ??= server.icons;
|
|
33
36
|
} else {
|
|
34
37
|
catalog.set(key, {
|
|
35
38
|
name: server.name,
|
|
36
39
|
title: server.title,
|
|
40
|
+
...(server.description ? { description: server.description } : {}),
|
|
41
|
+
...(server.websiteUrl ? { websiteUrl: server.websiteUrl } : {}),
|
|
42
|
+
...(server.icons ? { icons: server.icons } : {}),
|
|
37
43
|
provider,
|
|
38
44
|
endpointId: session.computerId,
|
|
39
45
|
configuredEnabled: server.configuredEnabled,
|
|
@@ -16,6 +16,7 @@ export function configuredProjectMcpServers(
|
|
|
16
16
|
const implementation = server.configDigest ?? "";
|
|
17
17
|
const value: ProjectMcpServer = {
|
|
18
18
|
name: server.name, title: server.title, provider, endpointId,
|
|
19
|
+
description: server.description, websiteUrl: server.websiteUrl, icons: server.icons,
|
|
19
20
|
configuredEnabled: server.configuredEnabled, allowed: server.allowed,
|
|
20
21
|
authStatus: server.authStatus, version: server.version,
|
|
21
22
|
configDigest: server.configDigest,
|
|
@@ -29,6 +30,7 @@ export function configuredProjectMcpServers(
|
|
|
29
30
|
// Two admitted workspaces disagree. Avoid claiming either version
|
|
30
31
|
// as the Project's selected implementation.
|
|
31
32
|
rows.set(key, { ...value, version: undefined, configDigest: undefined,
|
|
33
|
+
description: undefined, websiteUrl: undefined, icons: undefined,
|
|
32
34
|
authStatus: "conflict" });
|
|
33
35
|
} else if (!prior) rows.set(key, value);
|
|
34
36
|
if (!prior) implementations.set(key, implementation);
|
|
@@ -63,6 +65,7 @@ export function mergeConfiguredMcpServers(
|
|
|
63
65
|
|| (configuredRow.configDigest != null || execution.configDigest != null)
|
|
64
66
|
&& configuredRow.configDigest !== execution.configDigest) {
|
|
65
67
|
rows.set(key, { ...configuredRow, version: undefined, configDigest: undefined,
|
|
68
|
+
description: undefined, websiteUrl: undefined, icons: undefined,
|
|
66
69
|
authStatus: "conflict",
|
|
67
70
|
sessionIds: execution.sessionIds });
|
|
68
71
|
continue;
|
|
@@ -70,6 +73,9 @@ export function mergeConfiguredMcpServers(
|
|
|
70
73
|
rows.set(key, {
|
|
71
74
|
...configuredRow, ...execution,
|
|
72
75
|
version: execution.version ?? configuredRow.version,
|
|
76
|
+
description: execution.description ?? configuredRow.description,
|
|
77
|
+
websiteUrl: execution.websiteUrl ?? configuredRow.websiteUrl,
|
|
78
|
+
icons: execution.icons ?? configuredRow.icons,
|
|
73
79
|
configDigest: execution.configDigest ?? configuredRow.configDigest,
|
|
74
80
|
authStatus: execution.authStatus ?? configuredRow.authStatus,
|
|
75
81
|
});
|
|
@@ -98,20 +98,20 @@ async function startNewAgentTask(input: {
|
|
|
98
98
|
}
|
|
99
99
|
const settle = async (result: ReplyResult, late: boolean) => {
|
|
100
100
|
input.origin?.registry.complete(input.origin.scope,
|
|
101
|
-
{ ok: result.ok, sessionId: result.ok ? result.sessionId : undefined });
|
|
102
|
-
if (!result.ok && message.messageId) {
|
|
101
|
+
{ ok: result.ok || result.delivery === 'native', sessionId: result.ok ? result.sessionId : undefined });
|
|
102
|
+
if (!result.ok && message.messageId && result.delivery !== 'native') {
|
|
103
103
|
if (late) rejectCompletedDelivery(message.messageId, result.error);
|
|
104
104
|
await sendDeliveryReceipt(client, message.messageId, 'rejected', result.error,
|
|
105
105
|
undefined, input.peerPublicKey);
|
|
106
106
|
}
|
|
107
107
|
return result.ok ? say(result.text, result.sessionId, true)
|
|
108
|
-
: say(late ? `The ${displayName} task stopped: ${result.error}`
|
|
108
|
+
: say(late || result.delivery === 'native' ? `The ${displayName} task stopped: ${result.error}`
|
|
109
109
|
: `Could not create a ${displayName} task: ${result.error}`, undefined, true);
|
|
110
110
|
};
|
|
111
111
|
const early = await acceptWhenStarted(run, (result) => settle(result, true));
|
|
112
112
|
if (early === "started") return "accepted";
|
|
113
113
|
await settle(early, false);
|
|
114
|
-
return early.ok ? "accepted" : "rejected";
|
|
114
|
+
return early.ok || early.delivery === 'native' ? "accepted" : "rejected";
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
export async function handleUserMessage(
|
|
@@ -196,15 +196,18 @@ export async function handleUserMessage(
|
|
|
196
196
|
}
|
|
197
197
|
if (result.ok) await say(result.text, result.sessionId ?? target.sessionId, true);
|
|
198
198
|
else {
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
199
|
+
if (result.delivery !== 'native') {
|
|
200
|
+
if (late && message.messageId) rejectCompletedDelivery(message.messageId, result.error);
|
|
201
|
+
if (message.messageId) await sendDeliveryReceipt(client, message.messageId, 'rejected',
|
|
202
|
+
result.error, target.sessionId, peerPublicKey);
|
|
203
|
+
}
|
|
204
|
+
await say(late || result.delivery === 'native' ? `The run stopped: ${result.error}`
|
|
205
|
+
: `Could not deliver the message: ${result.error}`,
|
|
203
206
|
target.sessionId, true);
|
|
204
207
|
}
|
|
205
208
|
};
|
|
206
209
|
const early = await acceptWhenStarted(run, (result) => settle(result, true));
|
|
207
210
|
if (early === "started") return "accepted";
|
|
208
211
|
await settle(early, false);
|
|
209
|
-
return early.ok ? "accepted" : "rejected";
|
|
212
|
+
return early.ok || early.delivery === 'native' ? "accepted" : "rejected";
|
|
210
213
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Grok response admission
|
|
2
|
+
|
|
3
|
+
[`index.ts`](index.ts) reads Grok Build's `streaming-json` response contract.
|
|
4
|
+
Actual text, thinking, or a started tool call confirms that the prompt reached
|
|
5
|
+
execution. Startup metadata alone does not establish admission. Native error
|
|
6
|
+
messages remain failures and metadata is never returned as answer text.
|
|
7
|
+
|
|
8
|
+
Opening a session has a 60-second response-activity deadline, separate from the
|
|
9
|
+
long execution budget. A stalled opening stops only the process group GrantTap
|
|
10
|
+
started and asks the person to inspect the native task before retrying. Once
|
|
11
|
+
response activity is witnessed, a later timeout or run failure cannot rewrite
|
|
12
|
+
the accepted message as a rejected delivery. Execution failure still reaches
|
|
13
|
+
the chat. This does not grant tools, authenticate MCP, or alter Grok policy.
|
|
14
|
+
|
|
15
|
+
Behavior tests live in `../tests/grok-response.test.ts` and
|
|
16
|
+
`../tests/admission-deadline.test.ts`; durable receipt tests live in
|
|
17
|
+
`../../monitor/message-delivery/tests/run-outcome.test.ts`.
|
|
18
|
+
|
|
19
|
+
License: this module is distributed under the GrantTap Commercial Source License
|
|
20
|
+
in the repository-root `LICENSE` file.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ReplyResult } from '../payload/types';
|
|
2
|
+
import type { ProviderAdmission } from '../process';
|
|
3
|
+
|
|
4
|
+
function events(stdout: string): Record<string, unknown>[] {
|
|
5
|
+
return stdout.split('\n').flatMap(line => {
|
|
6
|
+
try {
|
|
7
|
+
const value: unknown = JSON.parse(line);
|
|
8
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
9
|
+
? [value as Record<string, unknown>] : [];
|
|
10
|
+
} catch { return []; }
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Response activity proves admission; startup lists and diagnostic text do not. */
|
|
15
|
+
export function grokAccepted(stdout: string): boolean {
|
|
16
|
+
return events(stdout).some(event => {
|
|
17
|
+
if (['text', 'thought'].includes(String(event.type))) {
|
|
18
|
+
return typeof event.data === 'string' && !!event.data.trim();
|
|
19
|
+
}
|
|
20
|
+
if (event.type === 'result') return typeof event.result === 'string' && !!event.result.trim();
|
|
21
|
+
return event.type === 'tool_call' && event.status === 'in_progress'
|
|
22
|
+
&& typeof event.toolCallId === 'string' && !!event.toolCallId;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Opening a native session has a separate limit from executing its long turn. */
|
|
27
|
+
export const grokAdmission: ProviderAdmission = {
|
|
28
|
+
confirmed: grokAccepted,
|
|
29
|
+
withinMs: 60_000,
|
|
30
|
+
timeoutMessage: 'Grok did not confirm receipt within 60s. Open the task before retrying.',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function parseGrok(stdout: string, fallback: string): ReplyResult {
|
|
34
|
+
let text = '';
|
|
35
|
+
let sessionId = fallback;
|
|
36
|
+
for (const event of events(stdout)) {
|
|
37
|
+
if (event.type === 'text' && typeof event.data === 'string') text += event.data;
|
|
38
|
+
if (event.type === 'result' && typeof event.result === 'string') text = event.result;
|
|
39
|
+
if (typeof event.sessionId === 'string') sessionId = event.sessionId;
|
|
40
|
+
if (typeof event.session_id === 'string') sessionId = event.session_id;
|
|
41
|
+
const error = typeof event.message === 'string' ? event.message : event.error;
|
|
42
|
+
if (event.type === 'error' && typeof error === 'string') {
|
|
43
|
+
return { ok: false, error: error.slice(0, 500) };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return text.trim() ? { ok: true, text: text.trim().slice(0, 4_000), sessionId }
|
|
47
|
+
: { ok: false, error: 'Grok Build returned an empty response.' };
|
|
48
|
+
}
|
|
@@ -72,6 +72,12 @@ export function runningProcessCount(key: string): number {
|
|
|
72
72
|
return running.get(key)?.size ?? 0;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
export type ProviderAdmission = {
|
|
76
|
+
confirmed: (stdout: string) => boolean;
|
|
77
|
+
withinMs: number;
|
|
78
|
+
timeoutMessage: string;
|
|
79
|
+
};
|
|
80
|
+
|
|
75
81
|
export function runProcess(
|
|
76
82
|
command: string,
|
|
77
83
|
args: string[],
|
|
@@ -80,6 +86,7 @@ export function runProcess(
|
|
|
80
86
|
parse: (stdout: string) => ReplyResult,
|
|
81
87
|
stdin?: string,
|
|
82
88
|
key?: string,
|
|
89
|
+
admission?: ProviderAdmission,
|
|
83
90
|
): Promise<ReplyResult> {
|
|
84
91
|
return new Promise((resolve) => {
|
|
85
92
|
let child: ChildProcess;
|
|
@@ -104,14 +111,20 @@ export function runProcess(
|
|
|
104
111
|
if (done) return;
|
|
105
112
|
done = true;
|
|
106
113
|
clearTimeout(timer);
|
|
114
|
+
clearTimeout(startupTimer);
|
|
107
115
|
untrack();
|
|
108
|
-
resolve(result);
|
|
116
|
+
resolve(!result.ok && admission?.confirmed(stdout) ? { ...result, delivery: 'native' } : result);
|
|
109
117
|
};
|
|
110
118
|
const timer = setTimeout(() => {
|
|
111
119
|
if (done) return;
|
|
112
120
|
signalOwnedProcess(child, "SIGKILL");
|
|
113
121
|
finish({ ok: false, error: `${command} did not respond within ${Math.round(timeoutMs / 1000)}s.` });
|
|
114
122
|
}, timeoutMs);
|
|
123
|
+
const startupTimer = admission ? setTimeout(() => {
|
|
124
|
+
if (done || admission.confirmed(stdout)) return;
|
|
125
|
+
signalOwnedProcess(child, "SIGKILL");
|
|
126
|
+
finish({ ok: false, error: admission.timeoutMessage });
|
|
127
|
+
}, admission.withinMs) : undefined;
|
|
115
128
|
child.stdout?.on("data", (chunk) => { stdout += chunk; });
|
|
116
129
|
child.stderr?.on("data", (chunk) => { stderr += chunk; });
|
|
117
130
|
child.on("error", (error) => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { grokAdmission, parseGrok } from "../grok-response";
|
|
1
2
|
import { randomUUID } from "node:crypto";
|
|
2
3
|
import type { SessionInfo } from "../../../../../packages/protocol/schema";
|
|
3
4
|
import { runProcess } from ".";
|
|
@@ -11,33 +12,6 @@ function grokBin(): string {
|
|
|
11
12
|
return resolveGrokBinary();
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
function parseGrok(stdout: string, fallback: string): ReplyResult {
|
|
15
|
-
let text = "";
|
|
16
|
-
let sessionId = fallback;
|
|
17
|
-
for (const line of stdout.split("\n")) {
|
|
18
|
-
if (!line.trim()) continue;
|
|
19
|
-
try {
|
|
20
|
-
const value = JSON.parse(line) as {
|
|
21
|
-
type?: string; data?: unknown; sessionId?: unknown; session_id?: unknown;
|
|
22
|
-
result?: unknown; error?: unknown;
|
|
23
|
-
};
|
|
24
|
-
if (value.type === "text" && typeof value.data === "string") text += value.data;
|
|
25
|
-
if (value.type === "result" && typeof value.result === "string") text = value.result;
|
|
26
|
-
if (typeof value.sessionId === "string") sessionId = value.sessionId;
|
|
27
|
-
if (typeof value.session_id === "string") sessionId = value.session_id;
|
|
28
|
-
if (value.type === "error" && typeof value.error === "string") {
|
|
29
|
-
return { ok: false, error: value.error.slice(0, 500) };
|
|
30
|
-
}
|
|
31
|
-
} catch {
|
|
32
|
-
// Ignore diagnostic lines outside Grok's streaming JSON contract.
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
const visible = text.trim() || stdout.trim();
|
|
36
|
-
return visible
|
|
37
|
-
? { ok: true, text: visible.slice(0, 4_000), sessionId }
|
|
38
|
-
: { ok: false, error: "Grok Build returned an empty response." };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
15
|
export function runCursorNew(
|
|
42
16
|
text: string, cwd: string, timeoutMs: number, model?: string, operationKey?: string, effort?: string,
|
|
43
17
|
): Promise<ReplyResult> {
|
|
@@ -62,7 +36,7 @@ export function runGrokNew(
|
|
|
62
36
|
"-p", text, "--output-format", "streaming-json",
|
|
63
37
|
];
|
|
64
38
|
return runProcess(grokBin(), args, cwd, timeoutMs, (stdout) => parseGrok(stdout, sessionId),
|
|
65
|
-
undefined, operationKey);
|
|
39
|
+
undefined, operationKey, grokAdmission);
|
|
66
40
|
}
|
|
67
41
|
|
|
68
42
|
export function runGrokResume(
|
|
@@ -79,6 +53,6 @@ export function runGrokResume(
|
|
|
79
53
|
return runProcess(
|
|
80
54
|
grokBin(), args, session.cwd, timeoutMs,
|
|
81
55
|
(stdout) => parseGrok(stdout, session.sessionId),
|
|
82
|
-
undefined, session.sessionId,
|
|
56
|
+
undefined, session.sessionId, grokAdmission,
|
|
83
57
|
);
|
|
84
58
|
}
|
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.89
|
|
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.89\";\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 22.13.0+ 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.89";
|
|
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
|
@@ -183,6 +183,7 @@ export const McpServerInfo = z.object({
|
|
|
183
183
|
allowed: z.boolean(),
|
|
184
184
|
authStatus: z.string().optional(),
|
|
185
185
|
title: z.string().max(160).optional(),
|
|
186
|
+
description: z.string().max(4_000).optional(),
|
|
186
187
|
websiteUrl: z.string().url().optional(),
|
|
187
188
|
version: z.string().max(80).optional(),
|
|
188
189
|
configDigest: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
@@ -3,6 +3,7 @@ import { SharedSkill, EndpointModelCatalog } from "./catalog/provider";
|
|
|
3
3
|
export * from "./catalog/provider";
|
|
4
4
|
import { ProjectCodeMap } from "./graph/code-map";
|
|
5
5
|
import { Identifier, Label, MeshProvider } from "./endpoint";
|
|
6
|
+
import { McpIcon } from "../capabilities";
|
|
6
7
|
import {
|
|
7
8
|
ProjectEnvironment,
|
|
8
9
|
ProjectExecutionPolicy,
|
|
@@ -44,6 +45,9 @@ export type IntegrationPeer = z.infer<typeof IntegrationPeer>;
|
|
|
44
45
|
export const ProjectMcpServer = z.object({
|
|
45
46
|
name: Label,
|
|
46
47
|
title: Label.optional(),
|
|
48
|
+
description: z.string().max(4_000).optional(),
|
|
49
|
+
websiteUrl: z.string().url().optional(),
|
|
50
|
+
icons: z.array(McpIcon).max(2).optional(),
|
|
47
51
|
provider: MeshProvider,
|
|
48
52
|
endpointId: Identifier,
|
|
49
53
|
configuredEnabled: z.boolean(),
|