tachibot-mcp 2.9.0 → 2.10.0
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.
|
@@ -12,6 +12,7 @@ import { getGrokApiKey, hasGrokApiKey } from "../utils/api-keys.js";
|
|
|
12
12
|
import { stripFormatting } from "../utils/format-stripper.js";
|
|
13
13
|
import { FORMAT_INSTRUCTION } from "../utils/format-constants.js";
|
|
14
14
|
import { tryOpenRouterGateway, isGatewayEnabled } from "../utils/openrouter-gateway.js";
|
|
15
|
+
import { withHeartbeat } from "../utils/streaming-helper.js";
|
|
15
16
|
// Note: renderOutput is applied centrally in server.ts safeAddTool() - no need to import here
|
|
16
17
|
const __filename = fileURLToPath(import.meta.url);
|
|
17
18
|
const __dirname = path.dirname(__filename);
|
|
@@ -131,7 +132,7 @@ export const grokReasonTool = {
|
|
|
131
132
|
context: z.string().optional().describe("Additional context for the problem"),
|
|
132
133
|
useHeavy: z.boolean().optional().describe("Use expensive Grok 4 Heavy model ($3/$15) for complex tasks")
|
|
133
134
|
}),
|
|
134
|
-
execute: async (args, { log }) => {
|
|
135
|
+
execute: async (args, { log, reportProgress }) => {
|
|
135
136
|
const { problem, approach = "first-principles", context, useHeavy } = args;
|
|
136
137
|
const approachPrompts = {
|
|
137
138
|
analytical: "Break down the problem systematically and analyze each component",
|
|
@@ -156,8 +157,10 @@ ${FORMAT_INSTRUCTION}`
|
|
|
156
157
|
const model = useHeavy ? GrokModel.GROK_4_HEAVY : GrokModel.GROK_4_1_FAST_REASONING;
|
|
157
158
|
const maxTokens = useHeavy ? 100000 : 16384; // 100k for heavy, 16k for normal reasoning
|
|
158
159
|
log?.info(`Using Grok model: ${model} for deep reasoning (max tokens: ${maxTokens}, cost: ${useHeavy ? 'expensive $3/$15' : 'cheap $0.20/$0.50'})`);
|
|
159
|
-
// Use
|
|
160
|
-
|
|
160
|
+
// Use heartbeat to prevent MCP timeout during long reasoning operations
|
|
161
|
+
const reportFn = reportProgress ?? (async () => { });
|
|
162
|
+
const result = await withHeartbeat(() => callGrok(messages, model, 0.7, maxTokens, true, 'llm-orchestration'), reportFn);
|
|
163
|
+
return stripFormatting(result);
|
|
161
164
|
}
|
|
162
165
|
};
|
|
163
166
|
/**
|
|
@@ -174,7 +177,7 @@ export const grokCodeTool = {
|
|
|
174
177
|
language: z.string().optional().describe("Programming language (e.g., 'typescript', 'python')"),
|
|
175
178
|
requirements: z.string().optional().describe("Specific requirements or focus areas")
|
|
176
179
|
}),
|
|
177
|
-
execute: async (args, { log }) => {
|
|
180
|
+
execute: async (args, { log, reportProgress }) => {
|
|
178
181
|
const { task, code, language, requirements } = args;
|
|
179
182
|
const taskPrompts = {
|
|
180
183
|
analyze: "Analyze this code for logic, structure, and potential issues",
|
|
@@ -198,8 +201,10 @@ ${FORMAT_INSTRUCTION}`
|
|
|
198
201
|
}
|
|
199
202
|
];
|
|
200
203
|
log?.info(`Using Grok 4.1 Fast Non-Reasoning (2M context, tool-calling optimized, $0.20/$0.50)`);
|
|
201
|
-
// Use
|
|
202
|
-
|
|
204
|
+
// Use heartbeat to prevent MCP timeout
|
|
205
|
+
const reportFn = reportProgress ?? (async () => { });
|
|
206
|
+
const result = await withHeartbeat(() => callGrok(messages, GrokModel.GROK_4_1_FAST, 0.2, 4000, true, 'code-analysis'), reportFn);
|
|
207
|
+
return stripFormatting(result);
|
|
203
208
|
}
|
|
204
209
|
};
|
|
205
210
|
/**
|
|
@@ -215,7 +220,7 @@ export const grokDebugTool = {
|
|
|
215
220
|
error: z.string().optional().describe("Error message or stack trace"),
|
|
216
221
|
context: z.string().optional().describe("Additional context about the environment or conditions")
|
|
217
222
|
}),
|
|
218
|
-
execute: async (args, { log }) => {
|
|
223
|
+
execute: async (args, { log, reportProgress }) => {
|
|
219
224
|
const { issue, code, error, context } = args;
|
|
220
225
|
let prompt = `Debug this issue: ${issue}\n`;
|
|
221
226
|
if (error) {
|
|
@@ -244,8 +249,10 @@ ${FORMAT_INSTRUCTION}`
|
|
|
244
249
|
}
|
|
245
250
|
];
|
|
246
251
|
log?.info(`Using Grok 4.1 Fast Non-Reasoning for debugging (tool-calling optimized, $0.20/$0.50)`);
|
|
247
|
-
// Use
|
|
248
|
-
|
|
252
|
+
// Use heartbeat to prevent MCP timeout
|
|
253
|
+
const reportFn = reportProgress ?? (async () => { });
|
|
254
|
+
const result = await withHeartbeat(() => callGrok(messages, GrokModel.GROK_4_1_FAST, 0.3, 3000, true, 'code-analysis'), reportFn);
|
|
255
|
+
return stripFormatting(result);
|
|
249
256
|
}
|
|
250
257
|
};
|
|
251
258
|
/**
|
|
@@ -262,7 +269,7 @@ export const grokArchitectTool = {
|
|
|
262
269
|
.optional()
|
|
263
270
|
.describe("Expected scale - must be one of: small, medium, large, enterprise")
|
|
264
271
|
}),
|
|
265
|
-
execute: async (args, { log }) => {
|
|
272
|
+
execute: async (args, { log, reportProgress }) => {
|
|
266
273
|
const { requirements, constraints, scale } = args;
|
|
267
274
|
const messages = [
|
|
268
275
|
{
|
|
@@ -279,8 +286,10 @@ ${FORMAT_INSTRUCTION}`
|
|
|
279
286
|
}
|
|
280
287
|
];
|
|
281
288
|
log?.info(`Using Grok 4.1 Fast Reasoning for architecture (latest model, $0.20/$0.50)`);
|
|
282
|
-
// Use
|
|
283
|
-
|
|
289
|
+
// Use heartbeat to prevent MCP timeout
|
|
290
|
+
const reportFn = reportProgress ?? (async () => { });
|
|
291
|
+
const result = await withHeartbeat(() => callGrok(messages, GrokModel.GROK_4_1_FAST_REASONING, 0.6, 4000, true, 'llm-orchestration'), reportFn);
|
|
292
|
+
return stripFormatting(result);
|
|
284
293
|
}
|
|
285
294
|
};
|
|
286
295
|
/**
|
|
@@ -296,7 +305,7 @@ export const grokBrainstormTool = {
|
|
|
296
305
|
numIdeas: z.number().optional().describe("Number of ideas to generate (default: 5)"),
|
|
297
306
|
forceHeavy: z.boolean().optional().describe("Use expensive Grok 4 Heavy model ($3/$15) for deeper creativity")
|
|
298
307
|
}),
|
|
299
|
-
execute: async (args, { log }) => {
|
|
308
|
+
execute: async (args, { log, reportProgress }) => {
|
|
300
309
|
const { topic, constraints, numIdeas = 5, forceHeavy = false } = args; // Changed: Default to cheap model
|
|
301
310
|
const messages = [
|
|
302
311
|
{
|
|
@@ -313,8 +322,10 @@ ${FORMAT_INSTRUCTION}`
|
|
|
313
322
|
// Use GROK_4_1_FAST_REASONING for creative brainstorming (needs reasoning for creativity), GROK_4_HEAVY only if explicitly requested
|
|
314
323
|
const model = forceHeavy ? GrokModel.GROK_4_HEAVY : GrokModel.GROK_4_1_FAST_REASONING;
|
|
315
324
|
log?.info(`Brainstorming with Grok model: ${model} (Heavy: ${forceHeavy}, cost: ${forceHeavy ? 'expensive $3/$15' : 'cheap $0.20/$0.50 - latest 4.1'})`);
|
|
316
|
-
// Use
|
|
317
|
-
|
|
325
|
+
// Use heartbeat to prevent MCP timeout
|
|
326
|
+
const reportFn = reportProgress ?? (async () => { });
|
|
327
|
+
const result = await withHeartbeat(() => callGrok(messages, model, 0.95, 4000, true, 'llm-orchestration'), reportFn);
|
|
328
|
+
return stripFormatting(result);
|
|
318
329
|
}
|
|
319
330
|
};
|
|
320
331
|
/**
|
|
@@ -12,6 +12,7 @@ import { tryOpenRouterGateway, isGatewayEnabled } from "../utils/openrouter-gate
|
|
|
12
12
|
import { OPENAI_MODELS } from "../config/model-constants.js";
|
|
13
13
|
import { FORMAT_INSTRUCTION } from "../utils/format-constants.js";
|
|
14
14
|
import { stripFormatting } from "../utils/format-stripper.js";
|
|
15
|
+
import { withHeartbeat } from "../utils/streaming-helper.js";
|
|
15
16
|
const __filename = fileURLToPath(import.meta.url);
|
|
16
17
|
const __dirname = path.dirname(__filename);
|
|
17
18
|
config({ path: path.resolve(__dirname, '../../../.env') });
|
|
@@ -476,7 +477,7 @@ export const openaiGpt5ReasonTool = {
|
|
|
476
477
|
.default("analytical")
|
|
477
478
|
.describe("Reasoning mode - must be one of: mathematical, scientific, logical, analytical")
|
|
478
479
|
}),
|
|
479
|
-
execute: async (args, { log }) => {
|
|
480
|
+
execute: async (args, { log, reportProgress }) => {
|
|
480
481
|
const modePrompts = {
|
|
481
482
|
mathematical: "Focus on mathematical proofs, calculations, and formal logic",
|
|
482
483
|
scientific: "Apply scientific method and empirical reasoning",
|
|
@@ -497,8 +498,9 @@ ${FORMAT_INSTRUCTION}`
|
|
|
497
498
|
content: args.query
|
|
498
499
|
}
|
|
499
500
|
];
|
|
500
|
-
// Use
|
|
501
|
-
|
|
501
|
+
// Use heartbeat to prevent MCP timeout during reasoning
|
|
502
|
+
const reportFn = reportProgress ?? (async () => { });
|
|
503
|
+
return await withHeartbeat(() => callOpenAI(messages, OPENAI_MODELS.DEFAULT, 0.7, 4000, "high"), reportFn);
|
|
502
504
|
}
|
|
503
505
|
};
|
|
504
506
|
/**
|
|
@@ -565,7 +567,7 @@ export const openaiCodeReviewTool = {
|
|
|
565
567
|
.optional()
|
|
566
568
|
.describe("Focus areas - array of: security, performance, readability, bugs, best-practices")
|
|
567
569
|
}),
|
|
568
|
-
execute: async (args, { log }) => {
|
|
570
|
+
execute: async (args, { log, reportProgress }) => {
|
|
569
571
|
const focusText = args.focusAreas
|
|
570
572
|
? `Focus especially on: ${args.focusAreas.join(', ')}`
|
|
571
573
|
: "Review all aspects: security, performance, readability, bugs, and best practices";
|
|
@@ -584,7 +586,9 @@ ${FORMAT_INSTRUCTION}`
|
|
|
584
586
|
content: `Review this code:\n\`\`\`${args.language || ''}\n${args.code}\n\`\`\``
|
|
585
587
|
}
|
|
586
588
|
];
|
|
587
|
-
|
|
589
|
+
// Use heartbeat to prevent MCP timeout
|
|
590
|
+
const reportFn = reportProgress ?? (async () => { });
|
|
591
|
+
return await withHeartbeat(() => callOpenAI(messages, OPENAI_MODELS.DEFAULT, 0.3, 4000, "medium"), reportFn);
|
|
588
592
|
}
|
|
589
593
|
};
|
|
590
594
|
/**
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { FORMAT_INSTRUCTION } from "../utils/format-constants.js";
|
|
7
7
|
import { stripFormatting } from "../utils/format-stripper.js";
|
|
8
|
+
import { withHeartbeat } from "../utils/streaming-helper.js";
|
|
8
9
|
// NOTE: dotenv is loaded in server.ts before any imports
|
|
9
10
|
// No need to reload here - just read from process.env
|
|
10
11
|
// OpenRouter API configuration
|
|
@@ -114,7 +115,7 @@ export const qwenCoderTool = {
|
|
|
114
115
|
language: z.string().optional().describe("Programming language (e.g., 'typescript', 'python')"),
|
|
115
116
|
useFree: z.boolean().optional().default(false).describe("Use free tier model instead of premium")
|
|
116
117
|
}),
|
|
117
|
-
execute: async (args, { log }) => {
|
|
118
|
+
execute: async (args, { log, reportProgress }) => {
|
|
118
119
|
const taskPrompts = {
|
|
119
120
|
generate: "Generate new code according to requirements",
|
|
120
121
|
review: "Review code for quality, bugs, and improvements",
|
|
@@ -138,7 +139,9 @@ ${FORMAT_INSTRUCTION}`;
|
|
|
138
139
|
{ role: "user", content: userPrompt }
|
|
139
140
|
];
|
|
140
141
|
const model = args.useFree === true ? OpenRouterModel.QWEN3_30B : OpenRouterModel.QWEN3_CODER;
|
|
141
|
-
|
|
142
|
+
// Use heartbeat to prevent MCP timeout
|
|
143
|
+
const reportFn = reportProgress ?? (async () => { });
|
|
144
|
+
return await withHeartbeat(() => callOpenRouter(messages, model, 0.2, 8000), reportFn);
|
|
142
145
|
}
|
|
143
146
|
};
|
|
144
147
|
/**
|
|
@@ -274,7 +277,7 @@ export const qwenAlgoTool = {
|
|
|
274
277
|
.default("general")
|
|
275
278
|
.describe("Analysis focus - must be one of: optimize, complexity, data-structure, memory, general")
|
|
276
279
|
}),
|
|
277
|
-
execute: async (args, { log }) => {
|
|
280
|
+
execute: async (args, { log, reportProgress }) => {
|
|
278
281
|
const focusPrompts = {
|
|
279
282
|
optimize: "Focus on performance optimization, bottlenecks, and algorithmic improvements.",
|
|
280
283
|
complexity: "Focus on time/space complexity analysis (best, average, worst case).",
|
|
@@ -296,7 +299,9 @@ ${FORMAT_INSTRUCTION}`
|
|
|
296
299
|
: args.problem
|
|
297
300
|
}
|
|
298
301
|
];
|
|
299
|
-
|
|
302
|
+
// Use heartbeat to prevent MCP timeout
|
|
303
|
+
const reportFn = reportProgress ?? (async () => { });
|
|
304
|
+
return await withHeartbeat(() => callOpenRouter(messages, OpenRouterModel.QWQ_32B, 0.3, 8000), reportFn);
|
|
300
305
|
}
|
|
301
306
|
};
|
|
302
307
|
/**
|
|
@@ -355,7 +360,7 @@ export const kimiThinkingTool = {
|
|
|
355
360
|
.describe("Reasoning approach - must be one of: step-by-step, analytical, creative, systematic"),
|
|
356
361
|
maxSteps: z.number().optional().default(3).describe("Maximum reasoning steps (default: 3)")
|
|
357
362
|
}),
|
|
358
|
-
execute: async (args, { log }) => {
|
|
363
|
+
execute: async (args, { log, reportProgress }) => {
|
|
359
364
|
const approachPrompts = {
|
|
360
365
|
"step-by-step": "Break down the problem into clear steps and solve systematically",
|
|
361
366
|
analytical: "Analyze the problem deeply, considering multiple perspectives and implications",
|
|
@@ -375,12 +380,13 @@ ${FORMAT_INSTRUCTION}`
|
|
|
375
380
|
content: args.problem
|
|
376
381
|
}
|
|
377
382
|
];
|
|
378
|
-
//
|
|
379
|
-
|
|
383
|
+
// Use heartbeat to prevent MCP timeout during reasoning
|
|
384
|
+
const reportFn = reportProgress ?? (async () => { });
|
|
385
|
+
return await withHeartbeat(() => callOpenRouter(messages, OpenRouterModel.KIMI_K2_THINKING, 0.4, 3000, {
|
|
380
386
|
top_p: 0.9,
|
|
381
387
|
presence_penalty: 0.1,
|
|
382
388
|
frequency_penalty: 0.2
|
|
383
|
-
});
|
|
389
|
+
}), reportFn);
|
|
384
390
|
}
|
|
385
391
|
};
|
|
386
392
|
/**
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming Helper - Progress Heartbeats for Long-Running MCP Tools
|
|
3
|
+
*
|
|
4
|
+
* Prevents MCP timeout (~60s) by sending periodic progress notifications.
|
|
5
|
+
* Uses standard MCP notifications/progress which should work with all clients.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Create a heartbeat that sends progress notifications at regular intervals.
|
|
9
|
+
* Useful for keeping MCP connections alive during long-running operations.
|
|
10
|
+
*/
|
|
11
|
+
export function createHeartbeat(options) {
|
|
12
|
+
const { intervalMs = 5000, reportProgress, onError } = options;
|
|
13
|
+
let progress = 0;
|
|
14
|
+
let stopped = false;
|
|
15
|
+
const interval = setInterval(async () => {
|
|
16
|
+
if (stopped)
|
|
17
|
+
return;
|
|
18
|
+
// Increment progress, cap at 90% (leave room for completion)
|
|
19
|
+
progress = Math.min(progress + 10, 90);
|
|
20
|
+
try {
|
|
21
|
+
await reportProgress({ progress, total: 100 });
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
// Client may have disconnected - stop heartbeat
|
|
25
|
+
if (onError && error instanceof Error) {
|
|
26
|
+
onError(error);
|
|
27
|
+
}
|
|
28
|
+
stopped = true;
|
|
29
|
+
clearInterval(interval);
|
|
30
|
+
}
|
|
31
|
+
}, intervalMs);
|
|
32
|
+
return {
|
|
33
|
+
stop: () => {
|
|
34
|
+
stopped = true;
|
|
35
|
+
clearInterval(interval);
|
|
36
|
+
},
|
|
37
|
+
complete: async () => {
|
|
38
|
+
stopped = true;
|
|
39
|
+
clearInterval(interval);
|
|
40
|
+
try {
|
|
41
|
+
await reportProgress({ progress: 100, total: 100 });
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Ignore completion errors - client may have disconnected
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Execute a function with automatic progress heartbeats.
|
|
51
|
+
* Heartbeat starts immediately, stops when function completes (success or error).
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* const result = await withHeartbeat(
|
|
56
|
+
* () => callExternalAPI(prompt),
|
|
57
|
+
* context.reportProgress
|
|
58
|
+
* );
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export async function withHeartbeat(fn, reportProgress, intervalMs = 5000) {
|
|
62
|
+
const heartbeat = createHeartbeat({ intervalMs, reportProgress });
|
|
63
|
+
try {
|
|
64
|
+
const result = await fn();
|
|
65
|
+
await heartbeat.complete();
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
heartbeat.stop();
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Wrapper for streaming API responses with heartbeat support.
|
|
75
|
+
* Sends progress while waiting for streaming response to complete.
|
|
76
|
+
*
|
|
77
|
+
* @param stream - AsyncIterable of chunks (e.g., from OpenAI streaming API)
|
|
78
|
+
* @param reportProgress - Progress reporter from MCP context
|
|
79
|
+
* @param onChunk - Optional callback for each chunk (for streamContent)
|
|
80
|
+
*/
|
|
81
|
+
export async function withStreamingHeartbeat(stream, reportProgress, onChunk) {
|
|
82
|
+
const heartbeat = createHeartbeat({ reportProgress });
|
|
83
|
+
const chunks = [];
|
|
84
|
+
try {
|
|
85
|
+
for await (const chunk of stream) {
|
|
86
|
+
chunks.push(chunk);
|
|
87
|
+
if (onChunk) {
|
|
88
|
+
await onChunk(chunk);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
await heartbeat.complete();
|
|
92
|
+
return chunks;
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
heartbeat.stop();
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Check if reportProgress is available in the context.
|
|
101
|
+
* FastMCP always provides it, but this is a safety check.
|
|
102
|
+
*/
|
|
103
|
+
export function hasProgressSupport(context) {
|
|
104
|
+
return (typeof context === 'object' &&
|
|
105
|
+
context !== null &&
|
|
106
|
+
'reportProgress' in context &&
|
|
107
|
+
typeof context.reportProgress === 'function');
|
|
108
|
+
}
|
package/package.json
CHANGED