@softerist/heuristic-mcp 3.0.17 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config.jsonc +23 -6
- package/features/ann-config.js +7 -14
- package/features/clear-cache.js +3 -3
- package/features/find-similar-code.js +17 -22
- package/features/hybrid-search.js +59 -67
- package/features/index-codebase.js +305 -268
- package/features/lifecycle.js +370 -176
- package/features/package-version.js +15 -26
- package/features/register.js +75 -57
- package/features/resources.js +21 -47
- package/features/set-workspace.js +31 -43
- package/index.js +912 -200
- package/lib/cache-utils.js +95 -99
- package/lib/cache.js +121 -166
- package/lib/cli.js +246 -238
- package/lib/config.js +232 -62
- package/lib/constants.js +22 -2
- package/lib/embed-query-process.js +13 -29
- package/lib/embedding-process.js +29 -19
- package/lib/embedding-worker.js +166 -149
- package/lib/ignore-patterns.js +39 -39
- package/lib/json-writer.js +7 -34
- package/lib/logging.js +52 -48
- package/lib/onnx-backend.js +4 -4
- package/lib/path-utils.js +4 -21
- package/lib/project-detector.js +3 -3
- package/lib/server-lifecycle.js +148 -35
- package/lib/settings-editor.js +25 -18
- package/lib/slice-normalize.js +6 -16
- package/lib/tokenizer.js +56 -109
- package/lib/utils.js +62 -81
- package/lib/vector-store-binary.js +7 -7
- package/lib/vector-store-sqlite.js +35 -67
- package/lib/workspace-cache-key.js +36 -0
- package/lib/workspace-env.js +55 -14
- package/package.json +86 -86
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Package Version Lookup Tool
|
|
3
|
-
*
|
|
4
|
-
* Fetches the latest version of packages from various registries.
|
|
5
|
-
* Supports npm, PyPI, crates.io, Maven, Go, RubyGems, NuGet, Packagist, Hex, pub.dev, and more.
|
|
6
|
-
*/
|
|
7
1
|
|
|
8
|
-
|
|
2
|
+
|
|
3
|
+
|
|
9
4
|
const REGISTRIES = {
|
|
10
5
|
npm: {
|
|
11
6
|
name: 'npm',
|
|
@@ -20,7 +15,7 @@ const REGISTRIES = {
|
|
|
20
15
|
pattern: /^(?:pip:|pypi:)(.+)$/,
|
|
21
16
|
url: (pkg) => `https://pypi.org/pypi/${encodeURIComponent(pkg)}/json`,
|
|
22
17
|
parse: (data) => data.info.version,
|
|
23
|
-
detect: () => false,
|
|
18
|
+
detect: () => false,
|
|
24
19
|
},
|
|
25
20
|
crates: {
|
|
26
21
|
name: 'crates.io',
|
|
@@ -59,7 +54,7 @@ const REGISTRIES = {
|
|
|
59
54
|
parse: (data) => {
|
|
60
55
|
const pkgName = Object.keys(data.packages)[0];
|
|
61
56
|
const versions = data.packages[pkgName];
|
|
62
|
-
|
|
57
|
+
|
|
63
58
|
const stable = versions.find((v) => !v.version.includes('dev'));
|
|
64
59
|
return stable ? stable.version : versions[0].version;
|
|
65
60
|
},
|
|
@@ -86,7 +81,7 @@ const REGISTRIES = {
|
|
|
86
81
|
name: 'Maven Central',
|
|
87
82
|
pattern: /^(?:maven:|java:)(.+)$/,
|
|
88
83
|
url: (pkg) => {
|
|
89
|
-
|
|
84
|
+
|
|
90
85
|
const [group, artifact] = pkg.includes(':') ? pkg.split(':') : pkg.split('/');
|
|
91
86
|
if (!artifact) return null;
|
|
92
87
|
return `https://search.maven.org/solrsearch/select?q=g:${encodeURIComponent(group)}+AND+a:${encodeURIComponent(artifact)}&rows=1&wt=json`;
|
|
@@ -114,11 +109,9 @@ const REGISTRIES = {
|
|
|
114
109
|
},
|
|
115
110
|
};
|
|
116
111
|
|
|
117
|
-
|
|
118
|
-
* Detect the registry for a package based on its name or prefix
|
|
119
|
-
*/
|
|
112
|
+
|
|
120
113
|
function detectRegistry(packageName) {
|
|
121
|
-
|
|
114
|
+
|
|
122
115
|
for (const [key, registry] of Object.entries(REGISTRIES)) {
|
|
123
116
|
if (registry.pattern.test(packageName) && key !== 'npm') {
|
|
124
117
|
const match = packageName.match(registry.pattern);
|
|
@@ -128,7 +121,7 @@ function detectRegistry(packageName) {
|
|
|
128
121
|
}
|
|
129
122
|
}
|
|
130
123
|
|
|
131
|
-
|
|
124
|
+
|
|
132
125
|
for (const registry of Object.values(REGISTRIES)) {
|
|
133
126
|
if (registry.detect(packageName)) {
|
|
134
127
|
const match = packageName.match(registry.pattern);
|
|
@@ -136,14 +129,12 @@ function detectRegistry(packageName) {
|
|
|
136
129
|
}
|
|
137
130
|
}
|
|
138
131
|
|
|
139
|
-
|
|
132
|
+
|
|
140
133
|
const npmMatch = packageName.match(REGISTRIES.npm.pattern);
|
|
141
134
|
return { registry: REGISTRIES.npm, cleanName: npmMatch ? npmMatch[1] : packageName };
|
|
142
135
|
}
|
|
143
136
|
|
|
144
|
-
|
|
145
|
-
* Fetch the latest version of a package from its registry
|
|
146
|
-
*/
|
|
137
|
+
|
|
147
138
|
async function fetchPackageVersion(packageName, timeoutMs = 10000) {
|
|
148
139
|
const { registry, cleanName } = detectRegistry(packageName);
|
|
149
140
|
|
|
@@ -220,9 +211,7 @@ async function fetchPackageVersion(packageName, timeoutMs = 10000) {
|
|
|
220
211
|
}
|
|
221
212
|
}
|
|
222
213
|
|
|
223
|
-
|
|
224
|
-
* Get supported registries list for help text
|
|
225
|
-
*/
|
|
214
|
+
|
|
226
215
|
function getSupportedRegistries() {
|
|
227
216
|
return Object.entries(REGISTRIES).map(([key, reg]) => ({
|
|
228
217
|
key,
|
|
@@ -231,7 +220,7 @@ function getSupportedRegistries() {
|
|
|
231
220
|
}));
|
|
232
221
|
}
|
|
233
222
|
|
|
234
|
-
|
|
223
|
+
|
|
235
224
|
export function getToolDefinition() {
|
|
236
225
|
return {
|
|
237
226
|
name: 'e_check_package_version',
|
|
@@ -253,12 +242,12 @@ export function getToolDefinition() {
|
|
|
253
242
|
readOnlyHint: true,
|
|
254
243
|
destructiveHint: false,
|
|
255
244
|
idempotentHint: true,
|
|
256
|
-
openWorldHint: true,
|
|
245
|
+
openWorldHint: true,
|
|
257
246
|
},
|
|
258
247
|
};
|
|
259
248
|
}
|
|
260
249
|
|
|
261
|
-
|
|
250
|
+
|
|
262
251
|
export async function handleToolCall(request) {
|
|
263
252
|
const args = request.params?.arguments || {};
|
|
264
253
|
const packageName = args.package;
|
|
@@ -298,5 +287,5 @@ export async function handleToolCall(request) {
|
|
|
298
287
|
}
|
|
299
288
|
}
|
|
300
289
|
|
|
301
|
-
|
|
290
|
+
|
|
302
291
|
export { fetchPackageVersion, detectRegistry, getSupportedRegistries, REGISTRIES };
|
package/features/register.js
CHANGED
|
@@ -3,7 +3,6 @@ import { writeFileSync } from 'fs';
|
|
|
3
3
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import os from 'os';
|
|
6
|
-
import { fileURLToPath } from 'url';
|
|
7
6
|
import {
|
|
8
7
|
parseJsonc,
|
|
9
8
|
upsertMcpServerEntryInText,
|
|
@@ -17,9 +16,9 @@ function getUserHomeDir() {
|
|
|
17
16
|
return os.homedir();
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
|
|
21
20
|
function detectCurrentIDE() {
|
|
22
|
-
|
|
21
|
+
|
|
23
22
|
if (process.env.ANTIGRAVITY_AGENT) {
|
|
24
23
|
return 'Antigravity';
|
|
25
24
|
}
|
|
@@ -47,18 +46,18 @@ function detectCurrentIDE() {
|
|
|
47
46
|
return 'Windsurf';
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
|
|
49
|
+
|
|
51
50
|
return null;
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
|
|
53
|
+
|
|
55
54
|
function getConfigPaths() {
|
|
56
55
|
const platform = process.platform;
|
|
57
56
|
const home = getUserHomeDir();
|
|
58
57
|
const currentIDE = detectCurrentIDE();
|
|
59
58
|
const allPaths = [];
|
|
60
59
|
|
|
61
|
-
|
|
60
|
+
|
|
62
61
|
allPaths.push({
|
|
63
62
|
name: 'Antigravity',
|
|
64
63
|
path: path.join(home, '.gemini', 'antigravity', 'mcp_config.json'),
|
|
@@ -66,7 +65,7 @@ function getConfigPaths() {
|
|
|
66
65
|
canCreate: true,
|
|
67
66
|
});
|
|
68
67
|
|
|
69
|
-
|
|
68
|
+
|
|
70
69
|
allPaths.push({
|
|
71
70
|
name: 'Codex',
|
|
72
71
|
path: path.join(home, '.codex', 'config.toml'),
|
|
@@ -74,7 +73,7 @@ function getConfigPaths() {
|
|
|
74
73
|
canCreate: true,
|
|
75
74
|
});
|
|
76
75
|
|
|
77
|
-
|
|
76
|
+
|
|
78
77
|
if (platform === 'darwin') {
|
|
79
78
|
allPaths.push({
|
|
80
79
|
name: 'Claude Desktop',
|
|
@@ -97,7 +96,7 @@ function getConfigPaths() {
|
|
|
97
96
|
});
|
|
98
97
|
}
|
|
99
98
|
|
|
100
|
-
|
|
99
|
+
|
|
101
100
|
if (platform === 'darwin') {
|
|
102
101
|
allPaths.push({
|
|
103
102
|
name: 'Cursor',
|
|
@@ -139,7 +138,7 @@ function getConfigPaths() {
|
|
|
139
138
|
preferredContainerKey: 'mcpServers',
|
|
140
139
|
});
|
|
141
140
|
|
|
142
|
-
|
|
141
|
+
|
|
143
142
|
allPaths.push({
|
|
144
143
|
name: 'Warp',
|
|
145
144
|
path: path.join(home, '.warp', 'mcp_settings.json'),
|
|
@@ -157,7 +156,7 @@ function getConfigPaths() {
|
|
|
157
156
|
});
|
|
158
157
|
}
|
|
159
158
|
|
|
160
|
-
|
|
159
|
+
|
|
161
160
|
if (platform === 'darwin') {
|
|
162
161
|
allPaths.push({
|
|
163
162
|
name: 'VS Code',
|
|
@@ -212,15 +211,15 @@ function getConfigPaths() {
|
|
|
212
211
|
});
|
|
213
212
|
}
|
|
214
213
|
|
|
215
|
-
|
|
216
|
-
|
|
214
|
+
|
|
215
|
+
|
|
217
216
|
return allPaths.map((entry) => ({
|
|
218
217
|
...entry,
|
|
219
218
|
canCreate: entry.canCreate || entry.name === currentIDE,
|
|
220
219
|
}));
|
|
221
220
|
}
|
|
222
221
|
|
|
223
|
-
|
|
222
|
+
|
|
224
223
|
function forceLog(message) {
|
|
225
224
|
try {
|
|
226
225
|
if (process.platform !== 'win32') {
|
|
@@ -240,7 +239,7 @@ function normalizeIdeName(name) {
|
|
|
240
239
|
.replace(/[\s_-]+/g, '');
|
|
241
240
|
}
|
|
242
241
|
|
|
243
|
-
function ideMatchesFilter(name, filter) {
|
|
242
|
+
function ideMatchesFilter(name, filter) {
|
|
244
243
|
if (!filter) return true;
|
|
245
244
|
const normalizedName = normalizeIdeName(name);
|
|
246
245
|
const normalizedFilter = normalizeIdeName(filter);
|
|
@@ -261,27 +260,41 @@ function ideMatchesFilter(name, filter) {
|
|
|
261
260
|
return normalizedName === 'vscode' || normalizedName === 'vscodeinsiders';
|
|
262
261
|
}
|
|
263
262
|
|
|
264
|
-
return false;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function getServerConfigForIde(name) {
|
|
267
|
+
const normalizedName = normalizeIdeName(name);
|
|
268
|
+
const config = {
|
|
269
|
+
command: 'heuristic-mcp',
|
|
270
|
+
args: [],
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
if (normalizedName === 'antigravity') {
|
|
274
|
+
// Prefer explicit workspace forwarding in VS Code-compatible clients.
|
|
275
|
+
// If the variable is not expanded by the IDE, CLI parsing safely ignores it.
|
|
276
|
+
config.args = [
|
|
277
|
+
'--workspace',
|
|
278
|
+
'${workspaceFolder}',
|
|
279
|
+
'--workspace',
|
|
280
|
+
'${workspaceRoot}',
|
|
281
|
+
'--workspace',
|
|
282
|
+
'${workspace}',
|
|
283
|
+
];
|
|
284
|
+
// Allow provider-specific workspace env discovery as a backup signal.
|
|
285
|
+
config.env = {
|
|
286
|
+
HEURISTIC_MCP_ENABLE_DYNAMIC_WORKSPACE_ENV: 'true',
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return config;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export async function register(filter = null) {
|
|
294
|
+
const currentIDE = detectCurrentIDE();
|
|
295
|
+
|
|
296
|
+
const configPaths = getConfigPaths();
|
|
297
|
+
let registeredCount = 0;
|
|
285
298
|
|
|
286
299
|
forceLog(`[Auto-Register] Detecting IDE configurations...`);
|
|
287
300
|
|
|
@@ -291,7 +304,7 @@ export async function register(filter = null) {
|
|
|
291
304
|
}
|
|
292
305
|
|
|
293
306
|
try {
|
|
294
|
-
|
|
307
|
+
|
|
295
308
|
let fileExists = true;
|
|
296
309
|
|
|
297
310
|
try {
|
|
@@ -299,7 +312,7 @@ export async function register(filter = null) {
|
|
|
299
312
|
} catch {
|
|
300
313
|
fileExists = false;
|
|
301
314
|
|
|
302
|
-
|
|
315
|
+
|
|
303
316
|
if (canCreate) {
|
|
304
317
|
try {
|
|
305
318
|
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
@@ -311,14 +324,14 @@ export async function register(filter = null) {
|
|
|
311
324
|
continue;
|
|
312
325
|
}
|
|
313
326
|
} else {
|
|
314
|
-
|
|
327
|
+
|
|
315
328
|
continue;
|
|
316
329
|
}
|
|
317
330
|
}
|
|
318
331
|
|
|
319
|
-
let content = '';
|
|
320
|
-
if (fileExists) {
|
|
321
|
-
content = await fs.readFile(configPath, 'utf-8');
|
|
332
|
+
let content = '';
|
|
333
|
+
if (fileExists) {
|
|
334
|
+
content = await fs.readFile(configPath, 'utf-8');
|
|
322
335
|
if (format === 'json' && content.trim()) {
|
|
323
336
|
const parsed = parseJsonc(content);
|
|
324
337
|
if (!parsed) {
|
|
@@ -327,12 +340,13 @@ export async function register(filter = null) {
|
|
|
327
340
|
);
|
|
328
341
|
continue;
|
|
329
342
|
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const serverConfig = getServerConfigForIde(name);
|
|
347
|
+
const updated =
|
|
348
|
+
format === 'toml'
|
|
349
|
+
? upsertMcpServerEntryInToml(content, 'heuristic-mcp', serverConfig)
|
|
336
350
|
: upsertMcpServerEntryInText(
|
|
337
351
|
content,
|
|
338
352
|
'heuristic-mcp',
|
|
@@ -346,7 +360,7 @@ export async function register(filter = null) {
|
|
|
346
360
|
continue;
|
|
347
361
|
}
|
|
348
362
|
|
|
349
|
-
|
|
363
|
+
|
|
350
364
|
writeFileSync(configPath, updated);
|
|
351
365
|
|
|
352
366
|
forceLog(`\x1b[32m[Auto-Register] ✅ Successfully registered with ${name}\x1b[0m`);
|
|
@@ -356,18 +370,22 @@ export async function register(filter = null) {
|
|
|
356
370
|
}
|
|
357
371
|
}
|
|
358
372
|
|
|
359
|
-
if (registeredCount === 0) {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
373
|
+
if (registeredCount === 0) {
|
|
374
|
+
const manualServerConfig = {
|
|
375
|
+
command: 'heuristic-mcp',
|
|
376
|
+
args: [],
|
|
377
|
+
};
|
|
378
|
+
forceLog(`[Auto-Register] No compatible IDE configurations found to update.`);
|
|
379
|
+
forceLog(
|
|
380
|
+
`[Auto-Register] Manual Config:\n${JSON.stringify({ mcpServers: { 'heuristic-mcp': manualServerConfig } }, null, 2)}`
|
|
381
|
+
);
|
|
382
|
+
} else {
|
|
383
|
+
|
|
366
384
|
forceLog('\n\x1b[36m' + '='.repeat(60));
|
|
367
385
|
forceLog(' 🚀 Heuristic MCP Installed & Configured! ');
|
|
368
386
|
forceLog('='.repeat(60) + '\x1b[0m');
|
|
369
387
|
|
|
370
|
-
|
|
388
|
+
|
|
371
389
|
const home = getUserHomeDir();
|
|
372
390
|
const cacheRoot =
|
|
373
391
|
process.platform === 'win32'
|
package/features/resources.js
CHANGED
|
@@ -1,58 +1,41 @@
|
|
|
1
|
-
|
|
2
|
-
* MCP Resources Feature
|
|
3
|
-
*
|
|
4
|
-
* Exposes workspace files as MCP resources for discovery and reading.
|
|
5
|
-
*/
|
|
1
|
+
|
|
6
2
|
|
|
7
3
|
import fs from 'fs/promises';
|
|
8
4
|
import path from 'path';
|
|
9
5
|
import { fdir } from 'fdir';
|
|
10
6
|
import { getMimeType } from '../lib/constants.js';
|
|
11
7
|
|
|
12
|
-
|
|
13
|
-
* Convert a file path to a file:// URI.
|
|
14
|
-
* @param {string} filePath - Absolute file path
|
|
15
|
-
* @returns {string}
|
|
16
|
-
*/
|
|
8
|
+
|
|
17
9
|
function pathToUri(filePath) {
|
|
18
|
-
|
|
10
|
+
|
|
19
11
|
const normalized = filePath.replace(/\\/g, '/');
|
|
20
|
-
|
|
12
|
+
|
|
21
13
|
if (/^[a-zA-Z]:/.test(normalized)) {
|
|
22
14
|
return `file:///${normalized}`;
|
|
23
15
|
}
|
|
24
16
|
return `file://${normalized}`;
|
|
25
17
|
}
|
|
26
18
|
|
|
27
|
-
|
|
28
|
-
* Convert a file:// URI back to a file path.
|
|
29
|
-
* @param {string} uri
|
|
30
|
-
* @returns {string}
|
|
31
|
-
*/
|
|
19
|
+
|
|
32
20
|
function uriToPath(uri) {
|
|
33
21
|
if (!uri.startsWith('file://')) {
|
|
34
22
|
throw new Error(`Invalid file URI: ${uri}`);
|
|
35
23
|
}
|
|
36
|
-
let filePath = uri.slice(7);
|
|
37
|
-
|
|
24
|
+
let filePath = uri.slice(7);
|
|
25
|
+
|
|
38
26
|
if (/^\/[a-zA-Z]:/.test(filePath)) {
|
|
39
|
-
filePath = filePath.slice(1);
|
|
27
|
+
filePath = filePath.slice(1);
|
|
40
28
|
}
|
|
41
|
-
|
|
29
|
+
|
|
42
30
|
filePath = decodeURIComponent(filePath);
|
|
43
|
-
|
|
31
|
+
|
|
44
32
|
if (process.platform === 'win32') {
|
|
45
33
|
filePath = filePath.replace(/\//g, '\\');
|
|
46
34
|
}
|
|
47
35
|
return filePath;
|
|
48
36
|
}
|
|
49
37
|
|
|
50
|
-
|
|
51
|
-
* Check if a path is within the workspace directory.
|
|
52
|
-
* @param {string} filePath - Absolute file path
|
|
53
|
-
* @param {string} workspaceDir - Workspace directory
|
|
54
|
-
* @returns {boolean}
|
|
55
|
-
*/
|
|
38
|
+
|
|
56
39
|
function isWithinWorkspace(filePath, workspaceDir) {
|
|
57
40
|
const resolvedPath = path.resolve(filePath);
|
|
58
41
|
const resolvedWorkspace = path.resolve(workspaceDir);
|
|
@@ -60,24 +43,20 @@ function isWithinWorkspace(filePath, workspaceDir) {
|
|
|
60
43
|
return !relativePath.startsWith('..') && !path.isAbsolute(relativePath);
|
|
61
44
|
}
|
|
62
45
|
|
|
63
|
-
|
|
64
|
-
* List resources handler for MCP.
|
|
65
|
-
* @param {object} config - Server configuration
|
|
66
|
-
* @returns {Promise<{resources: Array}>}
|
|
67
|
-
*/
|
|
46
|
+
|
|
68
47
|
export async function handleListResources(config) {
|
|
69
48
|
const workspaceDir = config.searchDirectory;
|
|
70
|
-
const maxResults = 500;
|
|
49
|
+
const maxResults = 500;
|
|
71
50
|
|
|
72
|
-
|
|
51
|
+
|
|
73
52
|
const allowedExtensions = new Set(
|
|
74
53
|
(config.fileExtensions || []).map(ext => `.${ext.toLowerCase()}`)
|
|
75
54
|
);
|
|
76
55
|
|
|
77
|
-
|
|
56
|
+
|
|
78
57
|
const excludedDirs = new Set();
|
|
79
58
|
for (const pattern of config.excludePatterns || []) {
|
|
80
|
-
|
|
59
|
+
|
|
81
60
|
const match = pattern.match(/(?:\*\*\/)?([^/*]+)(?:\/\*\*)?$/);
|
|
82
61
|
if (match && match[1] && !match[1].includes('*')) {
|
|
83
62
|
excludedDirs.add(match[1]);
|
|
@@ -85,7 +64,7 @@ export async function handleListResources(config) {
|
|
|
85
64
|
}
|
|
86
65
|
|
|
87
66
|
try {
|
|
88
|
-
|
|
67
|
+
|
|
89
68
|
const crawler = new fdir()
|
|
90
69
|
.withBasePath()
|
|
91
70
|
.withMaxDepth(10)
|
|
@@ -117,27 +96,22 @@ export async function handleListResources(config) {
|
|
|
117
96
|
}
|
|
118
97
|
}
|
|
119
98
|
|
|
120
|
-
|
|
121
|
-
* Read resource handler for MCP.
|
|
122
|
-
* @param {string} uri - Resource URI
|
|
123
|
-
* @param {object} config - Server configuration
|
|
124
|
-
* @returns {Promise<{contents: Array}>}
|
|
125
|
-
*/
|
|
99
|
+
|
|
126
100
|
export async function handleReadResource(uri, config) {
|
|
127
101
|
const workspaceDir = config.searchDirectory;
|
|
128
102
|
|
|
129
103
|
try {
|
|
130
104
|
const filePath = uriToPath(uri);
|
|
131
105
|
|
|
132
|
-
|
|
106
|
+
|
|
133
107
|
if (!isWithinWorkspace(filePath, workspaceDir)) {
|
|
134
108
|
throw new Error(`Access denied: ${uri} is outside workspace`);
|
|
135
109
|
}
|
|
136
110
|
|
|
137
|
-
|
|
111
|
+
|
|
138
112
|
await fs.access(filePath);
|
|
139
113
|
|
|
140
|
-
|
|
114
|
+
|
|
141
115
|
const content = await fs.readFile(filePath, 'utf-8');
|
|
142
116
|
|
|
143
117
|
return {
|