@opencodehub/mcp 0.1.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 +62 -0
- package/dist/analysis-bridge.d.ts +23 -0
- package/dist/analysis-bridge.d.ts.map +1 -0
- package/dist/analysis-bridge.js +83 -0
- package/dist/analysis-bridge.js.map +1 -0
- package/dist/connection-pool.d.ts +76 -0
- package/dist/connection-pool.d.ts.map +1 -0
- package/dist/connection-pool.js +179 -0
- package/dist/connection-pool.js.map +1 -0
- package/dist/error-envelope.d.ts +97 -0
- package/dist/error-envelope.d.ts.map +1 -0
- package/dist/error-envelope.js +75 -0
- package/dist/error-envelope.js.map +1 -0
- package/dist/group-resolver.d.ts +29 -0
- package/dist/group-resolver.d.ts.map +1 -0
- package/dist/group-resolver.js +100 -0
- package/dist/group-resolver.js.map +1 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +1 -0
- package/dist/next-step-hints.d.ts +24 -0
- package/dist/next-step-hints.d.ts.map +1 -0
- package/dist/next-step-hints.js +41 -0
- package/dist/next-step-hints.js.map +1 -0
- package/dist/repo-resolver.d.ts +88 -0
- package/dist/repo-resolver.d.ts.map +1 -0
- package/dist/repo-resolver.js +211 -0
- package/dist/repo-resolver.js.map +1 -0
- package/dist/repo-uri-for-entry.d.ts +25 -0
- package/dist/repo-uri-for-entry.d.ts.map +1 -0
- package/dist/repo-uri-for-entry.js +64 -0
- package/dist/repo-uri-for-entry.js.map +1 -0
- package/dist/resources/repo-cluster.d.ts +19 -0
- package/dist/resources/repo-cluster.d.ts.map +1 -0
- package/dist/resources/repo-cluster.js +203 -0
- package/dist/resources/repo-cluster.js.map +1 -0
- package/dist/resources/repo-clusters.d.ts +14 -0
- package/dist/resources/repo-clusters.d.ts.map +1 -0
- package/dist/resources/repo-clusters.js +97 -0
- package/dist/resources/repo-clusters.js.map +1 -0
- package/dist/resources/repo-context.d.ts +12 -0
- package/dist/resources/repo-context.d.ts.map +1 -0
- package/dist/resources/repo-context.js +84 -0
- package/dist/resources/repo-context.js.map +1 -0
- package/dist/resources/repo-process.d.ts +19 -0
- package/dist/resources/repo-process.d.ts.map +1 -0
- package/dist/resources/repo-process.js +220 -0
- package/dist/resources/repo-process.js.map +1 -0
- package/dist/resources/repo-processes.d.ts +13 -0
- package/dist/resources/repo-processes.d.ts.map +1 -0
- package/dist/resources/repo-processes.js +99 -0
- package/dist/resources/repo-processes.js.map +1 -0
- package/dist/resources/repo-schema.d.ts +13 -0
- package/dist/resources/repo-schema.d.ts.map +1 -0
- package/dist/resources/repo-schema.js +99 -0
- package/dist/resources/repo-schema.js.map +1 -0
- package/dist/resources/repos.d.ts +20 -0
- package/dist/resources/repos.d.ts.map +1 -0
- package/dist/resources/repos.js +58 -0
- package/dist/resources/repos.js.map +1 -0
- package/dist/resources/store-helper.d.ts +28 -0
- package/dist/resources/store-helper.d.ts.map +1 -0
- package/dist/resources/store-helper.js +58 -0
- package/dist/resources/store-helper.js.map +1 -0
- package/dist/resources/yaml.d.ts +10 -0
- package/dist/resources/yaml.d.ts.map +1 -0
- package/dist/resources/yaml.js +16 -0
- package/dist/resources/yaml.js.map +1 -0
- package/dist/server.d.ts +46 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +194 -0
- package/dist/server.js.map +1 -0
- package/dist/staleness.d.ts +19 -0
- package/dist/staleness.d.ts.map +1 -0
- package/dist/staleness.js +40 -0
- package/dist/staleness.js.map +1 -0
- package/dist/test-utils.d.ts +170 -0
- package/dist/test-utils.d.ts.map +1 -0
- package/dist/test-utils.js +473 -0
- package/dist/test-utils.js.map +1 -0
- package/dist/tools/api-impact.d.ts +47 -0
- package/dist/tools/api-impact.d.ts.map +1 -0
- package/dist/tools/api-impact.js +199 -0
- package/dist/tools/api-impact.js.map +1 -0
- package/dist/tools/confidence.d.ts +39 -0
- package/dist/tools/confidence.d.ts.map +1 -0
- package/dist/tools/confidence.js +58 -0
- package/dist/tools/confidence.js.map +1 -0
- package/dist/tools/context.d.ts +47 -0
- package/dist/tools/context.d.ts.map +1 -0
- package/dist/tools/context.js +577 -0
- package/dist/tools/context.js.map +1 -0
- package/dist/tools/dependencies.d.ts +29 -0
- package/dist/tools/dependencies.d.ts.map +1 -0
- package/dist/tools/dependencies.js +110 -0
- package/dist/tools/dependencies.js.map +1 -0
- package/dist/tools/detect-changes.d.ts +15 -0
- package/dist/tools/detect-changes.d.ts.map +1 -0
- package/dist/tools/detect-changes.js +78 -0
- package/dist/tools/detect-changes.js.map +1 -0
- package/dist/tools/group-contracts.d.ts +26 -0
- package/dist/tools/group-contracts.d.ts.map +1 -0
- package/dist/tools/group-contracts.js +251 -0
- package/dist/tools/group-contracts.js.map +1 -0
- package/dist/tools/group-cross-repo-links.d.ts +28 -0
- package/dist/tools/group-cross-repo-links.d.ts.map +1 -0
- package/dist/tools/group-cross-repo-links.js +128 -0
- package/dist/tools/group-cross-repo-links.js.map +1 -0
- package/dist/tools/group-list.d.ts +10 -0
- package/dist/tools/group-list.d.ts.map +1 -0
- package/dist/tools/group-list.js +74 -0
- package/dist/tools/group-list.js.map +1 -0
- package/dist/tools/group-query.d.ts +40 -0
- package/dist/tools/group-query.d.ts.map +1 -0
- package/dist/tools/group-query.js +209 -0
- package/dist/tools/group-query.js.map +1 -0
- package/dist/tools/group-status.d.ts +21 -0
- package/dist/tools/group-status.d.ts.map +1 -0
- package/dist/tools/group-status.js +121 -0
- package/dist/tools/group-status.js.map +1 -0
- package/dist/tools/group-sync.d.ts +23 -0
- package/dist/tools/group-sync.d.ts.map +1 -0
- package/dist/tools/group-sync.js +112 -0
- package/dist/tools/group-sync.js.map +1 -0
- package/dist/tools/impact.d.ts +36 -0
- package/dist/tools/impact.d.ts.map +1 -0
- package/dist/tools/impact.js +232 -0
- package/dist/tools/impact.js.map +1 -0
- package/dist/tools/license-audit.d.ts +34 -0
- package/dist/tools/license-audit.d.ts.map +1 -0
- package/dist/tools/license-audit.js +108 -0
- package/dist/tools/license-audit.js.map +1 -0
- package/dist/tools/list-dead-code.d.ts +26 -0
- package/dist/tools/list-dead-code.d.ts.map +1 -0
- package/dist/tools/list-dead-code.js +110 -0
- package/dist/tools/list-dead-code.js.map +1 -0
- package/dist/tools/list-findings-delta.d.ts +36 -0
- package/dist/tools/list-findings-delta.d.ts.map +1 -0
- package/dist/tools/list-findings-delta.js +274 -0
- package/dist/tools/list-findings-delta.js.map +1 -0
- package/dist/tools/list-findings.d.ts +30 -0
- package/dist/tools/list-findings.d.ts.map +1 -0
- package/dist/tools/list-findings.js +129 -0
- package/dist/tools/list-findings.js.map +1 -0
- package/dist/tools/list-repos.d.ts +17 -0
- package/dist/tools/list-repos.d.ts.map +1 -0
- package/dist/tools/list-repos.js +63 -0
- package/dist/tools/list-repos.js.map +1 -0
- package/dist/tools/owners.d.ts +23 -0
- package/dist/tools/owners.d.ts.map +1 -0
- package/dist/tools/owners.js +103 -0
- package/dist/tools/owners.js.map +1 -0
- package/dist/tools/pack-codebase.d.ts +76 -0
- package/dist/tools/pack-codebase.d.ts.map +1 -0
- package/dist/tools/pack-codebase.js +289 -0
- package/dist/tools/pack-codebase.js.map +1 -0
- package/dist/tools/project-profile.d.ts +28 -0
- package/dist/tools/project-profile.d.ts.map +1 -0
- package/dist/tools/project-profile.js +109 -0
- package/dist/tools/project-profile.js.map +1 -0
- package/dist/tools/query.d.ts +63 -0
- package/dist/tools/query.d.ts.map +1 -0
- package/dist/tools/query.js +662 -0
- package/dist/tools/query.js.map +1 -0
- package/dist/tools/remove-dead-code.d.ts +47 -0
- package/dist/tools/remove-dead-code.d.ts.map +1 -0
- package/dist/tools/remove-dead-code.js +258 -0
- package/dist/tools/remove-dead-code.js.map +1 -0
- package/dist/tools/rename.d.ts +21 -0
- package/dist/tools/rename.d.ts.map +1 -0
- package/dist/tools/rename.js +116 -0
- package/dist/tools/rename.js.map +1 -0
- package/dist/tools/risk-trends.d.ts +19 -0
- package/dist/tools/risk-trends.d.ts.map +1 -0
- package/dist/tools/risk-trends.js +73 -0
- package/dist/tools/risk-trends.js.map +1 -0
- package/dist/tools/route-map.d.ts +27 -0
- package/dist/tools/route-map.d.ts.map +1 -0
- package/dist/tools/route-map.js +119 -0
- package/dist/tools/route-map.js.map +1 -0
- package/dist/tools/scan.d.ts +27 -0
- package/dist/tools/scan.d.ts.map +1 -0
- package/dist/tools/scan.js +136 -0
- package/dist/tools/scan.js.map +1 -0
- package/dist/tools/shape-check.d.ts +53 -0
- package/dist/tools/shape-check.d.ts.map +1 -0
- package/dist/tools/shape-check.js +161 -0
- package/dist/tools/shape-check.js.map +1 -0
- package/dist/tools/shared.d.ts +101 -0
- package/dist/tools/shared.d.ts.map +1 -0
- package/dist/tools/shared.js +114 -0
- package/dist/tools/shared.js.map +1 -0
- package/dist/tools/signature.d.ts +38 -0
- package/dist/tools/signature.d.ts.map +1 -0
- package/dist/tools/signature.js +332 -0
- package/dist/tools/signature.js.map +1 -0
- package/dist/tools/sql.d.ts +34 -0
- package/dist/tools/sql.d.ts.map +1 -0
- package/dist/tools/sql.js +222 -0
- package/dist/tools/sql.js.map +1 -0
- package/dist/tools/tool-map.d.ts +24 -0
- package/dist/tools/tool-map.d.ts.map +1 -0
- package/dist/tools/tool-map.js +97 -0
- package/dist/tools/tool-map.js.map +1 -0
- package/dist/tools/verdict.d.ts +33 -0
- package/dist/tools/verdict.d.ts.map +1 -0
- package/dist/tools/verdict.js +102 -0
- package/dist/tools/verdict.js.map +1 -0
- package/package.json +76 -0
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# @opencodehub/mcp
|
|
2
|
+
|
|
3
|
+
Model Context Protocol server for OpenCodeHub. Wraps the analysis +
|
|
4
|
+
storage layer and exposes it to coding agents over stdio.
|
|
5
|
+
|
|
6
|
+
## Surface
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
codehub mcp # spawn the stdio server
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
- Transport is stdio only — no HTTP, no SSE, no daemon
|
|
13
|
+
(`packages/cli/src/commands/mcp.ts`).
|
|
14
|
+
- `list_repos` is the discovery entry point. Per-repo tools accept an
|
|
15
|
+
optional `repo` (registry name) or `repo_uri` alias (Sourcegraph-style
|
|
16
|
+
URI like `github.com/org/repo`, `local:<hash>` for unpublished repos);
|
|
17
|
+
with one repo registered both are optional.
|
|
18
|
+
- When ≥ 2 repos are registered and neither is supplied, the tool
|
|
19
|
+
returns an `AMBIGUOUS_REPO` error envelope with `choices[]` (capped at
|
|
20
|
+
10) so the caller can retry deterministically (see root `CLAUDE.md`).
|
|
21
|
+
- Every response carries a `next_steps` array and a
|
|
22
|
+
`_meta.codehub/staleness` entry when the index may be behind HEAD
|
|
23
|
+
(`packages/mcp/src/staleness.ts`).
|
|
24
|
+
|
|
25
|
+
## Tools
|
|
26
|
+
|
|
27
|
+
29 tools registered in `packages/mcp/src/server.ts:151-179`. Implementation
|
|
28
|
+
files live under `packages/mcp/src/tools/<id>.ts`.
|
|
29
|
+
|
|
30
|
+
| Group | Tools |
|
|
31
|
+
| ----------- | ---------------------------------------------------------------------------------------------------------- |
|
|
32
|
+
| Discovery | `list_repos`, `query`, `context`, `route_map`, `tool_map` |
|
|
33
|
+
| Impact | `impact`, `api_impact`, `detect_changes`, `shape_check`, `rename` |
|
|
34
|
+
| Snapshot | `pack_codebase`, `project_profile`, `dependencies`, `owners`, `risk_trends` |
|
|
35
|
+
| Findings | `scan`, `verdict`, `list_findings`, `list_findings_delta`, `license_audit` |
|
|
36
|
+
| Dead code | `list_dead_code`, `remove_dead_code` |
|
|
37
|
+
| Group | `group_list`, `group_query`, `group_status`, `group_contracts`, `group_cross_repo_links`, `group_sync` |
|
|
38
|
+
| Raw query | `sql` |
|
|
39
|
+
|
|
40
|
+
## Design
|
|
41
|
+
|
|
42
|
+
- **Single source of truth** — registration order in `server.ts` IS the
|
|
43
|
+
surface. `tool_map` introspects the live server so agents can list
|
|
44
|
+
tools without out-of-band documentation
|
|
45
|
+
(`packages/mcp/src/tools/tool-map.ts`).
|
|
46
|
+
- **Structured errors over prose** — every error returns
|
|
47
|
+
`structuredContent.error = { error_code, jsonrpc_code, ... }` so a
|
|
48
|
+
caller can branch on `error_code` instead of regex-matching
|
|
49
|
+
(`packages/mcp/src/error-envelope.ts`).
|
|
50
|
+
- **Repo resolution is centralised** — `repoResolver` and the
|
|
51
|
+
AMBIGUOUS_REPO envelope are wired through every per-repo tool so
|
|
52
|
+
ambiguity is reported once, consistently
|
|
53
|
+
(`packages/mcp/src/repo-resolver.ts`).
|
|
54
|
+
- **Connection pooling** — the graph store is held in a per-process
|
|
55
|
+
pool to amortise DuckDB cold starts across many tool calls
|
|
56
|
+
(`packages/mcp/src/connection-pool.ts`).
|
|
57
|
+
- **Lazy analysis** — heavy work (scan, code-pack, verdict) shells out
|
|
58
|
+
via `analysis-bridge` rather than running in the MCP process so a
|
|
59
|
+
hung scanner cannot stall the server (`packages/mcp/src/analysis-bridge.ts`).
|
|
60
|
+
|
|
61
|
+
See ADR 0012 for the `repo_uri`-as-typed-attribute rationale and the
|
|
62
|
+
root `CLAUDE.md` for the AMBIGUOUS_REPO retry contract.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge to `@opencodehub/analysis`.
|
|
3
|
+
*
|
|
4
|
+
* The analysis package exposes `runImpact`, `runRename`, `runDetectChanges`,
|
|
5
|
+
* and `computeStaleness`, which this bridge re-exports for the tool handlers.
|
|
6
|
+
* A slim inline-impact fallback remains as a safety net for repos where
|
|
7
|
+
* analysis cannot resolve the target — e.g. a bare node-id with no
|
|
8
|
+
* declaration row — so the `impact` tool always returns something actionable.
|
|
9
|
+
*/
|
|
10
|
+
import { type DetectChangesQuery, type DetectChangesResult, type FsAbstraction, type ImpactQuery, type ImpactResult, type RenameQuery, type RenameResult } from "@opencodehub/analysis";
|
|
11
|
+
import type { IGraphStore } from "@opencodehub/storage";
|
|
12
|
+
export type { DetectChangesQuery, DetectChangesResult, ImpactQuery, ImpactResult, RenameEdit, RenameQuery, RenameResult, } from "@opencodehub/analysis";
|
|
13
|
+
export declare function callRunImpact(store: IGraphStore, q: ImpactQuery): Promise<ImpactResult>;
|
|
14
|
+
export declare function callRunRename(store: IGraphStore, q: RenameQuery, repoRoot: string, fs?: FsAbstraction): Promise<RenameResult>;
|
|
15
|
+
export declare function callRunDetectChanges(store: IGraphStore, q: DetectChangesQuery): Promise<DetectChangesResult>;
|
|
16
|
+
/**
|
|
17
|
+
* Graph-only impact fallback. Produces the same `ImpactResult` shape as
|
|
18
|
+
* the analysis package but skips the name-to-id resolution step — useful
|
|
19
|
+
* when the target is already a node id and the caller does not need
|
|
20
|
+
* candidate disambiguation.
|
|
21
|
+
*/
|
|
22
|
+
export declare function inlineImpact(store: IGraphStore, q: ImpactQuery): Promise<ImpactResult>;
|
|
23
|
+
//# sourceMappingURL=analysis-bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analysis-bridge.d.ts","sourceRoot":"","sources":["../src/analysis-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAKL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,YAAY,EAClB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,YAAY,EACV,kBAAkB,EAClB,mBAAmB,EACnB,WAAW,EACX,YAAY,EACZ,UAAU,EACV,WAAW,EACX,YAAY,GACb,MAAM,uBAAuB,CAAC;AAE/B,wBAAsB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAE7F;AAED,wBAAsB,aAAa,CACjC,KAAK,EAAE,WAAW,EAClB,CAAC,EAAE,WAAW,EACd,QAAQ,EAAE,MAAM,EAChB,EAAE,CAAC,EAAE,aAAa,GACjB,OAAO,CAAC,YAAY,CAAC,CAEvB;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,WAAW,EAClB,CAAC,EAAE,kBAAkB,GACpB,OAAO,CAAC,mBAAmB,CAAC,CAE9B;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAgE5F"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge to `@opencodehub/analysis`.
|
|
3
|
+
*
|
|
4
|
+
* The analysis package exposes `runImpact`, `runRename`, `runDetectChanges`,
|
|
5
|
+
* and `computeStaleness`, which this bridge re-exports for the tool handlers.
|
|
6
|
+
* A slim inline-impact fallback remains as a safety net for repos where
|
|
7
|
+
* analysis cannot resolve the target — e.g. a bare node-id with no
|
|
8
|
+
* declaration row — so the `impact` tool always returns something actionable.
|
|
9
|
+
*/
|
|
10
|
+
import { runDetectChanges as analysisRunDetectChanges, runImpact as analysisRunImpact, runRename as analysisRunRename, createNodeFs, } from "@opencodehub/analysis";
|
|
11
|
+
export async function callRunImpact(store, q) {
|
|
12
|
+
return analysisRunImpact(store, q);
|
|
13
|
+
}
|
|
14
|
+
export async function callRunRename(store, q, repoRoot, fs) {
|
|
15
|
+
return analysisRunRename(store, q, fs ?? createNodeFs(), repoRoot);
|
|
16
|
+
}
|
|
17
|
+
export async function callRunDetectChanges(store, q) {
|
|
18
|
+
return analysisRunDetectChanges(store, q);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Graph-only impact fallback. Produces the same `ImpactResult` shape as
|
|
22
|
+
* the analysis package but skips the name-to-id resolution step — useful
|
|
23
|
+
* when the target is already a node id and the caller does not need
|
|
24
|
+
* candidate disambiguation.
|
|
25
|
+
*/
|
|
26
|
+
export async function inlineImpact(store, q) {
|
|
27
|
+
const maxDepth = q.maxDepth ?? 3;
|
|
28
|
+
const minConfidence = q.minConfidence ?? 0.3;
|
|
29
|
+
const direction = q.direction === "upstream" ? "up" : q.direction === "downstream" ? "down" : "both";
|
|
30
|
+
const travArgs = {
|
|
31
|
+
startId: q.target,
|
|
32
|
+
direction,
|
|
33
|
+
maxDepth,
|
|
34
|
+
minConfidence,
|
|
35
|
+
};
|
|
36
|
+
if (q.relationTypes && q.relationTypes.length > 0) {
|
|
37
|
+
travArgs.relationTypes = q.relationTypes;
|
|
38
|
+
}
|
|
39
|
+
const results = await store.traverse(travArgs);
|
|
40
|
+
const byDepthMap = new Map();
|
|
41
|
+
for (const r of results) {
|
|
42
|
+
let bucket = byDepthMap.get(r.depth);
|
|
43
|
+
if (!bucket) {
|
|
44
|
+
bucket = new Map();
|
|
45
|
+
byDepthMap.set(r.depth, bucket);
|
|
46
|
+
}
|
|
47
|
+
bucket.set(r.nodeId, true);
|
|
48
|
+
}
|
|
49
|
+
const depths = Array.from(byDepthMap.keys()).sort((a, b) => a - b);
|
|
50
|
+
const byDepth = depths.map((depth) => {
|
|
51
|
+
const bucket = byDepthMap.get(depth);
|
|
52
|
+
const ids = bucket ? Array.from(bucket.keys()) : [];
|
|
53
|
+
return {
|
|
54
|
+
depth,
|
|
55
|
+
nodes: ids.map((nodeId) => ({
|
|
56
|
+
id: nodeId,
|
|
57
|
+
name: nodeId,
|
|
58
|
+
filePath: "",
|
|
59
|
+
kind: "",
|
|
60
|
+
viaRelation: "CALLS",
|
|
61
|
+
})),
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
const d1 = byDepthMap.get(1)?.size ?? 0;
|
|
65
|
+
let risk = "LOW";
|
|
66
|
+
if (d1 >= 20)
|
|
67
|
+
risk = "CRITICAL";
|
|
68
|
+
else if (d1 >= 8)
|
|
69
|
+
risk = "HIGH";
|
|
70
|
+
else if (d1 >= 3)
|
|
71
|
+
risk = "MEDIUM";
|
|
72
|
+
return {
|
|
73
|
+
targetCandidates: [],
|
|
74
|
+
byDepth,
|
|
75
|
+
risk,
|
|
76
|
+
totalAffected: results.length,
|
|
77
|
+
ambiguous: false,
|
|
78
|
+
affectedProcesses: [],
|
|
79
|
+
affectedModules: [],
|
|
80
|
+
traversedEdges: [],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=analysis-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analysis-bridge.js","sourceRoot":"","sources":["../src/analysis-bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LRU-backed connection pool for graph stores.
|
|
3
|
+
*
|
|
4
|
+
* A single MCP session routinely fields back-to-back tool calls that all
|
|
5
|
+
* target the same repo; opening the underlying database for every call
|
|
6
|
+
* would be wasteful. We cache open `Store` (= `OpenStoreResult`) handles
|
|
7
|
+
* keyed by absolute repo path, with three safety guards on top of a plain
|
|
8
|
+
* LRU:
|
|
9
|
+
*
|
|
10
|
+
* 1. Per-key promise dedupe. Concurrent acquires for the same repo share
|
|
11
|
+
* a single in-flight open() — otherwise DuckDB will raise on the
|
|
12
|
+
* second connection opening the same file in read-write mode.
|
|
13
|
+
* 2. Reference counting. Release must decrement a per-entry counter; an
|
|
14
|
+
* eviction that lands on a still-in-use entry MUST NOT close it. We
|
|
15
|
+
* mark it `closed` deferred and the last release actually closes.
|
|
16
|
+
* 3. Idle TTL. lru-cache@11 bumps recency on every acquire, so a repo
|
|
17
|
+
* that is actively queried never evicts; an idle repo closes after
|
|
18
|
+
* 15 minutes.
|
|
19
|
+
*
|
|
20
|
+
* `shutdown()` drains the pool on stdio close so the server exits cleanly.
|
|
21
|
+
*
|
|
22
|
+
* The pool caches the composed `OpenStoreResult` so MCP tools can route
|
|
23
|
+
* graph-tier calls through `store.graph` and temporal-tier calls
|
|
24
|
+
* (cochanges, summaries, `--sql` escape hatch) through `store.temporal`.
|
|
25
|
+
* Backend selection follows the standard `openStore` resolution (env-
|
|
26
|
+
* driven `CODEHUB_STORE`, with auto-detect when unset).
|
|
27
|
+
* `OpenStoreResult.close()` is the deterministic composite close — for
|
|
28
|
+
* the DuckDB-only deployment that's a single underlying close.
|
|
29
|
+
*/
|
|
30
|
+
import { type Store } from "@opencodehub/storage";
|
|
31
|
+
export interface PoolEntry {
|
|
32
|
+
readonly store: Store;
|
|
33
|
+
refCount: number;
|
|
34
|
+
closed: boolean;
|
|
35
|
+
/** Set when an eviction fires while refCount > 0; close on last release. */
|
|
36
|
+
closePending: boolean;
|
|
37
|
+
}
|
|
38
|
+
export interface ConnectionPoolOptions {
|
|
39
|
+
readonly max?: number;
|
|
40
|
+
readonly ttlMs?: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Factory indirection keeps tests mockable without standing up the
|
|
44
|
+
* underlying database. Production always calls `openStore` so backend
|
|
45
|
+
* selection (DuckDB or the graph-db pairing) follows the env-driven
|
|
46
|
+
* resolution.
|
|
47
|
+
*/
|
|
48
|
+
export type StoreFactory = (dbPath: string) => Promise<Store>;
|
|
49
|
+
export declare class ConnectionPool {
|
|
50
|
+
private readonly cache;
|
|
51
|
+
private readonly inflight;
|
|
52
|
+
private readonly factory;
|
|
53
|
+
private disposed;
|
|
54
|
+
constructor(opts?: ConnectionPoolOptions, factory?: StoreFactory);
|
|
55
|
+
/**
|
|
56
|
+
* Acquire a store handle for the given repo. The caller MUST pair every
|
|
57
|
+
* acquire with a release. The `dbPath` argument is the absolute path to
|
|
58
|
+
* the on-disk DuckDB file; `repoKey` is a stable identifier used for
|
|
59
|
+
* caching (usually the absolute repo path).
|
|
60
|
+
*/
|
|
61
|
+
acquire(repoKey: string, dbPath: string): Promise<Store>;
|
|
62
|
+
/**
|
|
63
|
+
* Release a previously-acquired handle. If the entry was evicted while
|
|
64
|
+
* in use (`closePending`), the last release closes the store.
|
|
65
|
+
*/
|
|
66
|
+
release(repoKey: string): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Drain the pool: wait on any inflight opens, then close every cached
|
|
69
|
+
* entry. Safe to call multiple times.
|
|
70
|
+
*/
|
|
71
|
+
shutdown(): Promise<void>;
|
|
72
|
+
/** Test-only view of cached keys; stable iteration order is not guaranteed. */
|
|
73
|
+
size(): number;
|
|
74
|
+
private findEvicted;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=connection-pool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection-pool.d.ts","sourceRoot":"","sources":["../src/connection-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAa,KAAK,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAG7D,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,4EAA4E;IAC5E,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAKD;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;AAiB9D,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA8B;IACpD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyC;IAClE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAS;gBAEb,IAAI,GAAE,qBAA0B,EAAE,OAAO,GAAE,YAA6B;IA0BpF;;;;;OAKG;IACG,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAsC9D;;;OAGG;IACG,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY7C;;;OAGG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAsB/B,+EAA+E;IAC/E,IAAI,IAAI,MAAM;IAId,OAAO,CAAC,WAAW;CAYpB"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LRU-backed connection pool for graph stores.
|
|
3
|
+
*
|
|
4
|
+
* A single MCP session routinely fields back-to-back tool calls that all
|
|
5
|
+
* target the same repo; opening the underlying database for every call
|
|
6
|
+
* would be wasteful. We cache open `Store` (= `OpenStoreResult`) handles
|
|
7
|
+
* keyed by absolute repo path, with three safety guards on top of a plain
|
|
8
|
+
* LRU:
|
|
9
|
+
*
|
|
10
|
+
* 1. Per-key promise dedupe. Concurrent acquires for the same repo share
|
|
11
|
+
* a single in-flight open() — otherwise DuckDB will raise on the
|
|
12
|
+
* second connection opening the same file in read-write mode.
|
|
13
|
+
* 2. Reference counting. Release must decrement a per-entry counter; an
|
|
14
|
+
* eviction that lands on a still-in-use entry MUST NOT close it. We
|
|
15
|
+
* mark it `closed` deferred and the last release actually closes.
|
|
16
|
+
* 3. Idle TTL. lru-cache@11 bumps recency on every acquire, so a repo
|
|
17
|
+
* that is actively queried never evicts; an idle repo closes after
|
|
18
|
+
* 15 minutes.
|
|
19
|
+
*
|
|
20
|
+
* `shutdown()` drains the pool on stdio close so the server exits cleanly.
|
|
21
|
+
*
|
|
22
|
+
* The pool caches the composed `OpenStoreResult` so MCP tools can route
|
|
23
|
+
* graph-tier calls through `store.graph` and temporal-tier calls
|
|
24
|
+
* (cochanges, summaries, `--sql` escape hatch) through `store.temporal`.
|
|
25
|
+
* Backend selection follows the standard `openStore` resolution (env-
|
|
26
|
+
* driven `CODEHUB_STORE`, with auto-detect when unset).
|
|
27
|
+
* `OpenStoreResult.close()` is the deterministic composite close — for
|
|
28
|
+
* the DuckDB-only deployment that's a single underlying close.
|
|
29
|
+
*/
|
|
30
|
+
import { openStore } from "@opencodehub/storage";
|
|
31
|
+
import { LRUCache } from "lru-cache";
|
|
32
|
+
const DEFAULT_MAX = 8;
|
|
33
|
+
const DEFAULT_TTL_MS = 15 * 60 * 1000;
|
|
34
|
+
const defaultFactory = async (dbPath) => {
|
|
35
|
+
// openStore picks backend via CODEHUB_STORE (defaults to "duck"). We
|
|
36
|
+
// open read-only because every MCP tool is a reader; the ingestion
|
|
37
|
+
// pipeline owns writes and runs out-of-process.
|
|
38
|
+
const store = await openStore({ path: dbPath, readOnly: true });
|
|
39
|
+
await store.graph.open();
|
|
40
|
+
if (store.graphFile !== store.temporalFile) {
|
|
41
|
+
// Two distinct underlying files — open each side. For the default
|
|
42
|
+
// DuckDB backend graph and temporal alias the same instance and the
|
|
43
|
+
// second open() is a no-op.
|
|
44
|
+
await store.temporal.open();
|
|
45
|
+
}
|
|
46
|
+
return store;
|
|
47
|
+
};
|
|
48
|
+
export class ConnectionPool {
|
|
49
|
+
cache;
|
|
50
|
+
inflight = new Map();
|
|
51
|
+
factory;
|
|
52
|
+
disposed = false;
|
|
53
|
+
constructor(opts = {}, factory = defaultFactory) {
|
|
54
|
+
this.factory = factory;
|
|
55
|
+
this.cache = new LRUCache({
|
|
56
|
+
max: opts.max ?? DEFAULT_MAX,
|
|
57
|
+
ttl: opts.ttlMs ?? DEFAULT_TTL_MS,
|
|
58
|
+
updateAgeOnGet: true,
|
|
59
|
+
// The dispose callback fires on eviction (size or ttl) and on
|
|
60
|
+
// cache.clear(). We treat eviction as "this entry is no longer
|
|
61
|
+
// reachable from the cache"; if nobody is using it we close now,
|
|
62
|
+
// otherwise we let the last `release()` close it.
|
|
63
|
+
dispose: (entry, key) => {
|
|
64
|
+
if (entry.closed)
|
|
65
|
+
return;
|
|
66
|
+
if (entry.refCount === 0) {
|
|
67
|
+
entry.closed = true;
|
|
68
|
+
void entry.store.close().catch(() => {
|
|
69
|
+
/* swallow — best effort during eviction */
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
entry.closePending = true;
|
|
74
|
+
}
|
|
75
|
+
// Ensure we don't leak a stale inflight promise for an evicted key.
|
|
76
|
+
this.inflight.delete(key);
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Acquire a store handle for the given repo. The caller MUST pair every
|
|
82
|
+
* acquire with a release. The `dbPath` argument is the absolute path to
|
|
83
|
+
* the on-disk DuckDB file; `repoKey` is a stable identifier used for
|
|
84
|
+
* caching (usually the absolute repo path).
|
|
85
|
+
*/
|
|
86
|
+
async acquire(repoKey, dbPath) {
|
|
87
|
+
if (this.disposed) {
|
|
88
|
+
throw new Error("ConnectionPool is shut down");
|
|
89
|
+
}
|
|
90
|
+
const existing = this.cache.get(repoKey);
|
|
91
|
+
if (existing && !existing.closed) {
|
|
92
|
+
existing.refCount += 1;
|
|
93
|
+
return existing.store;
|
|
94
|
+
}
|
|
95
|
+
const pending = this.inflight.get(repoKey);
|
|
96
|
+
if (pending) {
|
|
97
|
+
const entry = await pending;
|
|
98
|
+
entry.refCount += 1;
|
|
99
|
+
return entry.store;
|
|
100
|
+
}
|
|
101
|
+
const promise = (async () => {
|
|
102
|
+
const store = await this.factory(dbPath);
|
|
103
|
+
const entry = {
|
|
104
|
+
store,
|
|
105
|
+
refCount: 0,
|
|
106
|
+
closed: false,
|
|
107
|
+
closePending: false,
|
|
108
|
+
};
|
|
109
|
+
this.cache.set(repoKey, entry);
|
|
110
|
+
return entry;
|
|
111
|
+
})();
|
|
112
|
+
this.inflight.set(repoKey, promise);
|
|
113
|
+
try {
|
|
114
|
+
const entry = await promise;
|
|
115
|
+
entry.refCount += 1;
|
|
116
|
+
return entry.store;
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
this.inflight.delete(repoKey);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Release a previously-acquired handle. If the entry was evicted while
|
|
124
|
+
* in use (`closePending`), the last release closes the store.
|
|
125
|
+
*/
|
|
126
|
+
async release(repoKey) {
|
|
127
|
+
const entry = this.cache.peek(repoKey) ?? this.findEvicted(repoKey);
|
|
128
|
+
if (!entry)
|
|
129
|
+
return;
|
|
130
|
+
if (entry.refCount > 0)
|
|
131
|
+
entry.refCount -= 1;
|
|
132
|
+
if (entry.refCount === 0 && entry.closePending && !entry.closed) {
|
|
133
|
+
entry.closed = true;
|
|
134
|
+
await entry.store.close().catch(() => {
|
|
135
|
+
/* swallow */
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Drain the pool: wait on any inflight opens, then close every cached
|
|
141
|
+
* entry. Safe to call multiple times.
|
|
142
|
+
*/
|
|
143
|
+
async shutdown() {
|
|
144
|
+
if (this.disposed)
|
|
145
|
+
return;
|
|
146
|
+
this.disposed = true;
|
|
147
|
+
// Wait for inflight opens so we don't leak half-opened DBs.
|
|
148
|
+
const pending = Array.from(this.inflight.values());
|
|
149
|
+
await Promise.allSettled(pending);
|
|
150
|
+
this.inflight.clear();
|
|
151
|
+
const entries = [];
|
|
152
|
+
for (const entry of this.cache.values())
|
|
153
|
+
entries.push(entry);
|
|
154
|
+
this.cache.clear(); // triggers dispose on remaining entries
|
|
155
|
+
await Promise.allSettled(entries.map(async (entry) => {
|
|
156
|
+
if (!entry.closed) {
|
|
157
|
+
entry.closed = true;
|
|
158
|
+
await entry.store.close();
|
|
159
|
+
}
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
/** Test-only view of cached keys; stable iteration order is not guaranteed. */
|
|
163
|
+
size() {
|
|
164
|
+
return this.cache.size;
|
|
165
|
+
}
|
|
166
|
+
findEvicted(_repoKey) {
|
|
167
|
+
// After dispose runs, the entry is gone from the cache; the caller
|
|
168
|
+
// holds no direct reference to it here. We intentionally don't store
|
|
169
|
+
// a secondary map — reference counting for evicted entries is tracked
|
|
170
|
+
// inside the entry object itself, which remains reachable via the
|
|
171
|
+
// store reference that the tool handler still holds. For the current
|
|
172
|
+
// MVP usage (single-threaded tool handlers that acquire + release in
|
|
173
|
+
// the same function) this branch is unreachable, so we return
|
|
174
|
+
// undefined and rely on the dispose path to have already closed the
|
|
175
|
+
// store if refCount was 0.
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=connection-pool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection-pool.js","sourceRoot":"","sources":["../src/connection-pool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Uniform error envelope for MCP tool responses.
|
|
3
|
+
*
|
|
4
|
+
* The OpenCodeHub PRD (§9.1) defines a single error code enumeration used
|
|
5
|
+
* across the MCP surface. Every tool that fails gracefully (i.e. the tool
|
|
6
|
+
* ran but the operation could not complete) returns this shape so agents
|
|
7
|
+
* can key on `error.code` to decide whether to retry, disambiguate, or
|
|
8
|
+
* abort.
|
|
9
|
+
*
|
|
10
|
+
* For protocol-level failures (unknown tool name, malformed JSON-RPC) the
|
|
11
|
+
* SDK's `McpError` class is thrown instead — those do not go through this
|
|
12
|
+
* helper.
|
|
13
|
+
*/
|
|
14
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
15
|
+
/** The fixed set of tool-level error codes exposed to MCP clients. */
|
|
16
|
+
export type ErrorCode = "STALENESS" | "INVALID_INPUT" | "NOT_FOUND" | "DB_ERROR" | "SCHEMA_MISMATCH" | "RATE_LIMITED" | "INTERNAL" | "NO_INDEX" | "AMBIGUOUS_REPO" | "EMBEDDER_MISMATCH";
|
|
17
|
+
/** Structured shape carried under `structuredContent.error`. */
|
|
18
|
+
export interface ErrorDetail {
|
|
19
|
+
readonly code: ErrorCode;
|
|
20
|
+
readonly message: string;
|
|
21
|
+
readonly hint?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* One registered repo exposed to the caller in an `AMBIGUOUS_REPO` envelope
|
|
25
|
+
* so the LLM can retry with an explicit `repo_uri`. Snake-case wire fields
|
|
26
|
+
* are intentional — this shape crosses the MCP boundary to an agent, and
|
|
27
|
+
* the research spec (§6.2 of research-m5m6.yaml) names them that way.
|
|
28
|
+
*
|
|
29
|
+
* `repo_uri` is derived from the registry at error-construction time.
|
|
30
|
+
* Once the registry surfaces the persisted RepoNode, this field will
|
|
31
|
+
* be pulled from there instead of being computed from
|
|
32
|
+
* `RegistryEntry.name`.
|
|
33
|
+
*/
|
|
34
|
+
export interface RepoChoice {
|
|
35
|
+
readonly repo_uri: string;
|
|
36
|
+
readonly default_branch: string | null;
|
|
37
|
+
readonly group: string | null;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Extended detail shape for `AMBIGUOUS_REPO`. Retains the legacy
|
|
41
|
+
* `{ code, message, hint }` surface so existing callers (and tests at
|
|
42
|
+
* error-envelope.test.ts:39-47) keep working; adds structured fields for
|
|
43
|
+
* LLM disambiguation.
|
|
44
|
+
*/
|
|
45
|
+
export interface AmbiguousRepoDetail extends ErrorDetail {
|
|
46
|
+
readonly code: "AMBIGUOUS_REPO";
|
|
47
|
+
/** Alias of `code` — matches the `error_code` field in the research spec. */
|
|
48
|
+
readonly error_code: "AMBIGUOUS_REPO";
|
|
49
|
+
/** JSON-RPC code for "invalid params" — per MCP spec. */
|
|
50
|
+
readonly jsonrpc_code: -32602;
|
|
51
|
+
/** Capped at 10. */
|
|
52
|
+
readonly choices: readonly RepoChoice[];
|
|
53
|
+
/** Full count of matching registry entries (may exceed `choices.length`). */
|
|
54
|
+
readonly total_matches: number;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Input to {@link toolAmbiguousRepoError}. Caller (typically the repo
|
|
58
|
+
* resolver at `repo-resolver.ts`) provides the full choice set; this
|
|
59
|
+
* builder caps it to 10 and reports the untruncated total.
|
|
60
|
+
*/
|
|
61
|
+
export interface AmbiguousRepoPayload {
|
|
62
|
+
readonly message: string;
|
|
63
|
+
readonly hint: string;
|
|
64
|
+
readonly choices: readonly RepoChoice[];
|
|
65
|
+
readonly totalMatches: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Build a tool-level error result. Both `content` (for clients that only
|
|
69
|
+
* read text) and `structuredContent` (for clients that honour the output
|
|
70
|
+
* schema) are populated, and `isError` is set so output-schema validation
|
|
71
|
+
* is skipped by the SDK per the 2025-06-18 spec revision.
|
|
72
|
+
*/
|
|
73
|
+
export declare function toolError(code: ErrorCode, message: string, hint?: string): CallToolResult;
|
|
74
|
+
/**
|
|
75
|
+
* Map an arbitrary thrown value to an `INTERNAL` error envelope. Used as a
|
|
76
|
+
* catch-all at the boundary of each tool handler so unexpected exceptions
|
|
77
|
+
* reach the agent as a structured error instead of tearing down the stdio
|
|
78
|
+
* transport.
|
|
79
|
+
*/
|
|
80
|
+
export declare function toolErrorFromUnknown(err: unknown, hint?: string): CallToolResult;
|
|
81
|
+
/**
|
|
82
|
+
* Max number of `choices[]` entries carried in an AMBIGUOUS_REPO envelope.
|
|
83
|
+
* More than 10 gets truncated; `total_matches` still reports the full count
|
|
84
|
+
* so the caller knows there is more.
|
|
85
|
+
*/
|
|
86
|
+
export declare const AMBIGUOUS_REPO_CHOICES_CAP = 10;
|
|
87
|
+
/**
|
|
88
|
+
* Build a structured AMBIGUOUS_REPO envelope. Wraps {@link toolError} so
|
|
89
|
+
* the legacy `{ code, message, hint }` fields stay intact (back-compat with
|
|
90
|
+
* `error-envelope.test.ts:39-47`) and layers on `error_code`, `choices[]`,
|
|
91
|
+
* `total_matches` for disambiguation by an agent.
|
|
92
|
+
*
|
|
93
|
+
* Choices are capped at {@link AMBIGUOUS_REPO_CHOICES_CAP}; `total_matches`
|
|
94
|
+
* always reports the pre-truncation count.
|
|
95
|
+
*/
|
|
96
|
+
export declare function toolAmbiguousRepoError(payload: AmbiguousRepoPayload): CallToolResult;
|
|
97
|
+
//# sourceMappingURL=error-envelope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-envelope.d.ts","sourceRoot":"","sources":["../src/error-envelope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE,sEAAsE;AACtE,MAAM,MAAM,SAAS,GACjB,WAAW,GACX,eAAe,GACf,WAAW,GACX,UAAU,GACV,iBAAiB,GACjB,cAAc,GACd,UAAU,GACV,UAAU,GACV,gBAAgB,GAChB,mBAAmB,CAAC;AAExB,gEAAgE;AAChE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAoB,SAAQ,WAAW;IACtD,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,6EAA6E;IAC7E,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC;IACtC,yDAAyD;IACzD,QAAQ,CAAC,YAAY,EAAE,CAAC,KAAK,CAAC;IAC9B,oBAAoB;IACpB,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,CAAC;IACxC,6EAA6E;IAC7E,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,CAAC;IACxC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,cAAc,CASzF;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,cAAc,CAGhF;AAED;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAE7C;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,oBAAoB,GAAG,cAAc,CAkBpF"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Uniform error envelope for MCP tool responses.
|
|
3
|
+
*
|
|
4
|
+
* The OpenCodeHub PRD (§9.1) defines a single error code enumeration used
|
|
5
|
+
* across the MCP surface. Every tool that fails gracefully (i.e. the tool
|
|
6
|
+
* ran but the operation could not complete) returns this shape so agents
|
|
7
|
+
* can key on `error.code` to decide whether to retry, disambiguate, or
|
|
8
|
+
* abort.
|
|
9
|
+
*
|
|
10
|
+
* For protocol-level failures (unknown tool name, malformed JSON-RPC) the
|
|
11
|
+
* SDK's `McpError` class is thrown instead — those do not go through this
|
|
12
|
+
* helper.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Build a tool-level error result. Both `content` (for clients that only
|
|
16
|
+
* read text) and `structuredContent` (for clients that honour the output
|
|
17
|
+
* schema) are populated, and `isError` is set so output-schema validation
|
|
18
|
+
* is skipped by the SDK per the 2025-06-18 spec revision.
|
|
19
|
+
*/
|
|
20
|
+
export function toolError(code, message, hint) {
|
|
21
|
+
const lines = [`Error (${code}): ${message}`];
|
|
22
|
+
if (hint)
|
|
23
|
+
lines.push(`Hint: ${hint}`);
|
|
24
|
+
const detail = hint ? { code, message, hint } : { code, message };
|
|
25
|
+
return {
|
|
26
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
27
|
+
structuredContent: { error: detail },
|
|
28
|
+
isError: true,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Map an arbitrary thrown value to an `INTERNAL` error envelope. Used as a
|
|
33
|
+
* catch-all at the boundary of each tool handler so unexpected exceptions
|
|
34
|
+
* reach the agent as a structured error instead of tearing down the stdio
|
|
35
|
+
* transport.
|
|
36
|
+
*/
|
|
37
|
+
export function toolErrorFromUnknown(err, hint) {
|
|
38
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
39
|
+
return toolError("INTERNAL", message, hint);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Max number of `choices[]` entries carried in an AMBIGUOUS_REPO envelope.
|
|
43
|
+
* More than 10 gets truncated; `total_matches` still reports the full count
|
|
44
|
+
* so the caller knows there is more.
|
|
45
|
+
*/
|
|
46
|
+
export const AMBIGUOUS_REPO_CHOICES_CAP = 10;
|
|
47
|
+
/**
|
|
48
|
+
* Build a structured AMBIGUOUS_REPO envelope. Wraps {@link toolError} so
|
|
49
|
+
* the legacy `{ code, message, hint }` fields stay intact (back-compat with
|
|
50
|
+
* `error-envelope.test.ts:39-47`) and layers on `error_code`, `choices[]`,
|
|
51
|
+
* `total_matches` for disambiguation by an agent.
|
|
52
|
+
*
|
|
53
|
+
* Choices are capped at {@link AMBIGUOUS_REPO_CHOICES_CAP}; `total_matches`
|
|
54
|
+
* always reports the pre-truncation count.
|
|
55
|
+
*/
|
|
56
|
+
export function toolAmbiguousRepoError(payload) {
|
|
57
|
+
const capped = payload.choices.slice(0, AMBIGUOUS_REPO_CHOICES_CAP);
|
|
58
|
+
const base = toolError("AMBIGUOUS_REPO", payload.message, payload.hint);
|
|
59
|
+
const baseDetail = base.structuredContent.error;
|
|
60
|
+
const detail = {
|
|
61
|
+
code: "AMBIGUOUS_REPO",
|
|
62
|
+
message: baseDetail.message,
|
|
63
|
+
...(baseDetail.hint !== undefined ? { hint: baseDetail.hint } : {}),
|
|
64
|
+
error_code: "AMBIGUOUS_REPO",
|
|
65
|
+
jsonrpc_code: -32602,
|
|
66
|
+
choices: capped,
|
|
67
|
+
total_matches: payload.totalMatches,
|
|
68
|
+
};
|
|
69
|
+
return {
|
|
70
|
+
content: base.content,
|
|
71
|
+
structuredContent: { error: detail },
|
|
72
|
+
isError: true,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=error-envelope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-envelope.js","sourceRoot":"","sources":["../src/error-envelope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAuEH;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,IAAe,EAAE,OAAe,EAAE,IAAa;IACvE,MAAM,KAAK,GAAG,CAAC,UAAU,IAAI,MAAM,OAAO,EAAE,CAAC,CAAC;IAC9C,IAAI,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;IACtC,MAAM,MAAM,GAAgB,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAC/E,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5D,iBAAiB,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE;QACpC,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAY,EAAE,IAAa;IAC9D,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjE,OAAO,SAAS,CAAC,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;AAC9C,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,EAAE,CAAC;AAE7C;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAA6B;IAClE,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,0BAA0B,CAAC,CAAC;IACpE,MAAM,IAAI,GAAG,SAAS,CAAC,gBAAgB,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE,MAAM,UAAU,GAAI,IAAI,CAAC,iBAA4C,CAAC,KAAK,CAAC;IAC5E,MAAM,MAAM,GAAwB;QAClC,IAAI,EAAE,gBAAgB;QACtB,OAAO,EAAE,UAAU,CAAC,OAAO;QAC3B,GAAG,CAAC,UAAU,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnE,UAAU,EAAE,gBAAgB;QAC5B,YAAY,EAAE,CAAC,KAAK;QACpB,OAAO,EAAE,MAAM;QACf,aAAa,EAAE,OAAO,CAAC,YAAY;KACpC,CAAC;IACF,OAAO;QACL,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,iBAAiB,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE;QACpC,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC"}
|