@softerist/heuristic-mcp 3.2.3 → 3.2.4
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 +387 -376
- package/config.jsonc +800 -800
- package/features/ann-config.js +102 -110
- package/features/clear-cache.js +81 -84
- package/features/find-similar-code.js +265 -286
- package/features/hybrid-search.js +487 -536
- package/features/index-codebase.js +3139 -3270
- package/features/lifecycle.js +1011 -1063
- package/features/package-version.js +277 -291
- package/features/register.js +351 -370
- package/features/resources.js +115 -130
- package/features/set-workspace.js +214 -240
- package/index.js +693 -758
- package/lib/cache-ops.js +22 -22
- package/lib/cache-utils.js +465 -519
- package/lib/cache.js +1749 -1849
- package/lib/call-graph.js +396 -396
- package/lib/cli.js +232 -226
- package/lib/config.js +1483 -1495
- package/lib/constants.js +511 -493
- package/lib/embed-query-process.js +206 -212
- package/lib/embedding-process.js +434 -451
- package/lib/embedding-worker.js +862 -934
- package/lib/ignore-patterns.js +276 -316
- package/lib/json-worker.js +14 -14
- package/lib/json-writer.js +302 -310
- package/lib/logging.js +116 -127
- package/lib/memory-logger.js +13 -13
- package/lib/onnx-backend.js +188 -193
- package/lib/path-utils.js +18 -23
- package/lib/project-detector.js +82 -84
- package/lib/server-lifecycle.js +133 -145
- package/lib/settings-editor.js +738 -739
- package/lib/slice-normalize.js +25 -31
- package/lib/tokenizer.js +168 -203
- package/lib/utils.js +364 -409
- package/lib/vector-store-binary.js +973 -991
- package/lib/vector-store-sqlite.js +377 -414
- package/lib/workspace-env.js +32 -34
- package/mcp_config.json +9 -9
- package/package.json +86 -86
- package/scripts/clear-cache.js +20 -20
- package/scripts/download-model.js +43 -43
- package/scripts/mcp-launcher.js +49 -49
- package/scripts/postinstall.js +12 -12
- package/search-configs.js +36 -36
package/index.js
CHANGED
|
@@ -1,44 +1,48 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
-
import { stop, start, status, logs } from './features/lifecycle.js';
|
|
4
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
-
import {
|
|
6
|
-
CallToolRequestSchema,
|
|
7
|
-
ListToolsRequestSchema,
|
|
8
|
-
ListResourcesRequestSchema,
|
|
9
|
-
ReadResourceRequestSchema,
|
|
10
|
-
RootsListChangedNotificationSchema,
|
|
11
|
-
} from '@modelcontextprotocol/sdk/types.js';
|
|
12
|
-
let transformersModule = null;
|
|
13
|
-
async function getTransformers() {
|
|
14
|
-
if (!transformersModule) {
|
|
15
|
-
transformersModule = await import('@huggingface/transformers');
|
|
16
|
-
if (transformersModule?.env) {
|
|
17
|
-
transformersModule.env.cacheDir = path.join(getGlobalCacheDir(), 'xenova');
|
|
18
|
-
}
|
|
19
|
-
if (transformersModule?.env) {
|
|
20
|
-
transformersModule.env.cacheDir = path.join(getGlobalCacheDir(), 'xenova');
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return transformersModule;
|
|
24
|
-
}
|
|
25
|
-
import { configureNativeOnnxBackend, getNativeOnnxStatus } from './lib/onnx-backend.js';
|
|
26
|
-
|
|
27
|
-
import fs from 'fs/promises';
|
|
28
|
-
import path from 'path';
|
|
29
|
-
import os from 'os';
|
|
30
|
-
|
|
31
|
-
import { createRequire } from 'module';
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { stop, start, status, logs } from './features/lifecycle.js';
|
|
4
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
+
import {
|
|
6
|
+
CallToolRequestSchema,
|
|
7
|
+
ListToolsRequestSchema,
|
|
8
|
+
ListResourcesRequestSchema,
|
|
9
|
+
ReadResourceRequestSchema,
|
|
10
|
+
RootsListChangedNotificationSchema,
|
|
11
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
12
|
+
let transformersModule = null;
|
|
13
|
+
async function getTransformers() {
|
|
14
|
+
if (!transformersModule) {
|
|
15
|
+
transformersModule = await import('@huggingface/transformers');
|
|
16
|
+
if (transformersModule?.env) {
|
|
17
|
+
transformersModule.env.cacheDir = path.join(getGlobalCacheDir(), 'xenova');
|
|
18
|
+
}
|
|
19
|
+
if (transformersModule?.env) {
|
|
20
|
+
transformersModule.env.cacheDir = path.join(getGlobalCacheDir(), 'xenova');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return transformersModule;
|
|
24
|
+
}
|
|
25
|
+
import { configureNativeOnnxBackend, getNativeOnnxStatus } from './lib/onnx-backend.js';
|
|
26
|
+
|
|
27
|
+
import fs from 'fs/promises';
|
|
28
|
+
import path from 'path';
|
|
29
|
+
import os from 'os';
|
|
30
|
+
|
|
31
|
+
import { createRequire } from 'module';
|
|
32
32
|
import { fileURLToPath } from 'url';
|
|
33
33
|
import { getWorkspaceCachePath } from './lib/workspace-cache-key.js';
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
|
|
35
|
+
const require = createRequire(import.meta.url);
|
|
36
|
+
const packageJson = require('./package.json');
|
|
37
|
+
|
|
39
38
|
import { loadConfig, getGlobalCacheDir, isNonProjectDirectory } from './lib/config.js';
|
|
40
39
|
import { clearStaleCaches } from './lib/cache-utils.js';
|
|
41
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
enableStderrOnlyLogging,
|
|
42
|
+
setupFileLogging,
|
|
43
|
+
getLogFilePath,
|
|
44
|
+
flushLogs,
|
|
45
|
+
} from './lib/logging.js';
|
|
42
46
|
import { parseArgs, printHelp } from './lib/cli.js';
|
|
43
47
|
import { clearCache } from './lib/cache-ops.js';
|
|
44
48
|
import { logMemory, startMemoryLogger } from './lib/memory-logger.js';
|
|
@@ -48,89 +52,90 @@ import {
|
|
|
48
52
|
acquireWorkspaceLock,
|
|
49
53
|
stopOtherHeuristicServers,
|
|
50
54
|
} from './lib/server-lifecycle.js';
|
|
51
|
-
|
|
55
|
+
|
|
52
56
|
import { EmbeddingsCache } from './lib/cache.js';
|
|
53
|
-
import {
|
|
57
|
+
import {
|
|
58
|
+
cleanupStaleBinaryArtifacts,
|
|
59
|
+
recordBinaryStoreCorruption,
|
|
60
|
+
} from './lib/vector-store-binary.js';
|
|
54
61
|
import { CodebaseIndexer } from './features/index-codebase.js';
|
|
55
62
|
import { HybridSearch } from './features/hybrid-search.js';
|
|
56
|
-
|
|
57
|
-
import * as IndexCodebaseFeature from './features/index-codebase.js';
|
|
58
|
-
import * as HybridSearchFeature from './features/hybrid-search.js';
|
|
59
|
-
import * as ClearCacheFeature from './features/clear-cache.js';
|
|
60
|
-
import * as FindSimilarCodeFeature from './features/find-similar-code.js';
|
|
61
|
-
import * as AnnConfigFeature from './features/ann-config.js';
|
|
62
|
-
import * as PackageVersionFeature from './features/package-version.js';
|
|
63
|
-
import * as SetWorkspaceFeature from './features/set-workspace.js';
|
|
64
|
-
import { handleListResources, handleReadResource } from './features/resources.js';
|
|
65
|
-
import { getWorkspaceEnvKeys } from './lib/workspace-env.js';
|
|
66
|
-
|
|
67
|
-
import {
|
|
68
|
-
MEMORY_LOG_INTERVAL_MS,
|
|
69
|
-
ONNX_THREAD_LIMIT,
|
|
70
|
-
BACKGROUND_INDEX_DELAY_MS,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
console.error(`[Memory]
|
|
92
|
-
console.error(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
console.info('[Memory]
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
63
|
+
|
|
64
|
+
import * as IndexCodebaseFeature from './features/index-codebase.js';
|
|
65
|
+
import * as HybridSearchFeature from './features/hybrid-search.js';
|
|
66
|
+
import * as ClearCacheFeature from './features/clear-cache.js';
|
|
67
|
+
import * as FindSimilarCodeFeature from './features/find-similar-code.js';
|
|
68
|
+
import * as AnnConfigFeature from './features/ann-config.js';
|
|
69
|
+
import * as PackageVersionFeature from './features/package-version.js';
|
|
70
|
+
import * as SetWorkspaceFeature from './features/set-workspace.js';
|
|
71
|
+
import { handleListResources, handleReadResource } from './features/resources.js';
|
|
72
|
+
import { getWorkspaceEnvKeys } from './lib/workspace-env.js';
|
|
73
|
+
|
|
74
|
+
import {
|
|
75
|
+
MEMORY_LOG_INTERVAL_MS,
|
|
76
|
+
ONNX_THREAD_LIMIT,
|
|
77
|
+
BACKGROUND_INDEX_DELAY_MS,
|
|
78
|
+
SERVER_KEEP_ALIVE_INTERVAL_MS,
|
|
79
|
+
} from './lib/constants.js';
|
|
80
|
+
const PID_FILE_NAME = '.heuristic-mcp.pid';
|
|
81
|
+
|
|
82
|
+
async function readLogTail(logPath, maxLines = 2000) {
|
|
83
|
+
const data = await fs.readFile(logPath, 'utf-8');
|
|
84
|
+
if (!data) return [];
|
|
85
|
+
const lines = data.split(/\r?\n/).filter(Boolean);
|
|
86
|
+
return lines.slice(-maxLines);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function printMemorySnapshot(workspaceDir) {
|
|
90
|
+
const activeConfig = await loadConfig(workspaceDir);
|
|
91
|
+
const logPath = getLogFilePath(activeConfig);
|
|
92
|
+
|
|
93
|
+
let lines;
|
|
94
|
+
try {
|
|
95
|
+
lines = await readLogTail(logPath);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
if (err.code === 'ENOENT') {
|
|
98
|
+
console.error(`[Memory] No log file found for workspace.`);
|
|
99
|
+
console.error(`[Memory] Expected location: ${logPath}`);
|
|
100
|
+
console.error(
|
|
101
|
+
'[Memory] Start the server with verbose logging (set "verbose": true), then try again.'
|
|
102
|
+
);
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
console.error(`[Memory] Failed to read log file: ${err.message}`);
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const memoryLines = lines.filter((line) => /Memory\s*\(/.test(line) || /Memory.*rss=/.test(line));
|
|
110
|
+
if (memoryLines.length === 0) {
|
|
111
|
+
console.info('[Memory] No memory snapshots found in logs.');
|
|
112
|
+
console.info('[Memory] Ensure "verbose": true in config and restart the server.');
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const idleLine =
|
|
117
|
+
[...memoryLines].reverse().find((line) => line.includes('after cache load')) ??
|
|
118
|
+
memoryLines[memoryLines.length - 1];
|
|
119
|
+
|
|
120
|
+
const logLine = (line) => {
|
|
121
|
+
console.info(line);
|
|
122
|
+
if (process.env.VITEST === 'true' || process.env.NODE_ENV === 'test') {
|
|
123
|
+
console.error(line);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
logLine(`[Memory] Idle snapshot: ${idleLine}`);
|
|
128
|
+
|
|
129
|
+
const latestLine = memoryLines[memoryLines.length - 1];
|
|
130
|
+
if (latestLine !== idleLine) {
|
|
131
|
+
logLine(`[Memory] Latest snapshot: ${latestLine}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
132
137
|
let embedder = null;
|
|
133
|
-
let unloadMainEmbedder = null;
|
|
138
|
+
let unloadMainEmbedder = null;
|
|
134
139
|
let cache = null;
|
|
135
140
|
let indexer = null;
|
|
136
141
|
let hybridSearch = null;
|
|
@@ -235,27 +240,27 @@ function registerProcessDiagnostics({ isServerMode, requestShutdown, getShutdown
|
|
|
235
240
|
handleFatalError('unhandledRejection', reason);
|
|
236
241
|
});
|
|
237
242
|
}
|
|
238
|
-
|
|
239
|
-
async function resolveWorkspaceFromEnvValue(rawValue) {
|
|
240
|
-
if (!rawValue || rawValue.includes('${')) return null;
|
|
241
|
-
const resolved = path.resolve(rawValue);
|
|
242
|
-
try {
|
|
243
|
-
const stats = await fs.stat(resolved);
|
|
244
|
-
if (!stats.isDirectory()) return null;
|
|
245
|
-
return resolved;
|
|
246
|
-
} catch {
|
|
247
|
-
return null;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
243
|
+
|
|
244
|
+
async function resolveWorkspaceFromEnvValue(rawValue) {
|
|
245
|
+
if (!rawValue || rawValue.includes('${')) return null;
|
|
246
|
+
const resolved = path.resolve(rawValue);
|
|
247
|
+
try {
|
|
248
|
+
const stats = await fs.stat(resolved);
|
|
249
|
+
if (!stats.isDirectory()) return null;
|
|
250
|
+
return resolved;
|
|
251
|
+
} catch {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
251
256
|
async function detectRuntimeWorkspaceFromEnv() {
|
|
252
257
|
for (const key of getWorkspaceEnvKeys()) {
|
|
253
258
|
const workspacePath = await resolveWorkspaceFromEnvValue(process.env[key]);
|
|
254
|
-
if (workspacePath) {
|
|
255
|
-
return { workspacePath, envKey: key };
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
+
if (workspacePath) {
|
|
260
|
+
return { workspacePath, envKey: key };
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
259
264
|
return null;
|
|
260
265
|
}
|
|
261
266
|
|
|
@@ -301,7 +306,8 @@ async function findAutoAttachWorkspaceCandidate({ excludeCacheDirectory = null }
|
|
|
301
306
|
for (const entry of cacheDirs) {
|
|
302
307
|
if (!entry.isDirectory()) continue;
|
|
303
308
|
const cacheDirectory = path.join(cacheRoot, entry.name);
|
|
304
|
-
if (normalizedExclude && normalizePathForCompare(cacheDirectory) === normalizedExclude)
|
|
309
|
+
if (normalizedExclude && normalizePathForCompare(cacheDirectory) === normalizedExclude)
|
|
310
|
+
continue;
|
|
305
311
|
|
|
306
312
|
const lockPath = path.join(cacheDirectory, 'server.lock.json');
|
|
307
313
|
try {
|
|
@@ -320,9 +326,7 @@ async function findAutoAttachWorkspaceCandidate({ excludeCacheDirectory = null }
|
|
|
320
326
|
rank,
|
|
321
327
|
});
|
|
322
328
|
continue;
|
|
323
|
-
} catch {
|
|
324
|
-
|
|
325
|
-
}
|
|
329
|
+
} catch {}
|
|
326
330
|
|
|
327
331
|
const metaPath = path.join(cacheDirectory, 'meta.json');
|
|
328
332
|
try {
|
|
@@ -341,9 +345,7 @@ async function findAutoAttachWorkspaceCandidate({ excludeCacheDirectory = null }
|
|
|
341
345
|
source: 'meta',
|
|
342
346
|
rank,
|
|
343
347
|
});
|
|
344
|
-
} catch {
|
|
345
|
-
|
|
346
|
-
}
|
|
348
|
+
} catch {}
|
|
347
349
|
}
|
|
348
350
|
|
|
349
351
|
const candidates = Array.from(candidatesByWorkspace.values());
|
|
@@ -357,7 +359,7 @@ async function findAutoAttachWorkspaceCandidate({ excludeCacheDirectory = null }
|
|
|
357
359
|
if (candidates.length === 1) return candidates[0];
|
|
358
360
|
return null;
|
|
359
361
|
}
|
|
360
|
-
|
|
362
|
+
|
|
361
363
|
async function maybeAutoSwitchWorkspace(request) {
|
|
362
364
|
if (process.env.VITEST === 'true' || process.env.NODE_ENV === 'test') return null;
|
|
363
365
|
if (!setWorkspaceFeatureInstance || !config?.searchDirectory) return null;
|
|
@@ -383,8 +385,6 @@ async function maybeAutoSwitchWorkspace(request) {
|
|
|
383
385
|
return detected.workspacePath;
|
|
384
386
|
}
|
|
385
387
|
|
|
386
|
-
|
|
387
|
-
|
|
388
388
|
async function detectWorkspaceFromRoots({ quiet = false } = {}) {
|
|
389
389
|
try {
|
|
390
390
|
const caps = server.getClientCapabilities();
|
|
@@ -408,14 +408,13 @@ async function detectWorkspaceFromRoots({ quiet = false } = {}) {
|
|
|
408
408
|
}
|
|
409
409
|
|
|
410
410
|
if (!quiet) {
|
|
411
|
-
console.info(`[Server] MCP roots received: ${result.roots.map(r => r.uri).join(', ')}`);
|
|
411
|
+
console.info(`[Server] MCP roots received: ${result.roots.map((r) => r.uri).join(', ')}`);
|
|
412
412
|
}
|
|
413
|
-
|
|
414
|
-
|
|
413
|
+
|
|
415
414
|
const rootPaths = result.roots
|
|
416
|
-
.map(r => r.uri)
|
|
417
|
-
.filter(uri => uri.startsWith('file://'))
|
|
418
|
-
.map(uri => {
|
|
415
|
+
.map((r) => r.uri)
|
|
416
|
+
.filter((uri) => uri.startsWith('file://'))
|
|
417
|
+
.map((uri) => {
|
|
419
418
|
try {
|
|
420
419
|
return fileURLToPath(uri);
|
|
421
420
|
} catch {
|
|
@@ -423,7 +422,7 @@ async function detectWorkspaceFromRoots({ quiet = false } = {}) {
|
|
|
423
422
|
}
|
|
424
423
|
})
|
|
425
424
|
.filter(Boolean);
|
|
426
|
-
|
|
425
|
+
|
|
427
426
|
if (rootPaths.length === 0) {
|
|
428
427
|
if (!quiet) {
|
|
429
428
|
console.info('[Server] No valid file:// roots found.');
|
|
@@ -440,7 +439,10 @@ async function detectWorkspaceFromRoots({ quiet = false } = {}) {
|
|
|
440
439
|
}
|
|
441
440
|
}
|
|
442
441
|
|
|
443
|
-
async function maybeAutoSwitchWorkspaceToPath(
|
|
442
|
+
async function maybeAutoSwitchWorkspaceToPath(
|
|
443
|
+
targetWorkspacePath,
|
|
444
|
+
{ source, reindex = false } = {}
|
|
445
|
+
) {
|
|
444
446
|
if (!setWorkspaceFeatureInstance || !config?.searchDirectory) return;
|
|
445
447
|
if (!targetWorkspacePath) return;
|
|
446
448
|
if (isNonProjectDirectory(targetWorkspacePath)) {
|
|
@@ -470,9 +472,7 @@ async function maybeAutoSwitchWorkspaceToPath(targetWorkspacePath, { source, rei
|
|
|
470
472
|
reindex,
|
|
471
473
|
});
|
|
472
474
|
if (!result.success) {
|
|
473
|
-
console.warn(
|
|
474
|
-
`[Server] Auto workspace switch failed (${source || 'auto'}): ${result.error}`
|
|
475
|
-
);
|
|
475
|
+
console.warn(`[Server] Auto workspace switch failed (${source || 'auto'}): ${result.error}`);
|
|
476
476
|
return;
|
|
477
477
|
}
|
|
478
478
|
trustWorkspacePath(targetWorkspacePath);
|
|
@@ -511,119 +511,108 @@ async function maybeAutoSwitchWorkspaceFromRoots(request) {
|
|
|
511
511
|
rootsProbeInFlight = null;
|
|
512
512
|
}
|
|
513
513
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
handler: PackageVersionFeature.handleToolCall,
|
|
547
|
-
},
|
|
514
|
+
|
|
515
|
+
const features = [
|
|
516
|
+
{
|
|
517
|
+
module: HybridSearchFeature,
|
|
518
|
+
instance: null,
|
|
519
|
+
handler: HybridSearchFeature.handleToolCall,
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
module: IndexCodebaseFeature,
|
|
523
|
+
instance: null,
|
|
524
|
+
handler: IndexCodebaseFeature.handleToolCall,
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
module: ClearCacheFeature,
|
|
528
|
+
instance: null,
|
|
529
|
+
handler: ClearCacheFeature.handleToolCall,
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
module: FindSimilarCodeFeature,
|
|
533
|
+
instance: null,
|
|
534
|
+
handler: FindSimilarCodeFeature.handleToolCall,
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
module: AnnConfigFeature,
|
|
538
|
+
instance: null,
|
|
539
|
+
handler: AnnConfigFeature.handleToolCall,
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
module: PackageVersionFeature,
|
|
543
|
+
instance: null,
|
|
544
|
+
handler: PackageVersionFeature.handleToolCall,
|
|
545
|
+
},
|
|
548
546
|
{
|
|
549
547
|
module: SetWorkspaceFeature,
|
|
550
548
|
instance: null,
|
|
551
549
|
handler: null,
|
|
552
550
|
},
|
|
553
551
|
];
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
552
|
+
|
|
557
553
|
async function initialize(workspaceDir) {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
config
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if (config.workerThreads !== 0) {
|
|
621
|
-
config.workerThreads = 0;
|
|
622
|
-
}
|
|
623
|
-
if (!config.embeddingProcessPerBatch) {
|
|
624
|
-
config.embeddingProcessPerBatch = true;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
554
|
+
config = await loadConfig(workspaceDir);
|
|
555
|
+
|
|
556
|
+
if (config.enableCache && config.cacheCleanup?.autoCleanup) {
|
|
557
|
+
console.info('[Server] Running automatic cache cleanup...');
|
|
558
|
+
const results = await clearStaleCaches({
|
|
559
|
+
...config.cacheCleanup,
|
|
560
|
+
logger: console,
|
|
561
|
+
});
|
|
562
|
+
if (results.removed > 0) {
|
|
563
|
+
console.info(
|
|
564
|
+
`[Server] Removed ${results.removed} stale cache ${results.removed === 1 ? 'directory' : 'directories'}`
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const isTest = Boolean(process.env.VITEST || process.env.VITEST_WORKER_ID);
|
|
570
|
+
if (config.enableExplicitGc && typeof global.gc !== 'function' && !isTest) {
|
|
571
|
+
console.warn(
|
|
572
|
+
'[Server] enableExplicitGc=true but this process was not started with --expose-gc; continuing with explicit GC disabled.'
|
|
573
|
+
);
|
|
574
|
+
console.warn(
|
|
575
|
+
'[Server] Tip: start with "npm start" or add --expose-gc to enable explicit GC again.'
|
|
576
|
+
);
|
|
577
|
+
config.enableExplicitGc = false;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
let mainBackendConfigured = false;
|
|
581
|
+
let nativeOnnxAvailable = null;
|
|
582
|
+
const ensureMainOnnxBackend = () => {
|
|
583
|
+
if (mainBackendConfigured) return;
|
|
584
|
+
nativeOnnxAvailable = configureNativeOnnxBackend({
|
|
585
|
+
log: config.verbose ? console.info : null,
|
|
586
|
+
label: '[Server]',
|
|
587
|
+
threads: {
|
|
588
|
+
intraOpNumThreads: ONNX_THREAD_LIMIT,
|
|
589
|
+
interOpNumThreads: 1,
|
|
590
|
+
},
|
|
591
|
+
});
|
|
592
|
+
mainBackendConfigured = true;
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
ensureMainOnnxBackend();
|
|
596
|
+
if (nativeOnnxAvailable === false) {
|
|
597
|
+
try {
|
|
598
|
+
const { env } = await getTransformers();
|
|
599
|
+
if (env?.backends?.onnx?.wasm) {
|
|
600
|
+
env.backends.onnx.wasm.numThreads = ONNX_THREAD_LIMIT;
|
|
601
|
+
}
|
|
602
|
+
} catch {}
|
|
603
|
+
const status = getNativeOnnxStatus();
|
|
604
|
+
const reason = status?.message || 'onnxruntime-node not available';
|
|
605
|
+
console.warn(`[Server] Native ONNX backend unavailable (${reason}); using WASM backend.`);
|
|
606
|
+
console.warn(
|
|
607
|
+
'[Server] Auto-safety: disabling workers and forcing embeddingProcessPerBatch for memory isolation.'
|
|
608
|
+
);
|
|
609
|
+
if (config.workerThreads !== 0) {
|
|
610
|
+
config.workerThreads = 0;
|
|
611
|
+
}
|
|
612
|
+
if (!config.embeddingProcessPerBatch) {
|
|
613
|
+
config.embeddingProcessPerBatch = true;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
627
616
|
const resolutionSource = config.workspaceResolution?.source || 'unknown';
|
|
628
617
|
if (resolutionSource === 'workspace-arg' || resolutionSource === 'env') {
|
|
629
618
|
trustWorkspacePath(config.searchDirectory);
|
|
@@ -651,7 +640,9 @@ async function initialize(workspaceDir) {
|
|
|
651
640
|
const details = killed
|
|
652
641
|
.map((entry) => `${entry.pid}${entry.workspace ? ` (${entry.workspace})` : ''}`)
|
|
653
642
|
.join(', ');
|
|
654
|
-
console.info(
|
|
643
|
+
console.info(
|
|
644
|
+
`[Server] Auto-stopped ${killed.length} stale heuristic-mcp server(s): ${details}`
|
|
645
|
+
);
|
|
655
646
|
}
|
|
656
647
|
if (failed.length > 0) {
|
|
657
648
|
const details = failed
|
|
@@ -683,155 +674,143 @@ async function initialize(workspaceDir) {
|
|
|
683
674
|
])
|
|
684
675
|
: [null, await setupFileLogging(config)];
|
|
685
676
|
}
|
|
686
|
-
if (logPath) {
|
|
687
|
-
console.info(`[Logs] Writing server logs to ${logPath}`);
|
|
688
|
-
console.info(`[Logs] Log viewer: heuristic-mcp --logs --workspace "${config.searchDirectory}"`);
|
|
689
|
-
}
|
|
690
|
-
{
|
|
691
|
-
const resolution = config.workspaceResolution || {};
|
|
692
|
-
const sourceLabel =
|
|
693
|
-
resolution.source === 'env' && resolution.envKey
|
|
694
|
-
? `env:${resolution.envKey}`
|
|
695
|
-
: resolution.source || 'unknown';
|
|
696
|
-
const baseLabel = resolution.baseDirectory || '(unknown)';
|
|
697
|
-
const searchLabel = resolution.searchDirectory || config.searchDirectory;
|
|
698
|
-
const overrideLabel = resolution.searchDirectoryFromConfig ? 'yes' : 'no';
|
|
699
|
-
console.info(
|
|
700
|
-
`[Server] Workspace resolved: source=${sourceLabel}, base=${baseLabel}, search=${searchLabel}, configOverride=${overrideLabel}`
|
|
701
|
-
);
|
|
702
|
-
if (resolution.fromPath) {
|
|
703
|
-
console.info(`[Server] Workspace resolution origin cwd: ${resolution.fromPath}`);
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
const workspaceEnvProbe = Array.isArray(resolution.workspaceEnvProbe)
|
|
707
|
-
? resolution.workspaceEnvProbe
|
|
708
|
-
: [];
|
|
709
|
-
if (workspaceEnvProbe.length > 0) {
|
|
710
|
-
const probePreview = workspaceEnvProbe.slice(0, 8).map((entry) => {
|
|
711
|
-
const scope = entry?.priority ? 'priority' : 'diagnostic';
|
|
712
|
-
const status = entry?.resolvedPath
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
console.info(
|
|
723
|
-
`[Server] Config: workerThreads=${config.workerThreads}, embeddingProcessPerBatch=${config.embeddingProcessPerBatch}`
|
|
724
|
-
);
|
|
725
|
-
console.info(
|
|
726
|
-
`[Server] Config: vectorStoreLoadMode=${config.vectorStoreLoadMode}, vectorCacheEntries=${config.vectorCacheEntries}`
|
|
727
|
-
);
|
|
728
|
-
|
|
729
|
-
if (pidPath) {
|
|
730
|
-
console.info(`[Server] PID file: ${pidPath}`);
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
console.info(
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
} catch (_e) {
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
try {
|
|
824
|
-
console.info('[Server] Preloading embedding model (background)...');
|
|
825
|
-
await embedder(' ');
|
|
826
|
-
} catch (err) {
|
|
827
|
-
console.warn(`[Server] Embedding model preload failed: ${err.message}`);
|
|
828
|
-
}
|
|
829
|
-
};
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
677
|
+
if (logPath) {
|
|
678
|
+
console.info(`[Logs] Writing server logs to ${logPath}`);
|
|
679
|
+
console.info(`[Logs] Log viewer: heuristic-mcp --logs --workspace "${config.searchDirectory}"`);
|
|
680
|
+
}
|
|
681
|
+
{
|
|
682
|
+
const resolution = config.workspaceResolution || {};
|
|
683
|
+
const sourceLabel =
|
|
684
|
+
resolution.source === 'env' && resolution.envKey
|
|
685
|
+
? `env:${resolution.envKey}`
|
|
686
|
+
: resolution.source || 'unknown';
|
|
687
|
+
const baseLabel = resolution.baseDirectory || '(unknown)';
|
|
688
|
+
const searchLabel = resolution.searchDirectory || config.searchDirectory;
|
|
689
|
+
const overrideLabel = resolution.searchDirectoryFromConfig ? 'yes' : 'no';
|
|
690
|
+
console.info(
|
|
691
|
+
`[Server] Workspace resolved: source=${sourceLabel}, base=${baseLabel}, search=${searchLabel}, configOverride=${overrideLabel}`
|
|
692
|
+
);
|
|
693
|
+
if (resolution.fromPath) {
|
|
694
|
+
console.info(`[Server] Workspace resolution origin cwd: ${resolution.fromPath}`);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const workspaceEnvProbe = Array.isArray(resolution.workspaceEnvProbe)
|
|
698
|
+
? resolution.workspaceEnvProbe
|
|
699
|
+
: [];
|
|
700
|
+
if (workspaceEnvProbe.length > 0) {
|
|
701
|
+
const probePreview = workspaceEnvProbe.slice(0, 8).map((entry) => {
|
|
702
|
+
const scope = entry?.priority ? 'priority' : 'diagnostic';
|
|
703
|
+
const status = entry?.resolvedPath
|
|
704
|
+
? `valid:${entry.resolvedPath}`
|
|
705
|
+
: `invalid:${entry?.value}`;
|
|
706
|
+
return `${entry?.key}[${scope}]=${status}`;
|
|
707
|
+
});
|
|
708
|
+
const suffix = workspaceEnvProbe.length > 8 ? ` (+${workspaceEnvProbe.length - 8} more)` : '';
|
|
709
|
+
console.info(`[Server] Workspace env probe: ${probePreview.join('; ')}${suffix}`);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
console.info(
|
|
714
|
+
`[Server] Config: workerThreads=${config.workerThreads}, embeddingProcessPerBatch=${config.embeddingProcessPerBatch}`
|
|
715
|
+
);
|
|
716
|
+
console.info(
|
|
717
|
+
`[Server] Config: vectorStoreLoadMode=${config.vectorStoreLoadMode}, vectorCacheEntries=${config.vectorCacheEntries}`
|
|
718
|
+
);
|
|
719
|
+
|
|
720
|
+
if (pidPath) {
|
|
721
|
+
console.info(`[Server] PID file: ${pidPath}`);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
try {
|
|
725
|
+
const globalCache = path.join(getGlobalCacheDir(), 'heuristic-mcp');
|
|
726
|
+
const localCache = path.join(process.cwd(), '.heuristic-mcp');
|
|
727
|
+
console.info(`[Server] Cache debug: Global=${globalCache}, Local=${localCache}`);
|
|
728
|
+
console.info(`[Server] Process CWD: ${process.cwd()}`);
|
|
729
|
+
console.info(
|
|
730
|
+
`[Server] Resolved workspace: ${config.searchDirectory} (via ${config.workspaceResolution?.source || 'unknown'})`
|
|
731
|
+
);
|
|
732
|
+
} catch (_e) {}
|
|
733
|
+
|
|
734
|
+
let stopStartupMemory = null;
|
|
735
|
+
if (config.verbose) {
|
|
736
|
+
logMemory('[Server] Memory (startup)');
|
|
737
|
+
stopStartupMemory = startMemoryLogger('[Server] Memory (startup)', MEMORY_LOG_INTERVAL_MS);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
try {
|
|
741
|
+
await fs.access(config.searchDirectory);
|
|
742
|
+
} catch {
|
|
743
|
+
console.error(`[Server] Error: Search directory "${config.searchDirectory}" does not exist`);
|
|
744
|
+
process.exit(1);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
console.info('[Server] Initializing features...');
|
|
748
|
+
let cachedEmbedderPromise = null;
|
|
749
|
+
const lazyEmbedder = async (...args) => {
|
|
750
|
+
if (!cachedEmbedderPromise) {
|
|
751
|
+
ensureMainOnnxBackend();
|
|
752
|
+
console.info(`[Server] Loading AI embedding model: ${config.embeddingModel}...`);
|
|
753
|
+
const modelLoadStart = Date.now();
|
|
754
|
+
const { pipeline } = await getTransformers();
|
|
755
|
+
cachedEmbedderPromise = pipeline('feature-extraction', config.embeddingModel, {
|
|
756
|
+
quantized: true,
|
|
757
|
+
dtype: 'fp32',
|
|
758
|
+
session_options: {
|
|
759
|
+
numThreads: 2,
|
|
760
|
+
intraOpNumThreads: 2,
|
|
761
|
+
interOpNumThreads: 2,
|
|
762
|
+
},
|
|
763
|
+
}).then((model) => {
|
|
764
|
+
const loadSeconds = ((Date.now() - modelLoadStart) / 1000).toFixed(1);
|
|
765
|
+
console.info(
|
|
766
|
+
`[Server] Embedding model loaded (${loadSeconds}s). Starting intensive indexing (expect high CPU)...`
|
|
767
|
+
);
|
|
768
|
+
console.info(`[Server] Embedding model ready: ${config.embeddingModel}`);
|
|
769
|
+
if (config.verbose) {
|
|
770
|
+
logMemory('[Server] Memory (after model load)');
|
|
771
|
+
}
|
|
772
|
+
return model;
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
const model = await cachedEmbedderPromise;
|
|
776
|
+
return model(...args);
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
const unloader = async () => {
|
|
780
|
+
if (!cachedEmbedderPromise) return false;
|
|
781
|
+
try {
|
|
782
|
+
const model = await cachedEmbedderPromise;
|
|
783
|
+
if (model && typeof model.dispose === 'function') {
|
|
784
|
+
await model.dispose();
|
|
785
|
+
}
|
|
786
|
+
cachedEmbedderPromise = null;
|
|
787
|
+
if (typeof global.gc === 'function') {
|
|
788
|
+
global.gc();
|
|
789
|
+
}
|
|
790
|
+
if (config.verbose) {
|
|
791
|
+
logMemory('[Server] Memory (after model unload)');
|
|
792
|
+
}
|
|
793
|
+
console.info('[Server] Embedding model unloaded to free memory.');
|
|
794
|
+
return true;
|
|
795
|
+
} catch (err) {
|
|
796
|
+
console.warn(`[Server] Error unloading embedding model: ${err.message}`);
|
|
797
|
+
cachedEmbedderPromise = null;
|
|
798
|
+
return false;
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
embedder = lazyEmbedder;
|
|
803
|
+
unloadMainEmbedder = unloader;
|
|
804
|
+
const preloadEmbeddingModel = async () => {
|
|
805
|
+
if (config.preloadEmbeddingModel === false) return;
|
|
806
|
+
try {
|
|
807
|
+
console.info('[Server] Preloading embedding model (background)...');
|
|
808
|
+
await embedder(' ');
|
|
809
|
+
} catch (err) {
|
|
810
|
+
console.warn(`[Server] Embedding model preload failed: ${err.message}`);
|
|
811
|
+
}
|
|
812
|
+
};
|
|
813
|
+
|
|
835
814
|
if (config.vectorStoreFormat === 'binary') {
|
|
836
815
|
try {
|
|
837
816
|
await cleanupStaleBinaryArtifacts(config.cacheDirectory, { logger: console });
|
|
@@ -840,37 +819,31 @@ async function initialize(workspaceDir) {
|
|
|
840
819
|
}
|
|
841
820
|
}
|
|
842
821
|
|
|
843
|
-
|
|
844
822
|
cache = new EmbeddingsCache(config);
|
|
845
823
|
console.info(`[Server] Cache directory: ${config.cacheDirectory}`);
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
const
|
|
851
|
-
const
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
features[
|
|
856
|
-
features[
|
|
857
|
-
features[
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
setWorkspaceFeatureInstance = setWorkspaceInstance;
|
|
870
|
-
features[6].instance = setWorkspaceInstance;
|
|
871
|
-
features[6].handler = SetWorkspaceFeature.createHandleToolCall(setWorkspaceInstance);
|
|
872
|
-
|
|
873
|
-
|
|
824
|
+
|
|
825
|
+
indexer = new CodebaseIndexer(embedder, cache, config, server);
|
|
826
|
+
hybridSearch = new HybridSearch(embedder, cache, config);
|
|
827
|
+
const cacheClearer = new ClearCacheFeature.CacheClearer(embedder, cache, config, indexer);
|
|
828
|
+
const findSimilarCode = new FindSimilarCodeFeature.FindSimilarCode(embedder, cache, config);
|
|
829
|
+
const annConfig = new AnnConfigFeature.AnnConfigTool(cache, config);
|
|
830
|
+
|
|
831
|
+
features[0].instance = hybridSearch;
|
|
832
|
+
features[1].instance = indexer;
|
|
833
|
+
features[2].instance = cacheClearer;
|
|
834
|
+
features[3].instance = findSimilarCode;
|
|
835
|
+
features[4].instance = annConfig;
|
|
836
|
+
|
|
837
|
+
const setWorkspaceInstance = new SetWorkspaceFeature.SetWorkspaceFeature(
|
|
838
|
+
config,
|
|
839
|
+
cache,
|
|
840
|
+
indexer,
|
|
841
|
+
getGlobalCacheDir
|
|
842
|
+
);
|
|
843
|
+
setWorkspaceFeatureInstance = setWorkspaceInstance;
|
|
844
|
+
features[6].instance = setWorkspaceInstance;
|
|
845
|
+
features[6].handler = SetWorkspaceFeature.createHandleToolCall(setWorkspaceInstance);
|
|
846
|
+
|
|
874
847
|
server.hybridSearch = hybridSearch;
|
|
875
848
|
|
|
876
849
|
const startBackgroundTasks = async () => {
|
|
@@ -897,7 +870,10 @@ async function initialize(workspaceDir) {
|
|
|
897
870
|
}
|
|
898
871
|
return true;
|
|
899
872
|
};
|
|
900
|
-
const tryAutoAttachWorkspaceCache = async (
|
|
873
|
+
const tryAutoAttachWorkspaceCache = async (
|
|
874
|
+
reason,
|
|
875
|
+
{ canReindex = workspaceLockAcquired } = {}
|
|
876
|
+
) => {
|
|
901
877
|
const candidate = await findAutoAttachWorkspaceCandidate({
|
|
902
878
|
excludeCacheDirectory: config.cacheDirectory,
|
|
903
879
|
});
|
|
@@ -985,44 +961,39 @@ async function initialize(workspaceDir) {
|
|
|
985
961
|
} finally {
|
|
986
962
|
stopStartupMemoryLogger();
|
|
987
963
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
})
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
{
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
},
|
|
1022
|
-
}
|
|
1023
|
-
);
|
|
1024
|
-
|
|
1025
|
-
|
|
964
|
+
|
|
965
|
+
console.info('[Server] Starting background indexing (delayed)...');
|
|
966
|
+
|
|
967
|
+
setTimeout(() => {
|
|
968
|
+
indexer
|
|
969
|
+
.indexAll()
|
|
970
|
+
.then(() => {
|
|
971
|
+
if (config.watchFiles) {
|
|
972
|
+
indexer.setupFileWatcher();
|
|
973
|
+
}
|
|
974
|
+
})
|
|
975
|
+
.catch((err) => {
|
|
976
|
+
console.error('[Server] Background indexing error:', err.message);
|
|
977
|
+
});
|
|
978
|
+
}, BACKGROUND_INDEX_DELAY_MS);
|
|
979
|
+
};
|
|
980
|
+
|
|
981
|
+
return { startBackgroundTasks, config };
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
const server = new Server(
|
|
985
|
+
{
|
|
986
|
+
name: 'heuristic-mcp',
|
|
987
|
+
version: packageJson.version,
|
|
988
|
+
},
|
|
989
|
+
{
|
|
990
|
+
capabilities: {
|
|
991
|
+
tools: {},
|
|
992
|
+
resources: {},
|
|
993
|
+
},
|
|
994
|
+
}
|
|
995
|
+
);
|
|
996
|
+
|
|
1026
997
|
server.setNotificationHandler(RootsListChangedNotificationSchema, async () => {
|
|
1027
998
|
console.info('[Server] Received roots/list_changed notification from client.');
|
|
1028
999
|
const newRoot = await detectWorkspaceFromRoots();
|
|
@@ -1033,9 +1004,7 @@ server.setNotificationHandler(RootsListChangedNotificationSchema, async () => {
|
|
|
1033
1004
|
});
|
|
1034
1005
|
}
|
|
1035
1006
|
});
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1007
|
+
|
|
1039
1008
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
1040
1009
|
await configReadyPromise;
|
|
1041
1010
|
if (configInitError || !config) {
|
|
@@ -1043,9 +1012,7 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
|
1043
1012
|
}
|
|
1044
1013
|
return await handleListResources(config);
|
|
1045
1014
|
});
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1015
|
+
|
|
1049
1016
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
1050
1017
|
await configReadyPromise;
|
|
1051
1018
|
if (configInitError || !config) {
|
|
@@ -1053,26 +1020,22 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
1053
1020
|
}
|
|
1054
1021
|
return await handleReadResource(request.params.uri, config);
|
|
1055
1022
|
});
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1023
|
+
|
|
1059
1024
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
1060
1025
|
await configReadyPromise;
|
|
1061
1026
|
if (configInitError || !config) {
|
|
1062
1027
|
throw configInitError ?? new Error('Server configuration is not initialized');
|
|
1063
1028
|
}
|
|
1064
1029
|
const tools = [];
|
|
1065
|
-
|
|
1066
|
-
for (const feature of features) {
|
|
1067
|
-
const toolDef = feature.module.getToolDefinition(config);
|
|
1068
|
-
tools.push(toolDef);
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
return { tools };
|
|
1072
|
-
});
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1030
|
+
|
|
1031
|
+
for (const feature of features) {
|
|
1032
|
+
const toolDef = feature.module.getToolDefinition(config);
|
|
1033
|
+
tools.push(toolDef);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
return { tools };
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1076
1039
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1077
1040
|
await configReadyPromise;
|
|
1078
1041
|
if (configInitError || !config) {
|
|
@@ -1218,17 +1181,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1218
1181
|
}
|
|
1219
1182
|
|
|
1220
1183
|
for (const feature of features) {
|
|
1221
|
-
const toolDef = feature.module.getToolDefinition(config);
|
|
1222
|
-
|
|
1184
|
+
const toolDef = feature.module.getToolDefinition(config);
|
|
1185
|
+
|
|
1223
1186
|
if (request.params.name === toolDef.name) {
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
1187
|
if (typeof feature.handler !== 'function') {
|
|
1227
1188
|
return {
|
|
1228
|
-
content: [
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1189
|
+
content: [
|
|
1190
|
+
{
|
|
1191
|
+
type: 'text',
|
|
1192
|
+
text: `Tool "${toolDef.name}" is not ready. Server may still be initializing.`,
|
|
1193
|
+
},
|
|
1194
|
+
],
|
|
1232
1195
|
isError: true,
|
|
1233
1196
|
};
|
|
1234
1197
|
}
|
|
@@ -1251,61 +1214,53 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1251
1214
|
if (toolDef.name === 'f_set_workspace' && !isToolResponseError(result)) {
|
|
1252
1215
|
trustWorkspacePath(config.searchDirectory);
|
|
1253
1216
|
}
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
},
|
|
1280
|
-
],
|
|
1281
|
-
isError: true,
|
|
1282
|
-
};
|
|
1283
|
-
});
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1217
|
+
|
|
1218
|
+
const searchTools = ['a_semantic_search', 'd_find_similar_code'];
|
|
1219
|
+
if (config.unloadModelAfterSearch && searchTools.includes(toolDef.name)) {
|
|
1220
|
+
setImmediate(async () => {
|
|
1221
|
+
if (typeof unloadMainEmbedder === 'function') {
|
|
1222
|
+
await unloadMainEmbedder();
|
|
1223
|
+
}
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
return result;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
return {
|
|
1232
|
+
content: [
|
|
1233
|
+
{
|
|
1234
|
+
type: 'text',
|
|
1235
|
+
text: `Unknown tool: ${request.params.name}`,
|
|
1236
|
+
},
|
|
1237
|
+
],
|
|
1238
|
+
isError: true,
|
|
1239
|
+
};
|
|
1240
|
+
});
|
|
1241
|
+
|
|
1287
1242
|
export async function main(argv = process.argv) {
|
|
1288
|
-
const parsed = parseArgs(argv);
|
|
1289
|
-
const {
|
|
1290
|
-
isServerMode,
|
|
1291
|
-
workspaceDir,
|
|
1292
|
-
wantsVersion,
|
|
1293
|
-
wantsHelp,
|
|
1294
|
-
wantsLogs,
|
|
1295
|
-
wantsMem,
|
|
1296
|
-
wantsNoFollow,
|
|
1297
|
-
tailLines,
|
|
1298
|
-
wantsStop,
|
|
1299
|
-
wantsStart,
|
|
1300
|
-
wantsCache,
|
|
1301
|
-
wantsClean,
|
|
1302
|
-
wantsStatus,
|
|
1303
|
-
wantsClearCache,
|
|
1304
|
-
startFilter,
|
|
1305
|
-
wantsFix,
|
|
1306
|
-
unknownFlags,
|
|
1307
|
-
} = parsed;
|
|
1308
|
-
|
|
1243
|
+
const parsed = parseArgs(argv);
|
|
1244
|
+
const {
|
|
1245
|
+
isServerMode,
|
|
1246
|
+
workspaceDir,
|
|
1247
|
+
wantsVersion,
|
|
1248
|
+
wantsHelp,
|
|
1249
|
+
wantsLogs,
|
|
1250
|
+
wantsMem,
|
|
1251
|
+
wantsNoFollow,
|
|
1252
|
+
tailLines,
|
|
1253
|
+
wantsStop,
|
|
1254
|
+
wantsStart,
|
|
1255
|
+
wantsCache,
|
|
1256
|
+
wantsClean,
|
|
1257
|
+
wantsStatus,
|
|
1258
|
+
wantsClearCache,
|
|
1259
|
+
startFilter,
|
|
1260
|
+
wantsFix,
|
|
1261
|
+
unknownFlags,
|
|
1262
|
+
} = parsed;
|
|
1263
|
+
|
|
1309
1264
|
let shutdownRequested = false;
|
|
1310
1265
|
let shutdownReason = 'natural';
|
|
1311
1266
|
const requestShutdown = (reason) => {
|
|
@@ -1324,146 +1279,130 @@ export async function main(argv = process.argv) {
|
|
|
1324
1279
|
if (isServerMode && !(process.env.VITEST === 'true' || process.env.NODE_ENV === 'test')) {
|
|
1325
1280
|
enableStderrOnlyLogging();
|
|
1326
1281
|
}
|
|
1327
|
-
if (wantsVersion) {
|
|
1328
|
-
console.info(packageJson.version);
|
|
1329
|
-
process.exit(0);
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
if (wantsHelp) {
|
|
1333
|
-
printHelp();
|
|
1334
|
-
process.exit(0);
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
if (workspaceDir) {
|
|
1338
|
-
console.info(`[Server] Workspace mode: ${workspaceDir}`);
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
const
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
console.
|
|
1391
|
-
console.
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
process.exit(0);
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
if (
|
|
1427
|
-
|
|
1428
|
-
process.
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
process.exit(
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
if (
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
process.exit(1);
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
if (wantsClean && !wantsCache) {
|
|
1455
|
-
console.error('[Error] --clean can only be used with --cache');
|
|
1456
|
-
printHelp();
|
|
1457
|
-
process.exit(1);
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
registerSignalHandlers(requestShutdown);
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1282
|
+
if (wantsVersion) {
|
|
1283
|
+
console.info(packageJson.version);
|
|
1284
|
+
process.exit(0);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
if (wantsHelp) {
|
|
1288
|
+
printHelp();
|
|
1289
|
+
process.exit(0);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
if (workspaceDir) {
|
|
1293
|
+
console.info(`[Server] Workspace mode: ${workspaceDir}`);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
if (wantsStop) {
|
|
1297
|
+
await stop();
|
|
1298
|
+
process.exit(0);
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
if (wantsStart) {
|
|
1302
|
+
await start(startFilter);
|
|
1303
|
+
process.exit(0);
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
if (wantsStatus) {
|
|
1307
|
+
await status({ fix: wantsFix, workspaceDir });
|
|
1308
|
+
process.exit(0);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
if (wantsCache) {
|
|
1312
|
+
await status({ fix: wantsClean, cacheOnly: true, workspaceDir });
|
|
1313
|
+
process.exit(0);
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
const clearIndex = parsed.rawArgs.indexOf('--clear');
|
|
1317
|
+
if (clearIndex !== -1) {
|
|
1318
|
+
const cacheId = parsed.rawArgs[clearIndex + 1];
|
|
1319
|
+
if (cacheId && !cacheId.startsWith('--')) {
|
|
1320
|
+
let cacheHome;
|
|
1321
|
+
if (process.platform === 'win32') {
|
|
1322
|
+
cacheHome = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
1323
|
+
} else if (process.platform === 'darwin') {
|
|
1324
|
+
cacheHome = path.join(os.homedir(), 'Library', 'Caches');
|
|
1325
|
+
} else {
|
|
1326
|
+
cacheHome = process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache');
|
|
1327
|
+
}
|
|
1328
|
+
const globalCacheRoot = path.join(cacheHome, 'heuristic-mcp');
|
|
1329
|
+
const trimmedId = String(cacheId).trim();
|
|
1330
|
+
const hasSeparators = trimmedId.includes('/') || trimmedId.includes('\\');
|
|
1331
|
+
const resolvedCachePath = path.resolve(globalCacheRoot, trimmedId);
|
|
1332
|
+
const relPath = path.relative(globalCacheRoot, resolvedCachePath);
|
|
1333
|
+
const isWithinRoot = relPath && !relPath.startsWith('..') && !path.isAbsolute(relPath);
|
|
1334
|
+
|
|
1335
|
+
if (!trimmedId || hasSeparators || !isWithinRoot) {
|
|
1336
|
+
console.error(`[Cache] ❌ Invalid cache id: ${cacheId}`);
|
|
1337
|
+
console.error('[Cache] Cache id must be a direct child of the cache root.');
|
|
1338
|
+
process.exit(1);
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
const cachePath = resolvedCachePath;
|
|
1342
|
+
|
|
1343
|
+
try {
|
|
1344
|
+
await fs.access(cachePath);
|
|
1345
|
+
console.info(`[Cache] Removing cache: ${cacheId}`);
|
|
1346
|
+
console.info(`[Cache] Path: ${cachePath}`);
|
|
1347
|
+
await fs.rm(cachePath, { recursive: true, force: true });
|
|
1348
|
+
console.info(`[Cache] ✅ Successfully removed cache ${cacheId}`);
|
|
1349
|
+
} catch (error) {
|
|
1350
|
+
if (error.code === 'ENOENT') {
|
|
1351
|
+
console.error(`[Cache] ❌ Cache not found: ${cacheId}`);
|
|
1352
|
+
console.error(`[Cache] Available caches in ${globalCacheRoot}:`);
|
|
1353
|
+
const dirs = await fs.readdir(globalCacheRoot).catch(() => []);
|
|
1354
|
+
dirs.forEach((dir) => console.error(` - ${dir}`));
|
|
1355
|
+
process.exit(1);
|
|
1356
|
+
} else {
|
|
1357
|
+
console.error(`[Cache] ❌ Failed to remove cache: ${error.message}`);
|
|
1358
|
+
process.exit(1);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
process.exit(0);
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
if (wantsClearCache) {
|
|
1366
|
+
await clearCache(workspaceDir);
|
|
1367
|
+
process.exit(0);
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
if (wantsLogs) {
|
|
1371
|
+
process.env.SMART_CODING_LOGS = 'true';
|
|
1372
|
+
process.env.SMART_CODING_VERBOSE = 'true';
|
|
1373
|
+
await logs({
|
|
1374
|
+
workspaceDir,
|
|
1375
|
+
tailLines,
|
|
1376
|
+
follow: !wantsNoFollow,
|
|
1377
|
+
});
|
|
1378
|
+
process.exit(0);
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
if (wantsMem) {
|
|
1382
|
+
const ok = await printMemorySnapshot(workspaceDir);
|
|
1383
|
+
process.exit(ok ? 0 : 1);
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
if (unknownFlags.length > 0) {
|
|
1387
|
+
console.error(`[Error] Unknown option(s): ${unknownFlags.join(', ')}`);
|
|
1388
|
+
printHelp();
|
|
1389
|
+
process.exit(1);
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
if (wantsFix && !wantsStatus) {
|
|
1393
|
+
console.error('[Error] --fix can only be used with --status (deprecated, use --cache --clean)');
|
|
1394
|
+
printHelp();
|
|
1395
|
+
process.exit(1);
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
if (wantsClean && !wantsCache) {
|
|
1399
|
+
console.error('[Error] --clean can only be used with --cache');
|
|
1400
|
+
printHelp();
|
|
1401
|
+
process.exit(1);
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
registerSignalHandlers(requestShutdown);
|
|
1405
|
+
|
|
1467
1406
|
const detectedRootPromise = new Promise((resolve) => {
|
|
1468
1407
|
const HANDSHAKE_TIMEOUT_MS = 1000;
|
|
1469
1408
|
let settled = false;
|
|
@@ -1474,7 +1413,9 @@ export async function main(argv = process.argv) {
|
|
|
1474
1413
|
};
|
|
1475
1414
|
|
|
1476
1415
|
const timer = setTimeout(() => {
|
|
1477
|
-
console.warn(
|
|
1416
|
+
console.warn(
|
|
1417
|
+
`[Server] MCP handshake timed out after ${HANDSHAKE_TIMEOUT_MS}ms, proceeding without roots.`
|
|
1418
|
+
);
|
|
1478
1419
|
resolveOnce(null);
|
|
1479
1420
|
}, HANDSHAKE_TIMEOUT_MS);
|
|
1480
1421
|
|
|
@@ -1499,11 +1440,8 @@ export async function main(argv = process.argv) {
|
|
|
1499
1440
|
});
|
|
1500
1441
|
}
|
|
1501
1442
|
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
1443
|
const detectedRoot = await detectedRootPromise;
|
|
1505
|
-
|
|
1506
|
-
|
|
1444
|
+
|
|
1507
1445
|
const effectiveWorkspace = detectedRoot || workspaceDir;
|
|
1508
1446
|
if (detectedRoot) {
|
|
1509
1447
|
console.info(`[Server] Using workspace from MCP roots: ${detectedRoot}`);
|
|
@@ -1520,50 +1458,49 @@ export async function main(argv = process.argv) {
|
|
|
1520
1458
|
throw err;
|
|
1521
1459
|
});
|
|
1522
1460
|
const { startBackgroundTasks } = await initWithResolve;
|
|
1523
|
-
|
|
1524
|
-
console.info('[Server] Heuristic MCP server started.');
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1461
|
+
|
|
1462
|
+
console.info('[Server] Heuristic MCP server started.');
|
|
1463
|
+
|
|
1464
|
+
void startBackgroundTasks().catch((err) => {
|
|
1465
|
+
console.error(`[Server] Background task error: ${err.message}`);
|
|
1466
|
+
});
|
|
1467
|
+
// Keep-Alive mechanism: ensure the process stays alive even if StdioServerTransport
|
|
1468
|
+
// temporarily loses its active handle status or during complex async chains.
|
|
1469
|
+
if (isServerMode) {
|
|
1470
|
+
setInterval(() => {
|
|
1471
|
+
// Logic to keep event loop active.
|
|
1472
|
+
// We don't need to do anything, just the presence of the timer is enough.
|
|
1473
|
+
}, SERVER_KEEP_ALIVE_INTERVAL_MS);
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
console.info('[Server] MCP server is now fully ready to accept requests.');
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1536
1479
|
async function gracefulShutdown(signal) {
|
|
1537
1480
|
console.info(`[Server] Received ${signal}, shutting down gracefully...`);
|
|
1538
1481
|
const exitCode = isCrashShutdownReason(signal) ? 1 : 0;
|
|
1539
|
-
|
|
1540
|
-
const cleanupTasks = [];
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
.
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
(
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
})().catch(() => console.info('[Server] Workers shutdown (with warnings)'))
|
|
1562
|
-
);
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1482
|
+
|
|
1483
|
+
const cleanupTasks = [];
|
|
1484
|
+
|
|
1485
|
+
if (indexer && indexer.watcher) {
|
|
1486
|
+
cleanupTasks.push(
|
|
1487
|
+
indexer.watcher
|
|
1488
|
+
.close()
|
|
1489
|
+
.then(() => console.info('[Server] File watcher stopped'))
|
|
1490
|
+
.catch(() => console.warn('[Server] Error closing watcher'))
|
|
1491
|
+
);
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
if (indexer && indexer.terminateWorkers) {
|
|
1495
|
+
cleanupTasks.push(
|
|
1496
|
+
(async () => {
|
|
1497
|
+
console.info('[Server] Terminating workers...');
|
|
1498
|
+
await indexer.terminateWorkers();
|
|
1499
|
+
console.info('[Server] Workers terminated');
|
|
1500
|
+
})().catch(() => console.info('[Server] Workers shutdown (with warnings)'))
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1567
1504
|
if (cache) {
|
|
1568
1505
|
if (!workspaceLockAcquired) {
|
|
1569
1506
|
console.info('[Server] Secondary/fallback mode: skipping cache save.');
|
|
@@ -1576,24 +1513,22 @@ async function gracefulShutdown(signal) {
|
|
|
1576
1513
|
);
|
|
1577
1514
|
}
|
|
1578
1515
|
}
|
|
1579
|
-
|
|
1516
|
+
|
|
1580
1517
|
await Promise.allSettled(cleanupTasks);
|
|
1581
1518
|
console.info('[Server] Goodbye!');
|
|
1582
1519
|
await flushLogs({ close: true, timeoutMs: 1500 }).catch(() => {});
|
|
1583
1520
|
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
1521
|
setTimeout(() => process.exit(exitCode), 100);
|
|
1587
1522
|
}
|
|
1588
|
-
|
|
1589
|
-
const isMain =
|
|
1590
|
-
process.argv[1] &&
|
|
1591
|
-
(path.resolve(process.argv[1]).toLowerCase() === fileURLToPath(import.meta.url).toLowerCase() ||
|
|
1592
|
-
process.argv[1].endsWith('heuristic-mcp') ||
|
|
1593
|
-
process.argv[1].endsWith('heuristic-mcp.js') ||
|
|
1594
|
-
path.basename(process.argv[1]) === 'index.js') &&
|
|
1595
|
-
!(process.env.VITEST === 'true' || process.env.NODE_ENV === 'test');
|
|
1596
|
-
|
|
1523
|
+
|
|
1524
|
+
const isMain =
|
|
1525
|
+
process.argv[1] &&
|
|
1526
|
+
(path.resolve(process.argv[1]).toLowerCase() === fileURLToPath(import.meta.url).toLowerCase() ||
|
|
1527
|
+
process.argv[1].endsWith('heuristic-mcp') ||
|
|
1528
|
+
process.argv[1].endsWith('heuristic-mcp.js') ||
|
|
1529
|
+
path.basename(process.argv[1]) === 'index.js') &&
|
|
1530
|
+
!(process.env.VITEST === 'true' || process.env.NODE_ENV === 'test');
|
|
1531
|
+
|
|
1597
1532
|
if (isMain) {
|
|
1598
1533
|
main().catch(async (err) => {
|
|
1599
1534
|
console.error(err);
|