@codragraph/cli 2.1.1 → 2.1.5
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 +36 -9
- package/dist/cli/ai-context.js +298 -1
- package/dist/cli/analyze.js +19 -2
- package/dist/cli/index.js +33 -12
- package/dist/cli/serve.d.ts +1 -0
- package/dist/cli/serve.js +3 -1
- package/dist/cli/setup.js +36 -19
- package/dist/cli/status.d.ts +13 -0
- package/dist/cli/status.js +99 -0
- package/dist/cli/tool.js +73 -33
- package/dist/config/ignore-service.js +3 -0
- package/dist/core/cgdb/pool-adapter.js +130 -20
- package/dist/core/graphstore/cgdb-row-source.js +3 -2
- package/dist/core/group/bridge-db.js +42 -10
- package/dist/core/ingestion/parsing-processor.js +7 -1
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +4 -0
- package/dist/core/ingestion/workers/parse-worker.js +1 -1
- package/dist/core/ingestion/workers/worker-pool.d.ts +14 -1
- package/dist/core/ingestion/workers/worker-pool.js +33 -17
- package/dist/core/run-analyze.d.ts +20 -0
- package/dist/core/run-analyze.js +225 -1
- package/dist/core/search/bm25-index.d.ts +0 -11
- package/dist/core/search/bm25-index.js +7 -84
- package/dist/core/search/hybrid-search.js +11 -3
- package/dist/mcp/local/local-backend.d.ts +2 -0
- package/dist/mcp/local/local-backend.js +235 -18
- package/dist/mcp/resources.js +2 -2
- package/dist/server/api.d.ts +14 -2
- package/dist/server/api.js +90 -7
- package/dist/server/mcp-http.d.ts +22 -0
- package/dist/server/mcp-http.js +21 -2
- package/dist/server/web-dashboard.d.ts +28 -0
- package/dist/server/web-dashboard.js +61 -0
- package/dist/web/assets/agent-D5lb0zXz.js +1089 -0
- package/dist/web/assets/architectureDiagram-EMZXCZ2Q-CZtc99v_.js +36 -0
- package/dist/web/assets/blockDiagram-IGV67L2C-BtoUp-6Y.js +132 -0
- package/dist/web/assets/c4Diagram-DFAF54RM-C4Hl3J2U.js +10 -0
- package/dist/web/assets/chunk-3GS5O3IE-DkUjU0WD.js +231 -0
- package/dist/web/assets/chunk-3YCYZ6SJ-CQkVgT_z.js +1 -0
- package/dist/web/assets/chunk-7RZVMHOQ-BitYcNVR.js +338 -0
- package/dist/web/assets/chunk-AEOMTBSW-BgTIXPsY.js +1 -0
- package/dist/web/assets/chunk-H3VCZNTA-Cx5XV_aC.js +13 -0
- package/dist/web/assets/chunk-HN6EAY2L-BBnyTNdB.js +1 -0
- package/dist/web/assets/chunk-KSICW3F5-BYzvDLNI.js +15 -0
- package/dist/web/assets/chunk-O5ABG6QK-dHwHzA6n.js +1 -0
- package/dist/web/assets/chunk-PK6DOVAG-CvsEnugt.js +206 -0
- package/dist/web/assets/chunk-RWUO3TPN-BgRTY0_k.js +1 -0
- package/dist/web/assets/chunk-TBF5ZNIQ-DL5stGM1.js +1 -0
- package/dist/web/assets/chunk-TU3PZOEN-RLyvLcv-.js +1 -0
- package/dist/web/assets/classDiagram-PPOCWD7C-DTr8QIOf.js +1 -0
- package/dist/web/assets/classDiagram-v2-23LJLIIU-DTr8QIOf.js +1 -0
- package/dist/web/assets/context-builder-22jU3V56.js +16 -0
- package/dist/web/assets/cose-bilkent-PNC4W37J-DVhePRYg.js +1 -0
- package/dist/web/assets/dagre-E77IOHMT-Dzx0A6ZU.js +4 -0
- package/dist/web/assets/diagram-H7BISOXX-CC9pRew1.js +43 -0
- package/dist/web/assets/diagram-JC5VWROH-Bau_i9tf.js +24 -0
- package/dist/web/assets/diagram-LXUTUG65-D9_FM2Gt.js +10 -0
- package/dist/web/assets/diagram-WEHSV5V5-BMlayouL.js +24 -0
- package/dist/web/assets/erDiagram-GCSMX5X6-C3dhDFA8.js +85 -0
- package/dist/web/assets/flowDiagram-OTCZ4VVT-CWSFWmhr.js +162 -0
- package/dist/web/assets/ganttDiagram-MUNLMDZQ-D3a67Yol.js +292 -0
- package/dist/web/assets/gitGraphDiagram-3HKGZ4G3-7jmry-vM.js +106 -0
- package/dist/web/assets/index-BgeqpYgd.js +1415 -0
- package/dist/web/assets/index-CT0GtFLZ.css +1 -0
- package/dist/web/assets/infoDiagram-MN7RKWGX-G7lhP0Ib.js +2 -0
- package/dist/web/assets/ishikawaDiagram-YMYX4NHK-DUoJvNP2.js +70 -0
- package/dist/web/assets/journeyDiagram-SO5T7YLQ-RMFPNNqz.js +139 -0
- package/dist/web/assets/kanban-definition-LJHFXRCJ-BzpDs1K9.js +89 -0
- package/dist/web/assets/katex-GD7MH7QM-DBQvrix-.js +261 -0
- package/dist/web/assets/mindmap-definition-2EUWGEK5-Bk0O4roa.js +96 -0
- package/dist/web/assets/pieDiagram-3IATQBI2-DKU7kpgS.js +30 -0
- package/dist/web/assets/quadrantDiagram-E256RVCF-BY0TGWCS.js +7 -0
- package/dist/web/assets/requirementDiagram-M5DCFWZL-DLHOVTSv.js +84 -0
- package/dist/web/assets/sankeyDiagram-L3NBLAOT-DVMj5rX2.js +10 -0
- package/dist/web/assets/sequenceDiagram-ZOUHS735-CJC73bV-.js +157 -0
- package/dist/web/assets/stateDiagram-MLPALWAM-BCFyESls.js +1 -0
- package/dist/web/assets/stateDiagram-v2-B5LQ5ZB2-DahzzIca.js +1 -0
- package/dist/web/assets/timeline-definition-5SPVSISX-TRSDRgPw.js +120 -0
- package/dist/web/assets/vennDiagram-IE5QUKF5-DNy7HRBM.js +34 -0
- package/dist/web/assets/wardley-RL74JXVD-BCRCBASE-B-eZEzf9.js +161 -0
- package/dist/web/assets/wardleyDiagram-XU3VSMPF-BP-r1xzR.js +20 -0
- package/dist/web/assets/xychartDiagram-ZHJ5623Y-Dr9r7a35.js +7 -0
- package/dist/web/codragraph-logo-512.png +0 -0
- package/dist/web/codragraph-logo.png +0 -0
- package/dist/web/favicon.png +0 -0
- package/dist/web/index.html +36 -0
- package/hooks/claude/codragraph-hook.cjs +18 -110
- package/hooks/claude/pre-tool-use.sh +6 -1
- package/package.json +3 -1
- package/scripts/build.js +62 -4
- package/scripts/patch-tree-sitter-swift.cjs +0 -1
- package/skills/codragraph-cli.md +1 -1
- package/vendor/leiden/index.cjs +272 -285
- package/vendor/leiden/utils.cjs +264 -274
- package/dist/_shared/lbug/schema-constants.d.ts +0 -16
- package/dist/_shared/lbug/schema-constants.d.ts.map +0 -1
- package/dist/_shared/lbug/schema-constants.js +0 -67
- package/dist/_shared/lbug/schema-constants.js.map +0 -1
- package/dist/core/graphstore/lbug-row-source.d.ts +0 -19
- package/dist/core/graphstore/lbug-row-source.js +0 -141
- package/dist/core/lbug/content-read.d.ts +0 -46
- package/dist/core/lbug/content-read.js +0 -64
- package/dist/core/lbug/csv-generator.d.ts +0 -29
- package/dist/core/lbug/csv-generator.js +0 -492
- package/dist/core/lbug/lbug-adapter.d.ts +0 -176
- package/dist/core/lbug/lbug-adapter.js +0 -1320
- package/dist/core/lbug/pool-adapter.d.ts +0 -93
- package/dist/core/lbug/pool-adapter.js +0 -550
- package/dist/core/lbug/schema.d.ts +0 -62
- package/dist/core/lbug/schema.js +0 -502
- package/dist/mcp/core/lbug-adapter.d.ts +0 -5
- package/dist/mcp/core/lbug-adapter.js +0 -5
package/dist/server/api.js
CHANGED
|
@@ -22,7 +22,8 @@ import { hybridSearch } from '../core/search/hybrid-search.js';
|
|
|
22
22
|
// Embedding imports are lazy (dynamic import) to avoid loading onnxruntime-node
|
|
23
23
|
// at server startup — crashes on unsupported Node ABI versions (#89)
|
|
24
24
|
import { LocalBackend } from '../mcp/local/local-backend.js';
|
|
25
|
-
import { mountMCPEndpoints } from './mcp-http.js';
|
|
25
|
+
import { getMcpHttpRouteGuidance, mountMCPEndpoints } from './mcp-http.js';
|
|
26
|
+
import { HOSTED_WEB_APP_URL, getWebDashboardInfo, mountWebDashboard, } from './web-dashboard.js';
|
|
26
27
|
import { fork } from 'child_process';
|
|
27
28
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
28
29
|
import { JobManager } from './analyze-job.js';
|
|
@@ -40,7 +41,7 @@ const pkg = _require('../../package.json');
|
|
|
40
41
|
* 10.0.0.0/8 → 10.x.x.x
|
|
41
42
|
* 172.16.0.0/12 → 172.16.x.x – 172.31.x.x
|
|
42
43
|
* 192.168.0.0/16 → 192.168.x.x
|
|
43
|
-
* - https://codragraph.vercel.app
|
|
44
|
+
* - Hosted CodraGraph web UI — defaults to https://codragraph.vercel.app
|
|
44
45
|
*
|
|
45
46
|
* @param origin - The value of the HTTP `Origin` request header, or `undefined`
|
|
46
47
|
* when the header is absent (non-browser request).
|
|
@@ -57,7 +58,7 @@ export const isAllowedOrigin = (origin) => {
|
|
|
57
58
|
origin === 'http://127.0.0.1' ||
|
|
58
59
|
origin.startsWith('http://[::1]:') ||
|
|
59
60
|
origin === 'http://[::1]' ||
|
|
60
|
-
origin ===
|
|
61
|
+
origin === HOSTED_WEB_APP_URL) {
|
|
61
62
|
return true;
|
|
62
63
|
}
|
|
63
64
|
// RFC 1918 private network ranges — allow any port on these hosts.
|
|
@@ -104,6 +105,30 @@ export const isIgnorableGraphQueryError = (err) => {
|
|
|
104
105
|
message.includes('not found') ||
|
|
105
106
|
message.includes('No table named'));
|
|
106
107
|
};
|
|
108
|
+
export const isGraphStoreCorruptionError = (err) => {
|
|
109
|
+
const message = (err instanceof Error ? err.message : String(err)).toLowerCase();
|
|
110
|
+
return (message.includes('wal checksum') ||
|
|
111
|
+
(message.includes('wal') && message.includes('corrupt')) ||
|
|
112
|
+
(message.includes('checksum') && message.includes('corrupt')) ||
|
|
113
|
+
message.includes('database disk image is malformed') ||
|
|
114
|
+
message.includes('database image is malformed'));
|
|
115
|
+
};
|
|
116
|
+
export const getGraphStoreErrorResponse = (err, operation) => {
|
|
117
|
+
if (!isGraphStoreCorruptionError(err))
|
|
118
|
+
return null;
|
|
119
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
120
|
+
return {
|
|
121
|
+
error: message || 'Graph store is corrupted',
|
|
122
|
+
code: 'GRAPHSTORE_CORRUPT',
|
|
123
|
+
operation,
|
|
124
|
+
recovery: [
|
|
125
|
+
'Stop overlapping codragraph serve, mcp, analyze, and embedding jobs for this repo.',
|
|
126
|
+
'Retry: npx @codragraph/cli analyze --force',
|
|
127
|
+
'If this repo had embeddings, preserve them with: npx @codragraph/cli analyze --force --embeddings',
|
|
128
|
+
'Only after approval, use: npx @codragraph/cli clean --force',
|
|
129
|
+
],
|
|
130
|
+
};
|
|
131
|
+
};
|
|
107
132
|
const ensureStreamIsWritable = (res, signal) => {
|
|
108
133
|
if (signal?.aborted || res.destroyed || res.writableEnded) {
|
|
109
134
|
throw new ClientDisconnectedError();
|
|
@@ -355,6 +380,8 @@ const mountSSEProgress = (app, routePath, jm) => {
|
|
|
355
380
|
});
|
|
356
381
|
};
|
|
357
382
|
const statusFromError = (err) => {
|
|
383
|
+
if (isGraphStoreCorruptionError(err))
|
|
384
|
+
return 503;
|
|
358
385
|
const msg = String(err?.message ?? '');
|
|
359
386
|
if (msg.includes('No indexed repositories') || msg.includes('not found'))
|
|
360
387
|
return 404;
|
|
@@ -371,9 +398,14 @@ const requestedRepo = (req) => {
|
|
|
371
398
|
}
|
|
372
399
|
return undefined;
|
|
373
400
|
};
|
|
374
|
-
export const createServer = async (port, host = '127.0.0.1') => {
|
|
401
|
+
export const createServer = async (port, host = '127.0.0.1', options = {}) => {
|
|
375
402
|
const app = express();
|
|
376
403
|
app.disable('x-powered-by');
|
|
404
|
+
let webDashboard = {
|
|
405
|
+
mode: options.web ?? 'local',
|
|
406
|
+
served: false,
|
|
407
|
+
hostedUrl: HOSTED_WEB_APP_URL,
|
|
408
|
+
};
|
|
377
409
|
// CORS: allow localhost, private/LAN networks, and the deployed site.
|
|
378
410
|
// Non-browser requests (curl, server-to-server) have no origin and are allowed.
|
|
379
411
|
// Disallowed origins get the response without Access-Control-Allow-Origin,
|
|
@@ -520,7 +552,15 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
520
552
|
else {
|
|
521
553
|
launchContext = 'global';
|
|
522
554
|
}
|
|
523
|
-
|
|
555
|
+
const displayHost = host === '::' || host === '0.0.0.0' ? 'localhost' : host;
|
|
556
|
+
const apiBaseUrl = `http://${displayHost}:${port}`;
|
|
557
|
+
res.json({
|
|
558
|
+
version: pkg.version,
|
|
559
|
+
launchContext,
|
|
560
|
+
nodeVersion: process.version,
|
|
561
|
+
mcp: getMcpHttpRouteGuidance(),
|
|
562
|
+
web: getWebDashboardInfo(webDashboard, apiBaseUrl),
|
|
563
|
+
});
|
|
524
564
|
});
|
|
525
565
|
// List all registered repos
|
|
526
566
|
app.get('/api/repos', async (_req, res) => {
|
|
@@ -995,10 +1035,13 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
995
1035
|
if (err instanceof ClientDisconnectedError) {
|
|
996
1036
|
return;
|
|
997
1037
|
}
|
|
1038
|
+
const graphStoreError = getGraphStoreErrorResponse(err, 'api.graph');
|
|
998
1039
|
const message = err.message || 'Failed to build graph';
|
|
999
1040
|
if (res.headersSent) {
|
|
1000
1041
|
try {
|
|
1001
|
-
res.write(JSON.stringify(
|
|
1042
|
+
res.write(JSON.stringify(graphStoreError
|
|
1043
|
+
? { type: 'error', ...graphStoreError }
|
|
1044
|
+
: { type: 'error', error: message }) + '\n');
|
|
1002
1045
|
}
|
|
1003
1046
|
catch {
|
|
1004
1047
|
// Best-effort only after streaming has started.
|
|
@@ -1006,6 +1049,10 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1006
1049
|
res.end();
|
|
1007
1050
|
return;
|
|
1008
1051
|
}
|
|
1052
|
+
if (graphStoreError) {
|
|
1053
|
+
res.status(503).json(graphStoreError);
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1009
1056
|
res.status(500).json({ error: message });
|
|
1010
1057
|
}
|
|
1011
1058
|
});
|
|
@@ -1031,6 +1078,11 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1031
1078
|
res.json({ result });
|
|
1032
1079
|
}
|
|
1033
1080
|
catch (err) {
|
|
1081
|
+
const graphStoreError = getGraphStoreErrorResponse(err, 'api.query');
|
|
1082
|
+
if (graphStoreError) {
|
|
1083
|
+
res.status(503).json(graphStoreError);
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1034
1086
|
res.status(500).json({ error: err.message || 'Query failed' });
|
|
1035
1087
|
}
|
|
1036
1088
|
});
|
|
@@ -1050,6 +1102,11 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1050
1102
|
res.json(result);
|
|
1051
1103
|
}
|
|
1052
1104
|
catch (err) {
|
|
1105
|
+
const graphStoreError = getGraphStoreErrorResponse(err, 'api.context');
|
|
1106
|
+
if (graphStoreError) {
|
|
1107
|
+
res.status(503).json(graphStoreError);
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1053
1110
|
res.status(statusFromError(err)).json({ error: err.message || 'Context query failed' });
|
|
1054
1111
|
}
|
|
1055
1112
|
});
|
|
@@ -1070,6 +1127,11 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1070
1127
|
res.json(result);
|
|
1071
1128
|
}
|
|
1072
1129
|
catch (err) {
|
|
1130
|
+
const graphStoreError = getGraphStoreErrorResponse(err, 'api.impact');
|
|
1131
|
+
if (graphStoreError) {
|
|
1132
|
+
res.status(503).json(graphStoreError);
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1073
1135
|
res.status(statusFromError(err)).json({ error: err.message || 'Impact query failed' });
|
|
1074
1136
|
}
|
|
1075
1137
|
});
|
|
@@ -1197,6 +1259,11 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1197
1259
|
res.json({ results });
|
|
1198
1260
|
}
|
|
1199
1261
|
catch (err) {
|
|
1262
|
+
const graphStoreError = getGraphStoreErrorResponse(err, 'api.search');
|
|
1263
|
+
if (graphStoreError) {
|
|
1264
|
+
res.status(503).json(graphStoreError);
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1200
1267
|
res.status(500).json({ error: err.message || 'Search failed' });
|
|
1201
1268
|
}
|
|
1202
1269
|
});
|
|
@@ -1813,6 +1880,7 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1813
1880
|
embedJobManager.cancelJob(req.params.jobId, 'Cancelled by user');
|
|
1814
1881
|
res.json({ id: job.id, status: 'failed', error: 'Cancelled by user' });
|
|
1815
1882
|
});
|
|
1883
|
+
webDashboard = mountWebDashboard(app, { mode: options.web ?? 'local' });
|
|
1816
1884
|
// Global error handler — catch anything the route handlers miss
|
|
1817
1885
|
app.use((err, _req, res, _next) => {
|
|
1818
1886
|
console.error('Unhandled error:', err);
|
|
@@ -1823,7 +1891,22 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1823
1891
|
await new Promise((resolve, reject) => {
|
|
1824
1892
|
const server = app.listen(port, host, () => {
|
|
1825
1893
|
const displayHost = host === '::' || host === '0.0.0.0' ? 'localhost' : host;
|
|
1826
|
-
|
|
1894
|
+
const localUrl = `http://${displayHost}:${port}`;
|
|
1895
|
+
console.log(`CodraGraph server running on ${localUrl}`);
|
|
1896
|
+
if (webDashboard.served) {
|
|
1897
|
+
console.log(`Web dashboard: ${localUrl}`);
|
|
1898
|
+
}
|
|
1899
|
+
else if (webDashboard.mode === 'hosted') {
|
|
1900
|
+
console.log(`Hosted dashboard: ${webDashboard.hostedUrl}`);
|
|
1901
|
+
console.log(`Connect it to local API: ${localUrl}`);
|
|
1902
|
+
}
|
|
1903
|
+
else if (webDashboard.mode === 'off') {
|
|
1904
|
+
console.log('Web dashboard disabled (--web off).');
|
|
1905
|
+
}
|
|
1906
|
+
else {
|
|
1907
|
+
console.warn(`Web dashboard not bundled: ${webDashboard.reason}`);
|
|
1908
|
+
console.warn(`Hosted dashboard: ${webDashboard.hostedUrl}`);
|
|
1909
|
+
}
|
|
1827
1910
|
resolve();
|
|
1828
1911
|
});
|
|
1829
1912
|
server.on('error', (err) => reject(err));
|
|
@@ -10,4 +10,26 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import type { Express } from 'express';
|
|
12
12
|
import type { LocalBackend } from '../mcp/local/local-backend.js';
|
|
13
|
+
export declare const MCP_HTTP_ENDPOINT = "/api/mcp";
|
|
14
|
+
export declare const UNSUPPORTED_MCP_REST_EXAMPLE = "/api/mcp/tools/list";
|
|
15
|
+
export interface McpHttpRouteGuidance {
|
|
16
|
+
endpoint: string;
|
|
17
|
+
transport: 'streamable-http';
|
|
18
|
+
note: string;
|
|
19
|
+
unsupportedRestExample: string;
|
|
20
|
+
powershellHealthCheck: string;
|
|
21
|
+
clientInstruction: string;
|
|
22
|
+
}
|
|
23
|
+
export declare const getMcpHttpRouteGuidance: () => McpHttpRouteGuidance;
|
|
24
|
+
export declare const getUnsupportedMcpRestRouteResponse: (path: string) => {
|
|
25
|
+
endpoint: string;
|
|
26
|
+
transport: "streamable-http";
|
|
27
|
+
note: string;
|
|
28
|
+
unsupportedRestExample: string;
|
|
29
|
+
powershellHealthCheck: string;
|
|
30
|
+
clientInstruction: string;
|
|
31
|
+
error: string;
|
|
32
|
+
code: string;
|
|
33
|
+
unsupportedRoute: string;
|
|
34
|
+
};
|
|
13
35
|
export declare function mountMCPEndpoints(app: Express, backend: LocalBackend): () => Promise<void>;
|
package/dist/server/mcp-http.js
CHANGED
|
@@ -11,6 +11,22 @@
|
|
|
11
11
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
12
12
|
import { createMCPServer } from '../mcp/server.js';
|
|
13
13
|
import { randomUUID } from 'crypto';
|
|
14
|
+
export const MCP_HTTP_ENDPOINT = '/api/mcp';
|
|
15
|
+
export const UNSUPPORTED_MCP_REST_EXAMPLE = '/api/mcp/tools/list';
|
|
16
|
+
export const getMcpHttpRouteGuidance = () => ({
|
|
17
|
+
endpoint: MCP_HTTP_ENDPOINT,
|
|
18
|
+
transport: 'streamable-http',
|
|
19
|
+
note: 'HTTP MCP is a protocol endpoint, not a REST tools namespace.',
|
|
20
|
+
unsupportedRestExample: UNSUPPORTED_MCP_REST_EXAMPLE,
|
|
21
|
+
powershellHealthCheck: "Invoke-RestMethod -Uri 'http://127.0.0.1:4747/api/info' -TimeoutSec 10",
|
|
22
|
+
clientInstruction: 'Point an MCP client at http://127.0.0.1:4747/api/mcp using StreamableHTTP.',
|
|
23
|
+
});
|
|
24
|
+
export const getUnsupportedMcpRestRouteResponse = (path) => ({
|
|
25
|
+
error: 'Unsupported MCP HTTP REST route',
|
|
26
|
+
code: 'MCP_HTTP_REST_ROUTE_UNSUPPORTED',
|
|
27
|
+
unsupportedRoute: path,
|
|
28
|
+
...getMcpHttpRouteGuidance(),
|
|
29
|
+
});
|
|
14
30
|
/** Idle sessions are evicted after 30 minutes */
|
|
15
31
|
const SESSION_TTL_MS = 30 * 60 * 1000;
|
|
16
32
|
/** Cleanup sweep runs every 5 minutes */
|
|
@@ -72,7 +88,7 @@ export function mountMCPEndpoints(app, backend) {
|
|
|
72
88
|
});
|
|
73
89
|
}
|
|
74
90
|
};
|
|
75
|
-
app.all(
|
|
91
|
+
app.all(MCP_HTTP_ENDPOINT, (req, res) => {
|
|
76
92
|
void handleMcpRequest(req, res).catch((err) => {
|
|
77
93
|
console.error('MCP HTTP request failed:', err);
|
|
78
94
|
if (res.headersSent)
|
|
@@ -84,6 +100,9 @@ export function mountMCPEndpoints(app, backend) {
|
|
|
84
100
|
});
|
|
85
101
|
});
|
|
86
102
|
});
|
|
103
|
+
app.all(/^\/api\/mcp\/.+/, (req, res) => {
|
|
104
|
+
res.status(200).json(getUnsupportedMcpRestRouteResponse(req.path));
|
|
105
|
+
});
|
|
87
106
|
const cleanup = async () => {
|
|
88
107
|
clearInterval(cleanupTimer);
|
|
89
108
|
const closers = [...sessions.values()].map(async (session) => {
|
|
@@ -95,6 +114,6 @@ export function mountMCPEndpoints(app, backend) {
|
|
|
95
114
|
sessions.clear();
|
|
96
115
|
await Promise.allSettled(closers);
|
|
97
116
|
};
|
|
98
|
-
console.log(
|
|
117
|
+
console.log(`MCP HTTP endpoint mounted at ${MCP_HTTP_ENDPOINT}`);
|
|
99
118
|
return cleanup;
|
|
100
119
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type Express } from 'express';
|
|
2
|
+
export declare const HOSTED_WEB_APP_URL: string;
|
|
3
|
+
export type WebDashboardMode = 'local' | 'hosted' | 'off';
|
|
4
|
+
export interface WebDashboardMountOptions {
|
|
5
|
+
mode?: WebDashboardMode;
|
|
6
|
+
webAppPath?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface WebDashboardMount {
|
|
9
|
+
mode: WebDashboardMode;
|
|
10
|
+
served: boolean;
|
|
11
|
+
hostedUrl: string;
|
|
12
|
+
localPath?: string;
|
|
13
|
+
reason?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface WebDashboardInfo {
|
|
16
|
+
mode: WebDashboardMode;
|
|
17
|
+
served: boolean;
|
|
18
|
+
localUrl: string | null;
|
|
19
|
+
hostedUrl: string;
|
|
20
|
+
apiBaseUrl: string;
|
|
21
|
+
reason?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare const normalizeWebDashboardMode: (raw: string | undefined) => WebDashboardMode;
|
|
24
|
+
export declare const getBundledWebAppCandidates: () => string[];
|
|
25
|
+
export declare const hasWebDashboardIndex: (candidate: string) => boolean;
|
|
26
|
+
export declare const resolveBundledWebAppPath: (candidates?: readonly string[]) => string | null;
|
|
27
|
+
export declare const mountWebDashboard: (app: Express, options?: WebDashboardMountOptions) => WebDashboardMount;
|
|
28
|
+
export declare const getWebDashboardInfo: (mount: WebDashboardMount, apiBaseUrl: string) => WebDashboardInfo;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
export const HOSTED_WEB_APP_URL = process.env.CODRAGRAPH_WEB_URL || 'https://codragraph.vercel.app';
|
|
6
|
+
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
export const normalizeWebDashboardMode = (raw) => {
|
|
8
|
+
const value = (raw ?? 'local').trim().toLowerCase();
|
|
9
|
+
if (value === 'local' || value === 'hosted' || value === 'off')
|
|
10
|
+
return value;
|
|
11
|
+
throw new Error(`Invalid web dashboard mode "${raw}". Use one of: local, hosted, off.`);
|
|
12
|
+
};
|
|
13
|
+
export const getBundledWebAppCandidates = () => [
|
|
14
|
+
// Published package: packages/core/dist/server/*.js -> packages/core/dist/web
|
|
15
|
+
path.resolve(moduleDir, '..', 'web'),
|
|
16
|
+
// Monorepo/dev fallback after running `npm --prefix apps/web run build`.
|
|
17
|
+
path.resolve(moduleDir, '..', '..', '..', '..', 'apps', 'web', 'dist'),
|
|
18
|
+
];
|
|
19
|
+
export const hasWebDashboardIndex = (candidate) => {
|
|
20
|
+
try {
|
|
21
|
+
return fs.statSync(path.join(candidate, 'index.html')).isFile();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
export const resolveBundledWebAppPath = (candidates = getBundledWebAppCandidates()) => {
|
|
28
|
+
for (const candidate of candidates) {
|
|
29
|
+
if (hasWebDashboardIndex(candidate))
|
|
30
|
+
return candidate;
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
};
|
|
34
|
+
export const mountWebDashboard = (app, options = {}) => {
|
|
35
|
+
const mode = options.mode ?? 'local';
|
|
36
|
+
if (mode !== 'local') {
|
|
37
|
+
return { mode, served: false, hostedUrl: HOSTED_WEB_APP_URL };
|
|
38
|
+
}
|
|
39
|
+
const webAppPath = options.webAppPath ?? resolveBundledWebAppPath();
|
|
40
|
+
if (!webAppPath || !hasWebDashboardIndex(webAppPath)) {
|
|
41
|
+
return {
|
|
42
|
+
mode,
|
|
43
|
+
served: false,
|
|
44
|
+
hostedUrl: HOSTED_WEB_APP_URL,
|
|
45
|
+
reason: 'Bundled web dashboard not found. Rebuild the package or use --web hosted.',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
app.use(express.static(webAppPath, { index: false }));
|
|
49
|
+
app.get(/^\/(?!api(?:\/|$)).*/, (_req, res) => {
|
|
50
|
+
res.sendFile(path.join(webAppPath, 'index.html'));
|
|
51
|
+
});
|
|
52
|
+
return { mode, served: true, hostedUrl: HOSTED_WEB_APP_URL, localPath: webAppPath };
|
|
53
|
+
};
|
|
54
|
+
export const getWebDashboardInfo = (mount, apiBaseUrl) => ({
|
|
55
|
+
mode: mount.mode,
|
|
56
|
+
served: mount.served,
|
|
57
|
+
localUrl: mount.served ? apiBaseUrl : null,
|
|
58
|
+
hostedUrl: mount.hostedUrl,
|
|
59
|
+
apiBaseUrl,
|
|
60
|
+
reason: mount.reason,
|
|
61
|
+
});
|