@hileeon/mcc 0.1.8 → 0.1.9
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 +226 -127
- package/dist/accounts/store.d.ts +1 -0
- package/dist/accounts/store.d.ts.map +1 -1
- package/dist/accounts/store.js.map +1 -1
- package/dist/commands/launch.d.ts +9 -0
- package/dist/commands/launch.d.ts.map +1 -0
- package/dist/commands/launch.js +158 -0
- package/dist/commands/launch.js.map +1 -0
- package/dist/commands/mcp.d.ts +9 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +112 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/profile.d.ts +8 -0
- package/dist/commands/profile.d.ts.map +1 -0
- package/dist/commands/profile.js +125 -0
- package/dist/commands/profile.js.map +1 -0
- package/dist/core/model-router.d.ts.map +1 -1
- package/dist/core/model-router.js +5 -2
- package/dist/core/model-router.js.map +1 -1
- package/dist/{dashboard-server.d.ts → dashboard/server.d.ts} +1 -1
- package/dist/dashboard/server.d.ts.map +1 -0
- package/dist/{dashboard-server.js → dashboard/server.js} +169 -51
- package/dist/dashboard/server.js.map +1 -0
- package/dist/mcc.d.ts +4 -2
- package/dist/mcc.d.ts.map +1 -1
- package/dist/mcc.js +121 -408
- package/dist/mcc.js.map +1 -1
- package/dist/mcp/mcp-config.d.ts +17 -1
- package/dist/mcp/mcp-config.d.ts.map +1 -1
- package/dist/mcp/mcp-config.js +50 -17
- package/dist/mcp/mcp-config.js.map +1 -1
- package/dist/proxy/proxy-daemon.d.ts.map +1 -1
- package/dist/proxy/proxy-daemon.js +17 -2
- package/dist/proxy/proxy-daemon.js.map +1 -1
- package/dist/proxy/proxy-entry.js +5 -3
- package/dist/proxy/proxy-entry.js.map +1 -1
- package/dist/proxy/proxy-server.d.ts.map +1 -1
- package/dist/proxy/proxy-server.js +32 -6
- package/dist/proxy/proxy-server.js.map +1 -1
- package/dist/shared/config.d.ts +15 -0
- package/dist/shared/config.d.ts.map +1 -0
- package/dist/shared/config.js +79 -0
- package/dist/shared/config.js.map +1 -0
- package/dist/shared/logger.d.ts +23 -18
- package/dist/shared/logger.d.ts.map +1 -1
- package/dist/shared/logger.js +17 -178
- package/dist/shared/logger.js.map +1 -1
- package/dist/shared/provider-preset-catalog.d.ts +6 -2
- package/dist/shared/provider-preset-catalog.d.ts.map +1 -1
- package/dist/shared/provider-preset-catalog.js +47 -26
- package/dist/shared/provider-preset-catalog.js.map +1 -1
- package/dist/ui/assets/index-ClqmrjNk.js +40 -0
- package/dist/ui/assets/index-CwMwQ-Z4.css +1 -0
- package/dist/ui/index.html +21 -13
- package/dist/update.d.ts +31 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +196 -0
- package/dist/update.js.map +1 -0
- package/lib/mcp/mcc-image-analysis-server.cjs +454 -454
- package/lib/mcp/mcc-websearch-server.cjs +339 -339
- package/lib/mcp-hooks/image-analysis-runtime.cjs +510 -510
- package/lib/mcp-hooks/image-analyzer-transformer.cjs +526 -526
- package/lib/mcp-hooks/websearch-transformer.cjs +1597 -1421
- package/lib/proxy/config/config-loader-facade.js +24 -24
- package/lib/proxy/glmt/delta-accumulator.js +362 -362
- package/lib/proxy/glmt/glmt-transformer.js +203 -203
- package/lib/proxy/glmt/index.js +40 -40
- package/lib/proxy/glmt/locale-enforcer.js +68 -68
- package/lib/proxy/glmt/pipeline/content-transformer.js +161 -161
- package/lib/proxy/glmt/pipeline/index.js +19 -19
- package/lib/proxy/glmt/pipeline/request-transformer.js +115 -115
- package/lib/proxy/glmt/pipeline/response-builder.js +204 -204
- package/lib/proxy/glmt/pipeline/stream-parser.js +233 -233
- package/lib/proxy/glmt/pipeline/tool-call-handler.js +77 -77
- package/lib/proxy/glmt/pipeline/types.js +5 -5
- package/lib/proxy/glmt/reasoning-enforcer.js +150 -150
- package/lib/proxy/glmt/sse-parser.js +101 -101
- package/lib/proxy/services/logging.js +13 -13
- package/lib/proxy/transformers/request-transformer.js +471 -471
- package/lib/proxy/transformers/sse-stream-transformer.js +198 -198
- package/lib/shared/logger.cjs +156 -138
- package/package.json +58 -41
- package/dist/dashboard-server.d.ts.map +0 -1
- package/dist/dashboard-server.js.map +0 -1
- package/dist/ui/assets/index-B16lhKZ6.js +0 -40
- package/dist/ui/assets/index-jEfiB6-h.css +0 -1
|
@@ -1,339 +1,339 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const {
|
|
4
|
-
getActiveProviderIds,
|
|
5
|
-
getQueryFingerprint,
|
|
6
|
-
getSkipReason,
|
|
7
|
-
hasAnyActiveProviders,
|
|
8
|
-
runLocalWebSearch,
|
|
9
|
-
shouldSkipHook,
|
|
10
|
-
traceWebSearchEvent,
|
|
11
|
-
} = require('../mcp-hooks/websearch-transformer.cjs');
|
|
12
|
-
|
|
13
|
-
const PROTOCOL_VERSION = '2024-11-05';
|
|
14
|
-
const SERVER_NAME = 'mcc-websearch';
|
|
15
|
-
const SERVER_VERSION = '1.0.0';
|
|
16
|
-
const TOOL_NAME = 'WebSearch';
|
|
17
|
-
const TOOL_ALIASES = ['search'];
|
|
18
|
-
const TOOL_DESCRIPTION =
|
|
19
|
-
'Third-party WebSearch replacement for MCC-managed Claude launches. Use this instead of Bash/curl/http fetches for web lookups. Provider order: Exa, Tavily, Brave Search, SearXNG, DuckDuckGo, then optional legacy CLI fallback.';
|
|
20
|
-
|
|
21
|
-
function isSupportedToolName(name) {
|
|
22
|
-
return name === TOOL_NAME || TOOL_ALIASES.includes(name);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
let inputBuffer = Buffer.alloc(0);
|
|
26
|
-
const sessionState = {
|
|
27
|
-
initializeCount: 0,
|
|
28
|
-
toolsListCount: 0,
|
|
29
|
-
exposed: false,
|
|
30
|
-
toolCalls: 0,
|
|
31
|
-
};
|
|
32
|
-
let sessionSummaryWritten = false;
|
|
33
|
-
|
|
34
|
-
function shouldExposeTools() {
|
|
35
|
-
return !shouldSkipHook() && hasAnyActiveProviders();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function getTools() {
|
|
39
|
-
if (!shouldExposeTools()) {
|
|
40
|
-
return [];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return [
|
|
44
|
-
{
|
|
45
|
-
name: TOOL_NAME,
|
|
46
|
-
description: TOOL_DESCRIPTION,
|
|
47
|
-
inputSchema: {
|
|
48
|
-
type: 'object',
|
|
49
|
-
properties: {
|
|
50
|
-
query: {
|
|
51
|
-
type: 'string',
|
|
52
|
-
description:
|
|
53
|
-
'Web query to resolve through CCS providers. Prefer this tool over ad hoc Bash/curl lookups when you need current web information.',
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
required: ['query'],
|
|
57
|
-
additionalProperties: false,
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function writeMessage(message) {
|
|
64
|
-
process.stdout.write(`${JSON.stringify(message)}\n`);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function writeResponse(id, result) {
|
|
68
|
-
writeMessage({
|
|
69
|
-
jsonrpc: '2.0',
|
|
70
|
-
id,
|
|
71
|
-
result,
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function writeError(id, code, message) {
|
|
76
|
-
writeMessage({
|
|
77
|
-
jsonrpc: '2.0',
|
|
78
|
-
id,
|
|
79
|
-
error: {
|
|
80
|
-
code,
|
|
81
|
-
message,
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async function handleToolCall(message) {
|
|
87
|
-
const id = message.id;
|
|
88
|
-
const params = message.params || {};
|
|
89
|
-
const toolArgs = params.arguments || {};
|
|
90
|
-
const toolName = params.name || '<missing>';
|
|
91
|
-
const query = typeof toolArgs.query === 'string' ? toolArgs.query.trim() : '';
|
|
92
|
-
const fingerprint = getQueryFingerprint(query);
|
|
93
|
-
|
|
94
|
-
if (!isSupportedToolName(toolName)) {
|
|
95
|
-
traceWebSearchEvent('mcp_tool_call_rejected', {
|
|
96
|
-
source: 'mcp',
|
|
97
|
-
reason: 'unknown_tool',
|
|
98
|
-
toolName,
|
|
99
|
-
});
|
|
100
|
-
writeError(id, -32602, `Unknown tool: ${toolName}`);
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
sessionState.toolCalls += 1;
|
|
105
|
-
traceWebSearchEvent('mcp_tool_call_received', {
|
|
106
|
-
source: 'mcp',
|
|
107
|
-
toolName,
|
|
108
|
-
...fingerprint,
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
if (!shouldExposeTools()) {
|
|
112
|
-
traceWebSearchEvent('mcp_tool_call_unavailable', {
|
|
113
|
-
source: 'mcp',
|
|
114
|
-
toolName,
|
|
115
|
-
exposed: false,
|
|
116
|
-
skipReason: getSkipReason(),
|
|
117
|
-
activeProviderIds: getActiveProviderIds(),
|
|
118
|
-
...fingerprint,
|
|
119
|
-
});
|
|
120
|
-
writeResponse(id, {
|
|
121
|
-
content: [
|
|
122
|
-
{
|
|
123
|
-
type: 'text',
|
|
124
|
-
text: 'CCS WebSearch is unavailable for this profile or no providers are ready.',
|
|
125
|
-
},
|
|
126
|
-
],
|
|
127
|
-
isError: true,
|
|
128
|
-
});
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (!query) {
|
|
133
|
-
traceWebSearchEvent('mcp_tool_call_rejected', {
|
|
134
|
-
source: 'mcp',
|
|
135
|
-
reason: 'empty_query',
|
|
136
|
-
toolName,
|
|
137
|
-
});
|
|
138
|
-
writeError(id, -32602, `Tool "${TOOL_NAME}" requires a non-empty string query.`);
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const result = await runLocalWebSearch(query);
|
|
143
|
-
if (result.success) {
|
|
144
|
-
traceWebSearchEvent('mcp_tool_call_result', {
|
|
145
|
-
source: 'mcp',
|
|
146
|
-
toolName,
|
|
147
|
-
success: true,
|
|
148
|
-
providerId: result.providerId,
|
|
149
|
-
providerName: result.providerName,
|
|
150
|
-
...fingerprint,
|
|
151
|
-
});
|
|
152
|
-
writeResponse(id, {
|
|
153
|
-
content: [{ type: 'text', text: result.content }],
|
|
154
|
-
});
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
traceWebSearchEvent('mcp_tool_call_result', {
|
|
159
|
-
source: 'mcp',
|
|
160
|
-
toolName,
|
|
161
|
-
success: false,
|
|
162
|
-
noActiveProviders: Boolean(result.noActiveProviders),
|
|
163
|
-
errorCount: result.errors.length,
|
|
164
|
-
...fingerprint,
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
const errorDetail =
|
|
168
|
-
result.noActiveProviders || result.errors.length === 0
|
|
169
|
-
? 'No active WebSearch providers are ready.'
|
|
170
|
-
: result.errors.map((entry) => `${entry.provider}: ${entry.error}`).join(' | ');
|
|
171
|
-
|
|
172
|
-
writeResponse(id, {
|
|
173
|
-
content: [
|
|
174
|
-
{
|
|
175
|
-
type: 'text',
|
|
176
|
-
text: `CCS local WebSearch failed for "${query}". ${errorDetail}`,
|
|
177
|
-
},
|
|
178
|
-
],
|
|
179
|
-
isError: true,
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
async function handleMessage(message) {
|
|
184
|
-
if (!message || message.jsonrpc !== '2.0' || typeof message.method !== 'string') {
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
switch (message.method) {
|
|
189
|
-
case 'initialize':
|
|
190
|
-
sessionState.initializeCount += 1;
|
|
191
|
-
sessionState.exposed = sessionState.exposed || shouldExposeTools();
|
|
192
|
-
traceWebSearchEvent('mcp_initialize', {
|
|
193
|
-
source: 'mcp',
|
|
194
|
-
exposed: shouldExposeTools(),
|
|
195
|
-
skipReason: getSkipReason(),
|
|
196
|
-
activeProviderIds: getActiveProviderIds(),
|
|
197
|
-
});
|
|
198
|
-
writeResponse(message.id, {
|
|
199
|
-
protocolVersion: PROTOCOL_VERSION,
|
|
200
|
-
capabilities: {
|
|
201
|
-
tools: {},
|
|
202
|
-
},
|
|
203
|
-
serverInfo: {
|
|
204
|
-
name: SERVER_NAME,
|
|
205
|
-
version: SERVER_VERSION,
|
|
206
|
-
},
|
|
207
|
-
});
|
|
208
|
-
return;
|
|
209
|
-
case 'notifications/initialized':
|
|
210
|
-
return;
|
|
211
|
-
case 'ping':
|
|
212
|
-
writeResponse(message.id, {});
|
|
213
|
-
return;
|
|
214
|
-
case 'tools/list':
|
|
215
|
-
sessionState.toolsListCount += 1;
|
|
216
|
-
{
|
|
217
|
-
const tools = getTools();
|
|
218
|
-
const exposed = tools.length > 0;
|
|
219
|
-
sessionState.exposed = sessionState.exposed || exposed;
|
|
220
|
-
traceWebSearchEvent('mcp_tools_list', {
|
|
221
|
-
source: 'mcp',
|
|
222
|
-
exposed,
|
|
223
|
-
toolNames: tools.map((tool) => tool.name),
|
|
224
|
-
activeProviderIds: getActiveProviderIds(),
|
|
225
|
-
skipReason: getSkipReason(),
|
|
226
|
-
});
|
|
227
|
-
writeResponse(message.id, { tools });
|
|
228
|
-
}
|
|
229
|
-
return;
|
|
230
|
-
case 'tools/call':
|
|
231
|
-
await handleToolCall(message);
|
|
232
|
-
return;
|
|
233
|
-
default:
|
|
234
|
-
if (message.id !== undefined) {
|
|
235
|
-
writeError(message.id, -32601, `Method not found: ${message.method}`);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function writeSessionSummary(exitCodeOrSignal) {
|
|
241
|
-
if (sessionSummaryWritten) {
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
sessionSummaryWritten = true;
|
|
246
|
-
traceWebSearchEvent('mcp_session_summary', {
|
|
247
|
-
source: 'mcp',
|
|
248
|
-
initializeCount: sessionState.initializeCount,
|
|
249
|
-
toolsListCount: sessionState.toolsListCount,
|
|
250
|
-
exposed: sessionState.exposed,
|
|
251
|
-
toolCalls: sessionState.toolCalls,
|
|
252
|
-
calledWebSearch: sessionState.toolCalls > 0,
|
|
253
|
-
likelyBypassed: sessionState.exposed && sessionState.toolCalls === 0 ? 'unknown' : false,
|
|
254
|
-
activeProviderIds: getActiveProviderIds(),
|
|
255
|
-
skipReason: getSkipReason(),
|
|
256
|
-
exitCode: typeof exitCodeOrSignal === 'number' ? exitCodeOrSignal : null,
|
|
257
|
-
exitSignal: typeof exitCodeOrSignal === 'string' ? exitCodeOrSignal : null,
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function parseMessages() {
|
|
262
|
-
while (true) {
|
|
263
|
-
let body;
|
|
264
|
-
const startsWithLegacyHeaders = inputBuffer
|
|
265
|
-
.slice(0, Math.min(inputBuffer.length, 32))
|
|
266
|
-
.toString('utf8')
|
|
267
|
-
.toLowerCase()
|
|
268
|
-
.startsWith('content-length:');
|
|
269
|
-
|
|
270
|
-
if (startsWithLegacyHeaders) {
|
|
271
|
-
const headerEnd = inputBuffer.indexOf('\r\n\r\n');
|
|
272
|
-
if (headerEnd === -1) {
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const headerText = inputBuffer.slice(0, headerEnd).toString('utf8');
|
|
277
|
-
const contentLengthMatch = headerText.match(/content-length:\s*(\d+)/i);
|
|
278
|
-
if (!contentLengthMatch) {
|
|
279
|
-
inputBuffer = Buffer.alloc(0);
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const contentLength = Number.parseInt(contentLengthMatch[1], 10);
|
|
284
|
-
const messageEnd = headerEnd + 4 + contentLength;
|
|
285
|
-
if (inputBuffer.length < messageEnd) {
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
body = inputBuffer.slice(headerEnd + 4, messageEnd).toString('utf8');
|
|
290
|
-
inputBuffer = inputBuffer.slice(messageEnd);
|
|
291
|
-
} else {
|
|
292
|
-
const newlineIndex = inputBuffer.indexOf('\n');
|
|
293
|
-
if (newlineIndex === -1) {
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
body = inputBuffer.slice(0, newlineIndex).toString('utf8').replace(/\r$/, '').trim();
|
|
298
|
-
inputBuffer = inputBuffer.slice(newlineIndex + 1);
|
|
299
|
-
if (!body) {
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
let message;
|
|
305
|
-
try {
|
|
306
|
-
message = JSON.parse(body);
|
|
307
|
-
} catch {
|
|
308
|
-
continue;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
Promise.resolve(handleMessage(message)).catch((error) => {
|
|
312
|
-
if (message && message.id !== undefined) {
|
|
313
|
-
writeError(message.id, -32603, (error && error.message) || 'Internal error');
|
|
314
|
-
}
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
process.stdin.on('data', (chunk) => {
|
|
320
|
-
inputBuffer = Buffer.concat([inputBuffer, chunk]);
|
|
321
|
-
parseMessages();
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
process.stdin.on('error', () => {
|
|
325
|
-
process.exit(0);
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
process.on('exit', (code) => {
|
|
329
|
-
writeSessionSummary(code);
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
['SIGINT', 'SIGTERM', 'SIGHUP'].forEach((signal) => {
|
|
333
|
-
process.on(signal, () => {
|
|
334
|
-
writeSessionSummary(signal);
|
|
335
|
-
process.exit(0);
|
|
336
|
-
});
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
process.stdin.resume();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
getActiveProviderIds,
|
|
5
|
+
getQueryFingerprint,
|
|
6
|
+
getSkipReason,
|
|
7
|
+
hasAnyActiveProviders,
|
|
8
|
+
runLocalWebSearch,
|
|
9
|
+
shouldSkipHook,
|
|
10
|
+
traceWebSearchEvent,
|
|
11
|
+
} = require('../mcp-hooks/websearch-transformer.cjs');
|
|
12
|
+
|
|
13
|
+
const PROTOCOL_VERSION = '2024-11-05';
|
|
14
|
+
const SERVER_NAME = 'mcc-websearch';
|
|
15
|
+
const SERVER_VERSION = '1.0.0';
|
|
16
|
+
const TOOL_NAME = 'WebSearch';
|
|
17
|
+
const TOOL_ALIASES = ['search'];
|
|
18
|
+
const TOOL_DESCRIPTION =
|
|
19
|
+
'Third-party WebSearch replacement for MCC-managed Claude launches. Use this instead of Bash/curl/http fetches for web lookups. Provider order: Exa, Tavily, Brave Search, SearXNG, DuckDuckGo, then optional legacy CLI fallback.';
|
|
20
|
+
|
|
21
|
+
function isSupportedToolName(name) {
|
|
22
|
+
return name === TOOL_NAME || TOOL_ALIASES.includes(name);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let inputBuffer = Buffer.alloc(0);
|
|
26
|
+
const sessionState = {
|
|
27
|
+
initializeCount: 0,
|
|
28
|
+
toolsListCount: 0,
|
|
29
|
+
exposed: false,
|
|
30
|
+
toolCalls: 0,
|
|
31
|
+
};
|
|
32
|
+
let sessionSummaryWritten = false;
|
|
33
|
+
|
|
34
|
+
function shouldExposeTools() {
|
|
35
|
+
return !shouldSkipHook() && hasAnyActiveProviders();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getTools() {
|
|
39
|
+
if (!shouldExposeTools()) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return [
|
|
44
|
+
{
|
|
45
|
+
name: TOOL_NAME,
|
|
46
|
+
description: TOOL_DESCRIPTION,
|
|
47
|
+
inputSchema: {
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {
|
|
50
|
+
query: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
description:
|
|
53
|
+
'Web query to resolve through CCS providers. Prefer this tool over ad hoc Bash/curl lookups when you need current web information.',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
required: ['query'],
|
|
57
|
+
additionalProperties: false,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function writeMessage(message) {
|
|
64
|
+
process.stdout.write(`${JSON.stringify(message)}\n`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function writeResponse(id, result) {
|
|
68
|
+
writeMessage({
|
|
69
|
+
jsonrpc: '2.0',
|
|
70
|
+
id,
|
|
71
|
+
result,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function writeError(id, code, message) {
|
|
76
|
+
writeMessage({
|
|
77
|
+
jsonrpc: '2.0',
|
|
78
|
+
id,
|
|
79
|
+
error: {
|
|
80
|
+
code,
|
|
81
|
+
message,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function handleToolCall(message) {
|
|
87
|
+
const id = message.id;
|
|
88
|
+
const params = message.params || {};
|
|
89
|
+
const toolArgs = params.arguments || {};
|
|
90
|
+
const toolName = params.name || '<missing>';
|
|
91
|
+
const query = typeof toolArgs.query === 'string' ? toolArgs.query.trim() : '';
|
|
92
|
+
const fingerprint = getQueryFingerprint(query);
|
|
93
|
+
|
|
94
|
+
if (!isSupportedToolName(toolName)) {
|
|
95
|
+
traceWebSearchEvent('mcp_tool_call_rejected', {
|
|
96
|
+
source: 'mcp',
|
|
97
|
+
reason: 'unknown_tool',
|
|
98
|
+
toolName,
|
|
99
|
+
});
|
|
100
|
+
writeError(id, -32602, `Unknown tool: ${toolName}`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
sessionState.toolCalls += 1;
|
|
105
|
+
traceWebSearchEvent('mcp_tool_call_received', {
|
|
106
|
+
source: 'mcp',
|
|
107
|
+
toolName,
|
|
108
|
+
...fingerprint,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (!shouldExposeTools()) {
|
|
112
|
+
traceWebSearchEvent('mcp_tool_call_unavailable', {
|
|
113
|
+
source: 'mcp',
|
|
114
|
+
toolName,
|
|
115
|
+
exposed: false,
|
|
116
|
+
skipReason: getSkipReason(),
|
|
117
|
+
activeProviderIds: getActiveProviderIds(),
|
|
118
|
+
...fingerprint,
|
|
119
|
+
});
|
|
120
|
+
writeResponse(id, {
|
|
121
|
+
content: [
|
|
122
|
+
{
|
|
123
|
+
type: 'text',
|
|
124
|
+
text: 'CCS WebSearch is unavailable for this profile or no providers are ready.',
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
isError: true,
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!query) {
|
|
133
|
+
traceWebSearchEvent('mcp_tool_call_rejected', {
|
|
134
|
+
source: 'mcp',
|
|
135
|
+
reason: 'empty_query',
|
|
136
|
+
toolName,
|
|
137
|
+
});
|
|
138
|
+
writeError(id, -32602, `Tool "${TOOL_NAME}" requires a non-empty string query.`);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const result = await runLocalWebSearch(query);
|
|
143
|
+
if (result.success) {
|
|
144
|
+
traceWebSearchEvent('mcp_tool_call_result', {
|
|
145
|
+
source: 'mcp',
|
|
146
|
+
toolName,
|
|
147
|
+
success: true,
|
|
148
|
+
providerId: result.providerId,
|
|
149
|
+
providerName: result.providerName,
|
|
150
|
+
...fingerprint,
|
|
151
|
+
});
|
|
152
|
+
writeResponse(id, {
|
|
153
|
+
content: [{ type: 'text', text: result.content }],
|
|
154
|
+
});
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
traceWebSearchEvent('mcp_tool_call_result', {
|
|
159
|
+
source: 'mcp',
|
|
160
|
+
toolName,
|
|
161
|
+
success: false,
|
|
162
|
+
noActiveProviders: Boolean(result.noActiveProviders),
|
|
163
|
+
errorCount: result.errors.length,
|
|
164
|
+
...fingerprint,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const errorDetail =
|
|
168
|
+
result.noActiveProviders || result.errors.length === 0
|
|
169
|
+
? 'No active WebSearch providers are ready.'
|
|
170
|
+
: result.errors.map((entry) => `${entry.provider}: ${entry.error}`).join(' | ');
|
|
171
|
+
|
|
172
|
+
writeResponse(id, {
|
|
173
|
+
content: [
|
|
174
|
+
{
|
|
175
|
+
type: 'text',
|
|
176
|
+
text: `CCS local WebSearch failed for "${query}". ${errorDetail}`,
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
isError: true,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function handleMessage(message) {
|
|
184
|
+
if (!message || message.jsonrpc !== '2.0' || typeof message.method !== 'string') {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
switch (message.method) {
|
|
189
|
+
case 'initialize':
|
|
190
|
+
sessionState.initializeCount += 1;
|
|
191
|
+
sessionState.exposed = sessionState.exposed || shouldExposeTools();
|
|
192
|
+
traceWebSearchEvent('mcp_initialize', {
|
|
193
|
+
source: 'mcp',
|
|
194
|
+
exposed: shouldExposeTools(),
|
|
195
|
+
skipReason: getSkipReason(),
|
|
196
|
+
activeProviderIds: getActiveProviderIds(),
|
|
197
|
+
});
|
|
198
|
+
writeResponse(message.id, {
|
|
199
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
200
|
+
capabilities: {
|
|
201
|
+
tools: {},
|
|
202
|
+
},
|
|
203
|
+
serverInfo: {
|
|
204
|
+
name: SERVER_NAME,
|
|
205
|
+
version: SERVER_VERSION,
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
return;
|
|
209
|
+
case 'notifications/initialized':
|
|
210
|
+
return;
|
|
211
|
+
case 'ping':
|
|
212
|
+
writeResponse(message.id, {});
|
|
213
|
+
return;
|
|
214
|
+
case 'tools/list':
|
|
215
|
+
sessionState.toolsListCount += 1;
|
|
216
|
+
{
|
|
217
|
+
const tools = getTools();
|
|
218
|
+
const exposed = tools.length > 0;
|
|
219
|
+
sessionState.exposed = sessionState.exposed || exposed;
|
|
220
|
+
traceWebSearchEvent('mcp_tools_list', {
|
|
221
|
+
source: 'mcp',
|
|
222
|
+
exposed,
|
|
223
|
+
toolNames: tools.map((tool) => tool.name),
|
|
224
|
+
activeProviderIds: getActiveProviderIds(),
|
|
225
|
+
skipReason: getSkipReason(),
|
|
226
|
+
});
|
|
227
|
+
writeResponse(message.id, { tools });
|
|
228
|
+
}
|
|
229
|
+
return;
|
|
230
|
+
case 'tools/call':
|
|
231
|
+
await handleToolCall(message);
|
|
232
|
+
return;
|
|
233
|
+
default:
|
|
234
|
+
if (message.id !== undefined) {
|
|
235
|
+
writeError(message.id, -32601, `Method not found: ${message.method}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function writeSessionSummary(exitCodeOrSignal) {
|
|
241
|
+
if (sessionSummaryWritten) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
sessionSummaryWritten = true;
|
|
246
|
+
traceWebSearchEvent('mcp_session_summary', {
|
|
247
|
+
source: 'mcp',
|
|
248
|
+
initializeCount: sessionState.initializeCount,
|
|
249
|
+
toolsListCount: sessionState.toolsListCount,
|
|
250
|
+
exposed: sessionState.exposed,
|
|
251
|
+
toolCalls: sessionState.toolCalls,
|
|
252
|
+
calledWebSearch: sessionState.toolCalls > 0,
|
|
253
|
+
likelyBypassed: sessionState.exposed && sessionState.toolCalls === 0 ? 'unknown' : false,
|
|
254
|
+
activeProviderIds: getActiveProviderIds(),
|
|
255
|
+
skipReason: getSkipReason(),
|
|
256
|
+
exitCode: typeof exitCodeOrSignal === 'number' ? exitCodeOrSignal : null,
|
|
257
|
+
exitSignal: typeof exitCodeOrSignal === 'string' ? exitCodeOrSignal : null,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function parseMessages() {
|
|
262
|
+
while (true) {
|
|
263
|
+
let body;
|
|
264
|
+
const startsWithLegacyHeaders = inputBuffer
|
|
265
|
+
.slice(0, Math.min(inputBuffer.length, 32))
|
|
266
|
+
.toString('utf8')
|
|
267
|
+
.toLowerCase()
|
|
268
|
+
.startsWith('content-length:');
|
|
269
|
+
|
|
270
|
+
if (startsWithLegacyHeaders) {
|
|
271
|
+
const headerEnd = inputBuffer.indexOf('\r\n\r\n');
|
|
272
|
+
if (headerEnd === -1) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const headerText = inputBuffer.slice(0, headerEnd).toString('utf8');
|
|
277
|
+
const contentLengthMatch = headerText.match(/content-length:\s*(\d+)/i);
|
|
278
|
+
if (!contentLengthMatch) {
|
|
279
|
+
inputBuffer = Buffer.alloc(0);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const contentLength = Number.parseInt(contentLengthMatch[1], 10);
|
|
284
|
+
const messageEnd = headerEnd + 4 + contentLength;
|
|
285
|
+
if (inputBuffer.length < messageEnd) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
body = inputBuffer.slice(headerEnd + 4, messageEnd).toString('utf8');
|
|
290
|
+
inputBuffer = inputBuffer.slice(messageEnd);
|
|
291
|
+
} else {
|
|
292
|
+
const newlineIndex = inputBuffer.indexOf('\n');
|
|
293
|
+
if (newlineIndex === -1) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
body = inputBuffer.slice(0, newlineIndex).toString('utf8').replace(/\r$/, '').trim();
|
|
298
|
+
inputBuffer = inputBuffer.slice(newlineIndex + 1);
|
|
299
|
+
if (!body) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
let message;
|
|
305
|
+
try {
|
|
306
|
+
message = JSON.parse(body);
|
|
307
|
+
} catch {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
Promise.resolve(handleMessage(message)).catch((error) => {
|
|
312
|
+
if (message && message.id !== undefined) {
|
|
313
|
+
writeError(message.id, -32603, (error && error.message) || 'Internal error');
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
process.stdin.on('data', (chunk) => {
|
|
320
|
+
inputBuffer = Buffer.concat([inputBuffer, chunk]);
|
|
321
|
+
parseMessages();
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
process.stdin.on('error', () => {
|
|
325
|
+
process.exit(0);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
process.on('exit', (code) => {
|
|
329
|
+
writeSessionSummary(code);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
['SIGINT', 'SIGTERM', 'SIGHUP'].forEach((signal) => {
|
|
333
|
+
process.on(signal, () => {
|
|
334
|
+
writeSessionSummary(signal);
|
|
335
|
+
process.exit(0);
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
process.stdin.resume();
|