lynkr 9.9.1 → 9.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.
Files changed (67) hide show
  1. package/README.md +50 -9
  2. package/bin/cli.js +2 -0
  3. package/bin/lynkr-init.js +4 -12
  4. package/bin/lynkr-reset.js +71 -0
  5. package/config/model-tiers.json +199 -52
  6. package/package.json +2 -2
  7. package/scripts/validate-difficulty-classifier.js +27 -6
  8. package/src/api/providers-handler.js +0 -1
  9. package/src/api/router.js +275 -160
  10. package/src/clients/databricks.js +95 -6
  11. package/src/config/index.js +3 -43
  12. package/src/orchestrator/index.js +117 -1235
  13. package/src/orchestrator/passthrough-stream.js +382 -0
  14. package/src/orchestrator/sse-transformer.js +408 -0
  15. package/src/routing/affinity-store.js +17 -3
  16. package/src/routing/difficulty-classifier.js +52 -10
  17. package/src/routing/index.js +35 -3
  18. package/src/routing/intent-score.js +20 -2
  19. package/src/routing/session-affinity.js +8 -1
  20. package/src/routing/side-channel-detector.js +103 -0
  21. package/src/server.js +0 -46
  22. package/src/agents/context-manager.js +0 -236
  23. package/src/agents/decomposition/dispatcher.js +0 -185
  24. package/src/agents/decomposition/gate.js +0 -136
  25. package/src/agents/decomposition/index.js +0 -183
  26. package/src/agents/decomposition/model-call.js +0 -75
  27. package/src/agents/decomposition/planner.js +0 -223
  28. package/src/agents/decomposition/synthesizer.js +0 -89
  29. package/src/agents/decomposition/telemetry.js +0 -55
  30. package/src/agents/definitions/loader.js +0 -653
  31. package/src/agents/executor.js +0 -457
  32. package/src/agents/index.js +0 -165
  33. package/src/agents/parallel-coordinator.js +0 -68
  34. package/src/agents/reflector.js +0 -331
  35. package/src/agents/skillbook.js +0 -331
  36. package/src/agents/store.js +0 -259
  37. package/src/edits/index.js +0 -171
  38. package/src/indexer/babel-parser.js +0 -213
  39. package/src/indexer/index.js +0 -1629
  40. package/src/indexer/navigation/index.js +0 -32
  41. package/src/indexer/navigation/providers/treeSitter.js +0 -36
  42. package/src/indexer/parser.js +0 -443
  43. package/src/tasks/store.js +0 -349
  44. package/src/tests/coverage.js +0 -173
  45. package/src/tests/index.js +0 -171
  46. package/src/tests/store.js +0 -213
  47. package/src/tools/agent-task.js +0 -145
  48. package/src/tools/code-mode.js +0 -304
  49. package/src/tools/decompose.js +0 -91
  50. package/src/tools/edits.js +0 -94
  51. package/src/tools/execution.js +0 -171
  52. package/src/tools/git.js +0 -1346
  53. package/src/tools/index.js +0 -306
  54. package/src/tools/indexer.js +0 -360
  55. package/src/tools/lazy-loader.js +0 -366
  56. package/src/tools/mcp-remote.js +0 -88
  57. package/src/tools/mcp.js +0 -116
  58. package/src/tools/process.js +0 -167
  59. package/src/tools/smart-selection.js +0 -180
  60. package/src/tools/stubs.js +0 -55
  61. package/src/tools/tasks.js +0 -260
  62. package/src/tools/tests.js +0 -132
  63. package/src/tools/tinyfish.js +0 -358
  64. package/src/tools/truncate.js +0 -106
  65. package/src/tools/web-client.js +0 -71
  66. package/src/tools/web.js +0 -415
  67. package/src/tools/workspace.js +0 -204
