@lzehrung/codegraph 2.1.2 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/codegraph-skill/codegraph/SKILL.md +21 -2
- package/dist/agent/explore.d.ts.map +1 -1
- package/dist/agent/explore.js +6 -1
- package/dist/agent/explore.js.map +1 -1
- package/dist/agent/handles.d.ts.map +1 -1
- package/dist/agent/handles.js +2 -8
- package/dist/agent/handles.js.map +1 -1
- package/dist/agent/orient.js +0 -16
- package/dist/agent/orient.js.map +1 -1
- package/dist/agent/query-index/store.d.ts +2 -0
- package/dist/agent/query-index/store.d.ts.map +1 -1
- package/dist/agent/query-index/store.js +4 -0
- package/dist/agent/query-index/store.js.map +1 -1
- package/dist/agent/session.js +3 -3
- package/dist/agent/session.js.map +1 -1
- package/dist/bin/cli.js +7303 -5659
- package/dist/bin/queryIndexWorker.js +357 -54
- package/dist/chunking/chunkSplit.js +0 -15
- package/dist/chunking/chunkSplit.js.map +1 -1
- package/dist/cli/commandCatalog.d.ts.map +1 -1
- package/dist/cli/commandCatalog.js +1 -0
- package/dist/cli/commandCatalog.js.map +1 -1
- package/dist/cli/commandTable.d.ts.map +1 -1
- package/dist/cli/commandTable.js +17 -0
- package/dist/cli/commandTable.js.map +1 -1
- package/dist/cli/compileCache.d.ts +5 -0
- package/dist/cli/compileCache.d.ts.map +1 -1
- package/dist/cli/compileCache.js +18 -0
- package/dist/cli/compileCache.js.map +1 -1
- package/dist/cli/context.d.ts.map +1 -1
- package/dist/cli/context.js +0 -1
- package/dist/cli/context.js.map +1 -1
- package/dist/cli/help.d.ts +2 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +23 -2
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/indexPolicy.d.ts.map +1 -1
- package/dist/cli/indexPolicy.js +1 -0
- package/dist/cli/indexPolicy.js.map +1 -1
- package/dist/cli/inspect.d.ts.map +1 -1
- package/dist/cli/inspect.js +5 -2
- package/dist/cli/inspect.js.map +1 -1
- package/dist/cli/options.d.ts +1 -1
- package/dist/cli/options.d.ts.map +1 -1
- package/dist/cli/options.js +31 -1
- package/dist/cli/options.js.map +1 -1
- package/dist/cli/server.d.ts +59 -0
- package/dist/cli/server.d.ts.map +1 -0
- package/dist/cli/server.js +906 -0
- package/dist/cli/server.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/documentLinks.d.ts.map +1 -1
- package/dist/documentLinks.js +0 -1
- package/dist/documentLinks.js.map +1 -1
- package/dist/drift/snapshot.js +1 -1
- package/dist/drift/snapshot.js.map +1 -1
- package/dist/duplicates/scoring.d.ts.map +1 -1
- package/dist/duplicates/scoring.js.map +1 -1
- package/dist/duplicates/unitCache.d.ts.map +1 -1
- package/dist/duplicates/unitCache.js +1 -1
- package/dist/duplicates/unitCache.js.map +1 -1
- package/dist/duplicates/units.d.ts.map +1 -1
- package/dist/duplicates/units.js +2 -3
- package/dist/duplicates/units.js.map +1 -1
- package/dist/duplicates.d.ts.map +1 -1
- package/dist/duplicates.js +0 -6
- package/dist/duplicates.js.map +1 -1
- package/dist/graph-edge-collector.d.ts +0 -2
- package/dist/graph-edge-collector.d.ts.map +1 -1
- package/dist/graph-edge-collector.js +2 -4
- package/dist/graph-edge-collector.js.map +1 -1
- package/dist/graphs/edgeResolution.d.ts.map +1 -1
- package/dist/graphs/specifiers.d.ts +1 -1
- package/dist/graphs/specifiers.d.ts.map +1 -1
- package/dist/graphs/specifiers.js +1 -1
- package/dist/graphs/specifiers.js.map +1 -1
- package/dist/graphs/symbol-graph-detailed.d.ts.map +1 -1
- package/dist/graphs/symbol-graph-detailed.js +0 -1
- package/dist/graphs/symbol-graph-detailed.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/indexer/build-cache/module-cache.d.ts.map +1 -1
- package/dist/indexer/build-cache/module-cache.js +1 -1
- package/dist/indexer/build-cache/module-cache.js.map +1 -1
- package/dist/indexer/build-cache/options.d.ts.map +1 -1
- package/dist/indexer/build-cache/options.js +0 -2
- package/dist/indexer/build-cache/options.js.map +1 -1
- package/dist/indexer/build-cache/project-snapshot.d.ts +9 -1
- package/dist/indexer/build-cache/project-snapshot.d.ts.map +1 -1
- package/dist/indexer/build-cache/project-snapshot.js +1 -5
- package/dist/indexer/build-cache/project-snapshot.js.map +1 -1
- package/dist/indexer/build-index.d.ts.map +1 -1
- package/dist/indexer/build-index.js +59 -44
- package/dist/indexer/build-index.js.map +1 -1
- package/dist/indexer/build-workers.d.ts +29 -2
- package/dist/indexer/build-workers.d.ts.map +1 -1
- package/dist/indexer/build-workers.js +54 -3
- package/dist/indexer/build-workers.js.map +1 -1
- package/dist/indexer/call-hierarchy.d.ts.map +1 -1
- package/dist/indexer/imports.d.ts +0 -3
- package/dist/indexer/imports.d.ts.map +1 -1
- package/dist/indexer/imports.js +1 -1
- package/dist/indexer/imports.js.map +1 -1
- package/dist/indexer/locals-and-exports.d.ts +2 -2
- package/dist/indexer/locals-and-exports.d.ts.map +1 -1
- package/dist/indexer/locals-and-exports.js +2 -3
- package/dist/indexer/locals-and-exports.js.map +1 -1
- package/dist/indexer/navigation-local.d.ts +2 -2
- package/dist/indexer/navigation-local.d.ts.map +1 -1
- package/dist/indexer/navigation-local.js +2 -2
- package/dist/indexer/navigation-local.js.map +1 -1
- package/dist/indexer/navigation-references.d.ts +1 -2
- package/dist/indexer/navigation-references.d.ts.map +1 -1
- package/dist/indexer/navigation-references.js +1 -1
- package/dist/indexer/navigation-references.js.map +1 -1
- package/dist/indexer/navigation.d.ts.map +1 -1
- package/dist/indexer/navigation.js +2 -4
- package/dist/indexer/navigation.js.map +1 -1
- package/dist/indexer/parse-context.d.ts +1 -4
- package/dist/indexer/parse-context.d.ts.map +1 -1
- package/dist/indexer/parse-context.js +11 -6
- package/dist/indexer/parse-context.js.map +1 -1
- package/dist/indexer/scope.d.ts +2 -2
- package/dist/indexer/scope.d.ts.map +1 -1
- package/dist/indexer/scope.js +1 -1
- package/dist/indexer/scope.js.map +1 -1
- package/dist/indexer.d.ts +3 -6
- package/dist/indexer.d.ts.map +1 -1
- package/dist/indexer.js +5 -8
- package/dist/indexer.js.map +1 -1
- package/dist/installer/registry.js +1 -27
- package/dist/installer/registry.js.map +1 -1
- package/dist/languages/definitions/c.d.ts.map +1 -1
- package/dist/languages/definitions/c.js +0 -1
- package/dist/languages/definitions/c.js.map +1 -1
- package/dist/languages/definitions/cFamily.d.ts +0 -1
- package/dist/languages/definitions/cFamily.d.ts.map +1 -1
- package/dist/languages/definitions/cFamily.js +0 -2
- package/dist/languages/definitions/cFamily.js.map +1 -1
- package/dist/languages/definitions/cpp.d.ts.map +1 -1
- package/dist/languages/definitions/cpp.js +0 -1
- package/dist/languages/definitions/cpp.js.map +1 -1
- package/dist/languages/definitions/csharp.d.ts.map +1 -1
- package/dist/languages/definitions/csharp.js +0 -2
- package/dist/languages/definitions/csharp.js.map +1 -1
- package/dist/languages/definitions/css.d.ts.map +1 -1
- package/dist/languages/definitions/css.js +0 -2
- package/dist/languages/definitions/css.js.map +1 -1
- package/dist/languages/definitions/go.d.ts.map +1 -1
- package/dist/languages/definitions/go.js +0 -2
- package/dist/languages/definitions/go.js.map +1 -1
- package/dist/languages/definitions/html.d.ts.map +1 -1
- package/dist/languages/definitions/html.js +0 -2
- package/dist/languages/definitions/html.js.map +1 -1
- package/dist/languages/definitions/htmlStub.d.ts.map +1 -1
- package/dist/languages/definitions/htmlStub.js +0 -2
- package/dist/languages/definitions/htmlStub.js.map +1 -1
- package/dist/languages/definitions/java.d.ts.map +1 -1
- package/dist/languages/definitions/java.js +0 -2
- package/dist/languages/definitions/java.js.map +1 -1
- package/dist/languages/definitions/javascript.d.ts.map +1 -1
- package/dist/languages/definitions/javascript.js +0 -2
- package/dist/languages/definitions/javascript.js.map +1 -1
- package/dist/languages/definitions/kotlin.d.ts.map +1 -1
- package/dist/languages/definitions/kotlin.js +0 -2
- package/dist/languages/definitions/kotlin.js.map +1 -1
- package/dist/languages/definitions/less.d.ts.map +1 -1
- package/dist/languages/definitions/less.js +0 -2
- package/dist/languages/definitions/less.js.map +1 -1
- package/dist/languages/definitions/php.d.ts.map +1 -1
- package/dist/languages/definitions/php.js +0 -2
- package/dist/languages/definitions/php.js.map +1 -1
- package/dist/languages/definitions/python.d.ts.map +1 -1
- package/dist/languages/definitions/python.js +0 -2
- package/dist/languages/definitions/python.js.map +1 -1
- package/dist/languages/definitions/ruby.d.ts.map +1 -1
- package/dist/languages/definitions/ruby.js +0 -2
- package/dist/languages/definitions/ruby.js.map +1 -1
- package/dist/languages/definitions/rust.d.ts.map +1 -1
- package/dist/languages/definitions/rust.js +0 -2
- package/dist/languages/definitions/rust.js.map +1 -1
- package/dist/languages/definitions/scss.d.ts.map +1 -1
- package/dist/languages/definitions/scss.js +0 -2
- package/dist/languages/definitions/scss.js.map +1 -1
- package/dist/languages/definitions/sql.d.ts.map +1 -1
- package/dist/languages/definitions/sql.js +0 -2
- package/dist/languages/definitions/sql.js.map +1 -1
- package/dist/languages/definitions/svelte.d.ts.map +1 -1
- package/dist/languages/definitions/svelte.js +0 -2
- package/dist/languages/definitions/svelte.js.map +1 -1
- package/dist/languages/definitions/swift.d.ts.map +1 -1
- package/dist/languages/definitions/swift.js +0 -2
- package/dist/languages/definitions/swift.js.map +1 -1
- package/dist/languages/definitions/typescript.d.ts.map +1 -1
- package/dist/languages/definitions/typescript.js +0 -3
- package/dist/languages/definitions/typescript.js.map +1 -1
- package/dist/languages/definitions/vue.d.ts.map +1 -1
- package/dist/languages/definitions/vue.js +0 -2
- package/dist/languages/definitions/vue.js.map +1 -1
- package/dist/languages/definitions/zig.d.ts.map +1 -1
- package/dist/languages/definitions/zig.js +0 -2
- package/dist/languages/definitions/zig.js.map +1 -1
- package/dist/languages/filePrep.d.ts +0 -2
- package/dist/languages/filePrep.d.ts.map +1 -1
- package/dist/languages/filePrep.js +0 -1
- package/dist/languages/filePrep.js.map +1 -1
- package/dist/languages/types.d.ts +19 -5
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages.d.ts +1 -3
- package/dist/languages.d.ts.map +1 -1
- package/dist/languages.js +0 -7
- package/dist/languages.js.map +1 -1
- package/dist/lifecycle/manifest.d.ts.map +1 -1
- package/dist/lifecycle/manifest.js +21 -4
- package/dist/lifecycle/manifest.js.map +1 -1
- package/dist/mcp/handlers.d.ts +275 -0
- package/dist/mcp/handlers.d.ts.map +1 -0
- package/dist/mcp/handlers.js +657 -0
- package/dist/mcp/handlers.js.map +1 -0
- package/dist/mcp/httpTransport.d.ts +14 -0
- package/dist/mcp/httpTransport.d.ts.map +1 -0
- package/dist/mcp/httpTransport.js +185 -0
- package/dist/mcp/httpTransport.js.map +1 -0
- package/dist/mcp/legacySessions.d.ts +38 -0
- package/dist/mcp/legacySessions.d.ts.map +1 -0
- package/dist/mcp/legacySessions.js +264 -0
- package/dist/mcp/legacySessions.js.map +1 -0
- package/dist/mcp/lifecycleHealth.d.ts +12 -0
- package/dist/mcp/lifecycleHealth.d.ts.map +1 -0
- package/dist/mcp/lifecycleHealth.js +37 -0
- package/dist/mcp/lifecycleHealth.js.map +1 -0
- package/dist/mcp/protocol.d.ts +35 -0
- package/dist/mcp/protocol.d.ts.map +1 -0
- package/dist/mcp/protocol.js +577 -0
- package/dist/mcp/protocol.js.map +1 -0
- package/dist/mcp/server.d.ts +9 -306
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +13 -1609
- package/dist/mcp/server.js.map +1 -1
- package/dist/native/bindingLoader.d.ts +27 -0
- package/dist/native/bindingLoader.d.ts.map +1 -1
- package/dist/native/bindingLoader.js +18 -1
- package/dist/native/bindingLoader.js.map +1 -1
- package/dist/native/contracts.d.ts +59 -12
- package/dist/native/contracts.d.ts.map +1 -1
- package/dist/native/execution.d.ts +10 -1
- package/dist/native/execution.d.ts.map +1 -1
- package/dist/native/execution.js +59 -3
- package/dist/native/execution.js.map +1 -1
- package/dist/native/projectedTree.d.ts +20 -5
- package/dist/native/projectedTree.d.ts.map +1 -1
- package/dist/native/projectedTree.js +100 -48
- package/dist/native/projectedTree.js.map +1 -1
- package/dist/native/runtime.d.ts +47 -1
- package/dist/native/runtime.d.ts.map +1 -1
- package/dist/native/runtime.js +151 -8
- package/dist/native/runtime.js.map +1 -1
- package/dist/native/runtimeCache.d.ts +92 -0
- package/dist/native/runtimeCache.d.ts.map +1 -1
- package/dist/native/runtimeCache.js +443 -13
- package/dist/native/runtimeCache.js.map +1 -1
- package/dist/native/treeShape.d.ts +21 -0
- package/dist/native/treeShape.d.ts.map +1 -0
- package/dist/native/treeShape.js +28 -0
- package/dist/native/treeShape.js.map +1 -0
- package/dist/native/treeSitterNative.d.ts +3 -3
- package/dist/native/treeSitterNative.d.ts.map +1 -1
- package/dist/native/treeSitterNative.js +2 -2
- package/dist/native/treeSitterNative.js.map +1 -1
- package/dist/presets.d.ts +7 -1
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +4 -0
- package/dist/presets.js.map +1 -1
- package/dist/review/deleted.js +2 -2
- package/dist/review/deleted.js.map +1 -1
- package/dist/review.js.map +1 -1
- package/dist/sql/extractFacts.d.ts.map +1 -1
- package/dist/sql/extractFacts.js +1 -1
- package/dist/sql/extractFacts.js.map +1 -1
- package/dist/util/lazySymbols.js +1 -1
- package/dist/util/lazySymbols.js.map +1 -1
- package/dist/util/resolution/jvm.js +1 -1
- package/dist/util/resolution/jvm.js.map +1 -1
- package/dist/util/resolution/php.d.ts.map +1 -1
- package/dist/util/resolution/php.js +0 -1
- package/dist/util/resolution/php.js.map +1 -1
- package/dist/util/resolution.d.ts.map +1 -1
- package/dist/util/resolution.js +5 -5
- package/dist/util/resolution.js.map +1 -1
- package/dist/worker/nativeExtractWorker.d.ts +10 -4
- package/dist/worker/nativeExtractWorker.d.ts.map +1 -1
- package/dist/worker/nativeExtractWorker.js +55 -3
- package/dist/worker/nativeExtractWorker.js.map +1 -1
- package/dist/worker/nativeWorkerPool.d.ts +4 -0
- package/dist/worker/nativeWorkerPool.d.ts.map +1 -1
- package/dist/worker/nativeWorkerPool.js +6 -1
- package/dist/worker/nativeWorkerPool.js.map +1 -1
- package/docs/mcp.md +16 -2
- package/package.json +3 -3
- package/dist/languages/definitions/loadLanguage.d.ts +0 -2
- package/dist/languages/definitions/loadLanguage.d.ts.map +0 -1
- package/dist/languages/definitions/loadLanguage.js +0 -2
- package/dist/languages/definitions/loadLanguage.js.map +0 -1
- package/dist/parserBackend.d.ts +0 -57
- package/dist/parserBackend.d.ts.map +0 -1
- package/dist/parserBackend.js +0 -33
- package/dist/parserBackend.js.map +0 -1
package/dist/mcp/server.js
CHANGED
|
@@ -1,859 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
-
import fs from "node:fs/promises";
|
|
4
|
-
import { createServer } from "node:http";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import { Transform } from "node:stream";
|
|
7
|
-
import { createMcpHandler, isInitializeRequest, isLegacyRequest, ProtocolError, ProtocolErrorCode, Server, } from "@modelcontextprotocol/server";
|
|
8
|
-
import { StdioServerTransport, serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
9
|
-
import { NodeStreamableHTTPServerTransport, originValidation, toNodeHandler, toWebRequest, } from "@modelcontextprotocol/node";
|
|
10
|
-
import { z } from "zod";
|
|
11
|
-
import { buildCodegraphArtifactWithSession } from "../agent/artifact.js";
|
|
12
|
-
import { explainCodegraphTargetWithSession } from "../agent/explain.js";
|
|
13
|
-
import { getCodegraphFileViewWithSession, MAX_FILE_VIEW_BYTES, MAX_FILE_VIEW_LINES, } from "../agent/fileView.js";
|
|
14
|
-
import { exploreCodegraphWithSession } from "../agent/explore.js";
|
|
15
|
-
import { orientCodegraphWithSession } from "../agent/orient.js";
|
|
16
|
-
import { getCodegraphPacketWithSession } from "../agent/packet.js";
|
|
17
|
-
import { MAX_GRAPH_DEPTH, searchCodegraphWithSession } from "../agent/search.js";
|
|
18
|
-
import { workspaceSymbolsWithSession } from "../agent/workspaceSymbols.js";
|
|
19
|
-
import { findImplementationsWithSession, findSubtypesWithSession, findSupertypesWithSession, } from "../agent/typeHierarchy.js";
|
|
20
|
-
import { findCalleesWithSession, findCallersWithSession } from "../agent/callHierarchy.js";
|
|
21
|
-
import { previewRenameWithSession } from "../agent/renamePreview.js";
|
|
22
|
-
import { buildRefactorPlanWithSession } from "../agent/refactorPlan.js";
|
|
23
|
-
import { parseAgentSymbolHandle } from "../agent/handles.js";
|
|
24
|
-
import { requireSemanticSymbol, resolveSemanticSymbol } from "../agent/semanticSymbols.js";
|
|
25
|
-
import { getDependencies, getReverseDependencies, getShortestPath } from "../graphs/queries.js";
|
|
26
|
-
import { findReferences, goToDefinition } from "../indexer/navigation.js";
|
|
27
|
-
import { parseQualifiedSymbolPath } from "../indexer/symbols.js";
|
|
28
|
-
import { analyzeImpactFromDiff } from "../impact/index.js";
|
|
29
|
-
import { DEFAULT_BOUNDED_IMPACT_BUDGETS } from "../impact/budgets.js";
|
|
30
|
-
import { buildReviewReport } from "../review.js";
|
|
31
|
-
import { boundReviewReportForTransport } from "../review/types.js";
|
|
32
|
-
import { SQLITE_ARTIFACT_FILE_SIGNATURES_METADATA_KEY, queryGraphSqliteRaw } from "../sqlite.js";
|
|
33
|
-
import { boundList, countOmitted } from "../presentation/bounds.js";
|
|
34
|
-
import { isPlainRecord } from "../util/guards.js";
|
|
35
|
-
import { toProjectDisplayPath } from "../util/paths.js";
|
|
36
|
-
import { errorMessage } from "../util/errors.js";
|
|
37
|
-
import { assertNoPrebuiltSessionWithBuildOptions, createAgentSession, listAgentSessionFiles, } from "../agent/session.js";
|
|
38
|
-
import { mapLimit } from "../util/concurrency.js";
|
|
39
|
-
import { assertRealPathCandidateWithinRoot, resolveProjectFile } from "../util/confinedFile.js";
|
|
40
|
-
import { SymbolKind } from "../indexer/types.js";
|
|
41
|
-
import { DEFAULT_WORKSPACE_SYMBOL_LIMIT, MAX_WORKSPACE_SYMBOL_LIMIT } from "../indexer/workspace-symbols.js";
|
|
42
|
-
import { assertMcpSqliteQueryResourceBounded, DEFAULT_SQLITE_BYTE_LIMIT, normalizeSqliteRowLimit, } from "./sqliteGuard.js";
|
|
43
|
-
import { DEFAULT_CALL_HIERARCHY_LIMIT, MAX_CALL_HIERARCHY_LIMIT, DEFAULT_MCP_COLLECTION_LIMIT, DEFAULT_TYPE_HIERARCHY_LIMIT, listCodegraphMcpTools, MAX_TYPE_HIERARCHY_LIMIT, MAX_MCP_COLLECTION_LIMIT, MAX_RENAME_PREVIEW_EDITS, MCP_TOOL_REGISTRY, MAX_REFACTOR_PLAN_LIMIT, } from "./tools.js";
|
|
44
|
-
import { buildAllowedHostHeaders, buildAllowedOriginHostnames, closeHttpServer, emptyAllowedHostHeaderRules, formatHostForUrl, getHeaderValue, getHttpServerPort, getRequestPath, isAllowedHostHeader, listenOnHttpServer, readJsonRequestBody, waitForHttpServerClose, writeJsonResponse, writeJsonRpcError, } from "./http.js";
|
|
1
|
+
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
45
2
|
import { getCurrentNativeBindingOrigin } from "../native/runtime.js";
|
|
46
|
-
import { captureCodegraphRuntimeIdentity
|
|
47
|
-
|
|
48
|
-
import {
|
|
3
|
+
import { captureCodegraphRuntimeIdentity } from "../runtimeIdentity.js";
|
|
4
|
+
import { createWarmedCodegraphMcpResources } from "./handlers.js";
|
|
5
|
+
import { assertMcpToolTimeout, createCodegraphMcpProtocolFactory, createCodegraphMcpStdioTransport, DEFAULT_MCP_TOOL_CONCURRENCY, DEFAULT_MCP_TOOL_TIMEOUT_MS, } from "./protocol.js";
|
|
6
|
+
import { startCodegraphMcpHttpServer } from "./httpTransport.js";
|
|
7
|
+
import { waitForHttpServerClose } from "./http.js";
|
|
49
8
|
import { awaitStdioMcpLifecycle, DEFAULT_MCP_STDIO_IDLE_TIMEOUT_MS } from "./stdioLifecycle.js";
|
|
50
|
-
export
|
|
51
|
-
export
|
|
52
|
-
export
|
|
53
|
-
export
|
|
54
|
-
export
|
|
55
|
-
|
|
56
|
-
const MAX_MCP_STDIO_FRAME_BYTES = 10 * 1024 * 1024;
|
|
57
|
-
const MAX_MCP_TOOL_TIMEOUT_MS = 2_147_483_647;
|
|
58
|
-
export const DEFAULT_MCP_TOOL_CONCURRENCY = 4;
|
|
59
|
-
function normalizeMcpToolConcurrency(value) {
|
|
60
|
-
if (!Number.isFinite(value))
|
|
61
|
-
return DEFAULT_MCP_TOOL_CONCURRENCY;
|
|
62
|
-
return Math.max(1, Math.floor(value));
|
|
63
|
-
}
|
|
64
|
-
function assertMcpHttpBodyTimeout(value) {
|
|
65
|
-
if (!Number.isSafeInteger(value) || value < 1 || value > MAX_MCP_HTTP_BODY_TIMEOUT_MS) {
|
|
66
|
-
throw new RangeError(`httpBodyTimeoutMs must be a positive integer no greater than ${MAX_MCP_HTTP_BODY_TIMEOUT_MS}.`);
|
|
67
|
-
}
|
|
68
|
-
return value;
|
|
69
|
-
}
|
|
70
|
-
function assertMcpToolTimeout(value) {
|
|
71
|
-
if (!Number.isSafeInteger(value) || value < 0 || value > MAX_MCP_TOOL_TIMEOUT_MS) {
|
|
72
|
-
throw new RangeError(`mcpToolTimeoutMs must be a whole number from 0 through ${MAX_MCP_TOOL_TIMEOUT_MS}; 0 disables the deadline.`);
|
|
73
|
-
}
|
|
74
|
-
return value;
|
|
75
|
-
}
|
|
76
|
-
const legacyRequestAbortStorage = new AsyncLocalStorage();
|
|
77
|
-
const MAX_MCP_FRESHNESS_CHANGED_FILES = 25;
|
|
78
|
-
const MAX_MCP_FRESHNESS_RETRIES = 3;
|
|
79
|
-
const SQLITE_ARTIFACT_STAT_CONCURRENCY = 64;
|
|
80
|
-
function assertMcpSessionOptions(options) {
|
|
81
|
-
assertNoPrebuiltSessionWithBuildOptions(options, "MCP server options");
|
|
82
|
-
}
|
|
83
|
-
function createCodegraphMcpSession(options, root) {
|
|
84
|
-
assertMcpSessionOptions(options);
|
|
85
|
-
return (options.session ??
|
|
86
|
-
createAgentSession({
|
|
87
|
-
root,
|
|
88
|
-
...(options.buildOptions ? { buildOptions: options.buildOptions } : {}),
|
|
89
|
-
freshness: { policy: "auto" },
|
|
90
|
-
}));
|
|
91
|
-
}
|
|
92
|
-
function startCodegraphMcpWarmup(session, warmup) {
|
|
93
|
-
if (warmup === "base") {
|
|
94
|
-
return session.loadProject({ symbolGraph: "skip" });
|
|
95
|
-
}
|
|
96
|
-
if (warmup === "symbols") {
|
|
97
|
-
return session.loadProject();
|
|
98
|
-
}
|
|
99
|
-
return undefined;
|
|
100
|
-
}
|
|
101
|
-
async function createWarmedCodegraphMcpResources(options) {
|
|
102
|
-
const root = path.resolve(options.root);
|
|
103
|
-
const session = createCodegraphMcpSession(options, root);
|
|
104
|
-
await startCodegraphMcpWarmup(session, options.warmup);
|
|
105
|
-
const { warmup, host, port, onHttpListen, ...handlerOptions } = options;
|
|
106
|
-
void warmup;
|
|
107
|
-
void host;
|
|
108
|
-
void port;
|
|
109
|
-
void onHttpListen;
|
|
110
|
-
return {
|
|
111
|
-
handlers: createCodegraphMcpHandlersForSession({ ...handlerOptions, root }, session),
|
|
112
|
-
session,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
const MCP_HTTP_PATH = "/mcp";
|
|
116
|
-
const MAX_MCP_HTTP_BODY_BYTES = 1_000_000;
|
|
117
|
-
export function createCodegraphMcpHandlers(options) {
|
|
118
|
-
const root = path.resolve(options.root);
|
|
119
|
-
const session = createCodegraphMcpSession(options, root);
|
|
120
|
-
return createCodegraphMcpHandlersForSession(options, session);
|
|
121
|
-
}
|
|
122
|
-
function createCodegraphMcpHandlersForSession(options, session) {
|
|
123
|
-
const root = path.resolve(options.root);
|
|
124
|
-
const readOnly = options.readOnly ?? true;
|
|
125
|
-
const realRoot = fs.realpath(root);
|
|
126
|
-
const configuredSqlitePath = options.artifactPath
|
|
127
|
-
? resolveArtifactSqlitePathCandidate(root, options.artifactPath)
|
|
128
|
-
: undefined;
|
|
129
|
-
const configuredSqliteOutDir = configuredSqlitePath ? path.dirname(configuredSqlitePath) : undefined;
|
|
130
|
-
const configuredSqliteCanRefresh = options.artifactPath ? !/\.(sqlite|db)$/i.test(options.artifactPath) : false;
|
|
131
|
-
let sqlitePath = configuredSqlitePath;
|
|
132
|
-
let sqliteOutDir = configuredSqliteOutDir;
|
|
133
|
-
let sqliteCanRefresh = configuredSqliteCanRefresh;
|
|
134
|
-
let refreshPromise;
|
|
135
|
-
let refreshEpoch = 0;
|
|
136
|
-
let artifactWritePromise;
|
|
137
|
-
const withArtifactWriteLock = async (operation) => {
|
|
138
|
-
const previous = artifactWritePromise;
|
|
139
|
-
const complete = Promise.withResolvers();
|
|
140
|
-
artifactWritePromise = complete.promise;
|
|
141
|
-
try {
|
|
142
|
-
if (previous)
|
|
143
|
-
await previous.catch(() => undefined);
|
|
144
|
-
return await operation();
|
|
145
|
-
}
|
|
146
|
-
finally {
|
|
147
|
-
complete.resolve();
|
|
148
|
-
if (artifactWritePromise === complete.promise)
|
|
149
|
-
artifactWritePromise = undefined;
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
const relative = (file) => toProjectDisplayPath(root, file);
|
|
153
|
-
const boundedLimit = (limit, fallback, max) => {
|
|
154
|
-
if (typeof limit !== "number" || !Number.isFinite(limit))
|
|
155
|
-
return fallback;
|
|
156
|
-
return Math.min(max, Math.max(0, Math.floor(limit)));
|
|
157
|
-
};
|
|
158
|
-
const staleFreshness = (files, reason) => {
|
|
159
|
-
const changedFiles = [...files].sort();
|
|
160
|
-
const boundedChangedFiles = changedFiles.slice(0, MAX_MCP_FRESHNESS_CHANGED_FILES);
|
|
161
|
-
return {
|
|
162
|
-
state: "stale",
|
|
163
|
-
changedFiles: boundedChangedFiles,
|
|
164
|
-
changedFileCount: changedFiles.length,
|
|
165
|
-
omittedChangedFileCount: countOmitted(changedFiles.length, boundedChangedFiles.length),
|
|
166
|
-
reason,
|
|
167
|
-
};
|
|
168
|
-
};
|
|
169
|
-
const checkMcpFreshness = async () => {
|
|
170
|
-
if (session.checkFreshness)
|
|
171
|
-
return await session.checkFreshness();
|
|
172
|
-
return { state: "fresh" };
|
|
173
|
-
};
|
|
174
|
-
const withFreshness = async (run) => {
|
|
175
|
-
for (let attempt = 0; attempt < MAX_MCP_FRESHNESS_RETRIES; attempt += 1) {
|
|
176
|
-
if (refreshPromise)
|
|
177
|
-
await refreshPromise;
|
|
178
|
-
const epoch = refreshEpoch;
|
|
179
|
-
const freshness = await checkMcpFreshness();
|
|
180
|
-
const result = await run();
|
|
181
|
-
if (epoch === refreshEpoch && !refreshPromise)
|
|
182
|
-
return { ...result, freshness };
|
|
183
|
-
}
|
|
184
|
-
throw new Error("Workspace refresh changed repeatedly while serving the request; retry after refresh completes.");
|
|
185
|
-
};
|
|
186
|
-
const formatSqliteFreshnessError = (freshness) => {
|
|
187
|
-
if (freshness.state === "fresh")
|
|
188
|
-
return "SQLite artifact freshness check unexpectedly failed.";
|
|
189
|
-
const reason = freshness.state === "stale" ? freshness.reason : "workspace changed after artifact build";
|
|
190
|
-
const changed = freshness.changedFiles.length ? ` Changed files: ${freshness.changedFiles.join(", ")}.` : "";
|
|
191
|
-
const omitted = freshness.state === "stale" && freshness.omittedChangedFileCount
|
|
192
|
-
? ` Omitted changed files: ${freshness.omittedChangedFileCount}.`
|
|
193
|
-
: "";
|
|
194
|
-
let action = "run artifact_build";
|
|
195
|
-
if (readOnly) {
|
|
196
|
-
action = "rebuild the artifact with write access enabled";
|
|
197
|
-
}
|
|
198
|
-
if (freshness.state === "stale") {
|
|
199
|
-
action = `run refresh_index, then ${action}`;
|
|
200
|
-
}
|
|
201
|
-
return `SQLite artifact is stale; ${action} before query_sqlite. ${reason}.${changed}${omitted}`;
|
|
202
|
-
};
|
|
203
|
-
const canRefreshSqliteArtifact = () => {
|
|
204
|
-
if (!sqlitePath || !sqliteOutDir || readOnly || !sqliteCanRefresh)
|
|
205
|
-
return false;
|
|
206
|
-
return path.basename(sqlitePath) === "codegraph.sqlite";
|
|
207
|
-
};
|
|
208
|
-
const rebuildSqliteArtifactForQuery = async () => await withArtifactWriteLock(async () => {
|
|
209
|
-
if (!sqliteOutDir)
|
|
210
|
-
throw new Error("SQLite artifact output directory is unavailable.");
|
|
211
|
-
const outDir = await assertWritableDirectoryRealPathWithinRoot(await realRoot, root, sqliteOutDir, "Artifact output directory");
|
|
212
|
-
const result = await buildCodegraphArtifactWithSession(session, {
|
|
213
|
-
root,
|
|
214
|
-
outDir,
|
|
215
|
-
filterOutDir: outDir,
|
|
216
|
-
sqlite: true,
|
|
217
|
-
force: true,
|
|
218
|
-
});
|
|
219
|
-
const sqliteArtifact = result.artifacts.sqlite;
|
|
220
|
-
if (!sqliteArtifact)
|
|
221
|
-
throw new Error("SQLite artifact refresh did not produce a SQLite file.");
|
|
222
|
-
sqlitePath = path.join(result.outDir, sqliteArtifact);
|
|
223
|
-
sqliteOutDir = result.outDir;
|
|
224
|
-
});
|
|
225
|
-
const refreshSqliteArtifactForQuery = async (freshness, refreshOptions) => {
|
|
226
|
-
if (freshness.state === "fresh")
|
|
227
|
-
return freshness;
|
|
228
|
-
if (freshness.state === "stale" && !refreshOptions?.allowStaleRebuild) {
|
|
229
|
-
throw new Error(formatSqliteFreshnessError(freshness));
|
|
230
|
-
}
|
|
231
|
-
if (!canRefreshSqliteArtifact())
|
|
232
|
-
throw new Error(formatSqliteFreshnessError(freshness));
|
|
233
|
-
await rebuildSqliteArtifactForQuery();
|
|
234
|
-
return { state: "refreshed", changedFiles: freshness.changedFiles };
|
|
235
|
-
};
|
|
236
|
-
const readSqliteArtifactSignatures = async (realSqlitePath) => {
|
|
237
|
-
const result = await queryGraphSqliteRaw(realSqlitePath, "SELECT value FROM graph_metadata WHERE key = ?;", [SQLITE_ARTIFACT_FILE_SIGNATURES_METADATA_KEY], { maxRows: 1 });
|
|
238
|
-
const rawValue = result.rows[0]?.[0];
|
|
239
|
-
if (typeof rawValue !== "string")
|
|
240
|
-
return null;
|
|
241
|
-
let parsed;
|
|
242
|
-
try {
|
|
243
|
-
parsed = JSON.parse(rawValue);
|
|
244
|
-
}
|
|
245
|
-
catch {
|
|
246
|
-
return null;
|
|
247
|
-
}
|
|
248
|
-
if (!Array.isArray(parsed))
|
|
249
|
-
return null;
|
|
250
|
-
const signatures = [];
|
|
251
|
-
for (const value of parsed) {
|
|
252
|
-
if (!isPlainRecord(value))
|
|
253
|
-
continue;
|
|
254
|
-
if (typeof value.path !== "string")
|
|
255
|
-
continue;
|
|
256
|
-
if (typeof value.size !== "number" || typeof value.mtimeMs !== "number")
|
|
257
|
-
continue;
|
|
258
|
-
signatures.push({ path: value.path, size: value.size, mtimeMs: value.mtimeMs });
|
|
259
|
-
}
|
|
260
|
-
return signatures;
|
|
261
|
-
};
|
|
262
|
-
const isFileInsideDirectory = (file, directory) => {
|
|
263
|
-
const relativeFile = path.relative(directory, file);
|
|
264
|
-
if (!relativeFile)
|
|
265
|
-
return true;
|
|
266
|
-
return !relativeFile.startsWith("..") && !path.isAbsolute(relativeFile);
|
|
267
|
-
};
|
|
268
|
-
const collectCurrentSqliteArtifactSignatures = async () => {
|
|
269
|
-
const outputDirectories = [];
|
|
270
|
-
if (sqliteOutDir) {
|
|
271
|
-
outputDirectories.push(sqliteOutDir);
|
|
272
|
-
const lexicalOutDir = path.resolve(root, path.relative(await realRoot, sqliteOutDir));
|
|
273
|
-
outputDirectories.push(lexicalOutDir);
|
|
274
|
-
}
|
|
275
|
-
let discoveredFiles;
|
|
276
|
-
if (session.discoverFiles) {
|
|
277
|
-
discoveredFiles = await session.discoverFiles();
|
|
278
|
-
}
|
|
279
|
-
else if (options.session) {
|
|
280
|
-
throw new Error("MCP session does not expose live file discovery for SQLite freshness.");
|
|
281
|
-
}
|
|
282
|
-
else {
|
|
283
|
-
discoveredFiles = await listAgentSessionFiles({
|
|
284
|
-
root,
|
|
285
|
-
...(options.buildOptions ? { buildOptions: options.buildOptions } : {}),
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
const currentFiles = discoveredFiles.filter((file) => !outputDirectories.some((directory) => isFileInsideDirectory(file, directory)));
|
|
289
|
-
const signatures = new Map();
|
|
290
|
-
await mapLimit(currentFiles, SQLITE_ARTIFACT_STAT_CONCURRENCY, async (file) => {
|
|
291
|
-
try {
|
|
292
|
-
const stat = await fs.stat(file);
|
|
293
|
-
if (!stat.isFile())
|
|
294
|
-
return;
|
|
295
|
-
signatures.set(relative(file), { path: file, size: stat.size, mtimeMs: stat.mtimeMs });
|
|
296
|
-
}
|
|
297
|
-
catch (error) {
|
|
298
|
-
if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
throw error;
|
|
302
|
-
}
|
|
303
|
-
});
|
|
304
|
-
return signatures;
|
|
305
|
-
};
|
|
306
|
-
const checkSqliteArtifactFreshness = async (realSqlitePath) => {
|
|
307
|
-
const storedSignatures = await readSqliteArtifactSignatures(realSqlitePath);
|
|
308
|
-
if (!storedSignatures)
|
|
309
|
-
return staleFreshness([], "SQLite artifact has no freshness baseline");
|
|
310
|
-
const storedByFile = new Map();
|
|
311
|
-
for (const signature of storedSignatures) {
|
|
312
|
-
storedByFile.set(relative(signature.path), signature);
|
|
313
|
-
}
|
|
314
|
-
const currentByFile = await collectCurrentSqliteArtifactSignatures();
|
|
315
|
-
const changedFiles = [];
|
|
316
|
-
for (const [file, currentSignature] of currentByFile.entries()) {
|
|
317
|
-
const storedSignature = storedByFile.get(file);
|
|
318
|
-
if (!storedSignature) {
|
|
319
|
-
changedFiles.push(file);
|
|
320
|
-
continue;
|
|
321
|
-
}
|
|
322
|
-
if (storedSignature.size !== currentSignature.size || storedSignature.mtimeMs !== currentSignature.mtimeMs) {
|
|
323
|
-
changedFiles.push(file);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
for (const file of storedByFile.keys()) {
|
|
327
|
-
if (currentByFile.has(file))
|
|
328
|
-
continue;
|
|
329
|
-
changedFiles.push(file);
|
|
330
|
-
}
|
|
331
|
-
if (!changedFiles.length)
|
|
332
|
-
return { state: "fresh" };
|
|
333
|
-
return staleFreshness(changedFiles, "SQLite artifact is older than files on disk");
|
|
334
|
-
};
|
|
335
|
-
const collectMcpDependencyEntries = async (request, collectEntries) => {
|
|
336
|
-
const snapshot = await session.loadProject({ symbolGraph: "skip" });
|
|
337
|
-
const limit = boundedLimit(request.limit, DEFAULT_MCP_COLLECTION_LIMIT, MAX_MCP_COLLECTION_LIMIT);
|
|
338
|
-
const queryOptions = {
|
|
339
|
-
...(request.depth !== undefined ? { depth: request.depth } : {}),
|
|
340
|
-
// Probe one entry past the display limit so `truncated` is exact
|
|
341
|
-
// rather than a `results.length === limit` heuristic (which cannot
|
|
342
|
-
// tell "exactly limit reachable files" apart from "more exist"),
|
|
343
|
-
// without re-walking the whole reachable graph for an exact total -
|
|
344
|
-
// see finding #44.
|
|
345
|
-
limit: limit + 1,
|
|
346
|
-
};
|
|
347
|
-
const resolvedSymbol = resolveSemanticSymbol(snapshot, request.file);
|
|
348
|
-
let targetFile;
|
|
349
|
-
if (resolvedSymbol) {
|
|
350
|
-
targetFile = resolvedSymbol.def.file;
|
|
351
|
-
}
|
|
352
|
-
else if (parseAgentSymbolHandle(request.file) || parseQualifiedSymbolPath(request.file)) {
|
|
353
|
-
targetFile = requireSemanticSymbol(snapshot, request.file).def.file;
|
|
354
|
-
}
|
|
355
|
-
else {
|
|
356
|
-
targetFile = await resolveProjectFile(await realRoot, root, request.file);
|
|
357
|
-
}
|
|
358
|
-
const collected = collectEntries(snapshot.fileGraph, targetFile, queryOptions);
|
|
359
|
-
const { items, omitted } = boundList(collected, limit);
|
|
360
|
-
const entries = items.map((dependency) => ({
|
|
361
|
-
file: relative(dependency.file),
|
|
362
|
-
depth: dependency.depth,
|
|
363
|
-
}));
|
|
364
|
-
return {
|
|
365
|
-
entries,
|
|
366
|
-
limit,
|
|
367
|
-
totalSeen: collected.length,
|
|
368
|
-
truncated: Boolean(omitted),
|
|
369
|
-
omitted,
|
|
370
|
-
};
|
|
371
|
-
};
|
|
372
|
-
const calls = async (request) => {
|
|
373
|
-
const findCalls = request.direction === "callers" ? findCallersWithSession : findCalleesWithSession;
|
|
374
|
-
return await findCalls(session, {
|
|
375
|
-
root,
|
|
376
|
-
handle: request.handle,
|
|
377
|
-
...(request.depth !== undefined ? { depth: request.depth } : {}),
|
|
378
|
-
limit: boundedLimit(request.limit, DEFAULT_CALL_HIERARCHY_LIMIT, MAX_CALL_HIERARCHY_LIMIT),
|
|
379
|
-
...(request.includeHeuristic !== undefined ? { includeHeuristic: request.includeHeuristic } : {}),
|
|
380
|
-
});
|
|
381
|
-
};
|
|
382
|
-
const typeHierarchy = async (request) => {
|
|
383
|
-
const findTypes = request.direction === "supertypes" ? findSupertypesWithSession : findSubtypesWithSession;
|
|
384
|
-
return await findTypes(session, {
|
|
385
|
-
root,
|
|
386
|
-
handle: request.handle,
|
|
387
|
-
...(request.depth !== undefined ? { depth: request.depth } : {}),
|
|
388
|
-
limit: boundedLimit(request.limit, DEFAULT_TYPE_HIERARCHY_LIMIT, MAX_TYPE_HIERARCHY_LIMIT),
|
|
389
|
-
});
|
|
390
|
-
};
|
|
391
|
-
const boundedReferencesFromResult = (result, limit) => {
|
|
392
|
-
if (result.status !== "ok") {
|
|
393
|
-
return { references: [], limit, totalSeen: 0, truncated: false, omitted: 0 };
|
|
394
|
-
}
|
|
395
|
-
const { items, omitted } = boundList(result.references, limit);
|
|
396
|
-
const references = items.map((reference) => ({ file: relative(reference.file), range: reference.range }));
|
|
397
|
-
return {
|
|
398
|
-
references,
|
|
399
|
-
limit,
|
|
400
|
-
totalSeen: result.references.length,
|
|
401
|
-
truncated: Boolean(omitted),
|
|
402
|
-
omitted,
|
|
403
|
-
};
|
|
404
|
-
};
|
|
405
|
-
const fileDeps = async (request) => await withFreshness(async () => {
|
|
406
|
-
const { entries, ...meta } = await collectMcpDependencyEntries(request, request.direction === "deps" ? getDependencies : getReverseDependencies);
|
|
407
|
-
return request.direction === "deps"
|
|
408
|
-
? { dependencies: entries, ...meta }
|
|
409
|
-
: { reverseDependencies: entries, ...meta };
|
|
410
|
-
});
|
|
411
|
-
return {
|
|
412
|
-
search: async (request) => await withFreshness(async () => await searchCodegraphWithSession(session, {
|
|
413
|
-
root,
|
|
414
|
-
query: request.query,
|
|
415
|
-
...(request.mode !== undefined ? { mode: request.mode } : {}),
|
|
416
|
-
...(request.from !== undefined ? { from: request.from } : {}),
|
|
417
|
-
...(request.depth !== undefined ? { depth: request.depth } : {}),
|
|
418
|
-
...(request.limit !== undefined ? { limit: request.limit } : {}),
|
|
419
|
-
})),
|
|
420
|
-
workspace_symbols: async (request) => await workspaceSymbolsWithSession(session, {
|
|
421
|
-
root,
|
|
422
|
-
query: request.query,
|
|
423
|
-
...(request.kinds !== undefined ? { kinds: request.kinds } : {}),
|
|
424
|
-
...(request.exportedOnly !== undefined ? { exportedOnly: request.exportedOnly } : {}),
|
|
425
|
-
...(request.includeImports !== undefined ? { includeImports: request.includeImports } : {}),
|
|
426
|
-
...(request.fileGlob !== undefined ? { fileGlob: request.fileGlob } : {}),
|
|
427
|
-
limit: boundedLimit(request.limit, DEFAULT_WORKSPACE_SYMBOL_LIMIT, MAX_WORKSPACE_SYMBOL_LIMIT),
|
|
428
|
-
}),
|
|
429
|
-
rename_preview: async (request) => await previewRenameWithSession(session, {
|
|
430
|
-
root,
|
|
431
|
-
handle: request.handle,
|
|
432
|
-
newName: request.newName,
|
|
433
|
-
...(request.includeComments !== undefined ? { includeComments: request.includeComments } : {}),
|
|
434
|
-
...(request.includeStrings !== undefined ? { includeStrings: request.includeStrings } : {}),
|
|
435
|
-
...(request.includeFilenames !== undefined ? { includeFilenames: request.includeFilenames } : {}),
|
|
436
|
-
...(request.maxEdits !== undefined ? { maxEdits: request.maxEdits } : {}),
|
|
437
|
-
}),
|
|
438
|
-
refactor_plan: async (request) => await buildRefactorPlanWithSession(session, {
|
|
439
|
-
root,
|
|
440
|
-
handle: request.handle,
|
|
441
|
-
...(request.renameTo !== undefined ? { renameTo: request.renameTo } : {}),
|
|
442
|
-
...(request.maxReferences !== undefined ? { maxReferences: request.maxReferences } : {}),
|
|
443
|
-
...(request.maxCallers !== undefined ? { maxCallers: request.maxCallers } : {}),
|
|
444
|
-
...(request.maxHierarchy !== undefined ? { maxHierarchy: request.maxHierarchy } : {}),
|
|
445
|
-
...(request.includeSource !== undefined ? { includeSource: request.includeSource } : {}),
|
|
446
|
-
}),
|
|
447
|
-
calls,
|
|
448
|
-
type_hierarchy: typeHierarchy,
|
|
449
|
-
file_deps: fileDeps,
|
|
450
|
-
callers: async (request) => await calls({ direction: "callers", ...request }),
|
|
451
|
-
callees: async (request) => await calls({ direction: "callees", ...request }),
|
|
452
|
-
supertypes: async (request) => await typeHierarchy({ direction: "supertypes", ...request }),
|
|
453
|
-
subtypes: async (request) => await typeHierarchy({ direction: "subtypes", ...request }),
|
|
454
|
-
implementations: async (request) => await findImplementationsWithSession(session, {
|
|
455
|
-
root,
|
|
456
|
-
handle: request.handle,
|
|
457
|
-
limit: boundedLimit(request.limit, DEFAULT_TYPE_HIERARCHY_LIMIT, MAX_TYPE_HIERARCHY_LIMIT),
|
|
458
|
-
}),
|
|
459
|
-
explore: async (request) => await withFreshness(async () => await exploreCodegraphWithSession(session, {
|
|
460
|
-
root,
|
|
461
|
-
query: request.query,
|
|
462
|
-
...(request.limit !== undefined ? { limit: request.limit } : {}),
|
|
463
|
-
...(request.maxPackets !== undefined ? { maxPackets: request.maxPackets } : {}),
|
|
464
|
-
...(request.maxPaths !== undefined ? { maxPaths: request.maxPaths } : {}),
|
|
465
|
-
...(request.includeSource !== undefined ? { includeSource: request.includeSource } : {}),
|
|
466
|
-
})),
|
|
467
|
-
orient: async (request) => await withFreshness(async () => await orientCodegraphWithSession(session, {
|
|
468
|
-
root,
|
|
469
|
-
...(request.includeRoots !== undefined ? { includeRoots: request.includeRoots } : {}),
|
|
470
|
-
...(request.budget !== undefined ? { budget: request.budget } : {}),
|
|
471
|
-
})),
|
|
472
|
-
packet_get: async (request) => await withFreshness(async () => await getCodegraphPacketWithSession(session, {
|
|
473
|
-
root,
|
|
474
|
-
target: request.target,
|
|
475
|
-
...(request.maxSymbols !== undefined ? { maxSymbols: request.maxSymbols } : {}),
|
|
476
|
-
...(request.maxSnippets !== undefined ? { maxSnippets: request.maxSnippets } : {}),
|
|
477
|
-
...(request.maxDuplicates !== undefined ? { maxDuplicates: request.maxDuplicates } : {}),
|
|
478
|
-
})),
|
|
479
|
-
get_file: async (request) => await getCodegraphFileViewWithSession(session, {
|
|
480
|
-
root,
|
|
481
|
-
file: request.file,
|
|
482
|
-
...(request.offset !== undefined ? { offset: request.offset } : {}),
|
|
483
|
-
...(request.limit !== undefined ? { limit: request.limit } : {}),
|
|
484
|
-
...(request.maxBytes !== undefined ? { maxBytes: request.maxBytes } : {}),
|
|
485
|
-
...(request.includeGraphContext !== undefined ? { includeGraphContext: request.includeGraphContext } : {}),
|
|
486
|
-
...(request.allowSensitive !== undefined ? { allowSensitive: request.allowSensitive } : {}),
|
|
487
|
-
...(options.buildOptions ? { buildOptions: options.buildOptions } : {}),
|
|
488
|
-
}),
|
|
489
|
-
get_symbol: async (request) => await withFreshness(async () => {
|
|
490
|
-
const explanation = await explainCodegraphTargetWithSession(session, { root, target: request.handle });
|
|
491
|
-
return explanation.target;
|
|
492
|
-
}),
|
|
493
|
-
goto: async (request) => await withFreshness(async () => {
|
|
494
|
-
const snapshot = await session.loadProject({ symbolGraph: "skip" });
|
|
495
|
-
if ("handle" in request) {
|
|
496
|
-
const resolved = requireSemanticSymbol(snapshot, request.handle);
|
|
497
|
-
return { status: "ok", definition: resolved.def };
|
|
498
|
-
}
|
|
499
|
-
return await goToDefinition(snapshot.index, {
|
|
500
|
-
file: await resolveProjectFile(await realRoot, root, request.file),
|
|
501
|
-
line: request.line,
|
|
502
|
-
column: request.column,
|
|
503
|
-
});
|
|
504
|
-
}),
|
|
505
|
-
refs: async (request) => await withFreshness(async () => {
|
|
506
|
-
const handle = "handle" in request ? request.handle : undefined;
|
|
507
|
-
const file = "file" in request ? request.file : undefined;
|
|
508
|
-
const line = "line" in request ? request.line : undefined;
|
|
509
|
-
const column = "column" in request ? request.column : undefined;
|
|
510
|
-
const hasAnyPosition = file !== undefined || line !== undefined || column !== undefined;
|
|
511
|
-
const hasCompletePosition = file !== undefined && line !== undefined && column !== undefined;
|
|
512
|
-
if (handle !== undefined && hasAnyPosition) {
|
|
513
|
-
throw new Error("refs requires either handle or file, line, and column.");
|
|
514
|
-
}
|
|
515
|
-
if (handle === undefined && !hasCompletePosition) {
|
|
516
|
-
throw new Error("refs requires either handle or file, line, and column.");
|
|
517
|
-
}
|
|
518
|
-
const limit = boundedLimit(request.limit, DEFAULT_MCP_COLLECTION_LIMIT, MAX_MCP_COLLECTION_LIMIT);
|
|
519
|
-
if (handle !== undefined) {
|
|
520
|
-
if (parseQualifiedSymbolPath(handle)) {
|
|
521
|
-
const snapshot = await session.loadProject({ symbolGraph: "skip" });
|
|
522
|
-
const resolved = requireSemanticSymbol(snapshot, handle);
|
|
523
|
-
// Probe one reference past the display limit so `truncated` is exact - see
|
|
524
|
-
// `collectMcpDependencyEntries` for the rationale (finding #44).
|
|
525
|
-
const result = await findReferences(snapshot.index, { def: resolved.def }, { maxReferences: limit + 1 });
|
|
526
|
-
return boundedReferencesFromResult(result, limit);
|
|
527
|
-
}
|
|
528
|
-
const explanation = await explainCodegraphTargetWithSession(session, {
|
|
529
|
-
root,
|
|
530
|
-
target: handle,
|
|
531
|
-
maxReferences: limit,
|
|
532
|
-
});
|
|
533
|
-
return {
|
|
534
|
-
references: explanation.references,
|
|
535
|
-
limit: explanation.limits.references,
|
|
536
|
-
totalSeen: explanation.references.length + explanation.omittedCounts.references,
|
|
537
|
-
truncated: explanation.omittedCounts.references > 0,
|
|
538
|
-
omitted: explanation.omittedCounts.references,
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
if (file === undefined || line === undefined || column === undefined) {
|
|
542
|
-
throw new Error("refs requires either handle or file, line, and column.");
|
|
543
|
-
}
|
|
544
|
-
const snapshot = await session.loadProject({ symbolGraph: "skip" });
|
|
545
|
-
const result = await findReferences(snapshot.index, {
|
|
546
|
-
file: await resolveProjectFile(await realRoot, root, file),
|
|
547
|
-
line,
|
|
548
|
-
column,
|
|
549
|
-
}, { maxReferences: limit + 1 });
|
|
550
|
-
return boundedReferencesFromResult(result, limit);
|
|
551
|
-
}),
|
|
552
|
-
deps: async (request) => await withFreshness(async () => {
|
|
553
|
-
const { entries, ...meta } = await collectMcpDependencyEntries(request, getDependencies);
|
|
554
|
-
return { dependencies: entries, ...meta };
|
|
555
|
-
}),
|
|
556
|
-
rdeps: async (request) => await withFreshness(async () => {
|
|
557
|
-
const { entries, ...meta } = await collectMcpDependencyEntries(request, getReverseDependencies);
|
|
558
|
-
return { reverseDependencies: entries, ...meta };
|
|
559
|
-
}),
|
|
560
|
-
path: async (request) => await withFreshness(async () => {
|
|
561
|
-
const snapshot = await session.loadProject({ symbolGraph: "skip" });
|
|
562
|
-
const result = getShortestPath(snapshot.fileGraph, await resolveProjectFile(await realRoot, root, request.from), await resolveProjectFile(await realRoot, root, request.to));
|
|
563
|
-
return {
|
|
564
|
-
path: result ? result.map(relative) : null,
|
|
565
|
-
};
|
|
566
|
-
}),
|
|
567
|
-
impact: async (request) => await withFreshness(async () => {
|
|
568
|
-
const snapshot = await session.loadProject({ symbolGraph: "skip" });
|
|
569
|
-
return (await analyzeImpactFromDiff(root, snapshot.index, {
|
|
570
|
-
...options.buildOptions,
|
|
571
|
-
provider: "git",
|
|
572
|
-
base: request.base,
|
|
573
|
-
head: request.head,
|
|
574
|
-
cwd: root,
|
|
575
|
-
compact: true,
|
|
576
|
-
depth: 2,
|
|
577
|
-
maxRefs: 200,
|
|
578
|
-
...DEFAULT_BOUNDED_IMPACT_BUDGETS,
|
|
579
|
-
}));
|
|
580
|
-
}),
|
|
581
|
-
review: async (request) => await withFreshness(async () => {
|
|
582
|
-
const report = await buildReviewReport(root, {
|
|
583
|
-
...options.buildOptions,
|
|
584
|
-
gitBase: request.base,
|
|
585
|
-
gitHead: request.head,
|
|
586
|
-
...(request.reviewDepth !== undefined ? { reviewDepth: request.reviewDepth } : {}),
|
|
587
|
-
}, {
|
|
588
|
-
...(session.loadProject
|
|
589
|
-
? {
|
|
590
|
-
loadIndex: async () => (await session.loadProject({ symbolGraph: "skip" })).index,
|
|
591
|
-
}
|
|
592
|
-
: {}),
|
|
593
|
-
...(session.loadDuplicateAnalysis ? { loadDuplicateAnalysis: session.loadDuplicateAnalysis } : {}),
|
|
594
|
-
});
|
|
595
|
-
// MCP is a bounded transport (finding #45); library callers that need the
|
|
596
|
-
// complete, unbounded report should call `buildReviewReport` directly.
|
|
597
|
-
return boundReviewReportForTransport(report);
|
|
598
|
-
}),
|
|
599
|
-
query_sqlite: async (request, executionOptions) => {
|
|
600
|
-
if (!sqlitePath) {
|
|
601
|
-
throw new Error("No SQLite artifact is available. Run artifact_build first or pass artifactPath.");
|
|
602
|
-
}
|
|
603
|
-
assertMcpSqliteQueryResourceBounded(request.query);
|
|
604
|
-
// The artifact's own baseline decides whether the query can be served; session freshness
|
|
605
|
-
// only decides whether an automatic rebuild is safe. A fresh artifact is served even when
|
|
606
|
-
// the in-memory session snapshot is stale, and an unsafe session refresh burst blocks the
|
|
607
|
-
// automatic rebuild instead of silently rebuilding from stale in-memory state.
|
|
608
|
-
let realSqlitePath = await assertRealPathCandidateWithinRoot(await realRoot, sqlitePath, "SQLite artifact");
|
|
609
|
-
let artifactFreshness = await checkSqliteArtifactFreshness(realSqlitePath);
|
|
610
|
-
if (artifactFreshness.state !== "fresh") {
|
|
611
|
-
const sessionFreshness = await checkMcpFreshness();
|
|
612
|
-
if (sessionFreshness.state === "stale") {
|
|
613
|
-
throw new Error(formatSqliteFreshnessError(sessionFreshness));
|
|
614
|
-
}
|
|
615
|
-
artifactFreshness = await refreshSqliteArtifactForQuery(artifactFreshness, { allowStaleRebuild: true });
|
|
616
|
-
realSqlitePath = await assertRealPathCandidateWithinRoot(await realRoot, sqlitePath, "SQLite artifact");
|
|
617
|
-
}
|
|
618
|
-
const result = await queryGraphSqliteRaw(realSqlitePath, request.query, request.params ?? [], {
|
|
619
|
-
maxRows: normalizeSqliteRowLimit(request.limit),
|
|
620
|
-
maxBytes: DEFAULT_SQLITE_BYTE_LIMIT,
|
|
621
|
-
...(executionOptions?.signal ? { signal: executionOptions.signal } : {}),
|
|
622
|
-
});
|
|
623
|
-
return { ...result, truncated: Boolean(result.truncated), freshness: artifactFreshness };
|
|
624
|
-
},
|
|
625
|
-
refresh_index: async (request) => await withArtifactWriteLock(async () => {
|
|
626
|
-
const warmup = request.warmup ?? "off";
|
|
627
|
-
const previousRefresh = refreshPromise;
|
|
628
|
-
const refresh = (async () => {
|
|
629
|
-
if (previousRefresh)
|
|
630
|
-
await previousRefresh.catch(() => undefined);
|
|
631
|
-
++refreshEpoch;
|
|
632
|
-
session.invalidate();
|
|
633
|
-
sqlitePath = configuredSqlitePath;
|
|
634
|
-
sqliteOutDir = configuredSqliteOutDir;
|
|
635
|
-
sqliteCanRefresh = configuredSqliteCanRefresh;
|
|
636
|
-
await startCodegraphMcpWarmup(session, warmup);
|
|
637
|
-
})();
|
|
638
|
-
refreshPromise = refresh;
|
|
639
|
-
try {
|
|
640
|
-
await refresh;
|
|
641
|
-
}
|
|
642
|
-
finally {
|
|
643
|
-
if (refreshPromise === refresh)
|
|
644
|
-
refreshPromise = undefined;
|
|
645
|
-
}
|
|
646
|
-
return { refreshed: true, warmup };
|
|
647
|
-
}),
|
|
648
|
-
artifact_build: async (request) => await withArtifactWriteLock(async () => {
|
|
649
|
-
if (readOnly) {
|
|
650
|
-
throw new Error("artifact_build is disabled in read-only MCP mode.");
|
|
651
|
-
}
|
|
652
|
-
const freshness = await checkMcpFreshness();
|
|
653
|
-
if (freshness.state === "stale") {
|
|
654
|
-
const changed = freshness.changedFiles.length ? ` Changed files: ${freshness.changedFiles.join(", ")}.` : "";
|
|
655
|
-
const omitted = freshness.omittedChangedFileCount
|
|
656
|
-
? ` Omitted changed files: ${freshness.omittedChangedFileCount}.`
|
|
657
|
-
: "";
|
|
658
|
-
throw new Error(`Cannot build artifacts from a stale MCP index; run refresh_index first. ${freshness.reason}.${changed}${omitted}`);
|
|
659
|
-
}
|
|
660
|
-
const outDir = request.outDir !== undefined
|
|
661
|
-
? await assertWritableDirectoryRealPathWithinRoot(await realRoot, root, request.outDir, "Artifact output directory")
|
|
662
|
-
: undefined;
|
|
663
|
-
const result = await buildCodegraphArtifactWithSession(session, {
|
|
664
|
-
root,
|
|
665
|
-
...(outDir !== undefined ? { outDir } : {}),
|
|
666
|
-
...(request.outDir !== undefined ? { filterOutDir: request.outDir } : {}),
|
|
667
|
-
...(request.sqlite !== undefined ? { sqlite: request.sqlite } : {}),
|
|
668
|
-
...(request.graphJson !== undefined ? { graphJson: request.graphJson } : {}),
|
|
669
|
-
...(request.report !== undefined ? { report: request.report } : {}),
|
|
670
|
-
...(request.questions !== undefined ? { questions: request.questions } : {}),
|
|
671
|
-
...(request.force !== undefined ? { force: request.force } : {}),
|
|
672
|
-
});
|
|
673
|
-
const sqliteArtifact = result.artifacts.sqlite;
|
|
674
|
-
if (sqliteArtifact) {
|
|
675
|
-
sqlitePath = path.join(result.outDir, sqliteArtifact);
|
|
676
|
-
sqliteOutDir = result.outDir;
|
|
677
|
-
sqliteCanRefresh = true;
|
|
678
|
-
}
|
|
679
|
-
return { ...result, freshness };
|
|
680
|
-
}),
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
function createMcpToolOperationTracker() {
|
|
684
|
-
let accepting = true;
|
|
685
|
-
const operations = new Set();
|
|
686
|
-
return {
|
|
687
|
-
isAccepting: () => accepting,
|
|
688
|
-
track: (operation) => {
|
|
689
|
-
if (!accepting)
|
|
690
|
-
return undefined;
|
|
691
|
-
const tracked = operation();
|
|
692
|
-
operations.add(tracked);
|
|
693
|
-
void tracked.then(() => operations.delete(tracked), () => operations.delete(tracked));
|
|
694
|
-
return tracked;
|
|
695
|
-
},
|
|
696
|
-
stop: () => {
|
|
697
|
-
accepting = false;
|
|
698
|
-
},
|
|
699
|
-
drain: async () => {
|
|
700
|
-
accepting = false;
|
|
701
|
-
await Promise.allSettled([...operations]);
|
|
702
|
-
},
|
|
703
|
-
};
|
|
704
|
-
}
|
|
705
|
-
function createMcpToolConcurrencyTracker(maximum) {
|
|
706
|
-
return { inFlight: 0, maximum: normalizeMcpToolConcurrency(maximum) };
|
|
707
|
-
}
|
|
708
|
-
export function createCodegraphMcpProtocolServer(handlers, runtimeIdentity = captureCodegraphRuntimeIdentity(getCurrentNativeBindingOrigin()), installedVersion = createInstalledVersionChecker(runtimeIdentity), toolCallState = { firstToolCallPending: true }, maxConcurrentToolCalls = DEFAULT_MCP_TOOL_CONCURRENCY, mcpToolTimeoutMs = DEFAULT_MCP_TOOL_TIMEOUT_MS) {
|
|
709
|
-
return createCodegraphMcpProtocolServerWithTracker(handlers, runtimeIdentity, installedVersion, toolCallState, assertMcpToolTimeout(mcpToolTimeoutMs), createMcpToolOperationTracker(), createMcpToolConcurrencyTracker(maxConcurrentToolCalls));
|
|
710
|
-
}
|
|
711
|
-
export function createCodegraphMcpProtocolServerWithTracker(handlers, runtimeIdentity, installedVersion, toolCallState, mcpToolTimeoutMs, toolOperations, toolConcurrency) {
|
|
712
|
-
const server = new Server({
|
|
713
|
-
name: "codegraph",
|
|
714
|
-
version: runtimeIdentity.runningVersion,
|
|
715
|
-
}, {
|
|
716
|
-
capabilities: { tools: {}, logging: {} },
|
|
717
|
-
});
|
|
718
|
-
const activeToolCalls = new Map();
|
|
719
|
-
server.setNotificationHandler("notifications/cancelled", (notification) => {
|
|
720
|
-
const requestId = notification.params.requestId;
|
|
721
|
-
if (requestId !== undefined) {
|
|
722
|
-
activeToolCalls.get(requestId)?.abort(new Error("MCP tool call was cancelled."));
|
|
723
|
-
}
|
|
724
|
-
});
|
|
725
|
-
server.setRequestHandler("tools/list", () => ({ tools: listCodegraphMcpTools() }));
|
|
726
|
-
server.setRequestHandler("tools/call", async (request, ctx) => {
|
|
727
|
-
if (!toolOperations.isAccepting()) {
|
|
728
|
-
throw new Error("MCP server is shutting down.");
|
|
729
|
-
}
|
|
730
|
-
if (toolConcurrency.inFlight >= toolConcurrency.maximum) {
|
|
731
|
-
throw new Error("MCP tool execution is busy; retry shortly.");
|
|
732
|
-
}
|
|
733
|
-
toolConcurrency.inFlight += 1;
|
|
734
|
-
const isFirstToolCall = toolCallState.firstToolCallPending;
|
|
735
|
-
toolCallState.firstToolCallPending = false;
|
|
736
|
-
const progressToken = isFirstToolCall ? getToolCallProgressToken(request.params) : undefined;
|
|
737
|
-
const emitFirstToolCallVisibility = async (level, progress, message) => {
|
|
738
|
-
if (!isFirstToolCall)
|
|
739
|
-
return;
|
|
740
|
-
try {
|
|
741
|
-
await ctx.mcpReq.log(level, message, "codegraph");
|
|
742
|
-
if (progressToken !== undefined) {
|
|
743
|
-
await ctx.mcpReq.notify({
|
|
744
|
-
method: "notifications/progress",
|
|
745
|
-
params: { progressToken, progress, total: 1, message },
|
|
746
|
-
});
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
catch (error) {
|
|
750
|
-
console.error(`[codegraph] MCP cold-start visibility failed: ${errorMessage(error)}`);
|
|
751
|
-
}
|
|
752
|
-
};
|
|
753
|
-
await emitFirstToolCallVisibility("info", 0, `Codegraph is warming the first tool call for '${request.params.name}'.`);
|
|
754
|
-
try {
|
|
755
|
-
installedVersion.check();
|
|
756
|
-
}
|
|
757
|
-
catch (error) {
|
|
758
|
-
console.error(`[codegraph] installed-version check failed: ${errorMessage(error)}`);
|
|
759
|
-
}
|
|
760
|
-
try {
|
|
761
|
-
const toolCallAbort = createMcpToolAbortSignal([ctx.mcpReq.signal, legacyRequestAbortStorage.getStore()], request.params.name, mcpToolTimeoutMs);
|
|
762
|
-
activeToolCalls.set(ctx.mcpReq.id, toolCallAbort);
|
|
763
|
-
try {
|
|
764
|
-
const operation = toolOperations.track(() => callMcpTool(handlers, request.params.name, request.params.arguments ?? {}, toolCallAbort.signal));
|
|
765
|
-
if (operation === undefined) {
|
|
766
|
-
toolConcurrency.inFlight -= 1;
|
|
767
|
-
throw new Error("MCP server is shutting down.");
|
|
768
|
-
}
|
|
769
|
-
const releaseToolCall = () => {
|
|
770
|
-
toolConcurrency.inFlight -= 1;
|
|
771
|
-
};
|
|
772
|
-
try {
|
|
773
|
-
const result = await awaitMcpToolOperation(toolCallAbort.signal, operation, releaseToolCall);
|
|
774
|
-
await emitFirstToolCallVisibility("info", 1, `Codegraph finished warming the first tool call for '${request.params.name}'.`);
|
|
775
|
-
return toToolResult(result);
|
|
776
|
-
}
|
|
777
|
-
catch (error) {
|
|
778
|
-
if (error instanceof ProtocolError || toolCallAbort.signal.aborted)
|
|
779
|
-
throw error;
|
|
780
|
-
return toToolErrorResult(error);
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
finally {
|
|
784
|
-
activeToolCalls.delete(ctx.mcpReq.id);
|
|
785
|
-
toolCallAbort.dispose();
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
catch (error) {
|
|
789
|
-
await emitFirstToolCallVisibility("error", 1, `Codegraph failed while warming the first tool call for '${request.params.name}'.`);
|
|
790
|
-
throw error;
|
|
791
|
-
}
|
|
792
|
-
});
|
|
793
|
-
return server;
|
|
794
|
-
}
|
|
795
|
-
function createCodegraphMcpProtocolFactory(handlers, runtimeIdentity, maxConcurrentToolCalls = DEFAULT_MCP_TOOL_CONCURRENCY, mcpToolTimeoutMs = DEFAULT_MCP_TOOL_TIMEOUT_MS) {
|
|
796
|
-
const toolConcurrency = createMcpToolConcurrencyTracker(maxConcurrentToolCalls);
|
|
797
|
-
const installedVersion = createInstalledVersionChecker(runtimeIdentity);
|
|
798
|
-
const toolCallState = { firstToolCallPending: true };
|
|
799
|
-
const toolOperations = createMcpToolOperationTracker();
|
|
800
|
-
const timeout = assertMcpToolTimeout(mcpToolTimeoutMs);
|
|
801
|
-
return {
|
|
802
|
-
create: () => createCodegraphMcpProtocolServerWithTracker(handlers, runtimeIdentity, installedVersion, toolCallState, timeout, toolOperations, toolConcurrency),
|
|
803
|
-
stop: () => toolOperations.stop(),
|
|
804
|
-
drain: () => toolOperations.drain(),
|
|
805
|
-
};
|
|
806
|
-
}
|
|
807
|
-
export function createParseErrorReportingStdin(input, output) {
|
|
808
|
-
let pending = Buffer.alloc(0);
|
|
809
|
-
const processFrame = (rawFrame, stream) => {
|
|
810
|
-
const frame = rawFrame.at(-1) === 13 ? rawFrame.subarray(0, -1) : rawFrame;
|
|
811
|
-
if (!frame.length)
|
|
812
|
-
return;
|
|
813
|
-
if (frame.length > MAX_MCP_STDIO_FRAME_BYTES) {
|
|
814
|
-
return new Error("MCP stdio frame exceeded 10 MiB.");
|
|
815
|
-
}
|
|
816
|
-
try {
|
|
817
|
-
JSON.parse(frame.toString("utf8"));
|
|
818
|
-
stream.push(frame);
|
|
819
|
-
stream.push("\n");
|
|
820
|
-
}
|
|
821
|
-
catch {
|
|
822
|
-
output.write(`${JSON.stringify({
|
|
823
|
-
jsonrpc: "2.0",
|
|
824
|
-
id: null,
|
|
825
|
-
error: { code: ProtocolErrorCode.ParseError, message: "Parse error" },
|
|
826
|
-
})}\n`);
|
|
827
|
-
}
|
|
828
|
-
};
|
|
829
|
-
const filter = new Transform({
|
|
830
|
-
transform(chunk, _encoding, callback) {
|
|
831
|
-
pending = Buffer.concat([pending, chunk]);
|
|
832
|
-
let newline = pending.indexOf(10);
|
|
833
|
-
while (newline >= 0) {
|
|
834
|
-
const error = processFrame(pending.subarray(0, newline), this);
|
|
835
|
-
pending = pending.subarray(newline + 1);
|
|
836
|
-
if (error !== undefined) {
|
|
837
|
-
callback(error);
|
|
838
|
-
return;
|
|
839
|
-
}
|
|
840
|
-
newline = pending.indexOf(10);
|
|
841
|
-
}
|
|
842
|
-
if (pending.length > MAX_MCP_STDIO_FRAME_BYTES) {
|
|
843
|
-
callback(new Error("MCP stdio frame exceeded 10 MiB."));
|
|
844
|
-
return;
|
|
845
|
-
}
|
|
846
|
-
callback();
|
|
847
|
-
},
|
|
848
|
-
flush(callback) {
|
|
849
|
-
const error = processFrame(pending, this);
|
|
850
|
-
pending = Buffer.alloc(0);
|
|
851
|
-
callback(error);
|
|
852
|
-
},
|
|
853
|
-
});
|
|
854
|
-
input.pipe(filter);
|
|
855
|
-
return filter;
|
|
856
|
-
}
|
|
9
|
+
export { listCodegraphMcpTools } from "./tools.js";
|
|
10
|
+
export { createCodegraphMcpHandlers } from "./handlers.js";
|
|
11
|
+
export { awaitMcpToolOperation, callMcpTool, createCodegraphMcpProtocolServer, createCodegraphMcpProtocolServerWithTracker, createParseErrorReportingStdin, DEFAULT_MCP_TOOL_CONCURRENCY, DEFAULT_MCP_TOOL_TIMEOUT_MS, } from "./protocol.js";
|
|
12
|
+
export { startCodegraphMcpHttpServer } from "./httpTransport.js";
|
|
13
|
+
export { DEFAULT_MCP_HTTP_BODY_TIMEOUT_MS, DEFAULT_MCP_HTTP_SESSION_EVICTION_INTERVAL_MS, DEFAULT_MCP_HTTP_SESSION_IDLE_MS, DEFAULT_MCP_HTTP_SESSION_MAX_COUNT, } from "./httpTransport.js";
|
|
14
|
+
export { runWithLegacyRequestAbortSignal } from "./legacySessions.js";
|
|
857
15
|
export async function serveCodegraphMcp(options) {
|
|
858
16
|
const configuredMcpToolTimeout = options.mcpToolTimeoutMs === undefined ? DEFAULT_MCP_TOOL_TIMEOUT_MS : options.mcpToolTimeoutMs;
|
|
859
17
|
const mcpToolTimeoutMs = assertMcpToolTimeout(configuredMcpToolTimeout);
|
|
@@ -871,7 +29,7 @@ export async function serveCodegraphMcp(options) {
|
|
|
871
29
|
const { handlers, session } = await createWarmedCodegraphMcpResources(options);
|
|
872
30
|
const runtimeIdentity = options.runtimeIdentity ?? captureCodegraphRuntimeIdentity(getCurrentNativeBindingOrigin());
|
|
873
31
|
const protocolFactory = createCodegraphMcpProtocolFactory(handlers, runtimeIdentity, options.mcpToolConcurrency ?? DEFAULT_MCP_TOOL_CONCURRENCY, mcpToolTimeoutMs);
|
|
874
|
-
const transport =
|
|
32
|
+
const transport = createCodegraphMcpStdioTransport(process.stdin, process.stdout);
|
|
875
33
|
const handle = serveStdio(protocolFactory.create, {
|
|
876
34
|
transport,
|
|
877
35
|
legacy: "serve",
|
|
@@ -895,758 +53,4 @@ export async function serveCodegraphMcp(options) {
|
|
|
895
53
|
}
|
|
896
54
|
process.exitCode = 0;
|
|
897
55
|
}
|
|
898
|
-
export async function startCodegraphMcpHttpServer(options) {
|
|
899
|
-
const httpBodyTimeoutMs = assertMcpHttpBodyTimeout(options.httpBodyTimeoutMs === undefined ? DEFAULT_MCP_HTTP_BODY_TIMEOUT_MS : options.httpBodyTimeoutMs);
|
|
900
|
-
const configuredMcpToolTimeout = options.mcpToolTimeoutMs === undefined ? DEFAULT_MCP_TOOL_TIMEOUT_MS : options.mcpToolTimeoutMs;
|
|
901
|
-
const mcpToolTimeoutMs = assertMcpToolTimeout(configuredMcpToolTimeout);
|
|
902
|
-
const host = options.host ?? "127.0.0.1";
|
|
903
|
-
const { handlers, session } = await createWarmedCodegraphMcpResources(options);
|
|
904
|
-
const runtimeIdentity = options.runtimeIdentity ?? captureCodegraphRuntimeIdentity(getCurrentNativeBindingOrigin());
|
|
905
|
-
const protocolFactory = createCodegraphMcpProtocolFactory(handlers, runtimeIdentity, options.mcpToolConcurrency ?? DEFAULT_MCP_TOOL_CONCURRENCY, mcpToolTimeoutMs);
|
|
906
|
-
const sessionStore = createLegacyMcpSessionStore({
|
|
907
|
-
idleMs: options.httpSessionIdleMs ?? DEFAULT_MCP_HTTP_SESSION_IDLE_MS,
|
|
908
|
-
maxCount: options.httpSessionMaxCount ?? DEFAULT_MCP_HTTP_SESSION_MAX_COUNT,
|
|
909
|
-
evictionIntervalMs: options.httpSessionEvictionIntervalMs ?? DEFAULT_MCP_HTTP_SESSION_EVICTION_INTERVAL_MS,
|
|
910
|
-
});
|
|
911
|
-
const modernHandler = createMcpHandler(protocolFactory.create, {
|
|
912
|
-
legacy: "reject",
|
|
913
|
-
onerror: (error) => {
|
|
914
|
-
console.error(`[codegraph] MCP HTTP error: ${error.message}`);
|
|
915
|
-
},
|
|
916
|
-
});
|
|
917
|
-
const modernNodeHandler = toNodeHandler(modernHandler, {
|
|
918
|
-
onerror: (error) => {
|
|
919
|
-
console.error(`[codegraph] MCP HTTP adapter error: ${error.message}`);
|
|
920
|
-
},
|
|
921
|
-
});
|
|
922
|
-
const validateOrigin = originValidation(buildAllowedOriginHostnames(host));
|
|
923
|
-
let allowedHostHeaders = emptyAllowedHostHeaderRules();
|
|
924
|
-
let closeResourcesPromise;
|
|
925
|
-
const closeResources = () => {
|
|
926
|
-
closeResourcesPromise ??= (async () => {
|
|
927
|
-
protocolFactory.stop();
|
|
928
|
-
sessionStore.stop();
|
|
929
|
-
try {
|
|
930
|
-
await closeMcpResources(sessionStore.sessions, modernHandler.close);
|
|
931
|
-
}
|
|
932
|
-
finally {
|
|
933
|
-
await protocolFactory.drain();
|
|
934
|
-
session.invalidate();
|
|
935
|
-
}
|
|
936
|
-
})();
|
|
937
|
-
return closeResourcesPromise;
|
|
938
|
-
};
|
|
939
|
-
const server = createServer((request, response) => {
|
|
940
|
-
void handleMcpHttpRequest(request, response, sessionStore, () => allowedHostHeaders, validateOrigin, modernNodeHandler, protocolFactory.create, httpBodyTimeoutMs);
|
|
941
|
-
});
|
|
942
|
-
server.on("close", () => {
|
|
943
|
-
void closeResources();
|
|
944
|
-
});
|
|
945
|
-
try {
|
|
946
|
-
await listenOnHttpServer(server, options.port, host);
|
|
947
|
-
}
|
|
948
|
-
catch (error) {
|
|
949
|
-
await closeResources();
|
|
950
|
-
throw error;
|
|
951
|
-
}
|
|
952
|
-
const address = server.address();
|
|
953
|
-
const actualPort = getHttpServerPort(address);
|
|
954
|
-
const urlHost = formatHostForUrl(host);
|
|
955
|
-
const url = `http://${urlHost}:${actualPort}${MCP_HTTP_PATH}`;
|
|
956
|
-
allowedHostHeaders = buildAllowedHostHeaders(host, actualPort);
|
|
957
|
-
return {
|
|
958
|
-
server,
|
|
959
|
-
host,
|
|
960
|
-
port: actualPort,
|
|
961
|
-
url,
|
|
962
|
-
close: async () => {
|
|
963
|
-
const requestsDrained = closeHttpServer(server);
|
|
964
|
-
await closeResources();
|
|
965
|
-
await requestsDrained;
|
|
966
|
-
// An initialize accepted before closeHttpServer() can register after closeResources() snapshots the session map.
|
|
967
|
-
await closeLegacyMcpSessions(sessionStore.sessions);
|
|
968
|
-
},
|
|
969
|
-
};
|
|
970
|
-
}
|
|
971
|
-
async function handleMcpHttpRequest(request, response, sessionStore, getAllowedHostHeaders, validateOrigin, modernNodeHandler, createProtocolServer, bodyTimeoutMs) {
|
|
972
|
-
const writeClosingJsonRpcError = (statusCode, message) => {
|
|
973
|
-
response.setHeader("connection", "close");
|
|
974
|
-
writeJsonRpcError(response, statusCode, message);
|
|
975
|
-
};
|
|
976
|
-
const requestPath = getRequestPath(request);
|
|
977
|
-
if (requestPath !== MCP_HTTP_PATH) {
|
|
978
|
-
writeJsonResponse(response, 404, { error: "Not found" });
|
|
979
|
-
return;
|
|
980
|
-
}
|
|
981
|
-
if (!isAllowedHostHeader(request, getAllowedHostHeaders())) {
|
|
982
|
-
writeJsonRpcError(response, 403, "Forbidden host header");
|
|
983
|
-
return;
|
|
984
|
-
}
|
|
985
|
-
if (!validateOrigin(request, response))
|
|
986
|
-
return;
|
|
987
|
-
try {
|
|
988
|
-
if (request.method === "POST") {
|
|
989
|
-
const parsedBody = await readJsonRequestBody(request, MAX_MCP_HTTP_BODY_BYTES, bodyTimeoutMs);
|
|
990
|
-
if (parsedBody.status === "too_large") {
|
|
991
|
-
writeJsonRpcError(response, 413, "MCP request body is too large");
|
|
992
|
-
return;
|
|
993
|
-
}
|
|
994
|
-
if (parsedBody.status === "timeout") {
|
|
995
|
-
writeClosingJsonRpcError(408, "MCP request body timed out");
|
|
996
|
-
return;
|
|
997
|
-
}
|
|
998
|
-
if (parsedBody.status === "invalid_json") {
|
|
999
|
-
writeJsonRpcError(response, 400, "Invalid JSON request body");
|
|
1000
|
-
return;
|
|
1001
|
-
}
|
|
1002
|
-
if (!isMcpNodeRequest(request)) {
|
|
1003
|
-
writeJsonRpcError(response, 400, "Invalid MCP request target");
|
|
1004
|
-
return;
|
|
1005
|
-
}
|
|
1006
|
-
const mcpRequest = request;
|
|
1007
|
-
const webRequest = await toWebRequest(mcpRequest, parsedBody.body);
|
|
1008
|
-
if (await isLegacyRequest(webRequest, parsedBody.body)) {
|
|
1009
|
-
await handleLegacyMcpHttpPost(request, response, parsedBody.body, sessionStore, createProtocolServer);
|
|
1010
|
-
}
|
|
1011
|
-
else {
|
|
1012
|
-
await modernNodeHandler(mcpRequest, response, parsedBody.body);
|
|
1013
|
-
}
|
|
1014
|
-
return;
|
|
1015
|
-
}
|
|
1016
|
-
if (request.method === "GET" || request.method === "DELETE") {
|
|
1017
|
-
await handleExistingMcpSessionRequest(request, response, sessionStore);
|
|
1018
|
-
return;
|
|
1019
|
-
}
|
|
1020
|
-
writeJsonRpcError(response, 405, "Method not allowed");
|
|
1021
|
-
}
|
|
1022
|
-
catch {
|
|
1023
|
-
if (!response.headersSent) {
|
|
1024
|
-
writeJsonRpcError(response, 500, "Internal server error", -32603);
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
async function handleLegacyMcpHttpPost(request, response, body, sessionStore, createProtocolServer) {
|
|
1029
|
-
const sessionId = getHeaderValue(request.headers["mcp-session-id"]);
|
|
1030
|
-
if (sessionId !== undefined) {
|
|
1031
|
-
const session = sessionStore.get(sessionId);
|
|
1032
|
-
if (!session) {
|
|
1033
|
-
writeJsonRpcError(response, 400, "Bad Request: No valid session ID provided");
|
|
1034
|
-
return;
|
|
1035
|
-
}
|
|
1036
|
-
sessionStore.touch(sessionId);
|
|
1037
|
-
await handleLegacyMcpSessionRequest(session, request, response, body);
|
|
1038
|
-
return;
|
|
1039
|
-
}
|
|
1040
|
-
if (!isInitializeRequest(body)) {
|
|
1041
|
-
writeJsonRpcError(response, 400, "Bad Request: No valid session ID provided");
|
|
1042
|
-
return;
|
|
1043
|
-
}
|
|
1044
|
-
const hasCapacity = await sessionStore.ensureCapacityForNewSession();
|
|
1045
|
-
if (!hasCapacity) {
|
|
1046
|
-
writeJsonRpcError(response, 503, "MCP session capacity reached: all configured sessions are active; close an existing session and retry.");
|
|
1047
|
-
return;
|
|
1048
|
-
}
|
|
1049
|
-
let capacityReservationHeld = true;
|
|
1050
|
-
const releaseCapacityReservation = () => {
|
|
1051
|
-
if (!capacityReservationHeld)
|
|
1052
|
-
return;
|
|
1053
|
-
capacityReservationHeld = false;
|
|
1054
|
-
sessionStore.releaseCapacityReservation();
|
|
1055
|
-
};
|
|
1056
|
-
const protocolServer = createProtocolServer();
|
|
1057
|
-
let initializedSessionId;
|
|
1058
|
-
// The transport callbacks close over the session, but the session needs the
|
|
1059
|
-
// transport, so the binding is resolved through a ref rather than a mutable let.
|
|
1060
|
-
const sessionRef = { current: undefined };
|
|
1061
|
-
const transport = new NodeStreamableHTTPServerTransport({
|
|
1062
|
-
enableJsonResponse: true,
|
|
1063
|
-
sessionIdGenerator: () => randomUUID(),
|
|
1064
|
-
onsessioninitialized: (newSessionId) => {
|
|
1065
|
-
initializedSessionId = newSessionId;
|
|
1066
|
-
const initialized = sessionRef.current;
|
|
1067
|
-
if (initialized === undefined) {
|
|
1068
|
-
throw new Error("MCP session state was not initialized before the transport session.");
|
|
1069
|
-
}
|
|
1070
|
-
sessionStore.set(newSessionId, initialized);
|
|
1071
|
-
releaseCapacityReservation();
|
|
1072
|
-
},
|
|
1073
|
-
onsessionclosed: (closedSessionId) => {
|
|
1074
|
-
void sessionStore.delete(closedSessionId);
|
|
1075
|
-
},
|
|
1076
|
-
});
|
|
1077
|
-
const session = {
|
|
1078
|
-
server: protocolServer,
|
|
1079
|
-
transport,
|
|
1080
|
-
lastActivityAt: Date.now(),
|
|
1081
|
-
inFlightRequests: 0,
|
|
1082
|
-
openSseStreams: 0,
|
|
1083
|
-
};
|
|
1084
|
-
sessionRef.current = session;
|
|
1085
|
-
// The SDK transport reports every per-request validation rejection through onerror
|
|
1086
|
-
// too (bad Accept header, wrong Content-Type, malformed JSON, an unsupported
|
|
1087
|
-
// protocol version, ...) - each of those already answered its own request with a
|
|
1088
|
-
// 4xx response and left the transport fully usable. Deleting the session here would
|
|
1089
|
-
// tear down an otherwise healthy session over one malformed follow-up request. Only
|
|
1090
|
-
// onclose reflects the transport actually shutting down (an explicit DELETE, an
|
|
1091
|
-
// eviction we triggered, or a real fatal failure), so session teardown is driven by
|
|
1092
|
-
// onclose alone; onerror only logs.
|
|
1093
|
-
transport.onerror = (error) => {
|
|
1094
|
-
console.error(`[codegraph] MCP HTTP session transport error: ${error.message}`);
|
|
1095
|
-
};
|
|
1096
|
-
transport.onclose = () => {
|
|
1097
|
-
if (initializedSessionId !== undefined)
|
|
1098
|
-
void sessionStore.delete(initializedSessionId);
|
|
1099
|
-
};
|
|
1100
|
-
try {
|
|
1101
|
-
await protocolServer.connect(transport);
|
|
1102
|
-
await handleLegacyMcpSessionRequest(session, request, response, body);
|
|
1103
|
-
if (initializedSessionId === undefined) {
|
|
1104
|
-
// The transport answered a pre-session 4xx (invalid Accept header, wrong
|
|
1105
|
-
// Content-Type, malformed JSON, ...) without throwing and without ever reaching
|
|
1106
|
-
// onsessioninitialized, so nothing else releases this capacity reservation or
|
|
1107
|
-
// closes this ad hoc protocol server/transport pair.
|
|
1108
|
-
releaseCapacityReservation();
|
|
1109
|
-
await closeMcpSession(session);
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
catch (error) {
|
|
1113
|
-
if (initializedSessionId !== undefined) {
|
|
1114
|
-
await sessionStore.delete(initializedSessionId);
|
|
1115
|
-
}
|
|
1116
|
-
else {
|
|
1117
|
-
await closeMcpSession(session);
|
|
1118
|
-
}
|
|
1119
|
-
releaseCapacityReservation();
|
|
1120
|
-
throw error;
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
async function handleExistingMcpSessionRequest(request, response, sessionStore) {
|
|
1124
|
-
const sessionId = getHeaderValue(request.headers["mcp-session-id"]);
|
|
1125
|
-
if (sessionId === undefined) {
|
|
1126
|
-
writeJsonRpcError(response, 400, "Invalid or missing session ID");
|
|
1127
|
-
return;
|
|
1128
|
-
}
|
|
1129
|
-
const session = sessionStore.get(sessionId);
|
|
1130
|
-
if (!session) {
|
|
1131
|
-
writeJsonRpcError(response, 400, "Invalid or missing session ID");
|
|
1132
|
-
return;
|
|
1133
|
-
}
|
|
1134
|
-
sessionStore.touch(sessionId);
|
|
1135
|
-
await handleLegacyMcpSessionRequest(session, request, response);
|
|
1136
|
-
}
|
|
1137
|
-
/**
|
|
1138
|
-
* Runs one legacy HTTP request with a signal that aborts when its connection closes.
|
|
1139
|
-
* The signal is scoped to this request and is available to nested MCP tool dispatch.
|
|
1140
|
-
*/
|
|
1141
|
-
export async function runWithLegacyRequestAbortSignal(request, response, operation) {
|
|
1142
|
-
const abortController = new AbortController();
|
|
1143
|
-
let completed = false;
|
|
1144
|
-
const abortDisconnectedRequest = () => {
|
|
1145
|
-
if (completed)
|
|
1146
|
-
return;
|
|
1147
|
-
if (request.aborted || (!response.writableFinished && response.destroyed)) {
|
|
1148
|
-
abortController.abort(new Error("MCP HTTP request connection closed."));
|
|
1149
|
-
}
|
|
1150
|
-
};
|
|
1151
|
-
request.on("aborted", abortDisconnectedRequest);
|
|
1152
|
-
request.on("close", abortDisconnectedRequest);
|
|
1153
|
-
response.on("close", abortDisconnectedRequest);
|
|
1154
|
-
try {
|
|
1155
|
-
return await legacyRequestAbortStorage.run(abortController.signal, operation);
|
|
1156
|
-
}
|
|
1157
|
-
finally {
|
|
1158
|
-
completed = true;
|
|
1159
|
-
request.off("aborted", abortDisconnectedRequest);
|
|
1160
|
-
request.off("close", abortDisconnectedRequest);
|
|
1161
|
-
response.off("close", abortDisconnectedRequest);
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
async function handleLegacyMcpSessionRequest(session, request, response, body) {
|
|
1165
|
-
const tracksSseStream = isLegacySseRequest(request);
|
|
1166
|
-
session.inFlightRequests += 1;
|
|
1167
|
-
if (tracksSseStream)
|
|
1168
|
-
session.openSseStreams += 1;
|
|
1169
|
-
try {
|
|
1170
|
-
await runWithLegacyRequestAbortSignal(request, response, async () => {
|
|
1171
|
-
await session.transport.handleRequest(request, response, body);
|
|
1172
|
-
});
|
|
1173
|
-
}
|
|
1174
|
-
finally {
|
|
1175
|
-
session.inFlightRequests -= 1;
|
|
1176
|
-
if (tracksSseStream)
|
|
1177
|
-
session.openSseStreams -= 1;
|
|
1178
|
-
session.lastActivityAt = Date.now();
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
function isLegacySseRequest(request) {
|
|
1182
|
-
if (request.method !== "GET")
|
|
1183
|
-
return false;
|
|
1184
|
-
return getHeaderValue(request.headers.accept)?.includes("text/event-stream") ?? false;
|
|
1185
|
-
}
|
|
1186
|
-
function createLegacyMcpSessionStore(options) {
|
|
1187
|
-
const sessions = new Map();
|
|
1188
|
-
let stopped = false;
|
|
1189
|
-
let evictionTimer;
|
|
1190
|
-
let pendingInitializations = 0;
|
|
1191
|
-
const store = {
|
|
1192
|
-
sessions,
|
|
1193
|
-
get(sessionId) {
|
|
1194
|
-
return sessions.get(sessionId);
|
|
1195
|
-
},
|
|
1196
|
-
set(sessionId, session) {
|
|
1197
|
-
sessions.set(sessionId, session);
|
|
1198
|
-
},
|
|
1199
|
-
touch(sessionId) {
|
|
1200
|
-
const session = sessions.get(sessionId);
|
|
1201
|
-
if (!session)
|
|
1202
|
-
return;
|
|
1203
|
-
session.lastActivityAt = Date.now();
|
|
1204
|
-
},
|
|
1205
|
-
async delete(sessionId) {
|
|
1206
|
-
const session = sessions.get(sessionId);
|
|
1207
|
-
if (!session)
|
|
1208
|
-
return;
|
|
1209
|
-
sessions.delete(sessionId);
|
|
1210
|
-
await closeMcpSession(session);
|
|
1211
|
-
},
|
|
1212
|
-
async ensureCapacityForNewSession() {
|
|
1213
|
-
await evictIdleLegacyMcpSessions(store, options.idleMs);
|
|
1214
|
-
if (sessions.size + pendingInitializations < options.maxCount) {
|
|
1215
|
-
pendingInitializations += 1;
|
|
1216
|
-
return true;
|
|
1217
|
-
}
|
|
1218
|
-
const oldest = [...sessions.entries()]
|
|
1219
|
-
.filter(([, session]) => !session.inFlightRequests && !session.openSseStreams)
|
|
1220
|
-
.sort((left, right) => left[1].lastActivityAt - right[1].lastActivityAt);
|
|
1221
|
-
while (sessions.size + pendingInitializations >= options.maxCount && oldest.length) {
|
|
1222
|
-
const oldestSession = oldest.shift();
|
|
1223
|
-
if (oldestSession === undefined)
|
|
1224
|
-
break;
|
|
1225
|
-
const [sessionId, session] = oldestSession;
|
|
1226
|
-
if (session.inFlightRequests || session.openSseStreams)
|
|
1227
|
-
continue;
|
|
1228
|
-
await store.delete(sessionId);
|
|
1229
|
-
}
|
|
1230
|
-
if (sessions.size + pendingInitializations >= options.maxCount)
|
|
1231
|
-
return false;
|
|
1232
|
-
pendingInitializations += 1;
|
|
1233
|
-
return true;
|
|
1234
|
-
},
|
|
1235
|
-
releaseCapacityReservation() {
|
|
1236
|
-
if (pendingInitializations)
|
|
1237
|
-
pendingInitializations -= 1;
|
|
1238
|
-
},
|
|
1239
|
-
stop() {
|
|
1240
|
-
stopped = true;
|
|
1241
|
-
if (evictionTimer !== undefined) {
|
|
1242
|
-
clearInterval(evictionTimer);
|
|
1243
|
-
evictionTimer = undefined;
|
|
1244
|
-
}
|
|
1245
|
-
},
|
|
1246
|
-
};
|
|
1247
|
-
if (options.idleMs > 0 && options.evictionIntervalMs > 0) {
|
|
1248
|
-
evictionTimer = setInterval(() => {
|
|
1249
|
-
if (stopped)
|
|
1250
|
-
return;
|
|
1251
|
-
void evictIdleLegacyMcpSessions(store, options.idleMs).catch((error) => {
|
|
1252
|
-
console.error(`[codegraph] MCP HTTP session eviction failed: ${errorMessage(error)}`);
|
|
1253
|
-
});
|
|
1254
|
-
}, options.evictionIntervalMs);
|
|
1255
|
-
evictionTimer.unref?.();
|
|
1256
|
-
}
|
|
1257
|
-
return store;
|
|
1258
|
-
}
|
|
1259
|
-
async function evictIdleLegacyMcpSessions(store, idleMs) {
|
|
1260
|
-
if (idleMs <= 0)
|
|
1261
|
-
return;
|
|
1262
|
-
const cutoff = Date.now() - idleMs;
|
|
1263
|
-
for (const [sessionId, session] of store.sessions) {
|
|
1264
|
-
if (session.lastActivityAt > cutoff || session.inFlightRequests || session.openSseStreams)
|
|
1265
|
-
continue;
|
|
1266
|
-
await store.delete(sessionId);
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
async function closeMcpSession(session) {
|
|
1270
|
-
await Promise.allSettled([session.transport.close(), session.server.close()]);
|
|
1271
|
-
}
|
|
1272
|
-
async function closeLegacyMcpSessions(sessions) {
|
|
1273
|
-
const legacySessions = [...sessions.values()];
|
|
1274
|
-
sessions.clear();
|
|
1275
|
-
await Promise.allSettled(legacySessions.map((session) => closeMcpSession(session)));
|
|
1276
|
-
}
|
|
1277
|
-
async function closeMcpResources(sessions, closeModernHandler) {
|
|
1278
|
-
await Promise.allSettled([closeLegacyMcpSessions(sessions), closeModernHandler()]);
|
|
1279
|
-
}
|
|
1280
|
-
function isMcpNodeRequest(request) {
|
|
1281
|
-
return request.method !== undefined && request.url !== undefined;
|
|
1282
|
-
}
|
|
1283
|
-
function createMcpToolAbortSignal(requestSignals, toolName, timeoutMs) {
|
|
1284
|
-
const controller = new AbortController();
|
|
1285
|
-
const listeners = [];
|
|
1286
|
-
for (const requestSignal of requestSignals) {
|
|
1287
|
-
if (requestSignal === undefined)
|
|
1288
|
-
continue;
|
|
1289
|
-
const onAbort = () => {
|
|
1290
|
-
controller.abort(requestSignal.reason);
|
|
1291
|
-
};
|
|
1292
|
-
listeners.push({ signal: requestSignal, onAbort });
|
|
1293
|
-
if (requestSignal.aborted)
|
|
1294
|
-
onAbort();
|
|
1295
|
-
else
|
|
1296
|
-
requestSignal.addEventListener("abort", onAbort, { once: true });
|
|
1297
|
-
}
|
|
1298
|
-
const timeout = timeoutMs > 0
|
|
1299
|
-
? setTimeout(() => {
|
|
1300
|
-
controller.abort(new Error(`MCP tool '${toolName}' exceeded the configured deadline of ${timeoutMs} ms.`));
|
|
1301
|
-
}, timeoutMs)
|
|
1302
|
-
: undefined;
|
|
1303
|
-
timeout?.unref?.();
|
|
1304
|
-
return {
|
|
1305
|
-
signal: controller.signal,
|
|
1306
|
-
abort: (reason) => controller.abort(reason),
|
|
1307
|
-
dispose: () => {
|
|
1308
|
-
clearTimeout(timeout);
|
|
1309
|
-
for (const listener of listeners)
|
|
1310
|
-
listener.signal.removeEventListener("abort", listener.onAbort);
|
|
1311
|
-
},
|
|
1312
|
-
};
|
|
1313
|
-
}
|
|
1314
|
-
export function awaitMcpToolOperation(signal, operation, onSettled) {
|
|
1315
|
-
void operation.then(onSettled, onSettled);
|
|
1316
|
-
return withAbortSignal(signal, operation);
|
|
1317
|
-
}
|
|
1318
|
-
function withAbortSignal(signal, operation) {
|
|
1319
|
-
if (!signal)
|
|
1320
|
-
return operation;
|
|
1321
|
-
const cancellationError = () => {
|
|
1322
|
-
const reason = signal.reason;
|
|
1323
|
-
if (reason instanceof Error && reason.name !== "AbortError")
|
|
1324
|
-
return reason;
|
|
1325
|
-
return new Error("MCP tool call was cancelled.");
|
|
1326
|
-
};
|
|
1327
|
-
if (signal.aborted)
|
|
1328
|
-
return Promise.reject(cancellationError());
|
|
1329
|
-
const cancellation = Promise.withResolvers();
|
|
1330
|
-
const onAbort = () => cancellation.reject(cancellationError());
|
|
1331
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
1332
|
-
return Promise.race([operation, cancellation.promise]).finally(() => signal.removeEventListener("abort", onAbort));
|
|
1333
|
-
}
|
|
1334
|
-
export async function callMcpTool(handlers, name, input, signal) {
|
|
1335
|
-
const tool = MCP_TOOL_REGISTRY.find((entry) => entry.name === name);
|
|
1336
|
-
if (!tool)
|
|
1337
|
-
throw new ProtocolError(ProtocolErrorCode.MethodNotFound, `Unknown MCP tool: ${name}`);
|
|
1338
|
-
switch (tool.dispatch.handler) {
|
|
1339
|
-
case "search":
|
|
1340
|
-
return await handlers.search(parseMcpToolInput(searchSchema, input, name), signal);
|
|
1341
|
-
case "workspace_symbols":
|
|
1342
|
-
return await handlers.workspace_symbols(parseMcpToolInput(workspaceSymbolsSchema, input, name), signal);
|
|
1343
|
-
case "rename_preview":
|
|
1344
|
-
return await handlers.rename_preview(parseMcpToolInput(renamePreviewSchema, input, name), signal);
|
|
1345
|
-
case "refactor_plan":
|
|
1346
|
-
return await handlers.refactor_plan(parseMcpToolInput(refactorPlanSchema, input, name), signal);
|
|
1347
|
-
case "calls":
|
|
1348
|
-
if (tool.dispatch.direction) {
|
|
1349
|
-
return await handlers.calls({ ...parseMcpToolInput(callHierarchySchema, input, name), direction: tool.dispatch.direction }, signal);
|
|
1350
|
-
}
|
|
1351
|
-
return await handlers.calls(parseMcpToolInput(callsSchema, input, name), signal);
|
|
1352
|
-
case "type_hierarchy":
|
|
1353
|
-
if (tool.dispatch.direction) {
|
|
1354
|
-
return await handlers.type_hierarchy({ ...parseMcpToolInput(typeHierarchySchema, input, name), direction: tool.dispatch.direction }, signal);
|
|
1355
|
-
}
|
|
1356
|
-
return await handlers.type_hierarchy(parseMcpToolInput(typeHierarchyUnifiedSchema, input, name), signal);
|
|
1357
|
-
case "implementations":
|
|
1358
|
-
return await handlers.implementations(parseMcpToolInput(implementationsSchema, input, name), signal);
|
|
1359
|
-
case "explore":
|
|
1360
|
-
return await handlers.explore(parseMcpToolInput(exploreSchema, input, name), signal);
|
|
1361
|
-
case "orient":
|
|
1362
|
-
return await handlers.orient(parseMcpToolInput(orientSchema, input, name), signal);
|
|
1363
|
-
case "packet_get":
|
|
1364
|
-
return await handlers.packet_get(parseMcpToolInput(packetGetSchema, input, name), signal);
|
|
1365
|
-
case "get_file":
|
|
1366
|
-
return await handlers.get_file(parseMcpToolInput(getFileSchema, input, name), signal);
|
|
1367
|
-
case "get_symbol":
|
|
1368
|
-
return await handlers.get_symbol(parseMcpToolInput(handleSchema, input, name), signal);
|
|
1369
|
-
case "goto":
|
|
1370
|
-
return await callGotoTool(handlers, input, signal);
|
|
1371
|
-
case "refs":
|
|
1372
|
-
return await callRefsTool(handlers, input, signal);
|
|
1373
|
-
case "file_deps":
|
|
1374
|
-
if (tool.dispatch.direction) {
|
|
1375
|
-
return await handlers.file_deps({ ...parseMcpToolInput(fileGraphSchema, input, name), direction: tool.dispatch.direction }, signal);
|
|
1376
|
-
}
|
|
1377
|
-
return await handlers.file_deps(parseMcpToolInput(fileDepsUnifiedSchema, input, name), signal);
|
|
1378
|
-
case "path":
|
|
1379
|
-
return await handlers.path(parseMcpToolInput(pathSchema, input, name), signal);
|
|
1380
|
-
case "impact":
|
|
1381
|
-
return await handlers.impact(parseMcpToolInput(gitRangeSchema, input, name), signal);
|
|
1382
|
-
case "review":
|
|
1383
|
-
return await handlers.review(parseMcpToolInput(reviewSchema, input, name), signal);
|
|
1384
|
-
case "query_sqlite":
|
|
1385
|
-
return await handlers.query_sqlite(parseMcpToolInput(querySqliteSchema, input, name), {
|
|
1386
|
-
...(signal ? { signal } : {}),
|
|
1387
|
-
});
|
|
1388
|
-
case "refresh_index":
|
|
1389
|
-
return await handlers.refresh_index(parseMcpToolInput(refreshIndexSchema, input, name), signal);
|
|
1390
|
-
case "artifact_build":
|
|
1391
|
-
return await handlers.artifact_build(parseMcpToolInput(artifactBuildSchema, input, name), signal);
|
|
1392
|
-
}
|
|
1393
|
-
}
|
|
1394
|
-
async function callGotoTool(handlers, input, signal) {
|
|
1395
|
-
const request = parseMcpToolInput(navigationSchema, input, "goto");
|
|
1396
|
-
if (request.handle !== undefined)
|
|
1397
|
-
return await handlers.goto({ handle: request.handle }, signal);
|
|
1398
|
-
if (request.file === undefined || request.line === undefined || request.column === undefined) {
|
|
1399
|
-
throw new ProtocolError(ProtocolErrorCode.InvalidParams, "goto requires either handle or file, line, and column.");
|
|
1400
|
-
}
|
|
1401
|
-
return await handlers.goto({ file: request.file, line: request.line, column: request.column }, signal);
|
|
1402
|
-
}
|
|
1403
|
-
async function callRefsTool(handlers, input, signal) {
|
|
1404
|
-
const request = parseMcpToolInput(refsSchema, input, "refs");
|
|
1405
|
-
if (request.handle !== undefined) {
|
|
1406
|
-
return await handlers.refs({ handle: request.handle, ...(request.limit !== undefined ? { limit: request.limit } : {}) }, signal);
|
|
1407
|
-
}
|
|
1408
|
-
if (request.file === undefined || request.line === undefined || request.column === undefined) {
|
|
1409
|
-
throw new ProtocolError(ProtocolErrorCode.InvalidParams, "refs requires either handle or file, line, and column.");
|
|
1410
|
-
}
|
|
1411
|
-
return await handlers.refs({
|
|
1412
|
-
file: request.file,
|
|
1413
|
-
line: request.line,
|
|
1414
|
-
column: request.column,
|
|
1415
|
-
...(request.limit !== undefined ? { limit: request.limit } : {}),
|
|
1416
|
-
}, signal);
|
|
1417
|
-
}
|
|
1418
|
-
function toToolResult(value) {
|
|
1419
|
-
return {
|
|
1420
|
-
content: [
|
|
1421
|
-
{
|
|
1422
|
-
type: "text",
|
|
1423
|
-
text: JSON.stringify(value, null, 2),
|
|
1424
|
-
},
|
|
1425
|
-
],
|
|
1426
|
-
};
|
|
1427
|
-
}
|
|
1428
|
-
function toToolErrorResult(error) {
|
|
1429
|
-
const message = errorMessage(error).replace(/(?:[A-Za-z]:)?[/\\][^'"\n]*?(?=(?:[/\\][^'"\n]*)?['"]|$)/g, "<path>");
|
|
1430
|
-
return { isError: true, content: [{ type: "text", text: message }] };
|
|
1431
|
-
}
|
|
1432
|
-
const mcpProgressTokenSchema = z.union([z.string(), z.number()]);
|
|
1433
|
-
const toolCallMetaSchema = z
|
|
1434
|
-
.object({
|
|
1435
|
-
_meta: z.object({ progressToken: mcpProgressTokenSchema.optional() }).passthrough().optional(),
|
|
1436
|
-
progressToken: mcpProgressTokenSchema.optional(),
|
|
1437
|
-
})
|
|
1438
|
-
.passthrough();
|
|
1439
|
-
function getToolCallProgressToken(params) {
|
|
1440
|
-
const parsed = toolCallMetaSchema.safeParse(params);
|
|
1441
|
-
if (!parsed.success)
|
|
1442
|
-
return undefined;
|
|
1443
|
-
return parsed.data._meta?.progressToken ?? parsed.data.progressToken;
|
|
1444
|
-
}
|
|
1445
|
-
function formatMcpInvalidParams(toolName, error) {
|
|
1446
|
-
const details = error.issues
|
|
1447
|
-
.map((issue) => {
|
|
1448
|
-
const path = issue.path.length ? issue.path.join(".") : "(root)";
|
|
1449
|
-
return `${path}: ${issue.message}`;
|
|
1450
|
-
})
|
|
1451
|
-
.join("; ");
|
|
1452
|
-
return `Invalid parameters for ${toolName}: ${details}`;
|
|
1453
|
-
}
|
|
1454
|
-
function parseMcpToolInput(schema, input, toolName) {
|
|
1455
|
-
const parsed = schema.safeParse(input);
|
|
1456
|
-
if (parsed.success)
|
|
1457
|
-
return parsed.data;
|
|
1458
|
-
throw new ProtocolError(ProtocolErrorCode.InvalidParams, formatMcpInvalidParams(toolName, parsed.error));
|
|
1459
|
-
}
|
|
1460
|
-
const searchSchema = z
|
|
1461
|
-
.object({
|
|
1462
|
-
query: z.string(),
|
|
1463
|
-
mode: z.enum(["hybrid", "symbol", "path", "text", "graph", "sql"]).optional(),
|
|
1464
|
-
from: z.string().optional(),
|
|
1465
|
-
depth: z.number().int().nonnegative().max(MAX_GRAPH_DEPTH).optional(),
|
|
1466
|
-
limit: z.number().int().nonnegative().optional(),
|
|
1467
|
-
})
|
|
1468
|
-
.strict();
|
|
1469
|
-
const workspaceSymbolsSchema = z
|
|
1470
|
-
.object({
|
|
1471
|
-
query: z.string(),
|
|
1472
|
-
kinds: z.array(z.nativeEnum(SymbolKind)).optional(),
|
|
1473
|
-
exportedOnly: z.boolean().optional(),
|
|
1474
|
-
includeImports: z.boolean().optional(),
|
|
1475
|
-
fileGlob: z.string().optional(),
|
|
1476
|
-
limit: z.number().int().nonnegative().max(MAX_WORKSPACE_SYMBOL_LIMIT).optional(),
|
|
1477
|
-
})
|
|
1478
|
-
.strict();
|
|
1479
|
-
const renamePreviewSchema = z
|
|
1480
|
-
.object({
|
|
1481
|
-
handle: z.string(),
|
|
1482
|
-
newName: z.string(),
|
|
1483
|
-
includeComments: z.boolean().optional(),
|
|
1484
|
-
includeStrings: z.boolean().optional(),
|
|
1485
|
-
includeFilenames: z.boolean().optional(),
|
|
1486
|
-
maxEdits: z.number().int().min(1).max(MAX_RENAME_PREVIEW_EDITS).optional(),
|
|
1487
|
-
})
|
|
1488
|
-
.strict();
|
|
1489
|
-
const refactorPlanSchema = z
|
|
1490
|
-
.object({
|
|
1491
|
-
handle: z.string(),
|
|
1492
|
-
renameTo: z.string().optional(),
|
|
1493
|
-
maxReferences: z.number().int().nonnegative().max(MAX_REFACTOR_PLAN_LIMIT).optional(),
|
|
1494
|
-
maxCallers: z.number().int().nonnegative().max(MAX_REFACTOR_PLAN_LIMIT).optional(),
|
|
1495
|
-
maxHierarchy: z.number().int().nonnegative().max(MAX_REFACTOR_PLAN_LIMIT).optional(),
|
|
1496
|
-
includeSource: z.boolean().optional(),
|
|
1497
|
-
})
|
|
1498
|
-
.strict();
|
|
1499
|
-
const callHierarchySchema = z
|
|
1500
|
-
.object({
|
|
1501
|
-
handle: z.string(),
|
|
1502
|
-
depth: z.number().int().min(1).max(5).optional(),
|
|
1503
|
-
limit: z.number().int().nonnegative().max(500).optional(),
|
|
1504
|
-
includeHeuristic: z.boolean().optional(),
|
|
1505
|
-
})
|
|
1506
|
-
.strict();
|
|
1507
|
-
const callsSchema = callHierarchySchema
|
|
1508
|
-
.extend({
|
|
1509
|
-
direction: z.enum(["callers", "callees"]),
|
|
1510
|
-
})
|
|
1511
|
-
.strict();
|
|
1512
|
-
const typeHierarchySchema = z
|
|
1513
|
-
.object({
|
|
1514
|
-
handle: z.string(),
|
|
1515
|
-
depth: z.number().int().min(1).max(10).optional(),
|
|
1516
|
-
limit: z.number().int().nonnegative().max(500).optional(),
|
|
1517
|
-
})
|
|
1518
|
-
.strict();
|
|
1519
|
-
const typeHierarchyUnifiedSchema = typeHierarchySchema
|
|
1520
|
-
.extend({
|
|
1521
|
-
direction: z.enum(["supertypes", "subtypes"]),
|
|
1522
|
-
})
|
|
1523
|
-
.strict();
|
|
1524
|
-
const implementationsSchema = z
|
|
1525
|
-
.object({
|
|
1526
|
-
handle: z.string(),
|
|
1527
|
-
limit: z.number().int().nonnegative().max(500).optional(),
|
|
1528
|
-
})
|
|
1529
|
-
.strict();
|
|
1530
|
-
const exploreSchema = z
|
|
1531
|
-
.object({
|
|
1532
|
-
query: z.string(),
|
|
1533
|
-
limit: z.number().int().nonnegative().max(50).optional(),
|
|
1534
|
-
maxPackets: z.number().int().nonnegative().max(10).optional(),
|
|
1535
|
-
maxPaths: z.number().int().nonnegative().max(10).optional(),
|
|
1536
|
-
includeSource: z.boolean().optional(),
|
|
1537
|
-
})
|
|
1538
|
-
.strict();
|
|
1539
|
-
const orientSchema = z
|
|
1540
|
-
.object({
|
|
1541
|
-
includeRoots: z.array(z.string()).optional(),
|
|
1542
|
-
budget: z.enum(["small", "medium", "large"]).optional(),
|
|
1543
|
-
})
|
|
1544
|
-
.strict();
|
|
1545
|
-
const packetGetSchema = z
|
|
1546
|
-
.object({
|
|
1547
|
-
target: z.string(),
|
|
1548
|
-
maxSymbols: z.number().int().positive().max(200).optional(),
|
|
1549
|
-
maxSnippets: z.number().int().positive().max(50).optional(),
|
|
1550
|
-
maxDuplicates: z.number().int().positive().max(20).optional(),
|
|
1551
|
-
})
|
|
1552
|
-
.strict();
|
|
1553
|
-
const getFileSchema = z
|
|
1554
|
-
.object({
|
|
1555
|
-
file: z.string(),
|
|
1556
|
-
offset: z.number().int().positive().optional(),
|
|
1557
|
-
limit: z.number().int().positive().max(MAX_FILE_VIEW_LINES).optional(),
|
|
1558
|
-
maxBytes: z.number().int().positive().max(MAX_FILE_VIEW_BYTES).optional(),
|
|
1559
|
-
includeGraphContext: z.boolean().optional(),
|
|
1560
|
-
allowSensitive: z.boolean().optional(),
|
|
1561
|
-
})
|
|
1562
|
-
.strict();
|
|
1563
|
-
const handleSchema = z
|
|
1564
|
-
.object({
|
|
1565
|
-
handle: z.string(),
|
|
1566
|
-
})
|
|
1567
|
-
.strict();
|
|
1568
|
-
const navigationSchema = z
|
|
1569
|
-
.object({
|
|
1570
|
-
handle: z.string().optional(),
|
|
1571
|
-
file: z.string().optional(),
|
|
1572
|
-
line: z.number().int().positive().optional(),
|
|
1573
|
-
column: z.number().int().nonnegative().optional(),
|
|
1574
|
-
})
|
|
1575
|
-
.strict()
|
|
1576
|
-
.refine((request) => {
|
|
1577
|
-
const hasHandle = request.handle !== undefined;
|
|
1578
|
-
const hasAnyPosition = request.file !== undefined || request.line !== undefined || request.column !== undefined;
|
|
1579
|
-
const hasCompletePosition = request.file !== undefined && request.line !== undefined && request.column !== undefined;
|
|
1580
|
-
return hasHandle ? !hasAnyPosition : hasCompletePosition;
|
|
1581
|
-
}, { message: "goto requires either handle or file, line, and column." });
|
|
1582
|
-
const refsSchema = z
|
|
1583
|
-
.object({
|
|
1584
|
-
handle: z.string().optional(),
|
|
1585
|
-
file: z.string().optional(),
|
|
1586
|
-
line: z.number().int().positive().optional(),
|
|
1587
|
-
column: z.number().int().nonnegative().optional(),
|
|
1588
|
-
limit: z.number().int().nonnegative().optional(),
|
|
1589
|
-
})
|
|
1590
|
-
.strict()
|
|
1591
|
-
.refine((request) => {
|
|
1592
|
-
const hasHandle = request.handle !== undefined;
|
|
1593
|
-
const hasAnyPosition = request.file !== undefined || request.line !== undefined || request.column !== undefined;
|
|
1594
|
-
const hasCompletePosition = request.file !== undefined && request.line !== undefined && request.column !== undefined;
|
|
1595
|
-
return hasHandle ? !hasAnyPosition : hasCompletePosition;
|
|
1596
|
-
}, {
|
|
1597
|
-
message: "refs requires either handle or file, line, and column.",
|
|
1598
|
-
});
|
|
1599
|
-
const fileGraphSchema = z
|
|
1600
|
-
.object({
|
|
1601
|
-
file: z.string(),
|
|
1602
|
-
depth: z.number().int().nonnegative().optional(),
|
|
1603
|
-
limit: z.number().int().nonnegative().optional(),
|
|
1604
|
-
})
|
|
1605
|
-
.strict();
|
|
1606
|
-
const fileDepsUnifiedSchema = fileGraphSchema
|
|
1607
|
-
.extend({
|
|
1608
|
-
direction: z.enum(["deps", "rdeps"]),
|
|
1609
|
-
})
|
|
1610
|
-
.strict();
|
|
1611
|
-
const pathSchema = z
|
|
1612
|
-
.object({
|
|
1613
|
-
from: z.string(),
|
|
1614
|
-
to: z.string(),
|
|
1615
|
-
})
|
|
1616
|
-
.strict();
|
|
1617
|
-
const gitRangeSchema = z
|
|
1618
|
-
.object({
|
|
1619
|
-
base: z.string(),
|
|
1620
|
-
head: z.string(),
|
|
1621
|
-
})
|
|
1622
|
-
.strict();
|
|
1623
|
-
const reviewSchema = z
|
|
1624
|
-
.object({
|
|
1625
|
-
base: z.string(),
|
|
1626
|
-
head: z.string(),
|
|
1627
|
-
reviewDepth: z.enum(["minimal", "standard", "deep"]).optional(),
|
|
1628
|
-
})
|
|
1629
|
-
.strict();
|
|
1630
|
-
const querySqliteSchema = z
|
|
1631
|
-
.object({
|
|
1632
|
-
query: z.string(),
|
|
1633
|
-
params: z.array(z.union([z.string(), z.number(), z.null()])).optional(),
|
|
1634
|
-
limit: z.number().int().nonnegative().optional(),
|
|
1635
|
-
})
|
|
1636
|
-
.strict();
|
|
1637
|
-
const refreshIndexSchema = z
|
|
1638
|
-
.object({
|
|
1639
|
-
warmup: z.enum(["off", "base", "symbols"]).optional(),
|
|
1640
|
-
})
|
|
1641
|
-
.strict();
|
|
1642
|
-
const artifactBuildSchema = z
|
|
1643
|
-
.object({
|
|
1644
|
-
outDir: z.string().optional(),
|
|
1645
|
-
sqlite: z.boolean().optional(),
|
|
1646
|
-
graphJson: z.boolean().optional(),
|
|
1647
|
-
report: z.boolean().optional(),
|
|
1648
|
-
questions: z.boolean().optional(),
|
|
1649
|
-
force: z.boolean().optional(),
|
|
1650
|
-
})
|
|
1651
|
-
.strict();
|
|
1652
56
|
//# sourceMappingURL=server.js.map
|