@codragraph/cli 2.1.0 → 2.1.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 +62 -21
- package/dist/_shared/cgdb/schema-constants.d.ts +2 -2
- package/dist/_shared/cgdb/schema-constants.d.ts.map +1 -1
- package/dist/_shared/cgdb/schema-constants.js +3 -0
- package/dist/_shared/cgdb/schema-constants.js.map +1 -1
- package/dist/_shared/feature-clusters.d.ts +99 -0
- package/dist/_shared/feature-clusters.d.ts.map +1 -0
- package/dist/_shared/feature-clusters.js +2 -0
- package/dist/_shared/feature-clusters.js.map +1 -0
- package/dist/_shared/graph/types.d.ts +16 -2
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/index.d.ts +1 -0
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/index.js.map +1 -1
- package/dist/_shared/pipeline.d.ts +1 -1
- package/dist/_shared/pipeline.d.ts.map +1 -1
- package/dist/cli/ai-context.js +4 -0
- package/dist/cli/analyze.js +46 -26
- package/dist/cli/index.js +39 -1
- package/dist/cli/serve.d.ts +1 -0
- package/dist/cli/serve.js +3 -1
- package/dist/cli/setup.js +42 -21
- package/dist/cli/status.d.ts +13 -0
- package/dist/cli/status.js +99 -0
- package/dist/cli/tool.d.ts +25 -0
- package/dist/cli/tool.js +74 -0
- package/dist/config/ignore-service.js +2 -0
- package/dist/config/supported-languages.d.ts +3 -3
- package/dist/config/supported-languages.js +3 -3
- package/dist/core/cgdb/cgdb-adapter.js +19 -3
- package/dist/core/cgdb/csv-generator.js +33 -2
- package/dist/core/cgdb/schema.d.ts +2 -1
- package/dist/core/cgdb/schema.js +55 -0
- package/dist/core/embeddings/embedder.js +4 -2
- package/dist/core/graphstore/cgdb-row-source.js +3 -2
- package/dist/core/graphstore/index.d.ts +1 -1
- package/dist/core/graphstore/index.js +1 -1
- package/dist/core/group/bridge-db.js +42 -10
- package/dist/core/group/service.d.ts +16 -0
- package/dist/core/group/service.js +360 -0
- package/dist/core/ingestion/emit-references.d.ts +1 -1
- package/dist/core/ingestion/emit-references.js +1 -1
- package/dist/core/ingestion/feature-cluster-processor.d.ts +62 -0
- package/dist/core/ingestion/feature-cluster-processor.js +626 -0
- package/dist/core/ingestion/finalize-orchestrator.js +1 -1
- package/dist/core/ingestion/model/registration-table.js +1 -0
- package/dist/core/ingestion/model/resolve.d.ts +2 -2
- package/dist/core/ingestion/model/resolve.js +3 -3
- package/dist/core/ingestion/model/semantic-model.d.ts +1 -1
- package/dist/core/ingestion/model/semantic-model.js +1 -1
- package/dist/core/ingestion/model/symbol-table.d.ts +1 -1
- package/dist/core/ingestion/model/symbol-table.js +1 -1
- package/dist/core/ingestion/pipeline-phases/feature-clusters.d.ts +17 -0
- package/dist/core/ingestion/pipeline-phases/feature-clusters.js +88 -0
- package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
- package/dist/core/ingestion/pipeline-phases/index.js +1 -0
- package/dist/core/ingestion/pipeline.d.ts +4 -0
- package/dist/core/ingestion/pipeline.js +9 -5
- package/dist/core/run-analyze.d.ts +21 -0
- package/dist/core/run-analyze.js +213 -6
- package/dist/core/search/hybrid-search.js +11 -3
- package/dist/mcp/core/embedder.js +5 -2
- package/dist/mcp/local/local-backend.d.ts +12 -0
- package/dist/mcp/local/local-backend.js +381 -3
- package/dist/mcp/resources.js +139 -0
- package/dist/mcp/tools.js +174 -2
- package/dist/server/api.d.ts +14 -2
- package/dist/server/api.js +206 -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/storage/repo-manager.d.ts +6 -1
- package/dist/storage/repo-manager.js +5 -1
- package/dist/types/pipeline.d.ts +2 -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 +24 -9
- package/hooks/claude/pre-tool-use.sh +6 -1
- package/package.json +15 -4
- package/scripts/build.js +75 -16
- package/scripts/patch-tree-sitter-swift.cjs +0 -1
- package/skills/codragraph-cli.md +17 -1
- package/skills/codragraph-guide.md +6 -2
- package/skills/codragraph-onboarding.md +2 -2
- 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/mcp/tools.js
CHANGED
|
@@ -84,6 +84,177 @@ SERVICE: optional monorepo path prefix (POSIX-style, case-sensitive segments). W
|
|
|
84
84
|
required: ['query'],
|
|
85
85
|
},
|
|
86
86
|
},
|
|
87
|
+
{
|
|
88
|
+
name: 'feature_clusters',
|
|
89
|
+
description: `List human-facing feature clusters such as Settings, AI, Auth, Billing, MCP, or Ingestion.
|
|
90
|
+
|
|
91
|
+
WHEN TO USE: First step for targeted implementation/refactoring when you need the functional area map before loading files. This is the product/domain layer above algorithmic Community nodes.
|
|
92
|
+
AFTER THIS: Use feature_context(name) for members with file paths and line ranges, then context() or impact() on specific symbols.`,
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
query: {
|
|
97
|
+
type: 'string',
|
|
98
|
+
description: 'Optional cluster owner search term (e.g. "settings", "AI", "billing").',
|
|
99
|
+
},
|
|
100
|
+
repo: {
|
|
101
|
+
type: 'string',
|
|
102
|
+
description: 'Repository name or path. Omit if only one repo is indexed.',
|
|
103
|
+
},
|
|
104
|
+
limit: {
|
|
105
|
+
type: 'number',
|
|
106
|
+
description: 'Max feature clusters to return (default: 100)',
|
|
107
|
+
default: 100,
|
|
108
|
+
minimum: 1,
|
|
109
|
+
maximum: 500,
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
required: [],
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: 'feature_context',
|
|
117
|
+
description: `Get a complete context pack for one FeatureCluster.
|
|
118
|
+
|
|
119
|
+
Returns the cluster metadata, member symbols/files with line ranges, outgoing/incoming feature dependencies, and related execution processes.
|
|
120
|
+
|
|
121
|
+
WHEN TO USE: Before editing a feature area like Settings or AI. This narrows exploration to the exact files and symbols in that feature cluster.`,
|
|
122
|
+
inputSchema: {
|
|
123
|
+
type: 'object',
|
|
124
|
+
properties: {
|
|
125
|
+
name: {
|
|
126
|
+
type: 'string',
|
|
127
|
+
description: 'Feature cluster name, slug, or id (e.g. "Settings", "settings").',
|
|
128
|
+
},
|
|
129
|
+
repo: {
|
|
130
|
+
type: 'string',
|
|
131
|
+
description: 'Repository name or path. Omit if only one repo is indexed.',
|
|
132
|
+
},
|
|
133
|
+
limit: {
|
|
134
|
+
type: 'number',
|
|
135
|
+
description: 'Max members to return (default: 100)',
|
|
136
|
+
default: 100,
|
|
137
|
+
minimum: 1,
|
|
138
|
+
maximum: 500,
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
required: ['name'],
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: 'cluster_query',
|
|
146
|
+
description: `Cluster-first alias for feature_clusters.
|
|
147
|
+
|
|
148
|
+
WHEN TO USE: Ask which product/domain cluster owns an area like Settings, AI, Auth, or Billing before loading files.`,
|
|
149
|
+
inputSchema: {
|
|
150
|
+
type: 'object',
|
|
151
|
+
properties: {
|
|
152
|
+
query: {
|
|
153
|
+
type: 'string',
|
|
154
|
+
description: 'Optional cluster owner search term (e.g. "settings", "AI", "billing").',
|
|
155
|
+
},
|
|
156
|
+
repo: {
|
|
157
|
+
type: 'string',
|
|
158
|
+
description: 'Repository name or path. Omit if only one repo is indexed.',
|
|
159
|
+
},
|
|
160
|
+
limit: {
|
|
161
|
+
type: 'number',
|
|
162
|
+
description: 'Max feature clusters to return (default: 100)',
|
|
163
|
+
default: 100,
|
|
164
|
+
minimum: 1,
|
|
165
|
+
maximum: 500,
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
required: [],
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: 'cluster_context',
|
|
173
|
+
description: `Cluster-first alias for feature_context.
|
|
174
|
+
|
|
175
|
+
Returns a FeatureCluster context pack with members, entry points, routes, tools, tests, docs, dependencies, and safe edit surface.`,
|
|
176
|
+
inputSchema: {
|
|
177
|
+
type: 'object',
|
|
178
|
+
properties: {
|
|
179
|
+
name: {
|
|
180
|
+
type: 'string',
|
|
181
|
+
description: 'Feature cluster name, slug, or id (e.g. "Settings", "settings").',
|
|
182
|
+
},
|
|
183
|
+
repo: {
|
|
184
|
+
type: 'string',
|
|
185
|
+
description: 'Repository name or path. Omit if only one repo is indexed.',
|
|
186
|
+
},
|
|
187
|
+
limit: {
|
|
188
|
+
type: 'number',
|
|
189
|
+
description: 'Max members to return (default: 100)',
|
|
190
|
+
default: 100,
|
|
191
|
+
minimum: 1,
|
|
192
|
+
maximum: 500,
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
required: ['name'],
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: 'context_pack',
|
|
200
|
+
description: `Get the compact agent context pack for a FeatureCluster.
|
|
201
|
+
|
|
202
|
+
WHEN TO USE: Before a refactor or implementation task where the agent should avoid re-exploring the full repo.`,
|
|
203
|
+
inputSchema: {
|
|
204
|
+
type: 'object',
|
|
205
|
+
properties: {
|
|
206
|
+
name: {
|
|
207
|
+
type: 'string',
|
|
208
|
+
description: 'Feature cluster name, slug, or id (e.g. "AI", "ai").',
|
|
209
|
+
},
|
|
210
|
+
repo: {
|
|
211
|
+
type: 'string',
|
|
212
|
+
description: 'Repository name or path. Omit if only one repo is indexed.',
|
|
213
|
+
},
|
|
214
|
+
limit: {
|
|
215
|
+
type: 'number',
|
|
216
|
+
description: 'Max members to return (default: 100)',
|
|
217
|
+
default: 100,
|
|
218
|
+
minimum: 1,
|
|
219
|
+
maximum: 500,
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
required: ['name'],
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: 'cluster_impact',
|
|
227
|
+
description: `Assess feature-level blast radius for a FeatureCluster.
|
|
228
|
+
|
|
229
|
+
Returns upstream/downstream cluster dependencies plus the same context pack and safe edit surface used for targeted edits.`,
|
|
230
|
+
inputSchema: {
|
|
231
|
+
type: 'object',
|
|
232
|
+
properties: {
|
|
233
|
+
name: {
|
|
234
|
+
type: 'string',
|
|
235
|
+
description: 'Feature cluster name, slug, or id.',
|
|
236
|
+
},
|
|
237
|
+
direction: {
|
|
238
|
+
type: 'string',
|
|
239
|
+
enum: ['upstream', 'downstream', 'both'],
|
|
240
|
+
description: 'Dependency direction to inspect.',
|
|
241
|
+
default: 'upstream',
|
|
242
|
+
},
|
|
243
|
+
repo: {
|
|
244
|
+
type: 'string',
|
|
245
|
+
description: 'Repository name or path. Omit if only one repo is indexed.',
|
|
246
|
+
},
|
|
247
|
+
limit: {
|
|
248
|
+
type: 'number',
|
|
249
|
+
description: 'Max members to include in the context pack (default: 100)',
|
|
250
|
+
default: 100,
|
|
251
|
+
minimum: 1,
|
|
252
|
+
maximum: 500,
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
required: ['name'],
|
|
256
|
+
},
|
|
257
|
+
},
|
|
87
258
|
{
|
|
88
259
|
name: 'cypher',
|
|
89
260
|
description: `Execute Cypher query against the code knowledge graph.
|
|
@@ -92,10 +263,10 @@ WHEN TO USE: Complex structural queries that search/explore can't answer. READ c
|
|
|
92
263
|
AFTER THIS: Use context() on result symbols for deeper context.
|
|
93
264
|
|
|
94
265
|
SCHEMA:
|
|
95
|
-
- Nodes: File, Folder, Function, Class, Interface, Method, CodeElement, Community, Process, Route, Tool
|
|
266
|
+
- Nodes: File, Folder, Function, Class, Interface, Method, CodeElement, Community, Process, FeatureCluster, Route, Tool
|
|
96
267
|
- Multi-language nodes (use backticks): \`Struct\`, \`Enum\`, \`Trait\`, \`Impl\`, etc.
|
|
97
268
|
- All edges via single CodeRelation table with 'type' property
|
|
98
|
-
- Edge types: CONTAINS, DEFINES, CALLS, IMPORTS, EXTENDS, IMPLEMENTS, HAS_METHOD, HAS_PROPERTY, ACCESSES, METHOD_OVERRIDES, METHOD_IMPLEMENTS, MEMBER_OF, STEP_IN_PROCESS, HANDLES_ROUTE, FETCHES, HANDLES_TOOL, ENTRY_POINT_OF
|
|
269
|
+
- Edge types: CONTAINS, DEFINES, CALLS, IMPORTS, EXTENDS, IMPLEMENTS, HAS_METHOD, HAS_PROPERTY, ACCESSES, METHOD_OVERRIDES, METHOD_IMPLEMENTS, MEMBER_OF, STEP_IN_PROCESS, HANDLES_ROUTE, FETCHES, HANDLES_TOOL, ENTRY_POINT_OF, WRAPS, QUERIES, FEATURE_MEMBER_OF, FEATURE_DEPENDS_ON
|
|
99
270
|
- Edge properties: type (STRING), confidence (DOUBLE), reason (STRING), step (INT32)
|
|
100
271
|
|
|
101
272
|
EXAMPLES:
|
|
@@ -129,6 +300,7 @@ TIPS:
|
|
|
129
300
|
- All relationships use single CodeRelation table — filter with {type: 'CALLS'} etc.
|
|
130
301
|
- Community = auto-detected functional area (Leiden algorithm). Properties: heuristicLabel, cohesion, symbolCount, keywords, description, enrichedBy
|
|
131
302
|
- Process = execution flow trace from entry point to terminal. Properties: heuristicLabel, processType, stepCount, communities, entryPointId, terminalId
|
|
303
|
+
- FeatureCluster = product/domain area for targeted context packs. Properties: name, slug, featureKind, summary, repo, service, memberCount, entryPointIds, routes, tools, testCoverageHints, lastIndexedCommit, confidence, signals
|
|
132
304
|
- Use heuristicLabel (not label) for human-readable community/process names`,
|
|
133
305
|
inputSchema: {
|
|
134
306
|
type: 'object',
|
package/dist/server/api.d.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import express from 'express';
|
|
11
11
|
import { type GraphNode, type GraphRelationship } from '../_shared/index.js';
|
|
12
|
+
import { type WebDashboardMode } from './web-dashboard.js';
|
|
12
13
|
/**
|
|
13
14
|
* Determine whether an HTTP Origin header value is allowed by CORS policy.
|
|
14
15
|
*
|
|
@@ -20,13 +21,16 @@ import { type GraphNode, type GraphRelationship } from '../_shared/index.js';
|
|
|
20
21
|
* 10.0.0.0/8 → 10.x.x.x
|
|
21
22
|
* 172.16.0.0/12 → 172.16.x.x – 172.31.x.x
|
|
22
23
|
* 192.168.0.0/16 → 192.168.x.x
|
|
23
|
-
* - https://codragraph.vercel.app
|
|
24
|
+
* - Hosted CodraGraph web UI — defaults to https://codragraph.vercel.app
|
|
24
25
|
*
|
|
25
26
|
* @param origin - The value of the HTTP `Origin` request header, or `undefined`
|
|
26
27
|
* when the header is absent (non-browser request).
|
|
27
28
|
* @returns `true` if the origin is allowed, `false` otherwise.
|
|
28
29
|
*/
|
|
29
30
|
export declare const isAllowedOrigin: (origin: string | undefined) => boolean;
|
|
31
|
+
export interface CreateServerOptions {
|
|
32
|
+
web?: WebDashboardMode;
|
|
33
|
+
}
|
|
30
34
|
type GraphStreamRecord = {
|
|
31
35
|
type: 'node';
|
|
32
36
|
data: GraphNode;
|
|
@@ -41,7 +45,15 @@ export declare class ClientDisconnectedError extends Error {
|
|
|
41
45
|
constructor();
|
|
42
46
|
}
|
|
43
47
|
export declare const isIgnorableGraphQueryError: (err: unknown) => boolean;
|
|
48
|
+
export interface GraphStoreErrorResponse {
|
|
49
|
+
error: string;
|
|
50
|
+
code: 'GRAPHSTORE_CORRUPT';
|
|
51
|
+
operation: string;
|
|
52
|
+
recovery: string[];
|
|
53
|
+
}
|
|
54
|
+
export declare const isGraphStoreCorruptionError: (err: unknown) => boolean;
|
|
55
|
+
export declare const getGraphStoreErrorResponse: (err: unknown, operation: string) => GraphStoreErrorResponse | null;
|
|
44
56
|
export declare const writeNdjsonRecord: (res: express.Response, record: GraphStreamRecord, signal?: AbortSignal) => Promise<void>;
|
|
45
57
|
export declare const streamGraphNdjson: (res: express.Response, includeContent?: boolean, signal?: AbortSignal) => Promise<void>;
|
|
46
|
-
export declare const createServer: (port: number, host?: string) => Promise<void>;
|
|
58
|
+
export declare const createServer: (port: number, host?: string, options?: CreateServerOptions) => Promise<void>;
|
|
47
59
|
export {};
|
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();
|
|
@@ -202,6 +227,9 @@ const getNodeQuery = (table, includeContent) => {
|
|
|
202
227
|
if (table === 'Process') {
|
|
203
228
|
return `MATCH (n:${tableLabel}) RETURN n.id AS id, n.label AS label, n.heuristicLabel AS heuristicLabel, n.processType AS processType, n.stepCount AS stepCount, n.communities AS communities, n.entryPointId AS entryPointId, n.terminalId AS terminalId`;
|
|
204
229
|
}
|
|
230
|
+
if (table === 'FeatureCluster') {
|
|
231
|
+
return `MATCH (n:${tableLabel}) RETURN n.id AS id, n.name AS name, n.slug AS slug, n.featureKind AS featureKind, n.summary AS summary, n.description AS description, n.repo AS repo, n.service AS service, n.signals AS signals, n.memberCount AS memberCount, n.entryPointIds AS entryPointIds, n.routes AS routes, n.tools AS tools, n.testCoverageHints AS testCoverageHints, n.lastIndexedCommit AS lastIndexedCommit, n.confidence AS confidence, n.source AS source`;
|
|
232
|
+
}
|
|
205
233
|
if (table === 'Route') {
|
|
206
234
|
return `MATCH (n:${tableLabel}) RETURN n.id AS id, n.name AS name, n.filePath AS filePath, n.responseKeys AS responseKeys, n.errorKeys AS errorKeys, n.middleware AS middleware`;
|
|
207
235
|
}
|
|
@@ -233,6 +261,20 @@ const mapGraphNodeRow = (table, row, includeContent) => ({
|
|
|
233
261
|
communities: row.communities,
|
|
234
262
|
entryPointId: row.entryPointId,
|
|
235
263
|
terminalId: row.terminalId,
|
|
264
|
+
slug: row.slug,
|
|
265
|
+
featureKind: row.featureKind,
|
|
266
|
+
summary: row.summary,
|
|
267
|
+
repo: row.repo,
|
|
268
|
+
service: row.service,
|
|
269
|
+
signals: row.signals,
|
|
270
|
+
memberCount: row.memberCount,
|
|
271
|
+
entryPointIds: row.entryPointIds,
|
|
272
|
+
routes: row.routes,
|
|
273
|
+
tools: row.tools,
|
|
274
|
+
testCoverageHints: row.testCoverageHints,
|
|
275
|
+
lastIndexedCommit: row.lastIndexedCommit,
|
|
276
|
+
confidence: row.confidence,
|
|
277
|
+
source: row.source,
|
|
236
278
|
},
|
|
237
279
|
});
|
|
238
280
|
const mapGraphRelationshipRow = (row) => ({
|
|
@@ -338,6 +380,8 @@ const mountSSEProgress = (app, routePath, jm) => {
|
|
|
338
380
|
});
|
|
339
381
|
};
|
|
340
382
|
const statusFromError = (err) => {
|
|
383
|
+
if (isGraphStoreCorruptionError(err))
|
|
384
|
+
return 503;
|
|
341
385
|
const msg = String(err?.message ?? '');
|
|
342
386
|
if (msg.includes('No indexed repositories') || msg.includes('not found'))
|
|
343
387
|
return 404;
|
|
@@ -354,9 +398,14 @@ const requestedRepo = (req) => {
|
|
|
354
398
|
}
|
|
355
399
|
return undefined;
|
|
356
400
|
};
|
|
357
|
-
export const createServer = async (port, host = '127.0.0.1') => {
|
|
401
|
+
export const createServer = async (port, host = '127.0.0.1', options = {}) => {
|
|
358
402
|
const app = express();
|
|
359
403
|
app.disable('x-powered-by');
|
|
404
|
+
let webDashboard = {
|
|
405
|
+
mode: options.web ?? 'local',
|
|
406
|
+
served: false,
|
|
407
|
+
hostedUrl: HOSTED_WEB_APP_URL,
|
|
408
|
+
};
|
|
360
409
|
// CORS: allow localhost, private/LAN networks, and the deployed site.
|
|
361
410
|
// Non-browser requests (curl, server-to-server) have no origin and are allowed.
|
|
362
411
|
// Disallowed origins get the response without Access-Control-Allow-Origin,
|
|
@@ -503,7 +552,15 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
503
552
|
else {
|
|
504
553
|
launchContext = 'global';
|
|
505
554
|
}
|
|
506
|
-
|
|
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
|
+
});
|
|
507
564
|
});
|
|
508
565
|
// List all registered repos
|
|
509
566
|
app.get('/api/repos', async (_req, res) => {
|
|
@@ -978,10 +1035,13 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
978
1035
|
if (err instanceof ClientDisconnectedError) {
|
|
979
1036
|
return;
|
|
980
1037
|
}
|
|
1038
|
+
const graphStoreError = getGraphStoreErrorResponse(err, 'api.graph');
|
|
981
1039
|
const message = err.message || 'Failed to build graph';
|
|
982
1040
|
if (res.headersSent) {
|
|
983
1041
|
try {
|
|
984
|
-
res.write(JSON.stringify(
|
|
1042
|
+
res.write(JSON.stringify(graphStoreError
|
|
1043
|
+
? { type: 'error', ...graphStoreError }
|
|
1044
|
+
: { type: 'error', error: message }) + '\n');
|
|
985
1045
|
}
|
|
986
1046
|
catch {
|
|
987
1047
|
// Best-effort only after streaming has started.
|
|
@@ -989,6 +1049,10 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
989
1049
|
res.end();
|
|
990
1050
|
return;
|
|
991
1051
|
}
|
|
1052
|
+
if (graphStoreError) {
|
|
1053
|
+
res.status(503).json(graphStoreError);
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
992
1056
|
res.status(500).json({ error: message });
|
|
993
1057
|
}
|
|
994
1058
|
});
|
|
@@ -1014,9 +1078,63 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1014
1078
|
res.json({ result });
|
|
1015
1079
|
}
|
|
1016
1080
|
catch (err) {
|
|
1081
|
+
const graphStoreError = getGraphStoreErrorResponse(err, 'api.query');
|
|
1082
|
+
if (graphStoreError) {
|
|
1083
|
+
res.status(503).json(graphStoreError);
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1017
1086
|
res.status(500).json({ error: err.message || 'Query failed' });
|
|
1018
1087
|
}
|
|
1019
1088
|
});
|
|
1089
|
+
// Symbol context through the same LocalBackend path as MCP `context`
|
|
1090
|
+
app.post('/api/context', async (req, res) => {
|
|
1091
|
+
try {
|
|
1092
|
+
const name = String(req.body?.name ?? '').trim();
|
|
1093
|
+
if (!name) {
|
|
1094
|
+
res.status(400).json({ error: 'Missing "name" in request body' });
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
const result = await backend.callTool('context', {
|
|
1098
|
+
...req.body,
|
|
1099
|
+
name,
|
|
1100
|
+
repo: requestedRepo(req),
|
|
1101
|
+
});
|
|
1102
|
+
res.json(result);
|
|
1103
|
+
}
|
|
1104
|
+
catch (err) {
|
|
1105
|
+
const graphStoreError = getGraphStoreErrorResponse(err, 'api.context');
|
|
1106
|
+
if (graphStoreError) {
|
|
1107
|
+
res.status(503).json(graphStoreError);
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
res.status(statusFromError(err)).json({ error: err.message || 'Context query failed' });
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
// Symbol impact through the same LocalBackend path as MCP `impact`
|
|
1114
|
+
app.post('/api/impact', async (req, res) => {
|
|
1115
|
+
try {
|
|
1116
|
+
const target = String(req.body?.target ?? '').trim();
|
|
1117
|
+
if (!target) {
|
|
1118
|
+
res.status(400).json({ error: 'Missing "target" in request body' });
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
const result = await backend.callTool('impact', {
|
|
1122
|
+
...req.body,
|
|
1123
|
+
target,
|
|
1124
|
+
direction: req.body?.direction ?? 'upstream',
|
|
1125
|
+
repo: requestedRepo(req),
|
|
1126
|
+
});
|
|
1127
|
+
res.json(result);
|
|
1128
|
+
}
|
|
1129
|
+
catch (err) {
|
|
1130
|
+
const graphStoreError = getGraphStoreErrorResponse(err, 'api.impact');
|
|
1131
|
+
if (graphStoreError) {
|
|
1132
|
+
res.status(503).json(graphStoreError);
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
res.status(statusFromError(err)).json({ error: err.message || 'Impact query failed' });
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1020
1138
|
// Search (supports mode: 'hybrid' | 'semantic' | 'bm25', and optional enrichment)
|
|
1021
1139
|
app.post('/api/search', async (req, res) => {
|
|
1022
1140
|
try {
|
|
@@ -1141,6 +1259,11 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1141
1259
|
res.json({ results });
|
|
1142
1260
|
}
|
|
1143
1261
|
catch (err) {
|
|
1262
|
+
const graphStoreError = getGraphStoreErrorResponse(err, 'api.search');
|
|
1263
|
+
if (graphStoreError) {
|
|
1264
|
+
res.status(503).json(graphStoreError);
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1144
1267
|
res.status(500).json({ error: err.message || 'Search failed' });
|
|
1145
1268
|
}
|
|
1146
1269
|
});
|
|
@@ -1326,6 +1449,66 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1326
1449
|
.json({ error: err.message || 'Failed to query cluster detail' });
|
|
1327
1450
|
}
|
|
1328
1451
|
});
|
|
1452
|
+
// List all feature clusters
|
|
1453
|
+
app.get('/api/feature-clusters', async (req, res) => {
|
|
1454
|
+
try {
|
|
1455
|
+
const limit = req.query.limit ? Number.parseInt(String(req.query.limit), 10) : undefined;
|
|
1456
|
+
const query = String(req.query.query ?? '');
|
|
1457
|
+
const result = await backend.queryFeatureClusters(requestedRepo(req), limit, query);
|
|
1458
|
+
res.json(result);
|
|
1459
|
+
}
|
|
1460
|
+
catch (err) {
|
|
1461
|
+
res
|
|
1462
|
+
.status(statusFromError(err))
|
|
1463
|
+
.json({ error: err.message || 'Failed to query feature clusters' });
|
|
1464
|
+
}
|
|
1465
|
+
});
|
|
1466
|
+
// Feature cluster detail
|
|
1467
|
+
app.get('/api/feature-cluster', async (req, res) => {
|
|
1468
|
+
try {
|
|
1469
|
+
const name = String(req.query.name ?? '').trim();
|
|
1470
|
+
if (!name) {
|
|
1471
|
+
res.status(400).json({ error: 'Missing "name" query parameter' });
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
const limit = req.query.limit ? Number.parseInt(String(req.query.limit), 10) : undefined;
|
|
1475
|
+
const result = await backend.queryFeatureContext(name, requestedRepo(req), limit);
|
|
1476
|
+
if (result?.error) {
|
|
1477
|
+
res.status(404).json({ error: result.error });
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1480
|
+
res.json(result);
|
|
1481
|
+
}
|
|
1482
|
+
catch (err) {
|
|
1483
|
+
res
|
|
1484
|
+
.status(statusFromError(err))
|
|
1485
|
+
.json({ error: err.message || 'Failed to query feature cluster detail' });
|
|
1486
|
+
}
|
|
1487
|
+
});
|
|
1488
|
+
// Feature cluster impact/context pack
|
|
1489
|
+
app.get(['/api/feature-impact', '/api/cluster-impact'], async (req, res) => {
|
|
1490
|
+
try {
|
|
1491
|
+
const name = String(req.query.name ?? '').trim();
|
|
1492
|
+
if (!name) {
|
|
1493
|
+
res.status(400).json({ error: 'Missing "name" query parameter' });
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
const limit = req.query.limit ? Number.parseInt(String(req.query.limit), 10) : undefined;
|
|
1497
|
+
const directionText = String(req.query.direction ?? 'upstream');
|
|
1498
|
+
const direction = directionText === 'downstream' || directionText === 'both' ? directionText : 'upstream';
|
|
1499
|
+
const result = await backend.queryFeatureImpact(name, requestedRepo(req), direction, limit);
|
|
1500
|
+
if (result?.error) {
|
|
1501
|
+
res.status(404).json({ error: result.error });
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
res.json(result);
|
|
1505
|
+
}
|
|
1506
|
+
catch (err) {
|
|
1507
|
+
res
|
|
1508
|
+
.status(statusFromError(err))
|
|
1509
|
+
.json({ error: err.message || 'Failed to query feature cluster impact' });
|
|
1510
|
+
}
|
|
1511
|
+
});
|
|
1329
1512
|
// ── Analyze API ──────────────────────────────────────────────────────
|
|
1330
1513
|
// POST /api/analyze — start a new analysis job
|
|
1331
1514
|
app.post('/api/analyze', async (req, res) => {
|
|
@@ -1697,6 +1880,7 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1697
1880
|
embedJobManager.cancelJob(req.params.jobId, 'Cancelled by user');
|
|
1698
1881
|
res.json({ id: job.id, status: 'failed', error: 'Cancelled by user' });
|
|
1699
1882
|
});
|
|
1883
|
+
webDashboard = mountWebDashboard(app, { mode: options.web ?? 'local' });
|
|
1700
1884
|
// Global error handler — catch anything the route handlers miss
|
|
1701
1885
|
app.use((err, _req, res, _next) => {
|
|
1702
1886
|
console.error('Unhandled error:', err);
|
|
@@ -1707,7 +1891,22 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1707
1891
|
await new Promise((resolve, reject) => {
|
|
1708
1892
|
const server = app.listen(port, host, () => {
|
|
1709
1893
|
const displayHost = host === '::' || host === '0.0.0.0' ? 'localhost' : host;
|
|
1710
|
-
|
|
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
|
+
}
|
|
1711
1910
|
resolve();
|
|
1712
1911
|
});
|
|
1713
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;
|