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