@@ -1,213 +0,0 @@
1
- const db = require("../db");
2
- const logger = require("../logger");
3
-
4
- const OUTPUT_SNIPPET_LIMIT = 2000;
5
-
6
- const insertTestRunStmt = db.prepare(
7
- `INSERT INTO test_runs (
8
- profile,
9
- status,
10
- command,
11
- args,
12
- cwd,
13
- exit_code,
14
- timed_out,
15
- duration_ms,
16
- sandbox,
17
- stdout,
18
- stderr,
19
- coverage,
20
- created_at
21
- ) VALUES (
22
- @profile,
23
- @status,
24
- @command,
25
- @args,
26
- @cwd,
27
- @exit_code,
28
- @timed_out,
29
- @duration_ms,
30
- @sandbox,
31
- @stdout,
32
- @stderr,
33
- @coverage,
34
- @created_at
35
- )`,
36
- );
37
-
38
- const listRecentTestRunsStmt = db.prepare(
39
- `SELECT
40
- id,
41
- profile,
42
- status,
43
- command,
44
- args,
45
- cwd,
46
- exit_code,
47
- timed_out,
48
- duration_ms,
49
- sandbox,
50
- stdout,
51
- stderr,
52
- coverage,
53
- created_at
54
- FROM test_runs
55
- ORDER BY created_at DESC
56
- LIMIT ?`,
57
- );
58
-
59
- const countAllTestRunsStmt = db.prepare(`SELECT COUNT(1) AS total FROM test_runs`);
60
- const countPassedTestRunsStmt = db.prepare(
61
- `SELECT COUNT(1) AS total FROM test_runs WHERE status = 'passed'`,
62
- );
63
- const selectLatestTestRunStmt = db.prepare(
64
- `SELECT
65
- id,
66
- profile,
67
- status,
68
- command,
69
- args,
70
- cwd,
71
- exit_code,
72
- timed_out,
73
- duration_ms,
74
- sandbox,
75
- stdout,
76
- stderr,
77
- coverage,
78
- created_at
79
- FROM test_runs
80
- ORDER BY created_at DESC
81
- LIMIT 1`,
82
- );
83
-
84
- function truncateOutput(output, limit = OUTPUT_SNIPPET_LIMIT) {
85
- if (typeof output !== "string" || output.length === 0) {
86
- return { text: output ?? "", truncated: false };
87
- }
88
- if (output.length <= limit) {
89
- return { text: output, truncated: false };
90
- }
91
- return {
92
- text: output.slice(output.length - limit),
93
- truncated: true,
94
- };
95
- }
96
-
97
- function parseJson(value, fallback = null) {
98
- if (typeof value !== "string" || value.length === 0) return fallback;
99
- try {
100
- return JSON.parse(value);
101
- } catch (err) {
102
- logger.debug({ err }, "Failed to parse JSON payload in test_runs");
103
- return fallback;
104
- }
105
- }
106
-
107
- function normaliseArgs(value) {
108
- const parsed = parseJson(value, []);
109
- if (!Array.isArray(parsed)) return [];
110
- return parsed.map((item) => String(item));
111
- }
112
-
113
- function normaliseCoverage(value) {
114
- const parsed = parseJson(value, null);
115
- if (!parsed || typeof parsed !== "object") return null;
116
- return parsed;
117
- }
118
-
119
- function normaliseTestRunRow(row, { includeLogs = false } = {}) {
120
- if (!row) return null;
121
- const stdoutResult = includeLogs ? { text: row.stdout ?? "", truncated: false } : truncateOutput(row.stdout);
122
- const stderrResult = includeLogs ? { text: row.stderr ?? "", truncated: false } : truncateOutput(row.stderr);
123
- return {
124
- id: row.id,
125
- profile: row.profile ?? null,
126
- status: row.status ?? null,
127
- command: row.command ?? null,
128
- args: normaliseArgs(row.args),
129
- cwd: row.cwd ?? null,
130
- exitCode: typeof row.exit_code === "number" ? row.exit_code : null,
131
- timedOut: row.timed_out === 1,
132
- durationMs: typeof row.duration_ms === "number" ? row.duration_ms : null,
133
- sandbox: row.sandbox ?? null,
134
- stdout: stdoutResult.text ?? "",
135
- stdoutTruncated: stdoutResult.truncated,
136
- stderr: stderrResult.text ?? "",
137
- stderrTruncated: stderrResult.truncated,
138
- coverage: normaliseCoverage(row.coverage),
139
- createdAt: new Date(Number(row.created_at ?? Date.now())).toISOString(),
140
- };
141
- }
142
-
143
- function createTestRun({
144
- profile,
145
- status,
146
- command,
147
- args,
148
- cwd,
149
- exitCode,
150
- timedOut,
151
- durationMs,
152
- sandbox,
153
- stdout,
154
- stderr,
155
- coverage,
156
- createdAt,
157
- }) {
158
- const payload = {
159
- profile: profile ?? null,
160
- status: status ?? null,
161
- command: command ?? "",
162
- args: Array.isArray(args) && args.length ? JSON.stringify(args.map(String)) : null,
163
- cwd: cwd ?? null,
164
- exit_code: typeof exitCode === "number" ? exitCode : null,
165
- timed_out: timedOut ? 1 : 0,
166
- duration_ms: typeof durationMs === "number" ? durationMs : null,
167
- sandbox: sandbox ?? null,
168
- stdout: typeof stdout === "string" ? stdout : stdout ?? "",
169
- stderr: typeof stderr === "string" ? stderr : stderr ?? "",
170
- coverage: coverage ? JSON.stringify(coverage) : null,
171
- created_at: Number.isFinite(createdAt) ? Math.trunc(createdAt) : Date.now(),
172
- };
173
- const info = insertTestRunStmt.run(payload);
174
- return normaliseTestRunRow(
175
- {
176
- id: info.lastInsertRowid,
177
- ...payload,
178
- },
179
- { includeLogs: true },
180
- );
181
- }
182
-
183
- function listTestRuns({ limit = 5, includeLogs = false } = {}) {
184
- const clamped = Math.min(Math.max(Number(limit) || 5, 1), 50);
185
- const rows = listRecentTestRunsStmt.all(clamped);
186
- return rows.map((row) => normaliseTestRunRow(row, { includeLogs }));
187
- }
188
-
189
- function getTestSummary({ includeRecent = false, recentLimit = 5 } = {}) {
190
- const totals = countAllTestRunsStmt.get();
191
- const passed = countPassedTestRunsStmt.get();
192
- const latest = selectLatestTestRunStmt.get();
193
- const totalRuns = Number(totals?.total ?? 0);
194
- const passedRuns = Number(passed?.total ?? 0);
195
- const summary = {
196
- totalRuns,
197
- passRate: totalRuns > 0 ? Number(((passedRuns / totalRuns) * 100).toFixed(2)) : null,
198
- lastRun: latest ? normaliseTestRunRow(latest, { includeLogs: false }) : null,
199
- };
200
- if (includeRecent) {
201
- summary.recentRuns = listTestRuns({
202
- limit: recentLimit,
203
- includeLogs: false,
204
- });
205
- }
206
- return summary;
207
- }
208
-
209
- module.exports = {
210
- createTestRun,
211
- listTestRuns,
212
- getTestSummary,
213
- };
@@ -1,145 +0,0 @@
1
- const { registerTool } = require(".");
2
- const { spawnAgent, autoSelectAgent } = require("../agents");
3
- const logger = require("../logger");
4
-
5
- /**
6
- * Extract text from Anthropic content blocks format
7
- * Handles: [{"type":"text","text":"..."}] -> "..."
8
- */
9
- function extractTextFromContentBlocks(content) {
10
- if (typeof content !== 'string') {
11
- return content;
12
- }
13
-
14
- const trimmed = content.trim();
15
- if (!trimmed.startsWith('[')) {
16
- return content;
17
- }
18
-
19
- try {
20
- const parsed = JSON.parse(trimmed);
21
- if (!Array.isArray(parsed)) {
22
- return content;
23
- }
24
-
25
- // Extract text from content blocks
26
- const textParts = parsed
27
- .filter(block => block && typeof block === 'object')
28
- .map(block => {
29
- if (block.type === 'text' && typeof block.text === 'string') {
30
- return block.text;
31
- }
32
- if (typeof block.text === 'string') {
33
- return block.text;
34
- }
35
- return null;
36
- })
37
- .filter(text => text !== null);
38
-
39
- if (textParts.length > 0) {
40
- return textParts.join('\n\n');
41
- }
42
-
43
- return content;
44
- } catch {
45
- return content;
46
- }
47
- }
48
-
49
- function registerAgentTaskTool() {
50
- registerTool(
51
- "Task",
52
- async ({ args = {} }, context = {}) => {
53
- let subagentType = args.subagent_type || args.type;
54
- const prompt = args.prompt;
55
- const description = args.description || "Agent task";
56
-
57
- if (!prompt) {
58
- return {
59
- ok: false,
60
- status: 400,
61
- content: JSON.stringify({
62
- error: "prompt is required"
63
- }, null, 2)
64
- };
65
- }
66
-
67
- // Auto-select agent if not specified
68
- if (!subagentType) {
69
- const selected = autoSelectAgent(prompt);
70
- if (selected) {
71
- subagentType = selected.name;
72
- logger.info({
73
- selectedAgent: subagentType,
74
- prompt: prompt.slice(0, 50)
75
- }, "Auto-selected subagent");
76
- } else {
77
- subagentType = "Explore"; // Default fallback
78
- }
79
- }
80
-
81
- logger.info({
82
- subagentType,
83
- prompt: prompt.slice(0, 100),
84
- sessionId: context.sessionId,
85
- cwd: context.cwd
86
- }, "Task tool: spawning subagent");
87
-
88
- try {
89
- const result = await spawnAgent(subagentType, prompt, {
90
- sessionId: context.sessionId,
91
- cwd: context.cwd, // Pass client CWD to subagent
92
- mainContext: context.mainContext // Pass minimal context
93
- });
94
-
95
- if (result.success) {
96
- // Extract text from Anthropic content blocks if present
97
- const cleanContent = extractTextFromContentBlocks(result.result);
98
-
99
- return {
100
- ok: true,
101
- status: 200,
102
- content: cleanContent,
103
- metadata: {
104
- agentType: subagentType,
105
- agentId: result.stats.agentId,
106
- steps: result.stats.steps,
107
- durationMs: result.stats.durationMs
108
- }
109
- };
110
- } else {
111
- return {
112
- ok: false,
113
- status: 500,
114
- content: JSON.stringify({
115
- error: "Subagent execution failed",
116
- message: result.error
117
- }, null, 2)
118
- };
119
- }
120
-
121
- } catch (error) {
122
- logger.error({
123
- error: error.message,
124
- subagentType
125
- }, "Task tool: subagent error");
126
-
127
- return {
128
- ok: false,
129
- status: 500,
130
- content: JSON.stringify({
131
- error: "Subagent error",
132
- message: error.message
133
- }, null, 2)
134
- };
135
- }
136
- },
137
- { category: "agents" }
138
- );
139
-
140
- logger.info("Task tool registered");
141
- }
142
-
143
- module.exports = {
144
- registerAgentTaskTool
145
- };
@@ -1,304 +0,0 @@
1
- /**
2
- * Code Mode — Meta-Tools for MCP Token Optimization
3
- *
4
- * Replaces 100+ individual MCP tool definitions with 4 meta-tools,
5
- * reducing tool-catalog token overhead from ~17,500 to ~700 tokens.
6
- *
7
- * Inspired by Bifrost's Code Mode. Instead of sending every MCP tool
8
- * schema in every request, the LLM discovers tools lazily:
9
- * 1. mcp_list_tools → discover available tools (compact)
10
- * 2. mcp_tool_info → load full schema for one tool
11
- * 3. mcp_tool_docs → get usage examples
12
- * 4. mcp_execute → execute a tool by name
13
- *
14
- * Activation: CODE_MODE_ENABLED=true
15
- *
16
- * @module tools/code-mode
17
- */
18
-
19
- const { registerTool } = require('.');
20
- const { listServers, ensureClient } = require('../mcp');
21
- const config = require('../config');
22
- const logger = require('../logger');
23
-
24
- // ── Tool List Cache ─────────────────────────────────────────────────
25
-
26
- let toolListCache = null;
27
- let toolListCacheTs = 0;
28
-
29
- function getCacheTtl() {
30
- return config.mcp?.codeMode?.toolListCacheTtl || 60_000;
31
- }
32
-
33
- /**
34
- * Fetch tool lists from all MCP servers, with caching.
35
- * @param {string} [filterServerId] - Optional: only fetch from this server
36
- * @param {boolean} [forceRefresh] - Bypass cache
37
- * @returns {Promise<Object>} { serverId: [{ name, description }] }
38
- */
39
- async function fetchToolList(filterServerId, forceRefresh = false) {
40
- const now = Date.now();
41
- if (!forceRefresh && toolListCache && (now - toolListCacheTs < getCacheTtl())) {
42
- if (filterServerId) {
43
- return { [filterServerId]: toolListCache[filterServerId] || [] };
44
- }
45
- return toolListCache;
46
- }
47
-
48
- const servers = listServers();
49
- const result = {};
50
-
51
- await Promise.all(
52
- servers.map(async (server) => {
53
- if (filterServerId && server.id !== filterServerId) return;
54
- try {
55
- const client = await ensureClient(server.id);
56
- if (!client) {
57
- result[server.id] = { error: 'Server not available' };
58
- return;
59
- }
60
- const response = await client.request('tools/list', {});
61
- const tools = Array.isArray(response?.tools) ? response.tools : [];
62
- result[server.id] = tools.map(t => ({
63
- name: t.name ?? t.method ?? 'unknown',
64
- description: (t.description || '').substring(0, 100),
65
- }));
66
- } catch (err) {
67
- result[server.id] = { error: err.message };
68
- }
69
- })
70
- );
71
-
72
- // Update cache if we fetched all servers
73
- if (!filterServerId) {
74
- toolListCache = result;
75
- toolListCacheTs = now;
76
- }
77
-
78
- return filterServerId ? { [filterServerId]: result[filterServerId] || [] } : result;
79
- }
80
-
81
- /**
82
- * Fetch full tool schema for a specific tool on a specific server.
83
- * @param {string} serverId
84
- * @param {string} toolName
85
- * @returns {Promise<Object|null>}
86
- */
87
- async function fetchToolSchema(serverId, toolName) {
88
- const client = await ensureClient(serverId);
89
- if (!client) return null;
90
-
91
- const response = await client.request('tools/list', {});
92
- const tools = Array.isArray(response?.tools) ? response.tools : [];
93
- return tools.find(t => (t.name ?? t.method) === toolName) || null;
94
- }
95
-
96
- /**
97
- * Generate a usage example from a tool's input schema.
98
- * @param {Object} tool - Tool definition with inputSchema
99
- * @returns {string} Example JSON
100
- */
101
- function generateExample(tool) {
102
- const schema = tool.inputSchema || tool.input_schema || {};
103
- const props = schema.properties || {};
104
- const example = {};
105
-
106
- for (const [key, def] of Object.entries(props)) {
107
- if (def.type === 'string') example[key] = def.example || `<${key}>`;
108
- else if (def.type === 'number' || def.type === 'integer') example[key] = def.example || 0;
109
- else if (def.type === 'boolean') example[key] = def.example ?? true;
110
- else if (def.type === 'array') example[key] = [];
111
- else if (def.type === 'object') example[key] = {};
112
- else example[key] = null;
113
- }
114
-
115
- return JSON.stringify(example, null, 2);
116
- }
117
-
118
- // ── Meta-Tool Registration ──────────────────────────────────────────
119
-
120
- function registerCodeModeTools() {
121
- // 1. mcp_list_tools — discover available tools
122
- registerTool(
123
- 'mcp_list_tools',
124
- async ({ args = {} }) => {
125
- const serverId = args.server_id || null;
126
- const forceRefresh = args.force_refresh === true;
127
- const result = await fetchToolList(serverId, forceRefresh);
128
-
129
- // Add summary stats
130
- let totalTools = 0;
131
- for (const tools of Object.values(result)) {
132
- if (Array.isArray(tools)) totalTools += tools.length;
133
- }
134
-
135
- return {
136
- ok: true,
137
- status: 200,
138
- content: JSON.stringify({ total_tools: totalTools, servers: result }, null, 2),
139
- };
140
- },
141
- {
142
- category: 'code-mode',
143
- description: 'List all available MCP tools across all servers. Returns tool names and brief descriptions. Use this first to discover what tools are available.',
144
- input_schema: {
145
- type: 'object',
146
- properties: {
147
- server_id: { type: 'string', description: 'Optional: filter to a specific MCP server ID' },
148
- force_refresh: { type: 'boolean', description: 'Bypass cache and refresh tool list' },
149
- },
150
- },
151
- }
152
- );
153
-
154
- // 2. mcp_tool_info — load full schema for one tool
155
- registerTool(
156
- 'mcp_tool_info',
157
- async ({ args = {} }) => {
158
- const serverId = args.server_id;
159
- const toolName = args.tool_name;
160
-
161
- if (!serverId || !toolName) {
162
- throw new Error('mcp_tool_info requires server_id and tool_name');
163
- }
164
-
165
- const tool = await fetchToolSchema(serverId, toolName);
166
- if (!tool) {
167
- throw new Error(`Tool "${toolName}" not found on server "${serverId}"`);
168
- }
169
-
170
- return {
171
- ok: true,
172
- status: 200,
173
- content: JSON.stringify({
174
- server: serverId,
175
- name: tool.name ?? tool.method,
176
- description: tool.description || '',
177
- inputSchema: tool.inputSchema || tool.input_schema || {},
178
- }, null, 2),
179
- };
180
- },
181
- {
182
- category: 'code-mode',
183
- description: 'Get the full schema and detailed description for a specific MCP tool. Use after mcp_list_tools to get the exact parameters needed before calling mcp_execute.',
184
- input_schema: {
185
- type: 'object',
186
- properties: {
187
- server_id: { type: 'string', description: 'MCP server ID' },
188
- tool_name: { type: 'string', description: 'Tool name from mcp_list_tools' },
189
- },
190
- required: ['server_id', 'tool_name'],
191
- },
192
- }
193
- );
194
-
195
- // 3. mcp_tool_docs — usage examples
196
- registerTool(
197
- 'mcp_tool_docs',
198
- async ({ args = {} }) => {
199
- const serverId = args.server_id;
200
- const toolName = args.tool_name;
201
-
202
- if (!serverId || !toolName) {
203
- throw new Error('mcp_tool_docs requires server_id and tool_name');
204
- }
205
-
206
- const tool = await fetchToolSchema(serverId, toolName);
207
- if (!tool) {
208
- throw new Error(`Tool "${toolName}" not found on server "${serverId}"`);
209
- }
210
-
211
- const schema = tool.inputSchema || tool.input_schema || {};
212
- const params = Object.entries(schema.properties || {}).map(([name, def]) => ({
213
- name,
214
- type: def.type || 'any',
215
- required: (schema.required || []).includes(name),
216
- description: def.description || '',
217
- }));
218
-
219
- return {
220
- ok: true,
221
- status: 200,
222
- content: JSON.stringify({
223
- server: serverId,
224
- tool: tool.name ?? tool.method,
225
- description: tool.description || '',
226
- parameters: params,
227
- example_arguments: generateExample(tool),
228
- usage: `Use mcp_execute with server_id="${serverId}", tool_name="${toolName}", and arguments matching the schema above.`,
229
- }, null, 2),
230
- };
231
- },
232
- {
233
- category: 'code-mode',
234
- description: 'Get usage documentation, parameter details, and example arguments for an MCP tool.',
235
- input_schema: {
236
- type: 'object',
237
- properties: {
238
- server_id: { type: 'string', description: 'MCP server ID' },
239
- tool_name: { type: 'string', description: 'Tool name' },
240
- },
241
- required: ['server_id', 'tool_name'],
242
- },
243
- }
244
- );
245
-
246
- // 4. mcp_execute — execute a tool by name
247
- registerTool(
248
- 'mcp_execute',
249
- async ({ args = {} }) => {
250
- const serverId = args.server_id;
251
- const toolName = args.tool_name;
252
- const toolArgs = args.arguments ?? {};
253
-
254
- if (!serverId || !toolName) {
255
- throw new Error('mcp_execute requires server_id and tool_name');
256
- }
257
-
258
- const client = await ensureClient(serverId.trim());
259
- if (!client) {
260
- throw new Error(`MCP server "${serverId}" is not available.`);
261
- }
262
-
263
- const result = await client.request(toolName.trim(), toolArgs);
264
-
265
- return {
266
- ok: true,
267
- status: 200,
268
- content: JSON.stringify({
269
- server: serverId,
270
- tool: toolName,
271
- result,
272
- }, null, 2),
273
- metadata: { server: serverId, tool: toolName },
274
- };
275
- },
276
- {
277
- category: 'code-mode',
278
- description: 'Execute an MCP tool by name with JSON arguments. First use mcp_list_tools to discover tools, then mcp_tool_info to get the schema, then this tool to execute.',
279
- input_schema: {
280
- type: 'object',
281
- properties: {
282
- server_id: { type: 'string', description: 'MCP server ID' },
283
- tool_name: { type: 'string', description: 'Tool method name' },
284
- arguments: {
285
- type: 'object',
286
- description: 'JSON arguments matching the tool input schema',
287
- additionalProperties: true,
288
- },
289
- },
290
- required: ['server_id', 'tool_name'],
291
- },
292
- }
293
- );
294
-
295
- logger.info('[code-mode] Registered 4 meta-tools: mcp_list_tools, mcp_tool_info, mcp_tool_docs, mcp_execute');
296
- }
297
-
298
- module.exports = {
299
- registerCodeModeTools,
300
- // Exported for testing
301
- fetchToolList,
302
- fetchToolSchema,
303
- generateExample,
304
- };