@probelabs/probe 0.6.0-rc100

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 (115) hide show
  1. package/README.md +583 -0
  2. package/bin/.gitkeep +0 -0
  3. package/bin/probe +158 -0
  4. package/bin/probe-binary +0 -0
  5. package/build/agent/ProbeAgent.d.ts +199 -0
  6. package/build/agent/ProbeAgent.js +1486 -0
  7. package/build/agent/acp/README.md +347 -0
  8. package/build/agent/acp/connection.js +237 -0
  9. package/build/agent/acp/connection.test.js +311 -0
  10. package/build/agent/acp/examples/simple-client.js +212 -0
  11. package/build/agent/acp/examples/tool-lifecycle.js +230 -0
  12. package/build/agent/acp/final-test.js +173 -0
  13. package/build/agent/acp/index.js +5 -0
  14. package/build/agent/acp/integration.test.js +385 -0
  15. package/build/agent/acp/manual-test.js +410 -0
  16. package/build/agent/acp/protocol-test.js +190 -0
  17. package/build/agent/acp/server.js +448 -0
  18. package/build/agent/acp/server.test.js +371 -0
  19. package/build/agent/acp/test-runner.js +216 -0
  20. package/build/agent/acp/test-utils/README.md +315 -0
  21. package/build/agent/acp/test-utils/acp-tester.js +484 -0
  22. package/build/agent/acp/test-utils/mock-acp-client.js +434 -0
  23. package/build/agent/acp/tools.js +368 -0
  24. package/build/agent/acp/tools.test.js +334 -0
  25. package/build/agent/acp/types.js +218 -0
  26. package/build/agent/acp/types.test.js +327 -0
  27. package/build/agent/appTracer.js +360 -0
  28. package/build/agent/fileSpanExporter.js +169 -0
  29. package/build/agent/index.js +7426 -0
  30. package/build/agent/mcp/client.js +338 -0
  31. package/build/agent/mcp/config.js +313 -0
  32. package/build/agent/mcp/index.js +64 -0
  33. package/build/agent/mcp/xmlBridge.js +371 -0
  34. package/build/agent/mockProvider.js +53 -0
  35. package/build/agent/probeTool.js +257 -0
  36. package/build/agent/schemaUtils.js +1726 -0
  37. package/build/agent/simpleTelemetry.js +267 -0
  38. package/build/agent/telemetry.js +225 -0
  39. package/build/agent/tokenCounter.js +395 -0
  40. package/build/agent/tools.js +163 -0
  41. package/build/cli.js +49 -0
  42. package/build/delegate.js +267 -0
  43. package/build/directory-resolver.js +237 -0
  44. package/build/downloader.js +750 -0
  45. package/build/extract.js +149 -0
  46. package/build/index.js +70 -0
  47. package/build/mcp/index.js +514 -0
  48. package/build/mcp/index.ts +608 -0
  49. package/build/query.js +116 -0
  50. package/build/search.js +247 -0
  51. package/build/tools/common.js +410 -0
  52. package/build/tools/index.js +40 -0
  53. package/build/tools/langchain.js +88 -0
  54. package/build/tools/system-message.js +121 -0
  55. package/build/tools/vercel.js +271 -0
  56. package/build/utils/file-lister.js +193 -0
  57. package/build/utils.js +128 -0
  58. package/cjs/agent/ProbeAgent.cjs +5829 -0
  59. package/cjs/index.cjs +6217 -0
  60. package/cjs/package.json +3 -0
  61. package/index.d.ts +401 -0
  62. package/package.json +114 -0
  63. package/scripts/postinstall.js +172 -0
  64. package/src/agent/ProbeAgent.d.ts +199 -0
  65. package/src/agent/ProbeAgent.js +1486 -0
  66. package/src/agent/acp/README.md +347 -0
  67. package/src/agent/acp/connection.js +237 -0
  68. package/src/agent/acp/connection.test.js +311 -0
  69. package/src/agent/acp/examples/simple-client.js +212 -0
  70. package/src/agent/acp/examples/tool-lifecycle.js +230 -0
  71. package/src/agent/acp/final-test.js +173 -0
  72. package/src/agent/acp/index.js +5 -0
  73. package/src/agent/acp/integration.test.js +385 -0
  74. package/src/agent/acp/manual-test.js +410 -0
  75. package/src/agent/acp/protocol-test.js +190 -0
  76. package/src/agent/acp/server.js +448 -0
  77. package/src/agent/acp/server.test.js +371 -0
  78. package/src/agent/acp/test-runner.js +216 -0
  79. package/src/agent/acp/test-utils/README.md +315 -0
  80. package/src/agent/acp/test-utils/acp-tester.js +484 -0
  81. package/src/agent/acp/test-utils/mock-acp-client.js +434 -0
  82. package/src/agent/acp/tools.js +368 -0
  83. package/src/agent/acp/tools.test.js +334 -0
  84. package/src/agent/acp/types.js +218 -0
  85. package/src/agent/acp/types.test.js +327 -0
  86. package/src/agent/appTracer.js +360 -0
  87. package/src/agent/fileSpanExporter.js +169 -0
  88. package/src/agent/index.js +813 -0
  89. package/src/agent/mcp/client.js +338 -0
  90. package/src/agent/mcp/config.js +313 -0
  91. package/src/agent/mcp/index.js +64 -0
  92. package/src/agent/mcp/xmlBridge.js +371 -0
  93. package/src/agent/mockProvider.js +53 -0
  94. package/src/agent/probeTool.js +257 -0
  95. package/src/agent/schemaUtils.js +1726 -0
  96. package/src/agent/simpleTelemetry.js +267 -0
  97. package/src/agent/telemetry.js +225 -0
  98. package/src/agent/tokenCounter.js +395 -0
  99. package/src/agent/tools.js +163 -0
  100. package/src/cli.js +49 -0
  101. package/src/delegate.js +267 -0
  102. package/src/directory-resolver.js +237 -0
  103. package/src/downloader.js +750 -0
  104. package/src/extract.js +149 -0
  105. package/src/index.js +70 -0
  106. package/src/mcp/index.ts +608 -0
  107. package/src/query.js +116 -0
  108. package/src/search.js +247 -0
  109. package/src/tools/common.js +410 -0
  110. package/src/tools/index.js +40 -0
  111. package/src/tools/langchain.js +88 -0
  112. package/src/tools/system-message.js +121 -0
  113. package/src/tools/vercel.js +271 -0
  114. package/src/utils/file-lister.js +193 -0
  115. package/src/utils.js +128 -0
