@probelabs/probe 0.6.0-rc207 → 0.6.0-rc209
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/bin/binaries/probe-v0.6.0-rc209-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc209-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc209-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc209-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc209-x86_64-unknown-linux-musl.tar.gz +0 -0
- package/build/agent/ProbeAgent.d.ts +4 -2
- package/build/agent/ProbeAgent.js +2 -1
- package/build/agent/bashPermissions.js +88 -7
- package/build/agent/index.js +342 -24
- package/build/agent/mcp/client.js +236 -5
- package/build/agent/mcp/config.js +87 -0
- package/build/agent/mcp/xmlBridge.js +15 -5
- package/build/agent/simpleTelemetry.js +26 -0
- package/build/tools/bash.js +5 -3
- package/cjs/agent/ProbeAgent.cjs +314 -18
- package/cjs/agent/simpleTelemetry.cjs +22 -0
- package/cjs/index.cjs +336 -18
- package/index.d.ts +4 -2
- package/package.json +1 -1
- package/src/agent/ProbeAgent.d.ts +4 -2
- package/src/agent/ProbeAgent.js +2 -1
- package/src/agent/bashPermissions.js +88 -7
- package/src/agent/index.js +5 -5
- package/src/agent/mcp/client.js +236 -5
- package/src/agent/mcp/config.js +87 -0
- package/src/agent/mcp/xmlBridge.js +15 -5
- package/src/agent/simpleTelemetry.js +26 -0
- package/src/tools/bash.js +5 -3
- package/bin/binaries/probe-v0.6.0-rc207-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc207-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc207-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc207-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc207-x86_64-unknown-linux-musl.tar.gz +0 -0
package/src/agent/ProbeAgent.js
CHANGED
|
@@ -210,7 +210,8 @@ export class ProbeAgent {
|
|
|
210
210
|
this.maxIterations = options.maxIterations || null;
|
|
211
211
|
this.disableMermaidValidation = !!options.disableMermaidValidation;
|
|
212
212
|
this.disableJsonValidation = !!options.disableJsonValidation;
|
|
213
|
-
|
|
213
|
+
// Skills are disabled by default; enable via allowSkills or enableSkills
|
|
214
|
+
this.enableSkills = options.disableSkills ? false : !!(options.allowSkills || options.enableSkills);
|
|
214
215
|
if (Array.isArray(options.skillDirs)) {
|
|
215
216
|
this.skillDirs = options.skillDirs;
|
|
216
217
|
} else if (typeof options.skillDirs === 'string') {
|
|
@@ -85,9 +85,11 @@ export class BashPermissionChecker {
|
|
|
85
85
|
* @param {boolean} [config.disableDefaultAllow] - Disable default allow list
|
|
86
86
|
* @param {boolean} [config.disableDefaultDeny] - Disable default deny list
|
|
87
87
|
* @param {boolean} [config.debug] - Enable debug logging
|
|
88
|
+
* @param {Object} [config.tracer] - Optional tracer for telemetry
|
|
88
89
|
*/
|
|
89
90
|
constructor(config = {}) {
|
|
90
91
|
this.debug = config.debug || false;
|
|
92
|
+
this.tracer = config.tracer || null;
|
|
91
93
|
|
|
92
94
|
// Build allow patterns
|
|
93
95
|
this.allowPatterns = [];
|
|
@@ -122,6 +124,27 @@ export class BashPermissionChecker {
|
|
|
122
124
|
if (this.debug) {
|
|
123
125
|
console.log(`[BashPermissions] Total patterns - Allow: ${this.allowPatterns.length}, Deny: ${this.denyPatterns.length}`);
|
|
124
126
|
}
|
|
127
|
+
|
|
128
|
+
// Record initialization event
|
|
129
|
+
this.recordBashEvent('permissions.initialized', {
|
|
130
|
+
allowPatternCount: this.allowPatterns.length,
|
|
131
|
+
denyPatternCount: this.denyPatterns.length,
|
|
132
|
+
hasCustomAllowPatterns: !!(config.allow && config.allow.length > 0),
|
|
133
|
+
hasCustomDenyPatterns: !!(config.deny && config.deny.length > 0),
|
|
134
|
+
disableDefaultAllow: !!config.disableDefaultAllow,
|
|
135
|
+
disableDefaultDeny: !!config.disableDefaultDeny
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Record a bash telemetry event if tracer is available
|
|
141
|
+
* @param {string} eventType - Event type (e.g., 'permission.checked', 'permission.denied')
|
|
142
|
+
* @param {Object} data - Event data
|
|
143
|
+
*/
|
|
144
|
+
recordBashEvent(eventType, data = {}) {
|
|
145
|
+
if (this.tracer && typeof this.tracer.recordBashEvent === 'function') {
|
|
146
|
+
this.tracer.recordBashEvent(eventType, data);
|
|
147
|
+
}
|
|
125
148
|
}
|
|
126
149
|
|
|
127
150
|
/**
|
|
@@ -131,11 +154,17 @@ export class BashPermissionChecker {
|
|
|
131
154
|
*/
|
|
132
155
|
check(command) {
|
|
133
156
|
if (!command || typeof command !== 'string') {
|
|
134
|
-
|
|
157
|
+
const result = {
|
|
135
158
|
allowed: false,
|
|
136
159
|
reason: 'Invalid or empty command',
|
|
137
160
|
command: command
|
|
138
161
|
};
|
|
162
|
+
this.recordBashEvent('permission.denied', {
|
|
163
|
+
command: String(command),
|
|
164
|
+
reason: result.reason,
|
|
165
|
+
isComplex: false
|
|
166
|
+
});
|
|
167
|
+
return result;
|
|
139
168
|
}
|
|
140
169
|
|
|
141
170
|
// Check if this is a complex command
|
|
@@ -150,19 +179,32 @@ export class BashPermissionChecker {
|
|
|
150
179
|
const parsed = parseCommand(command);
|
|
151
180
|
|
|
152
181
|
if (parsed.error) {
|
|
153
|
-
|
|
182
|
+
const result = {
|
|
154
183
|
allowed: false,
|
|
155
184
|
reason: parsed.error,
|
|
156
185
|
command: command
|
|
157
186
|
};
|
|
187
|
+
this.recordBashEvent('permission.denied', {
|
|
188
|
+
command,
|
|
189
|
+
reason: result.reason,
|
|
190
|
+
isComplex: false,
|
|
191
|
+
parseError: true
|
|
192
|
+
});
|
|
193
|
+
return result;
|
|
158
194
|
}
|
|
159
195
|
|
|
160
196
|
if (!parsed.command) {
|
|
161
|
-
|
|
197
|
+
const result = {
|
|
162
198
|
allowed: false,
|
|
163
199
|
reason: 'No valid command found',
|
|
164
200
|
command: command
|
|
165
201
|
};
|
|
202
|
+
this.recordBashEvent('permission.denied', {
|
|
203
|
+
command,
|
|
204
|
+
reason: result.reason,
|
|
205
|
+
isComplex: false
|
|
206
|
+
});
|
|
207
|
+
return result;
|
|
166
208
|
}
|
|
167
209
|
|
|
168
210
|
if (this.debug) {
|
|
@@ -173,24 +215,39 @@ export class BashPermissionChecker {
|
|
|
173
215
|
// Check deny patterns first (deny takes precedence)
|
|
174
216
|
if (matchesAnyPattern(parsed, this.denyPatterns)) {
|
|
175
217
|
const matchedPatterns = this.denyPatterns.filter(pattern => matchesPattern(parsed, pattern));
|
|
176
|
-
|
|
218
|
+
const result = {
|
|
177
219
|
allowed: false,
|
|
178
220
|
reason: `Command matches deny pattern: ${matchedPatterns[0]}`,
|
|
179
221
|
command: command,
|
|
180
222
|
parsed: parsed,
|
|
181
223
|
matchedPatterns: matchedPatterns
|
|
182
224
|
};
|
|
225
|
+
this.recordBashEvent('permission.denied', {
|
|
226
|
+
command,
|
|
227
|
+
parsedCommand: parsed.command,
|
|
228
|
+
reason: 'matches_deny_pattern',
|
|
229
|
+
matchedPattern: matchedPatterns[0],
|
|
230
|
+
isComplex: false
|
|
231
|
+
});
|
|
232
|
+
return result;
|
|
183
233
|
}
|
|
184
234
|
|
|
185
235
|
// Check allow patterns
|
|
186
236
|
if (this.allowPatterns.length > 0) {
|
|
187
237
|
if (!matchesAnyPattern(parsed, this.allowPatterns)) {
|
|
188
|
-
|
|
238
|
+
const result = {
|
|
189
239
|
allowed: false,
|
|
190
240
|
reason: 'Command not in allow list',
|
|
191
241
|
command: command,
|
|
192
242
|
parsed: parsed
|
|
193
243
|
};
|
|
244
|
+
this.recordBashEvent('permission.denied', {
|
|
245
|
+
command,
|
|
246
|
+
parsedCommand: parsed.command,
|
|
247
|
+
reason: 'not_in_allow_list',
|
|
248
|
+
isComplex: false
|
|
249
|
+
});
|
|
250
|
+
return result;
|
|
194
251
|
}
|
|
195
252
|
}
|
|
196
253
|
|
|
@@ -206,6 +263,12 @@ export class BashPermissionChecker {
|
|
|
206
263
|
console.log(`[BashPermissions] ALLOWED - command passed all checks`);
|
|
207
264
|
}
|
|
208
265
|
|
|
266
|
+
this.recordBashEvent('permission.allowed', {
|
|
267
|
+
command,
|
|
268
|
+
parsedCommand: parsed.command,
|
|
269
|
+
isComplex: false
|
|
270
|
+
});
|
|
271
|
+
|
|
209
272
|
return result;
|
|
210
273
|
}
|
|
211
274
|
|
|
@@ -235,13 +298,20 @@ export class BashPermissionChecker {
|
|
|
235
298
|
if (this.debug) {
|
|
236
299
|
console.log(`[BashPermissions] DENIED - matches complex deny pattern: ${pattern}`);
|
|
237
300
|
}
|
|
238
|
-
|
|
301
|
+
const result = {
|
|
239
302
|
allowed: false,
|
|
240
303
|
reason: `Command matches deny pattern: ${pattern}`,
|
|
241
304
|
command: command,
|
|
242
305
|
isComplex: true,
|
|
243
306
|
matchedPatterns: [pattern]
|
|
244
307
|
};
|
|
308
|
+
this.recordBashEvent('permission.denied', {
|
|
309
|
+
command,
|
|
310
|
+
reason: 'matches_deny_pattern',
|
|
311
|
+
matchedPattern: pattern,
|
|
312
|
+
isComplex: true
|
|
313
|
+
});
|
|
314
|
+
return result;
|
|
245
315
|
}
|
|
246
316
|
}
|
|
247
317
|
|
|
@@ -251,12 +321,18 @@ export class BashPermissionChecker {
|
|
|
251
321
|
if (this.debug) {
|
|
252
322
|
console.log(`[BashPermissions] ALLOWED - matches complex allow pattern: ${pattern}`);
|
|
253
323
|
}
|
|
254
|
-
|
|
324
|
+
const result = {
|
|
255
325
|
allowed: true,
|
|
256
326
|
command: command,
|
|
257
327
|
isComplex: true,
|
|
258
328
|
matchedPattern: pattern
|
|
259
329
|
};
|
|
330
|
+
this.recordBashEvent('permission.allowed', {
|
|
331
|
+
command,
|
|
332
|
+
matchedPattern: pattern,
|
|
333
|
+
isComplex: true
|
|
334
|
+
});
|
|
335
|
+
return result;
|
|
260
336
|
}
|
|
261
337
|
}
|
|
262
338
|
|
|
@@ -264,6 +340,11 @@ export class BashPermissionChecker {
|
|
|
264
340
|
if (this.debug) {
|
|
265
341
|
console.log(`[BashPermissions] DENIED - no matching complex pattern found`);
|
|
266
342
|
}
|
|
343
|
+
this.recordBashEvent('permission.denied', {
|
|
344
|
+
command,
|
|
345
|
+
reason: 'no_matching_complex_pattern',
|
|
346
|
+
isComplex: true
|
|
347
|
+
});
|
|
267
348
|
return {
|
|
268
349
|
allowed: false,
|
|
269
350
|
reason: 'Complex shell commands require explicit allow patterns (e.g., "cd * && git *")',
|
package/src/agent/index.js
CHANGED
|
@@ -138,7 +138,7 @@ function parseArgs() {
|
|
|
138
138
|
noMermaidValidation: false, // New flag to disable mermaid validation
|
|
139
139
|
allowedTools: null, // Tool filtering: ['*'] = all, [] = none, ['tool1', 'tool2'] = specific
|
|
140
140
|
disableTools: false, // Convenience flag to disable all tools
|
|
141
|
-
|
|
141
|
+
allowSkills: false, // Enable skill discovery and activation (disabled by default)
|
|
142
142
|
skillDirs: null, // Comma-separated list of repo-relative skill directories
|
|
143
143
|
// Task management
|
|
144
144
|
enableTasks: false, // Enable task tracking for progress management
|
|
@@ -212,8 +212,8 @@ function parseArgs() {
|
|
|
212
212
|
} else if (arg === '--disable-tools') {
|
|
213
213
|
// Convenience flag to disable all tools (raw AI mode)
|
|
214
214
|
config.disableTools = true;
|
|
215
|
-
} else if (arg === '--
|
|
216
|
-
config.
|
|
215
|
+
} else if (arg === '--allow-skills') {
|
|
216
|
+
config.allowSkills = true;
|
|
217
217
|
} else if (arg === '--skills-dir' && i + 1 < args.length) {
|
|
218
218
|
config.skillDirs = args[++i].split(',').map(dir => dir.trim()).filter(Boolean);
|
|
219
219
|
} else if (arg === '--allow-tasks') {
|
|
@@ -280,8 +280,8 @@ Options:
|
|
|
280
280
|
Supports exclusion: '*,!bash' (all except bash)
|
|
281
281
|
--disable-tools Disable all tools (raw AI mode, no code analysis)
|
|
282
282
|
Convenience flag equivalent to --allowed-tools none
|
|
283
|
+
--allow-skills Enable skill discovery and activation (disabled by default)
|
|
283
284
|
--skills-dir <dirs> Comma-separated list of repo-relative skill directories to scan
|
|
284
|
-
--no-skills Disable skill discovery and activation
|
|
285
285
|
--allow-tasks Enable task management for tracking multi-step progress
|
|
286
286
|
--verbose Enable verbose output
|
|
287
287
|
--outline Use outline-xml format for code search results
|
|
@@ -841,7 +841,7 @@ async function main() {
|
|
|
841
841
|
disableMermaidValidation: config.noMermaidValidation,
|
|
842
842
|
allowedTools: config.allowedTools,
|
|
843
843
|
disableTools: config.disableTools,
|
|
844
|
-
|
|
844
|
+
allowSkills: config.allowSkills,
|
|
845
845
|
skillDirs: config.skillDirs,
|
|
846
846
|
enableBash: config.enableBash,
|
|
847
847
|
bashConfig: bashConfig,
|
package/src/agent/mcp/client.js
CHANGED
|
@@ -9,6 +9,47 @@ import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
|
9
9
|
import { WebSocketClientTransport } from '@modelcontextprotocol/sdk/client/websocket.js';
|
|
10
10
|
import { loadMCPConfiguration, parseEnabledServers, DEFAULT_TIMEOUT } from './config.js';
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Check if a method is allowed based on server's method filter configuration
|
|
14
|
+
* Supports wildcard patterns (e.g., "*_read", "search_*", "prefix_*_suffix")
|
|
15
|
+
* @param {string} methodName - The method name to check
|
|
16
|
+
* @param {string[]|null} allowedMethods - Array of allowed method patterns (null = all allowed)
|
|
17
|
+
* @param {string[]|null} blockedMethods - Array of blocked method patterns (null = none blocked)
|
|
18
|
+
* @returns {boolean} Whether the method is allowed
|
|
19
|
+
*/
|
|
20
|
+
export function isMethodAllowed(methodName, allowedMethods, blockedMethods) {
|
|
21
|
+
/**
|
|
22
|
+
* Check if a method name matches a pattern
|
|
23
|
+
* Supports * wildcard which matches any characters
|
|
24
|
+
* @param {string} name - Method name to check
|
|
25
|
+
* @param {string} pattern - Pattern to match against (may contain *)
|
|
26
|
+
* @returns {boolean} Whether the name matches the pattern
|
|
27
|
+
*/
|
|
28
|
+
const matchesPattern = (name, pattern) => {
|
|
29
|
+
if (!pattern.includes('*')) {
|
|
30
|
+
return name === pattern;
|
|
31
|
+
}
|
|
32
|
+
// Convert pattern to regex: escape special chars, replace * with .*
|
|
33
|
+
const regexPattern = pattern
|
|
34
|
+
.replace(/[.+?^${}()|[\]\\]/g, '\\$&') // Escape special regex chars
|
|
35
|
+
.replace(/\*/g, '.*'); // Replace * with .*
|
|
36
|
+
return new RegExp(`^${regexPattern}$`).test(name);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// If allowedMethods is specified (whitelist mode), only those methods are allowed
|
|
40
|
+
if (allowedMethods && allowedMethods.length > 0) {
|
|
41
|
+
return allowedMethods.some(pattern => matchesPattern(methodName, pattern));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// If blockedMethods is specified (blacklist mode), all methods except those are allowed
|
|
45
|
+
if (blockedMethods && blockedMethods.length > 0) {
|
|
46
|
+
return !blockedMethods.some(pattern => matchesPattern(methodName, pattern));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// No filter specified - all methods are allowed
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
12
53
|
/**
|
|
13
54
|
* Create transport based on configuration
|
|
14
55
|
* @param {Object} serverConfig - Server configuration
|
|
@@ -117,6 +158,18 @@ export class MCPClientManager {
|
|
|
117
158
|
this.tools = new Map();
|
|
118
159
|
this.debug = options.debug || process.env.DEBUG_MCP === '1';
|
|
119
160
|
this.config = null;
|
|
161
|
+
this.tracer = options.tracer || null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Record an MCP telemetry event if tracer is available
|
|
166
|
+
* @param {string} eventType - Event type (e.g., 'server.connect', 'tool.discovered')
|
|
167
|
+
* @param {Object} data - Event data
|
|
168
|
+
*/
|
|
169
|
+
recordMcpEvent(eventType, data = {}) {
|
|
170
|
+
if (this.tracer && typeof this.tracer.recordMcpEvent === 'function') {
|
|
171
|
+
this.tracer.recordMcpEvent(eventType, data);
|
|
172
|
+
}
|
|
120
173
|
}
|
|
121
174
|
|
|
122
175
|
/**
|
|
@@ -128,12 +181,24 @@ export class MCPClientManager {
|
|
|
128
181
|
this.config = config || loadMCPConfiguration();
|
|
129
182
|
const servers = parseEnabledServers(this.config);
|
|
130
183
|
|
|
184
|
+
// Record initialization start
|
|
185
|
+
this.recordMcpEvent('initialization.started', {
|
|
186
|
+
serverCount: servers.length,
|
|
187
|
+
serverNames: servers.map(s => s.name)
|
|
188
|
+
});
|
|
189
|
+
|
|
131
190
|
// Always log the number of servers found
|
|
132
191
|
console.error(`[MCP INFO] Found ${servers.length} enabled MCP server${servers.length !== 1 ? 's' : ''}`);
|
|
133
192
|
|
|
134
193
|
if (servers.length === 0) {
|
|
135
194
|
console.error('[MCP INFO] No MCP servers configured or enabled');
|
|
136
195
|
console.error('[MCP INFO] 0 MCP tools available');
|
|
196
|
+
this.recordMcpEvent('initialization.completed', {
|
|
197
|
+
connected: 0,
|
|
198
|
+
total: 0,
|
|
199
|
+
toolCount: 0,
|
|
200
|
+
tools: []
|
|
201
|
+
});
|
|
137
202
|
return {
|
|
138
203
|
connected: 0,
|
|
139
204
|
total: 0,
|
|
@@ -178,10 +243,19 @@ export class MCPClientManager {
|
|
|
178
243
|
});
|
|
179
244
|
}
|
|
180
245
|
|
|
246
|
+
// Record initialization completion
|
|
247
|
+
const toolNames = Array.from(this.tools.keys());
|
|
248
|
+
this.recordMcpEvent('initialization.completed', {
|
|
249
|
+
connected: connectedCount,
|
|
250
|
+
total: servers.length,
|
|
251
|
+
toolCount: this.tools.size,
|
|
252
|
+
tools: toolNames
|
|
253
|
+
});
|
|
254
|
+
|
|
181
255
|
return {
|
|
182
256
|
connected: connectedCount,
|
|
183
257
|
total: servers.length,
|
|
184
|
-
tools:
|
|
258
|
+
tools: toolNames
|
|
185
259
|
};
|
|
186
260
|
}
|
|
187
261
|
|
|
@@ -192,6 +266,14 @@ export class MCPClientManager {
|
|
|
192
266
|
async connectToServer(serverConfig) {
|
|
193
267
|
const { name } = serverConfig;
|
|
194
268
|
|
|
269
|
+
// Record connection attempt
|
|
270
|
+
this.recordMcpEvent('server.connecting', {
|
|
271
|
+
serverName: name,
|
|
272
|
+
transport: serverConfig.transport,
|
|
273
|
+
hasAllowedMethods: !!(serverConfig.allowedMethods && serverConfig.allowedMethods.length > 0),
|
|
274
|
+
hasBlockedMethods: !!(serverConfig.blockedMethods && serverConfig.blockedMethods.length > 0)
|
|
275
|
+
});
|
|
276
|
+
|
|
195
277
|
try {
|
|
196
278
|
if (this.debug) {
|
|
197
279
|
console.error(`[MCP DEBUG] Connecting to ${name} via ${serverConfig.transport}...`);
|
|
@@ -223,10 +305,34 @@ export class MCPClientManager {
|
|
|
223
305
|
|
|
224
306
|
// Fetch and register tools
|
|
225
307
|
const toolsResponse = await client.listTools();
|
|
226
|
-
const
|
|
308
|
+
const totalToolCount = toolsResponse?.tools?.length || 0;
|
|
309
|
+
let registeredCount = 0;
|
|
310
|
+
let filteredCount = 0;
|
|
311
|
+
const registeredTools = [];
|
|
312
|
+
const filteredTools = [];
|
|
227
313
|
|
|
228
314
|
if (toolsResponse && toolsResponse.tools) {
|
|
315
|
+
const { allowedMethods, blockedMethods } = serverConfig;
|
|
316
|
+
const allToolNames = toolsResponse.tools.map(t => t.name);
|
|
317
|
+
|
|
318
|
+
// Record tools discovered from server
|
|
319
|
+
this.recordMcpEvent('tools.discovered', {
|
|
320
|
+
serverName: name,
|
|
321
|
+
toolCount: totalToolCount,
|
|
322
|
+
tools: allToolNames
|
|
323
|
+
});
|
|
324
|
+
|
|
229
325
|
for (const tool of toolsResponse.tools) {
|
|
326
|
+
// Apply method filtering based on server config
|
|
327
|
+
if (!isMethodAllowed(tool.name, allowedMethods, blockedMethods)) {
|
|
328
|
+
filteredCount++;
|
|
329
|
+
filteredTools.push(tool.name);
|
|
330
|
+
if (this.debug) {
|
|
331
|
+
console.error(`[MCP DEBUG] Filtered out tool: ${tool.name} (not allowed by method filter)`);
|
|
332
|
+
}
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
|
|
230
336
|
// Add server prefix to avoid conflicts
|
|
231
337
|
const qualifiedName = `${name}_${tool.name}`;
|
|
232
338
|
this.tools.set(qualifiedName, {
|
|
@@ -234,14 +340,68 @@ export class MCPClientManager {
|
|
|
234
340
|
serverName: name,
|
|
235
341
|
originalName: tool.name
|
|
236
342
|
});
|
|
343
|
+
registeredCount++;
|
|
344
|
+
registeredTools.push(qualifiedName);
|
|
237
345
|
|
|
238
346
|
if (this.debug) {
|
|
239
347
|
console.error(`[MCP DEBUG] Registered tool: ${qualifiedName}`);
|
|
240
348
|
}
|
|
241
349
|
}
|
|
350
|
+
|
|
351
|
+
// Record method filtering results if any filtering was applied
|
|
352
|
+
if (filteredCount > 0) {
|
|
353
|
+
this.recordMcpEvent('tools.filtered', {
|
|
354
|
+
serverName: name,
|
|
355
|
+
filteredCount,
|
|
356
|
+
filteredTools,
|
|
357
|
+
allowedMethods: allowedMethods || [],
|
|
358
|
+
blockedMethods: blockedMethods || []
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Check for unmatched patterns in allowedMethods and warn users
|
|
363
|
+
if (allowedMethods && allowedMethods.length > 0) {
|
|
364
|
+
const unmatchedPatterns = allowedMethods.filter(pattern => {
|
|
365
|
+
// Check if this pattern matches at least one tool
|
|
366
|
+
return !allToolNames.some(toolName => isMethodAllowed(toolName, [pattern], null));
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
if (unmatchedPatterns.length > 0) {
|
|
370
|
+
console.error(`[MCP WARN] Server '${name}': The following allowedMethods patterns did not match any tools: ${unmatchedPatterns.join(', ')}`);
|
|
371
|
+
console.error(`[MCP WARN] Available methods from '${name}': ${allToolNames.join(', ')}`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Check for unmatched patterns in blockedMethods and warn users
|
|
376
|
+
if (blockedMethods && blockedMethods.length > 0) {
|
|
377
|
+
const unmatchedPatterns = blockedMethods.filter(pattern => {
|
|
378
|
+
// Check if this pattern matches at least one tool
|
|
379
|
+
return !allToolNames.some(toolName => !isMethodAllowed(toolName, null, [pattern]));
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
if (unmatchedPatterns.length > 0) {
|
|
383
|
+
console.error(`[MCP WARN] Server '${name}': The following blockedMethods patterns did not match any tools: ${unmatchedPatterns.join(', ')}`);
|
|
384
|
+
console.error(`[MCP WARN] Available methods from '${name}': ${allToolNames.join(', ')}`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Log connection result with filtering info
|
|
390
|
+
if (filteredCount > 0) {
|
|
391
|
+
console.error(`[MCP INFO] Connected to ${name}: ${registeredCount} tool${registeredCount !== 1 ? 's' : ''} loaded (${filteredCount} filtered out)`);
|
|
392
|
+
} else {
|
|
393
|
+
console.error(`[MCP INFO] Connected to ${name}: ${registeredCount} tool${registeredCount !== 1 ? 's' : ''} loaded`);
|
|
242
394
|
}
|
|
243
395
|
|
|
244
|
-
|
|
396
|
+
// Record successful connection
|
|
397
|
+
this.recordMcpEvent('server.connected', {
|
|
398
|
+
serverName: name,
|
|
399
|
+
transport: serverConfig.transport,
|
|
400
|
+
totalToolCount,
|
|
401
|
+
registeredCount,
|
|
402
|
+
filteredCount,
|
|
403
|
+
registeredTools
|
|
404
|
+
});
|
|
245
405
|
|
|
246
406
|
return true;
|
|
247
407
|
} catch (error) {
|
|
@@ -249,6 +409,14 @@ export class MCPClientManager {
|
|
|
249
409
|
if (this.debug) {
|
|
250
410
|
console.error(`[MCP DEBUG] Full error details:`, error);
|
|
251
411
|
}
|
|
412
|
+
|
|
413
|
+
// Record connection failure
|
|
414
|
+
this.recordMcpEvent('server.connection_failed', {
|
|
415
|
+
serverName: name,
|
|
416
|
+
transport: serverConfig.transport,
|
|
417
|
+
error: error.message
|
|
418
|
+
});
|
|
419
|
+
|
|
252
420
|
return false;
|
|
253
421
|
}
|
|
254
422
|
}
|
|
@@ -261,14 +429,32 @@ export class MCPClientManager {
|
|
|
261
429
|
async callTool(toolName, args) {
|
|
262
430
|
const tool = this.tools.get(toolName);
|
|
263
431
|
if (!tool) {
|
|
432
|
+
this.recordMcpEvent('tool.call_failed', {
|
|
433
|
+
toolName,
|
|
434
|
+
error: 'Unknown tool'
|
|
435
|
+
});
|
|
264
436
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
265
437
|
}
|
|
266
438
|
|
|
267
439
|
const clientInfo = this.clients.get(tool.serverName);
|
|
268
440
|
if (!clientInfo) {
|
|
441
|
+
this.recordMcpEvent('tool.call_failed', {
|
|
442
|
+
toolName,
|
|
443
|
+
serverName: tool.serverName,
|
|
444
|
+
error: 'Server not connected'
|
|
445
|
+
});
|
|
269
446
|
throw new Error(`Server ${tool.serverName} not connected`);
|
|
270
447
|
}
|
|
271
448
|
|
|
449
|
+
const startTime = Date.now();
|
|
450
|
+
|
|
451
|
+
// Record tool call start
|
|
452
|
+
this.recordMcpEvent('tool.call_started', {
|
|
453
|
+
toolName,
|
|
454
|
+
serverName: tool.serverName,
|
|
455
|
+
originalToolName: tool.originalName
|
|
456
|
+
});
|
|
457
|
+
|
|
272
458
|
try {
|
|
273
459
|
if (this.debug) {
|
|
274
460
|
console.error(`[MCP DEBUG] Calling ${toolName} with args:`, JSON.stringify(args, null, 2));
|
|
@@ -288,24 +474,48 @@ export class MCPClientManager {
|
|
|
288
474
|
});
|
|
289
475
|
|
|
290
476
|
// Race between the actual call and timeout
|
|
477
|
+
// Pass timeout to SDK's callTool to override its default 60s timeout
|
|
291
478
|
const result = await Promise.race([
|
|
292
479
|
clientInfo.client.callTool({
|
|
293
480
|
name: tool.originalName,
|
|
294
481
|
arguments: args
|
|
295
|
-
}),
|
|
482
|
+
}, undefined, { timeout }),
|
|
296
483
|
timeoutPromise
|
|
297
484
|
]);
|
|
298
485
|
|
|
486
|
+
const durationMs = Date.now() - startTime;
|
|
487
|
+
|
|
299
488
|
if (this.debug) {
|
|
300
489
|
console.error(`[MCP DEBUG] Tool ${toolName} executed successfully`);
|
|
301
490
|
}
|
|
302
491
|
|
|
492
|
+
// Record successful tool call
|
|
493
|
+
this.recordMcpEvent('tool.call_completed', {
|
|
494
|
+
toolName,
|
|
495
|
+
serverName: tool.serverName,
|
|
496
|
+
originalToolName: tool.originalName,
|
|
497
|
+
durationMs
|
|
498
|
+
});
|
|
499
|
+
|
|
303
500
|
return result;
|
|
304
501
|
} catch (error) {
|
|
502
|
+
const durationMs = Date.now() - startTime;
|
|
503
|
+
|
|
305
504
|
console.error(`[MCP ERROR] Error calling tool ${toolName}:`, error.message);
|
|
306
505
|
if (this.debug) {
|
|
307
506
|
console.error(`[MCP DEBUG] Full error details:`, error);
|
|
308
507
|
}
|
|
508
|
+
|
|
509
|
+
// Record failed tool call
|
|
510
|
+
this.recordMcpEvent('tool.call_failed', {
|
|
511
|
+
toolName,
|
|
512
|
+
serverName: tool.serverName,
|
|
513
|
+
originalToolName: tool.originalName,
|
|
514
|
+
error: error.message,
|
|
515
|
+
durationMs,
|
|
516
|
+
isTimeout: error.message.includes('timeout')
|
|
517
|
+
});
|
|
518
|
+
|
|
309
519
|
throw error;
|
|
310
520
|
}
|
|
311
521
|
}
|
|
@@ -357,6 +567,7 @@ export class MCPClientManager {
|
|
|
357
567
|
*/
|
|
358
568
|
async disconnect() {
|
|
359
569
|
const disconnectPromises = [];
|
|
570
|
+
const serverNames = Array.from(this.clients.keys());
|
|
360
571
|
|
|
361
572
|
if (this.clients.size === 0) {
|
|
362
573
|
if (this.debug) {
|
|
@@ -365,6 +576,12 @@ export class MCPClientManager {
|
|
|
365
576
|
return;
|
|
366
577
|
}
|
|
367
578
|
|
|
579
|
+
// Record disconnection start
|
|
580
|
+
this.recordMcpEvent('disconnection.started', {
|
|
581
|
+
serverCount: this.clients.size,
|
|
582
|
+
serverNames
|
|
583
|
+
});
|
|
584
|
+
|
|
368
585
|
if (this.debug) {
|
|
369
586
|
console.error(`[MCP DEBUG] Disconnecting from ${this.clients.size} MCP server${this.clients.size !== 1 ? 's' : ''}...`);
|
|
370
587
|
}
|
|
@@ -376,9 +593,16 @@ export class MCPClientManager {
|
|
|
376
593
|
if (this.debug) {
|
|
377
594
|
console.error(`[MCP DEBUG] Disconnected from ${name}`);
|
|
378
595
|
}
|
|
596
|
+
this.recordMcpEvent('server.disconnected', {
|
|
597
|
+
serverName: name
|
|
598
|
+
});
|
|
379
599
|
})
|
|
380
600
|
.catch(error => {
|
|
381
601
|
console.error(`[MCP ERROR] Error disconnecting from ${name}:`, error.message);
|
|
602
|
+
this.recordMcpEvent('server.disconnect_failed', {
|
|
603
|
+
serverName: name,
|
|
604
|
+
error: error.message
|
|
605
|
+
});
|
|
382
606
|
})
|
|
383
607
|
);
|
|
384
608
|
}
|
|
@@ -387,6 +611,12 @@ export class MCPClientManager {
|
|
|
387
611
|
this.clients.clear();
|
|
388
612
|
this.tools.clear();
|
|
389
613
|
|
|
614
|
+
// Record disconnection completion
|
|
615
|
+
this.recordMcpEvent('disconnection.completed', {
|
|
616
|
+
serverCount: serverNames.length,
|
|
617
|
+
serverNames
|
|
618
|
+
});
|
|
619
|
+
|
|
390
620
|
if (this.debug) {
|
|
391
621
|
console.error('[MCP DEBUG] All MCP connections closed');
|
|
392
622
|
}
|
|
@@ -405,5 +635,6 @@ export async function createMCPManager(options = {}) {
|
|
|
405
635
|
export default {
|
|
406
636
|
MCPClientManager,
|
|
407
637
|
createMCPManager,
|
|
408
|
-
createTransport
|
|
638
|
+
createTransport,
|
|
639
|
+
isMethodAllowed
|
|
409
640
|
};
|