0xray 4.0.30 → 4.0.32
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/.grok-plugin/plugin.json +1 -1
- package/.mcp.json +8 -0
- package/.opencode/skills/orchestrator/SKILL.md +12 -0
- package/.xray/features.json +1 -1
- package/dist/CHANGELOG.md +17 -0
- package/dist/cli/commands/status.d.ts +4 -2
- package/dist/cli/commands/status.js +6 -3
- package/dist/cli/commands/validate.js +9 -0
- package/dist/cli/goggles-status.d.ts +13 -0
- package/dist/cli/goggles-status.js +76 -0
- package/dist/cli/index.js +41 -2
- package/dist/integrations/grok/grok-cli.d.ts +3 -0
- package/dist/integrations/grok/grok-cli.js +41 -1
- package/dist/integrations/grok/hooks/grok-hook-utils.js +24 -4
- package/dist/integrations/grok/hooks/post-tool-use.js +2 -1
- package/dist/integrations/grok/hooks/pre-tool-use.js +90 -5
- package/dist/integrations/grok/hooks/session-start.js +7 -2
- package/dist/integrations/hooks/goggles-mcp.mjs +371 -0
- package/dist/integrations/hooks/goggles-pipeline.mjs +632 -0
- package/dist/integrations/hooks/goggles-planes.json +28 -0
- package/dist/integrations/hooks/plates.cjs +23 -0
- package/dist/integrations/hooks/station-hook-runtime.cjs +40 -11
- package/dist/integrations/hooks/station-memory-ingest.mjs +3 -0
- package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/dist/mcps/orchestrator/config/agent-capabilities.js +7 -0
- package/dist/memory-routing/plates.d.ts +1 -1
- package/dist/memory-routing/plates.js +5 -0
- package/dist/memory-routing/provider-loader.js +2 -0
- package/dist/nucleus/thin-dispatch.d.ts +1 -0
- package/dist/nucleus/thin-dispatch.js +4 -2
- package/dist/skills/orchestrator/SKILL.md +12 -0
- package/docs-site/docs/plates/glossary.md +27 -0
- package/docs-site/docs/plates/goggles.md +18 -0
- package/docs-site/docs/plates/grokbot.md +41 -0
- package/docs-site/docs/plates/host-pack.md +32 -0
- package/docs-site/docs/plates/house.md +42 -39
- package/docs-site/docs/plates/index.md +13 -1
- package/docs-site/docs/plates/kits.md +28 -0
- package/docs-site/docs/plates/suit.md +40 -0
- package/package.json +2 -1
- package/scripts/foundry/assert-packed-dist-cli.mjs +9 -0
- package/scripts/mjs/run-goggles-mcp.mjs +41 -0
- package/scripts/node/bridge-mcp-wiring.cjs +61 -21
- package/scripts/node/consumer-install-smoke.mjs +5 -0
- package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
- package/src/mcps/orchestrator/config/agent-capabilities.ts +8 -0
- package/src/skills/orchestrator/SKILL.md +12 -0
- package/vendor/@0xray/repertoire/dist/paths.js +4 -0
- package/xray/features.json +1 -1
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
ensureSessionBoot,
|
|
11
11
|
readStdinJson,
|
|
12
12
|
resolveSessionId,
|
|
13
|
+
normalizeWorkspaceRoot,
|
|
13
14
|
workspaceRoot,
|
|
14
15
|
writeSessionBoot,
|
|
15
16
|
} from './grok-hook-utils.js';
|
|
@@ -21,6 +22,7 @@ import {
|
|
|
21
22
|
scheduleAutonomousReportingMarker,
|
|
22
23
|
runInferenceImprovementLight,
|
|
23
24
|
} from '../../hooks/pipeline-hook-runtime.mjs';
|
|
25
|
+
import { maintainLens } from '../../hooks/goggles-pipeline.mjs';
|
|
24
26
|
|
|
25
27
|
function resolveHookEvent(event) {
|
|
26
28
|
if (process.env.GROK_HOOK_EVENT) return process.env.GROK_HOOK_EVENT;
|
|
@@ -115,7 +117,7 @@ async function main() {
|
|
|
115
117
|
try {
|
|
116
118
|
const event = await readStdinJson();
|
|
117
119
|
HOOK_EVENT = resolveHookEvent(event);
|
|
118
|
-
const eventRoot = event.workspaceRoot || event.cwd || root;
|
|
120
|
+
const eventRoot = normalizeWorkspaceRoot(event.workspaceRoot || event.cwd || root);
|
|
119
121
|
const sessionId = resolveSessionId(event);
|
|
120
122
|
if (HOOK_EVENT === 'user_prompt_submit' && sessionId) {
|
|
121
123
|
recordSynthesisTurnSlice(eventRoot, sessionId);
|
|
@@ -149,7 +151,7 @@ async function main() {
|
|
|
149
151
|
const payload = buildSessionBootPayload(eventRoot, source, {
|
|
150
152
|
hookEvent: HOOK_EVENT,
|
|
151
153
|
sessionId: event.sessionId || process.env.GROK_SESSION_ID || null,
|
|
152
|
-
...(intent ? { intent } : {}),
|
|
154
|
+
...(intent ? { intent, cardText: intent } : {}),
|
|
153
155
|
...(matchedSignals.length ? { matchedSignals } : {}),
|
|
154
156
|
});
|
|
155
157
|
|
|
@@ -167,6 +169,9 @@ async function main() {
|
|
|
167
169
|
|
|
168
170
|
try {
|
|
169
171
|
scheduleAutonomousReportingMarker(eventRoot);
|
|
172
|
+
if (HOOK_EVENT === 'pre_compact' || HOOK_EVENT === 'post_compact') {
|
|
173
|
+
maintainLens(eventRoot);
|
|
174
|
+
}
|
|
170
175
|
if (HOOK_EVENT === 'session_start') {
|
|
171
176
|
maybeRunReflectionStub(eventRoot);
|
|
172
177
|
runInferenceImprovementLight(eventRoot);
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Goggles MCP. Stdio. Server name `goggles`.
|
|
3
|
+
* Tools: look, status_lens. Same organ as the CLI.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
6
|
+
import { basename, join, resolve } from 'node:path';
|
|
7
|
+
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
8
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
9
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
10
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
11
|
+
import * as bundled from './goggles-pipeline.mjs';
|
|
12
|
+
|
|
13
|
+
const HERE = fileURLToPath(new URL('.', import.meta.url));
|
|
14
|
+
const SERVER_NAME = 'goggles';
|
|
15
|
+
const DRAWING = /docs-site\/docs\/plates\/[a-z0-9-]+\.md/i;
|
|
16
|
+
const CARD_PLANES = [
|
|
17
|
+
'ground',
|
|
18
|
+
'routing',
|
|
19
|
+
'house',
|
|
20
|
+
'boot',
|
|
21
|
+
'governance',
|
|
22
|
+
'memory-recall',
|
|
23
|
+
'orchestration',
|
|
24
|
+
'processor',
|
|
25
|
+
'reporting',
|
|
26
|
+
];
|
|
27
|
+
const SCOPES = ['ecosystem', 'part', 'one flow', 'one artifact'];
|
|
28
|
+
|
|
29
|
+
const LOOK_SCHEMA = {
|
|
30
|
+
type: 'object',
|
|
31
|
+
additionalProperties: false,
|
|
32
|
+
properties: {
|
|
33
|
+
kind: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
description: 'Optional. Use "0" or "actuality" for the lens. Omit when naming an outer.',
|
|
36
|
+
enum: ['0', 'actuality'],
|
|
37
|
+
},
|
|
38
|
+
outer: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
description: 'One outer name. digest and triage return a card. The others return a reading.',
|
|
41
|
+
enum: ['dichotomy', 'syncopate', 'synthesis', 'digest', 'triage', 'loop'],
|
|
42
|
+
},
|
|
43
|
+
scope: {
|
|
44
|
+
type: 'string',
|
|
45
|
+
description: 'Zoom only. Not a kind. One level.',
|
|
46
|
+
enum: SCOPES,
|
|
47
|
+
},
|
|
48
|
+
plane: {
|
|
49
|
+
type: 'string',
|
|
50
|
+
description: 'Card plane for digest or triage only. Not the lens.',
|
|
51
|
+
enum: CARD_PLANES,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const TOOLS = [
|
|
57
|
+
{
|
|
58
|
+
name: 'look',
|
|
59
|
+
description: 'Kind 0 is quiet on a match. It checks the six outer names. One outer is a reading. digest and triage return a card.',
|
|
60
|
+
inputSchema: LOOK_SCHEMA,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'status_lens',
|
|
64
|
+
description: 'Whether the organ is worn, and the last Kind 0. Quiet is a match. A missing organ is not drift.',
|
|
65
|
+
inputSchema: { type: 'object', additionalProperties: false, properties: {} },
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
function serverVersion() {
|
|
70
|
+
try {
|
|
71
|
+
const pkg = JSON.parse(readFileSync(join(HERE, '../../../package.json'), 'utf8'));
|
|
72
|
+
return typeof pkg.version === 'string' && pkg.version ? pkg.version : 'local';
|
|
73
|
+
} catch {
|
|
74
|
+
return 'local';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function deny(code, reason) {
|
|
79
|
+
return {
|
|
80
|
+
ok: false,
|
|
81
|
+
quiet: false,
|
|
82
|
+
mode: 'deny',
|
|
83
|
+
content: '',
|
|
84
|
+
error: { gate: 'goggles', decision: 'deny', code, reason },
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function quiet(mode) {
|
|
89
|
+
return { ok: true, quiet: true, mode, content: '' };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function organApi() {
|
|
93
|
+
const override = typeof process.env.GOGGLES_ORGAN_PATH === 'string'
|
|
94
|
+
? process.env.GOGGLES_ORGAN_PATH.trim()
|
|
95
|
+
: '';
|
|
96
|
+
if (!override) return bundled;
|
|
97
|
+
if (!existsSync(override)) return null;
|
|
98
|
+
return import(pathToFileURL(override).href);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function projectRoot(root) {
|
|
102
|
+
if (typeof root === 'string' && root.trim()) return root;
|
|
103
|
+
const env = process.env.GOGGLES_ROOT;
|
|
104
|
+
if (typeof env === 'string' && env.trim() && env.trim() !== '.') return env.trim();
|
|
105
|
+
return process.cwd();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function heldReading(root) {
|
|
109
|
+
try {
|
|
110
|
+
const data = JSON.parse(readFileSync(join(root, '.xray', 'state', 'goggles-reading.json'), 'utf8'));
|
|
111
|
+
if (!data || typeof data !== 'object') return null;
|
|
112
|
+
if (typeof data.drift === 'string' && data.drift) return { drift: data.drift };
|
|
113
|
+
if (typeof data.plane !== 'string' || !data.plane) return null;
|
|
114
|
+
return { plane: data.plane, scope: typeof data.scope === 'string' ? data.scope : '' };
|
|
115
|
+
} catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function denyCode(reason) {
|
|
121
|
+
if (/wider|crosses parts|not one flow/i.test(reason)) return 'scope_wider';
|
|
122
|
+
if (/Name one plane/i.test(reason)) return 'name_one';
|
|
123
|
+
return 'leave_plane';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function drawingPaths(args) {
|
|
127
|
+
const hits = [];
|
|
128
|
+
for (const value of Object.values(args)) {
|
|
129
|
+
if (typeof value === 'string' && DRAWING.test(value)) hits.push(value);
|
|
130
|
+
}
|
|
131
|
+
return hits;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function argvFrom(args) {
|
|
135
|
+
const words = [];
|
|
136
|
+
if (args.kind === '0') words.push('kind', '0');
|
|
137
|
+
else if (args.kind === 'actuality') words.push('actuality');
|
|
138
|
+
else if (args.kind === '1' || args.kind === '2') words.push('kind', String(args.kind));
|
|
139
|
+
else if (typeof args.kind === 'string') words.push(args.kind);
|
|
140
|
+
if (args.outer === 'outer loop') words.push('outer', 'loop');
|
|
141
|
+
else if (typeof args.outer === 'string') words.push(args.outer);
|
|
142
|
+
if (typeof args.plane === 'string' && !DRAWING.test(args.plane)) words.push(args.plane);
|
|
143
|
+
if (args.scope === 'one flow') words.push('one', 'flow');
|
|
144
|
+
else if (args.scope === 'one artifact') words.push('one', 'artifact');
|
|
145
|
+
else if (typeof args.scope === 'string') words.push(args.scope);
|
|
146
|
+
return words;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function pureKind0(args) {
|
|
150
|
+
return (args.kind === '0' || args.kind === 'actuality')
|
|
151
|
+
&& args.outer == null
|
|
152
|
+
&& args.plane == null
|
|
153
|
+
&& args.scope == null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function checkArgs(args) {
|
|
157
|
+
if (!args || typeof args !== 'object' || Array.isArray(args)) {
|
|
158
|
+
return deny('invalid_args', 'look takes an object.');
|
|
159
|
+
}
|
|
160
|
+
const allowed = new Set(['kind', 'outer', 'scope', 'plane']);
|
|
161
|
+
for (const key of Object.keys(args)) {
|
|
162
|
+
if (!allowed.has(key)) return deny('invalid_args', 'Unknown argument.');
|
|
163
|
+
}
|
|
164
|
+
for (const key of ['kind', 'outer', 'scope', 'plane']) {
|
|
165
|
+
if (args[key] != null && typeof args[key] !== 'string') {
|
|
166
|
+
return deny('invalid_args', `${key} must be a string.`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (args.kind != null && !['0', 'actuality', '1', '2'].includes(args.kind)) {
|
|
170
|
+
return deny('invalid_args', 'kind is 0 or actuality.');
|
|
171
|
+
}
|
|
172
|
+
if (args.outer != null && !LOOK_SCHEMA.properties.outer.enum.includes(args.outer)) {
|
|
173
|
+
const setName = args.outer === 'outer' || args.outer === 'outer-loop' || args.outer === 'outer loop';
|
|
174
|
+
if (!setName) return deny('invalid_args', 'Name one outer.');
|
|
175
|
+
}
|
|
176
|
+
if (args.scope != null && !SCOPES.includes(args.scope)) {
|
|
177
|
+
return deny('invalid_args', 'scope is one zoom level.');
|
|
178
|
+
}
|
|
179
|
+
if (args.plane != null && !CARD_PLANES.includes(args.plane) && !DRAWING.test(args.plane)) {
|
|
180
|
+
return deny('invalid_args', 'plane is a card plane.');
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function publicCard(view) {
|
|
186
|
+
const notes = [];
|
|
187
|
+
if (view.flavor === 'triage') {
|
|
188
|
+
if (Array.isArray(view.empty) && view.empty.length) notes.push(`Empty: ${view.empty.join(', ')}`);
|
|
189
|
+
if (view.exam) notes.push(view.exam);
|
|
190
|
+
}
|
|
191
|
+
const skills = view.skills ? basename(String(view.skills)) : '';
|
|
192
|
+
if (view.narrow) {
|
|
193
|
+
return {
|
|
194
|
+
plane: view.plane,
|
|
195
|
+
flavor: view.flavor,
|
|
196
|
+
digest: view.digest || '',
|
|
197
|
+
files: Array.isArray(view.files) ? view.files : [],
|
|
198
|
+
notes,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
plane: view.plane,
|
|
203
|
+
flavor: view.flavor,
|
|
204
|
+
from: view.from || '',
|
|
205
|
+
digest: view.digest || '',
|
|
206
|
+
plate: view.plate || '',
|
|
207
|
+
entry: view.entry || '',
|
|
208
|
+
exit: view.exit || '',
|
|
209
|
+
files: Array.isArray(view.files) ? view.files : [],
|
|
210
|
+
skills,
|
|
211
|
+
setup: view.setup || '',
|
|
212
|
+
teardown: view.teardown || '',
|
|
213
|
+
worn: view.worn || '',
|
|
214
|
+
notes,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function answerLook(raw, root) {
|
|
219
|
+
if (raw != null && (typeof raw !== 'object' || Array.isArray(raw))) {
|
|
220
|
+
return deny('invalid_args', 'look takes an object.');
|
|
221
|
+
}
|
|
222
|
+
const args = raw && typeof raw === 'object' ? raw : {};
|
|
223
|
+
const bad = checkArgs(args);
|
|
224
|
+
if (bad) return bad;
|
|
225
|
+
const api = await organApi();
|
|
226
|
+
if (!api) return deny('invalid_args', 'Goggles organ not found.');
|
|
227
|
+
const here = projectRoot(root);
|
|
228
|
+
const drawings = drawingPaths(args);
|
|
229
|
+
if (drawings.length) {
|
|
230
|
+
const stop = api.organStop(heldReading(here), { text: drawings.join('\n'), paths: drawings, tool: 'look' });
|
|
231
|
+
if (stop && stop.reason) return deny(denyCode(stop.reason), stop.reason);
|
|
232
|
+
return quiet('reading');
|
|
233
|
+
}
|
|
234
|
+
if (pureKind0(args)) {
|
|
235
|
+
api.maintainLens(here);
|
|
236
|
+
const line = api.actualityLine();
|
|
237
|
+
if (!line) return quiet('kind0');
|
|
238
|
+
return { ok: true, quiet: false, mode: 'kind0', content: line };
|
|
239
|
+
}
|
|
240
|
+
const argv = argvFrom(args);
|
|
241
|
+
const text = api.look(argv).text;
|
|
242
|
+
if (text === 'Name one plane.') return deny('name_one', 'Name one plane.');
|
|
243
|
+
const cards = api.lookCards(argv);
|
|
244
|
+
if (Array.isArray(cards) && cards.length) {
|
|
245
|
+
const shown = cards.map(publicCard);
|
|
246
|
+
return {
|
|
247
|
+
ok: true,
|
|
248
|
+
quiet: false,
|
|
249
|
+
mode: 'card',
|
|
250
|
+
content: shown.length === 1 ? shown[0] : shown,
|
|
251
|
+
outer: args.outer,
|
|
252
|
+
scope: args.scope || null,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
if (!text) {
|
|
256
|
+
if (args.kind === '0' || args.kind === 'actuality') return quiet('kind0');
|
|
257
|
+
if (args.outer === 'digest' || args.outer === 'triage') return quiet('card');
|
|
258
|
+
return quiet('reading');
|
|
259
|
+
}
|
|
260
|
+
if (text.startsWith('Actuality. Drift:')) {
|
|
261
|
+
return { ok: true, quiet: false, mode: 'kind0', content: text };
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
ok: true,
|
|
265
|
+
quiet: false,
|
|
266
|
+
mode: 'reading',
|
|
267
|
+
content: text,
|
|
268
|
+
outer: args.outer ?? null,
|
|
269
|
+
scope: args.scope || null,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function lensBody(root) {
|
|
274
|
+
const file = join(root, '.xray', 'state', 'LENS.md');
|
|
275
|
+
if (!existsSync(file)) return { found: false, text: '' };
|
|
276
|
+
try {
|
|
277
|
+
return { found: true, text: readFileSync(file, 'utf8').trim() };
|
|
278
|
+
} catch {
|
|
279
|
+
return { found: false, text: '' };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export async function answerStatus(root) {
|
|
284
|
+
const api = await organApi();
|
|
285
|
+
const override = typeof process.env.GOGGLES_ORGAN_PATH === 'string'
|
|
286
|
+
? process.env.GOGGLES_ORGAN_PATH.trim()
|
|
287
|
+
: '';
|
|
288
|
+
const organPath = override || fileURLToPath(new URL('./goggles-pipeline.mjs', import.meta.url));
|
|
289
|
+
const planesPath = api ? api.planesFile() : '';
|
|
290
|
+
const organOk = existsSync(organPath);
|
|
291
|
+
const planesOk = Boolean(planesPath) && existsSync(planesPath);
|
|
292
|
+
const worn = Boolean(api) && organOk && planesOk;
|
|
293
|
+
const here = projectRoot(root);
|
|
294
|
+
const lens = lensBody(here);
|
|
295
|
+
let kind0 = 'none';
|
|
296
|
+
let kind0Text = '';
|
|
297
|
+
if (lens.found && !lens.text) kind0 = 'quiet';
|
|
298
|
+
else if (lens.found) {
|
|
299
|
+
kind0 = 'drift';
|
|
300
|
+
kind0Text = lens.text;
|
|
301
|
+
} else if (worn) {
|
|
302
|
+
const line = api.actualityLine();
|
|
303
|
+
if (line) {
|
|
304
|
+
kind0 = 'drift';
|
|
305
|
+
kind0Text = line;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
ok: true,
|
|
310
|
+
organ: worn ? 'worn' : 'not_found',
|
|
311
|
+
organPath: organOk ? organPath : '',
|
|
312
|
+
planesPath: planesOk ? planesPath : '',
|
|
313
|
+
kind0,
|
|
314
|
+
kind0Text,
|
|
315
|
+
lensPath: '.xray/state/LENS.md',
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export async function dispatchTool(name, args, root) {
|
|
320
|
+
const bare = String(name || '').replace(/^goggles__/, '');
|
|
321
|
+
if (bare === 'status_lens') {
|
|
322
|
+
const input = args && typeof args === 'object' ? args : {};
|
|
323
|
+
if (Object.keys(input).length) {
|
|
324
|
+
return { payload: deny('invalid_args', 'status_lens takes no arguments.'), isError: true };
|
|
325
|
+
}
|
|
326
|
+
return { payload: await answerStatus(root), isError: false };
|
|
327
|
+
}
|
|
328
|
+
if (bare === 'look') {
|
|
329
|
+
const payload = await answerLook(args, root);
|
|
330
|
+
return { payload, isError: payload.mode === 'deny' };
|
|
331
|
+
}
|
|
332
|
+
return { payload: deny('invalid_args', 'Unknown tool.'), isError: true };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export function createGogglesServer() {
|
|
336
|
+
const server = new Server(
|
|
337
|
+
{ name: SERVER_NAME, version: serverVersion() },
|
|
338
|
+
{ capabilities: { tools: {} } },
|
|
339
|
+
);
|
|
340
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
341
|
+
tools: TOOLS.map((tool) => ({
|
|
342
|
+
name: tool.name,
|
|
343
|
+
description: tool.description,
|
|
344
|
+
inputSchema: tool.inputSchema,
|
|
345
|
+
})),
|
|
346
|
+
}));
|
|
347
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
348
|
+
const args = request.params.arguments ?? {};
|
|
349
|
+
const { payload, isError } = await dispatchTool(request.params.name, args, projectRoot());
|
|
350
|
+
return {
|
|
351
|
+
content: [{ type: 'text', text: JSON.stringify(payload) }],
|
|
352
|
+
isError,
|
|
353
|
+
};
|
|
354
|
+
});
|
|
355
|
+
return server;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async function main() {
|
|
359
|
+
const server = createGogglesServer();
|
|
360
|
+
const transport = new StdioServerTransport();
|
|
361
|
+
await server.connect(transport);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
365
|
+
const invoked = process.argv[1] ? resolve(process.argv[1]) : '';
|
|
366
|
+
if (invoked && resolve(thisFile) === invoked) {
|
|
367
|
+
main().catch((err) => {
|
|
368
|
+
process.stderr.write(`goggles MCP failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
369
|
+
process.exit(1);
|
|
370
|
+
});
|
|
371
|
+
}
|