@@ -0,0 +1,608 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import {
5
+ CallToolRequestSchema,
6
+ ErrorCode,
7
+ ListToolsRequestSchema,
8
+ McpError,
9
+ } from '@modelcontextprotocol/sdk/types.js';
10
+ import { exec } from 'child_process';
11
+ import { promisify } from 'util';
12
+ import path from 'path';
13
+ import fs from 'fs-extra';
14
+ import { fileURLToPath } from 'url';
15
+
16
+ // Import from parent package
17
+ import { search, query, extract, getBinaryPath, setBinaryPath } from '../index.js';
18
+
19
+ // Parse command-line arguments
20
+ function parseArgs(): { timeout?: number; format?: string } {
21
+ const args = process.argv.slice(2);
22
+ const config: { timeout?: number; format?: string } = {};
23
+
24
+ for (let i = 0; i < args.length; i++) {
25
+ if ((args[i] === '--timeout' || args[i] === '-t') && i + 1 < args.length) {
26
+ const timeout = parseInt(args[i + 1], 10);
27
+ if (!isNaN(timeout) && timeout > 0) {
28
+ config.timeout = timeout;
29
+ console.error(`Timeout set to ${timeout} seconds`);
30
+ } else {
31
+ console.error(`Invalid timeout value: ${args[i + 1]}. Using default.`);
32
+ }
33
+ i++; // Skip the next argument
34
+ } else if (args[i] === '--format' && i + 1 < args.length) {
35
+ config.format = args[i + 1];
36
+ console.error(`Format set to ${config.format}`);
37
+ i++; // Skip the next argument
38
+ } else if (args[i] === '--help' || args[i] === '-h') {
39
+ console.log(`
40
+ Probe MCP Server
41
+
42
+ Usage:
43
+ probe mcp [options]
44
+
45
+ Options:
46
+ --timeout, -t <seconds> Set timeout for search operations (default: 30)
47
+ --format <format> Set output format (json, outline-xml, etc.)
48
+ --help, -h Show this help message
49
+ `);
50
+ process.exit(0);
51
+ }
52
+ }
53
+
54
+ return config;
55
+ }
56
+
57
+ const cliConfig = parseArgs();
58
+
59
+ const execAsync = promisify(exec);
60
+
61
+ // Get the package.json to determine the version
62
+ const __filename = fileURLToPath(import.meta.url);
63
+ const __dirname = path.dirname(__filename);
64
+
65
+ // Try multiple possible locations for package.json
66
+ let packageVersion = '0.0.0';
67
+ const possiblePaths = [
68
+ path.resolve(__dirname, '..', 'package.json'), // When installed from npm: build/../package.json
69
+ path.resolve(__dirname, '..', '..', 'package.json') // In development: src/../package.json
70
+ ];
71
+
72
+ for (const packageJsonPath of possiblePaths) {
73
+ try {
74
+ if (fs.existsSync(packageJsonPath)) {
75
+ console.log(`Found package.json at: ${packageJsonPath}`);
76
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
77
+ if (packageJson.version) {
78
+ packageVersion = packageJson.version;
79
+ console.log(`Using version from package.json: ${packageVersion}`);
80
+ break;
81
+ }
82
+ }
83
+ } catch (error) {
84
+ console.error(`Error reading package.json at ${packageJsonPath}:`, error);
85
+ }
86
+ }
87
+
88
+ // If we still have 0.0.0, try to get version from npm package
89
+ if (packageVersion === '0.0.0') {
90
+ try {
91
+ // Try to get version from the package name itself
92
+ const result = await execAsync('npm list -g @probelabs/probe --json');
93
+ const npmList = JSON.parse(result.stdout);
94
+ if (npmList.dependencies && npmList.dependencies['@probelabs/probe']) {
95
+ packageVersion = npmList.dependencies['@probelabs/probe'].version;
96
+ console.log(`Using version from npm list: ${packageVersion}`);
97
+ }
98
+ } catch (error) {
99
+ console.error('Error getting version from npm:', error);
100
+ }
101
+ }
102
+
103
+ import { existsSync } from 'fs';
104
+
105
+ // Get the path to the bin directory
106
+ const binDir = path.resolve(__dirname, '..', 'bin');
107
+ console.log(`Bin directory: ${binDir}`);
108
+
109
+ // The @probelabs/probe package now handles binary path management internally
110
+ // We don't need to manage the binary path in the MCP server anymore
111
+
112
+ interface SearchCodeArgs {
113
+ path: string;
114
+ query: string | string[];
115
+ filesOnly?: boolean;
116
+ ignore?: string[];
117
+ excludeFilenames?: boolean;
118
+ exact?: boolean;
119
+ maxResults?: number;
120
+ maxTokens?: number;
121
+ allowTests?: boolean;
122
+ session?: string;
123
+ timeout?: number;
124
+ noGitignore?: boolean;
125
+ }
126
+
127
+ interface QueryCodeArgs {
128
+ path: string;
129
+ pattern: string;
130
+ language?: string;
131
+ ignore?: string[];
132
+ allowTests?: boolean;
133
+ maxResults?: number;
134
+ format?: 'markdown' | 'plain' | 'json' | 'color';
135
+ timeout?: number;
136
+ noGitignore?: boolean;
137
+ }
138
+
139
+ interface ExtractCodeArgs {
140
+ path: string;
141
+ files: string[];
142
+ allowTests?: boolean;
143
+ contextLines?: number;
144
+ format?: 'markdown' | 'plain' | 'json';
145
+ timeout?: number;
146
+ noGitignore?: boolean;
147
+ }
148
+
149
+ class ProbeServer {
150
+ private server: Server;
151
+ private defaultTimeout: number;
152
+ private defaultFormat?: string;
153
+
154
+ constructor(timeout: number = 30, format?: string) {
155
+ this.defaultTimeout = timeout;
156
+ this.defaultFormat = format;
157
+ this.server = new Server(
158
+ {
159
+ name: '@probelabs/probe',
160
+ version: packageVersion,
161
+ },
162
+ {
163
+ capabilities: {
164
+ tools: {},
165
+ },
166
+ }
167
+ );
168
+
169
+ this.setupToolHandlers();
170
+
171
+ // Error handling
172
+ this.server.onerror = (error) => console.error('[MCP Error]', error);
173
+ process.on('SIGINT', async () => {
174
+ await this.server.close();
175
+ process.exit(0);
176
+ });
177
+ }
178
+
179
+ private setupToolHandlers() {
180
+ // Use the tool descriptions defined at the top of the file
181
+
182
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
183
+ tools: [
184
+ {
185
+ name: 'search_code',
186
+ description: "Search code in the repository using ElasticSearch. Use this tool first for any code-related questions.",
187
+ inputSchema: {
188
+ type: 'object',
189
+ properties: {
190
+ path: {
191
+ type: 'string',
192
+ description: 'Absolute path to the directory to search in (e.g., "/Users/username/projects/myproject").',
193
+ },
194
+ query: {
195
+ type: 'string',
196
+ description: 'Elastic search query. Supports logical operators (AND, OR, NOT), and grouping with parentheses. Examples: "config", "(term1 OR term2) AND term3". Use quotes for exact matches, like function or type names.',
197
+ },
198
+ filesOnly: {
199
+ type: 'boolean',
200
+ description: 'Skip AST parsing and just output unique files',
201
+ },
202
+ ignore: {
203
+ type: 'array',
204
+ items: { type: 'string' },
205
+ description: 'Custom patterns to ignore (in addition to .gitignore and common patterns)'
206
+ },
207
+ excludeFilenames: {
208
+ type: 'boolean',
209
+ description: 'Exclude filenames from being used for matching'
210
+ },
211
+ exact: {
212
+ type: 'boolean',
213
+ description: 'Perform exact search without tokenization (case-insensitive)'
214
+ },
215
+ allowTests: {
216
+ type: 'boolean',
217
+ description: 'Allow test files and test code blocks in results (disabled by default)'
218
+ },
219
+ session: {
220
+ type: 'string',
221
+ description: 'Session identifier for caching. Set to "new" if unknown, or want to reset cache. Re-use session ID returned from previous searches',
222
+ default: "new",
223
+ },
224
+ timeout: {
225
+ type: 'number',
226
+ description: 'Timeout for the search operation in seconds (default: 30)',
227
+ },
228
+ noGitignore: {
229
+ type: 'boolean',
230
+ description: 'Skip .gitignore files (will use PROBE_NO_GITIGNORE environment variable if not set)',
231
+ }
232
+ },
233
+ required: ['path', 'query']
234
+ },
235
+ },
236
+ {
237
+ name: 'query_code',
238
+ description: "Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.",
239
+ inputSchema: {
240
+ type: 'object',
241
+ properties: {
242
+ path: {
243
+ type: 'string',
244
+ description: 'Absolute path to the directory to search in (e.g., "/Users/username/projects/myproject").',
245
+ },
246
+ pattern: {
247
+ type: 'string',
248
+ description: 'The ast-grep pattern to search for. Examples: "fn $NAME($$$PARAMS) $$$BODY" for Rust functions, "def $NAME($$$PARAMS): $$$BODY" for Python functions.',
249
+ },
250
+ language: {
251
+ type: 'string',
252
+ description: 'The programming language to search in. If not specified, the tool will try to infer the language from file extensions. Supported languages: rust, javascript, typescript, python, go, c, cpp, java, ruby, php, swift, csharp, yaml.',
253
+ },
254
+ ignore: {
255
+ type: 'array',
256
+ items: { type: 'string' },
257
+ description: 'Custom patterns to ignore (in addition to common patterns)',
258
+ },
259
+ maxResults: {
260
+ type: 'number',
261
+ description: 'Maximum number of results to return'
262
+ },
263
+ format: {
264
+ type: 'string',
265
+ enum: ['markdown', 'plain', 'json', 'color'],
266
+ description: 'Output format for the query results'
267
+ },
268
+ timeout: {
269
+ type: 'number',
270
+ description: 'Timeout for the query operation in seconds (default: 30)',
271
+ },
272
+ noGitignore: {
273
+ type: 'boolean',
274
+ description: 'Skip .gitignore files (will use PROBE_NO_GITIGNORE environment variable if not set)',
275
+ }
276
+ },
277
+ required: ['path', 'pattern']
278
+ },
279
+ },
280
+ {
281
+ name: 'extract_code',
282
+ description: "Extract code blocks from files based on line number, or symbol name. Fetch full file when line number is not provided.",
283
+ inputSchema: {
284
+ type: 'object',
285
+ properties: {
286
+ path: {
287
+ type: 'string',
288
+ description: 'Absolute path to the directory to search in (e.g., "/Users/username/projects/myproject").',
289
+ },
290
+ files: {
291
+ type: 'array',
292
+ items: { type: 'string' },
293
+ description: 'Files and lines or sybmbols to extract from: /path/to/file.rs:10, /path/to/file.rs#func_name Path should be absolute.',
294
+ },
295
+ allowTests: {
296
+ type: 'boolean',
297
+ description: 'Allow test files and test code blocks in results (disabled by default)',
298
+ },
299
+ contextLines: {
300
+ type: 'number',
301
+ description: 'Number of context lines to include before and after the extracted block when AST parsing fails to find a suitable node',
302
+ default: 0
303
+ },
304
+ format: {
305
+ type: 'string',
306
+ enum: ['markdown', 'plain', 'json'],
307
+ description: 'Output format for the extracted code',
308
+ default: 'markdown'
309
+ },
310
+ timeout: {
311
+ type: 'number',
312
+ description: 'Timeout for the extract operation in seconds (default: 30)',
313
+ },
314
+ noGitignore: {
315
+ type: 'boolean',
316
+ description: 'Skip .gitignore files (will use PROBE_NO_GITIGNORE environment variable if not set)',
317
+ }
318
+ },
319
+ required: ['path', 'files'],
320
+ },
321
+ },
322
+ ],
323
+ }));
324
+
325
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
326
+ if (request.params.name !== 'search_code' && request.params.name !== 'query_code' && request.params.name !== 'extract_code' &&
327
+ request.params.name !== 'probe' && request.params.name !== 'query' && request.params.name !== 'extract') {
328
+ throw new McpError(
329
+ ErrorCode.MethodNotFound,
330
+ `Unknown tool: ${request.params.name}`
331
+ );
332
+ }
333
+
334
+ try {
335
+ let result: string;
336
+
337
+ // Log the incoming request for debugging
338
+ console.error(`Received request for tool: ${request.params.name}`);
339
+ console.error(`Request arguments: ${JSON.stringify(request.params.arguments)}`);
340
+
341
+ // Handle both new tool names and legacy tool names
342
+ if (request.params.name === 'search_code' || request.params.name === 'probe') {
343
+ // Ensure arguments is an object
344
+ if (!request.params.arguments || typeof request.params.arguments !== 'object') {
345
+ throw new Error("Arguments must be an object");
346
+ }
347
+
348
+ const args = request.params.arguments as unknown as SearchCodeArgs;
349
+
350
+ // Validate required fields
351
+ if (!args.path) {
352
+ throw new Error("Path is required in arguments");
353
+ }
354
+ if (!args.query) {
355
+ throw new Error("Query is required in arguments");
356
+ }
357
+
358
+ result = await this.executeCodeSearch(args);
359
+ } else if (request.params.name === 'query_code' || request.params.name === 'query') {
360
+ const args = request.params.arguments as unknown as QueryCodeArgs;
361
+ result = await this.executeCodeQuery(args);
362
+ } else { // extract_code or extract
363
+ const args = request.params.arguments as unknown as ExtractCodeArgs;
364
+ result = await this.executeCodeExtract(args);
365
+ }
366
+
367
+ return {
368
+ content: [
369
+ {
370
+ type: 'text',
371
+ text: result,
372
+ },
373
+ ],
374
+ };
375
+ } catch (error) {
376
+ console.error(`Error executing ${request.params.name}:`, error);
377
+ return {
378
+ content: [
379
+ {
380
+ type: 'text',
381
+ text: `Error executing ${request.params.name}: ${error instanceof Error ? error.message : String(error)}`,
382
+ },
383
+ ],
384
+ isError: true,
385
+ };
386
+ }
387
+ });
388
+ }
389
+
390
+ private async executeCodeSearch(args: SearchCodeArgs): Promise<string> {
391
+ try {
392
+ // Ensure path is included in the options and is a non-empty string
393
+ if (!args.path || typeof args.path !== 'string' || args.path.trim() === '') {
394
+ throw new Error("Path is required and must be a non-empty string");
395
+ }
396
+
397
+ // Ensure query is included in the options
398
+ if (!args.query) {
399
+ throw new Error("Query is required");
400
+ }
401
+
402
+ // Log the arguments we received for debugging
403
+ console.error(`Received search arguments: path=${args.path}, query=${JSON.stringify(args.query)}`);
404
+
405
+ // Create a clean options object with only the essential properties first
406
+ const options: any = {
407
+ path: args.path.trim(), // Ensure path is trimmed
408
+ query: args.query
409
+ };
410
+
411
+ // Add optional parameters only if they exist
412
+ if (args.filesOnly !== undefined) options.filesOnly = args.filesOnly;
413
+ if (args.ignore !== undefined) options.ignore = args.ignore;
414
+ if (args.excludeFilenames !== undefined) options.excludeFilenames = args.excludeFilenames;
415
+ if (args.exact !== undefined) options.exact = args.exact;
416
+ if (args.maxResults !== undefined) options.maxResults = args.maxResults;
417
+ if (args.maxTokens !== undefined) options.maxTokens = args.maxTokens;
418
+ if (args.allowTests !== undefined) options.allowTests = args.allowTests;
419
+ // Use noGitignore from args, or fall back to PROBE_NO_GITIGNORE environment variable
420
+ if (args.noGitignore !== undefined) {
421
+ options.noGitignore = args.noGitignore;
422
+ } else if (process.env.PROBE_NO_GITIGNORE) {
423
+ options.noGitignore = process.env.PROBE_NO_GITIGNORE === 'true';
424
+ }
425
+ if (args.session !== undefined && args.session.trim() !== '') {
426
+ options.session = args.session;
427
+ } else {
428
+ options.session = "new";
429
+ }
430
+ // Use timeout from args, or fall back to instance default
431
+ if (args.timeout !== undefined) {
432
+ options.timeout = args.timeout;
433
+ } else if (this.defaultTimeout !== undefined) {
434
+ options.timeout = this.defaultTimeout;
435
+ }
436
+
437
+ // Handle format options
438
+ if (this.defaultFormat === 'outline-xml') {
439
+ // For outline-xml format, we pass it as a format flag to the search command
440
+ options.format = 'outline-xml';
441
+ } else if (this.defaultFormat === 'json') {
442
+ options.json = true;
443
+ }
444
+
445
+ console.error("Executing search with options:", JSON.stringify(options, null, 2));
446
+
447
+ // Double-check that path is still in the options object
448
+ if (!options.path) {
449
+ console.error("Path is missing from options object after construction");
450
+ throw new Error("Path is missing from options object");
451
+ }
452
+
453
+ try {
454
+ // Call search with the options object
455
+ const result = await search(options);
456
+ return result;
457
+ } catch (searchError: any) {
458
+ console.error("Search function error:", searchError);
459
+ throw new Error(`Search function error: ${searchError.message || String(searchError)}`);
460
+ }
461
+ } catch (error: any) {
462
+ console.error('Error executing code search:', error);
463
+ throw new McpError(
464
+ 'MethodNotFound' as unknown as ErrorCode,
465
+ `Error executing code search: ${error.message || String(error)}`
466
+ );
467
+ }
468
+ }
469
+
470
+ private async executeCodeQuery(args: QueryCodeArgs): Promise<string> {
471
+ try {
472
+ // Validate required parameters
473
+ if (!args.path) {
474
+ throw new Error("Path is required");
475
+ }
476
+ if (!args.pattern) {
477
+ throw new Error("Pattern is required");
478
+ }
479
+
480
+ // Create a single options object with both pattern and path
481
+ const options: any = {
482
+ path: args.path,
483
+ pattern: args.pattern,
484
+ language: args.language,
485
+ ignore: args.ignore,
486
+ allowTests: args.allowTests,
487
+ maxResults: args.maxResults,
488
+ format: args.format,
489
+ timeout: args.timeout || this.defaultTimeout
490
+ };
491
+
492
+ // Use noGitignore from args, or fall back to PROBE_NO_GITIGNORE environment variable
493
+ if (args.noGitignore !== undefined) {
494
+ options.noGitignore = args.noGitignore;
495
+ } else if (process.env.PROBE_NO_GITIGNORE) {
496
+ options.noGitignore = process.env.PROBE_NO_GITIGNORE === 'true';
497
+ }
498
+
499
+ console.log("Executing query with options:", JSON.stringify({
500
+ path: options.path,
501
+ pattern: options.pattern
502
+ }));
503
+
504
+ const result = await query(options);
505
+ return result;
506
+ } catch (error: any) {
507
+ console.error('Error executing code query:', error);
508
+ throw new McpError(
509
+ 'MethodNotFound' as unknown as ErrorCode,
510
+ `Error executing code query: ${error.message || String(error)}`
511
+ );
512
+ }
513
+ }
514
+
515
+ private async executeCodeExtract(args: ExtractCodeArgs): Promise<string> {
516
+ try {
517
+ // Validate required parameters
518
+ if (!args.path) {
519
+ throw new Error("Path is required");
520
+ }
521
+ if (!args.files || !Array.isArray(args.files) || args.files.length === 0) {
522
+ throw new Error("Files array is required and must not be empty");
523
+ }
524
+
525
+ // Create a single options object with files and other parameters
526
+ const options: any = {
527
+ files: args.files,
528
+ path: args.path,
529
+ allowTests: args.allowTests,
530
+ contextLines: args.contextLines,
531
+ format: args.format,
532
+ timeout: args.timeout || this.defaultTimeout
533
+ };
534
+
535
+ // Use noGitignore from args, or fall back to PROBE_NO_GITIGNORE environment variable
536
+ if (args.noGitignore !== undefined) {
537
+ options.noGitignore = args.noGitignore;
538
+ } else if (process.env.PROBE_NO_GITIGNORE) {
539
+ options.noGitignore = process.env.PROBE_NO_GITIGNORE === 'true';
540
+ }
541
+
542
+ // Call extract with the complete options object
543
+ try {
544
+ // Track request size for token usage
545
+ const requestSize = JSON.stringify(args).length;
546
+ const requestTokens = Math.ceil(requestSize / 4); // Approximate token count
547
+
548
+ // Execute the extract command
549
+ const result = await extract(options);
550
+
551
+ // Parse the result to extract token information if available
552
+ let responseTokens = 0;
553
+ let totalTokens = 0;
554
+
555
+ // Try to extract token information from the result
556
+ if (typeof result === 'string') {
557
+ const tokenMatch = result.match(/Total tokens returned: (\d+)/);
558
+ if (tokenMatch && tokenMatch[1]) {
559
+ responseTokens = parseInt(tokenMatch[1], 10);
560
+ totalTokens = requestTokens + responseTokens;
561
+ }
562
+
563
+ // Remove spinner debug output lines
564
+ const cleanedLines = result.split('\n').filter(line =>
565
+ !line.match(/^⠙|^⠹|^⠧|^⠇|^⠏/) &&
566
+ !line.includes('Thinking...Extract:') &&
567
+ !line.includes('Extract results:')
568
+ );
569
+
570
+ // Add token usage information if not already present
571
+ if (!result.includes('Token Usage:')) {
572
+ cleanedLines.push('');
573
+ cleanedLines.push('Token Usage:');
574
+ cleanedLines.push(` Request tokens: ${requestTokens}`);
575
+ cleanedLines.push(` Response tokens: ${responseTokens}`);
576
+ cleanedLines.push(` Total tokens: ${totalTokens}`);
577
+ }
578
+
579
+ return cleanedLines.join('\n');
580
+ }
581
+
582
+ return result;
583
+ } catch (error: any) {
584
+ console.error(`Error extracting:`, error);
585
+ return `Error extracting: ${error.message || String(error)}`;
586
+ }
587
+ } catch (error: any) {
588
+ console.error('Error executing code extract:', error);
589
+ throw new McpError(
590
+ 'MethodNotFound' as unknown as ErrorCode,
591
+ `Error executing code extract: ${error.message || String(error)}`
592
+ );
593
+ }
594
+ }
595
+
596
+ async run() {
597
+ // The @probelabs/probe package now handles binary path management internally
598
+ // We don't need to verify or download the binary in the MCP server anymore
599
+
600
+ // Just connect the server to the transport
601
+ const transport = new StdioServerTransport();
602
+ await this.server.connect(transport);
603
+ console.error('Probe MCP server running on stdio');
604
+ }
605
+ }
606
+
607
+ const server = new ProbeServer(cliConfig.timeout, cliConfig.format);
608
+ server.run().catch(console.error);