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