@colbymchenry/codegraph 0.9.7 → 0.9.9
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 +116 -32
- package/dist/bin/codegraph.d.ts +25 -0
- package/dist/bin/node-version-check.d.ts +37 -0
- package/dist/bin/uninstall.d.ts +14 -0
- package/dist/context/formatter.d.ts +30 -0
- package/dist/context/index.d.ts +119 -0
- package/dist/context/markers.d.ts +19 -0
- package/dist/db/index.d.ts +101 -0
- package/dist/db/migrations.d.ts +44 -0
- package/dist/db/queries.d.ts +344 -0
- package/dist/db/sqlite-adapter.d.ts +46 -0
- package/dist/directory.d.ts +57 -0
- package/dist/errors.d.ts +136 -0
- package/dist/extraction/dfm-extractor.d.ts +31 -0
- package/dist/extraction/generated-detection.d.ts +30 -0
- package/dist/extraction/grammars.d.ts +100 -0
- package/dist/extraction/index.d.ts +138 -0
- package/dist/extraction/languages/c-cpp.d.ts +4 -0
- package/dist/extraction/languages/csharp.d.ts +3 -0
- package/dist/extraction/languages/dart.d.ts +3 -0
- package/dist/extraction/languages/go.d.ts +3 -0
- package/dist/extraction/languages/index.d.ts +10 -0
- package/dist/extraction/languages/java.d.ts +3 -0
- package/dist/extraction/languages/javascript.d.ts +3 -0
- package/dist/extraction/languages/kotlin.d.ts +3 -0
- package/dist/extraction/languages/lua.d.ts +3 -0
- package/dist/extraction/languages/luau.d.ts +3 -0
- package/dist/extraction/languages/objc.d.ts +3 -0
- package/dist/extraction/languages/pascal.d.ts +3 -0
- package/dist/extraction/languages/php.d.ts +3 -0
- package/dist/extraction/languages/python.d.ts +3 -0
- package/dist/extraction/languages/ruby.d.ts +3 -0
- package/dist/extraction/languages/rust.d.ts +3 -0
- package/dist/extraction/languages/scala.d.ts +3 -0
- package/dist/extraction/languages/swift.d.ts +3 -0
- package/dist/extraction/languages/typescript.d.ts +3 -0
- package/dist/extraction/liquid-extractor.d.ts +52 -0
- package/dist/extraction/mybatis-extractor.d.ts +48 -0
- package/dist/extraction/parse-worker.d.ts +8 -0
- package/dist/extraction/svelte-extractor.d.ts +56 -0
- package/dist/extraction/tree-sitter-helpers.d.ts +28 -0
- package/dist/extraction/tree-sitter-types.d.ts +193 -0
- package/dist/extraction/tree-sitter.d.ts +317 -0
- package/dist/extraction/vue-extractor.d.ts +36 -0
- package/dist/extraction/wasm-runtime-flags.d.ts +38 -0
- package/dist/graph/index.d.ts +8 -0
- package/dist/graph/queries.d.ts +106 -0
- package/dist/graph/traversal.d.ts +127 -0
- package/dist/index.d.ts +498 -0
- package/dist/installer/config-writer.d.ts +28 -0
- package/dist/installer/index.d.ts +100 -0
- package/dist/installer/instructions-template.d.ts +18 -0
- package/dist/installer/targets/antigravity.d.ts +57 -0
- package/dist/installer/targets/claude.d.ts +56 -0
- package/dist/installer/targets/codex.d.ts +18 -0
- package/dist/installer/targets/cursor.d.ts +35 -0
- package/dist/installer/targets/gemini.d.ts +26 -0
- package/dist/installer/targets/hermes.d.ts +18 -0
- package/dist/installer/targets/kiro.d.ts +27 -0
- package/dist/installer/targets/opencode.d.ts +30 -0
- package/dist/installer/targets/registry.d.ts +35 -0
- package/dist/installer/targets/shared.d.ts +77 -0
- package/dist/installer/targets/toml.d.ts +52 -0
- package/dist/installer/targets/types.d.ts +101 -0
- package/dist/mcp/daemon-paths.d.ts +46 -0
- package/dist/mcp/daemon.d.ts +161 -0
- package/dist/mcp/engine.d.ts +105 -0
- package/dist/mcp/index.d.ts +112 -0
- package/dist/mcp/proxy.d.ts +81 -0
- package/dist/mcp/server-instructions.d.ts +19 -0
- package/dist/mcp/session.d.ts +77 -0
- package/dist/mcp/tools.d.ts +413 -0
- package/dist/mcp/transport.d.ts +188 -0
- package/dist/mcp/version.d.ts +19 -0
- package/dist/resolution/callback-synthesizer.d.ts +10 -0
- package/dist/resolution/frameworks/cargo-workspace.d.ts +18 -0
- package/dist/resolution/frameworks/csharp.d.ts +8 -0
- package/dist/resolution/frameworks/drupal.d.ts +51 -0
- package/dist/resolution/frameworks/expo-modules.d.ts +3 -0
- package/dist/resolution/frameworks/express.d.ts +8 -0
- package/dist/resolution/frameworks/fabric.d.ts +3 -0
- package/dist/resolution/frameworks/go.d.ts +8 -0
- package/dist/resolution/frameworks/index.d.ts +48 -0
- package/dist/resolution/frameworks/java.d.ts +8 -0
- package/dist/resolution/frameworks/laravel.d.ts +13 -0
- package/dist/resolution/frameworks/nestjs.d.ts +26 -0
- package/dist/resolution/frameworks/play.d.ts +19 -0
- package/dist/resolution/frameworks/python.d.ts +10 -0
- package/dist/resolution/frameworks/react-native.d.ts +3 -0
- package/dist/resolution/frameworks/react.d.ts +8 -0
- package/dist/resolution/frameworks/ruby.d.ts +8 -0
- package/dist/resolution/frameworks/rust.d.ts +8 -0
- package/dist/resolution/frameworks/svelte.d.ts +9 -0
- package/dist/resolution/frameworks/swift-objc.d.ts +37 -0
- package/dist/resolution/frameworks/swift.d.ts +10 -0
- package/dist/resolution/frameworks/vue.d.ts +9 -0
- package/dist/resolution/go-module.d.ts +26 -0
- package/dist/resolution/import-resolver.d.ts +68 -0
- package/dist/resolution/index.d.ts +116 -0
- package/dist/resolution/lru-cache.d.ts +24 -0
- package/dist/resolution/name-matcher.d.ts +32 -0
- package/dist/resolution/path-aliases.d.ts +68 -0
- package/dist/resolution/strip-comments.d.ts +27 -0
- package/dist/resolution/swift-objc-bridge.d.ts +134 -0
- package/dist/resolution/types.d.ts +209 -0
- package/dist/search/query-parser.d.ts +57 -0
- package/dist/search/query-utils.d.ts +71 -0
- package/dist/sync/git-hooks.d.ts +45 -0
- package/dist/sync/index.d.ts +19 -0
- package/dist/sync/watch-policy.d.ts +48 -0
- package/dist/sync/watcher.d.ts +191 -0
- package/dist/sync/worktree.d.ts +54 -0
- package/dist/types.d.ts +377 -0
- package/dist/ui/glyphs.d.ts +42 -0
- package/dist/ui/shimmer-progress.d.ts +11 -0
- package/dist/ui/shimmer-worker.d.ts +2 -0
- package/dist/ui/types.d.ts +17 -0
- package/dist/utils.d.ts +205 -0
- package/npm-sdk.js +75 -0
- package/package.json +18 -7
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared MCP daemon — issue #411.
|
|
3
|
+
*
|
|
4
|
+
* One detached `codegraph serve --mcp` daemon process per project root,
|
|
5
|
+
* accepting N concurrent MCP clients over a Unix-domain socket (or named pipe
|
|
6
|
+
* on Windows). Each incoming connection gets its own {@link MCPSession}; all
|
|
7
|
+
* sessions share a single {@link MCPEngine}, which means a single file watcher
|
|
8
|
+
* (one inotify set), a single SQLite connection (one WAL writer), and a single
|
|
9
|
+
* tree-sitter warm-up — paid once, amortized across every agent talking to the
|
|
10
|
+
* project.
|
|
11
|
+
*
|
|
12
|
+
* Lifecycle (see also `./index.ts` and `./proxy.ts`):
|
|
13
|
+
* - The daemon is spawned **detached** (its own session/process group, stdio
|
|
14
|
+
* decoupled) by the first launcher that finds no daemon running. It is NOT
|
|
15
|
+
* a child of any MCP host, so closing one terminal / Ctrl-C'ing one session
|
|
16
|
+
* can't take it down and sever the others. That's why this process has no
|
|
17
|
+
* PPID watchdog: it deliberately outlives every individual client.
|
|
18
|
+
* - Every MCP host talks to the daemon through a thin `proxy` process (the
|
|
19
|
+
* thing the host actually spawned). The proxy keeps the #277 PPID watchdog,
|
|
20
|
+
* so a SIGKILL'd host still reaps its proxy promptly; the proxy's socket
|
|
21
|
+
* close then decrements the daemon's refcount.
|
|
22
|
+
* - When the last client disconnects the daemon lingers for
|
|
23
|
+
* `CODEGRAPH_DAEMON_IDLE_TIMEOUT_MS` (default 300s) so back-to-back agent
|
|
24
|
+
* runs in the same project don't repay startup, then exits cleanly. This is
|
|
25
|
+
* what keeps a single-agent session from leaking a daemon forever (#277).
|
|
26
|
+
*
|
|
27
|
+
* What this file owns:
|
|
28
|
+
* - Listening on the daemon socket and spawning per-connection sessions.
|
|
29
|
+
* - The handshake "hello" line that lets a proxy verify it found a
|
|
30
|
+
* same-version daemon before piping any JSON-RPC through it.
|
|
31
|
+
* - The lockfile (`.codegraph/daemon.pid`) competing daemons arbitrate
|
|
32
|
+
* against — atomic `O_EXCL` create with the full record written in the same
|
|
33
|
+
* breath (no empty-file window) + cleanup on exit.
|
|
34
|
+
* - Reference counting + idle timeout.
|
|
35
|
+
* - Graceful shutdown on SIGTERM/SIGINT and idle exit.
|
|
36
|
+
*
|
|
37
|
+
* What this file does NOT own:
|
|
38
|
+
* - The proxy side (`./proxy.ts`).
|
|
39
|
+
* - The decision of *whether* to run as daemon at all — that's `MCPServer`.
|
|
40
|
+
* - The MCP protocol state machine — that's `./session.ts`.
|
|
41
|
+
*/
|
|
42
|
+
import { DaemonLockInfo } from './daemon-paths';
|
|
43
|
+
/** Bytes/parse-window for an oversized hello line — bounded against a malicious peer. */
|
|
44
|
+
declare const MAX_HELLO_LINE_BYTES = 4096;
|
|
45
|
+
/**
|
|
46
|
+
* Wire format for the one-shot hello line the daemon emits on every new
|
|
47
|
+
* connection. Versioned with the package's own semver so a 0.9.x proxy never
|
|
48
|
+
* pipes through a 0.10.x daemon (or vice-versa) — the proxy falls back to
|
|
49
|
+
* direct mode on mismatch rather than risk subtle wire incompatibilities.
|
|
50
|
+
*/
|
|
51
|
+
export interface DaemonHello {
|
|
52
|
+
codegraph: string;
|
|
53
|
+
pid: number;
|
|
54
|
+
socketPath: string;
|
|
55
|
+
protocol: 1;
|
|
56
|
+
}
|
|
57
|
+
export interface DaemonStartResult {
|
|
58
|
+
/** Always-non-null for a successfully-started daemon. */
|
|
59
|
+
socketPath: string;
|
|
60
|
+
/** Lockfile contents as written. */
|
|
61
|
+
lock: DaemonLockInfo;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Run as the shared daemon for `projectRoot`. Resolves once the socket is
|
|
65
|
+
* listening. The Daemon owns the socket, the engine, and the lockfile until
|
|
66
|
+
* `stop()` is called or it exits on idle/signal.
|
|
67
|
+
*
|
|
68
|
+
* Race-safe: callers must first call `tryAcquireDaemonLock(projectRoot)` and
|
|
69
|
+
* only construct a Daemon if they got the lock (`kind: 'acquired'`). The atomic
|
|
70
|
+
* `O_EXCL` create inside the acquire helper — which now also writes the full
|
|
71
|
+
* record before returning — is the only synchronization between competing
|
|
72
|
+
* daemons.
|
|
73
|
+
*/
|
|
74
|
+
export declare class Daemon {
|
|
75
|
+
private projectRoot;
|
|
76
|
+
private server;
|
|
77
|
+
private clients;
|
|
78
|
+
private idleTimer;
|
|
79
|
+
private idleTimeoutMs;
|
|
80
|
+
private engine;
|
|
81
|
+
private stopping;
|
|
82
|
+
private socketPath;
|
|
83
|
+
private pidPath;
|
|
84
|
+
constructor(projectRoot: string, opts?: {
|
|
85
|
+
idleTimeoutMs?: number;
|
|
86
|
+
});
|
|
87
|
+
/**
|
|
88
|
+
* Bind the socket, kick off engine init, and register signal handlers. The
|
|
89
|
+
* lockfile body was already written atomically by `tryAcquireDaemonLock`, so
|
|
90
|
+
* there is nothing to write here. The promise resolves once the server is
|
|
91
|
+
* listening — the daemon then sticks around until idle/shutdown.
|
|
92
|
+
*/
|
|
93
|
+
start(): Promise<DaemonStartResult>;
|
|
94
|
+
/** Currently-connected client count. Exposed for tests / status output. */
|
|
95
|
+
getClientCount(): number;
|
|
96
|
+
/** The socket path the daemon is (or will be) listening on. */
|
|
97
|
+
getSocketPath(): string;
|
|
98
|
+
/** Graceful shutdown: close all sessions, the engine, and clean up the lock. */
|
|
99
|
+
stop(reason?: string): Promise<void>;
|
|
100
|
+
private handleConnection;
|
|
101
|
+
private dropClient;
|
|
102
|
+
private armIdleTimer;
|
|
103
|
+
private disarmIdleTimer;
|
|
104
|
+
private cleanupLockfile;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Result of `tryAcquireDaemonLock`. Either we got the lockfile (caller becomes
|
|
108
|
+
* the daemon), or it already existed (caller should connect to the existing
|
|
109
|
+
* daemon as a proxy, or — if the holder is dead — clear it and retry).
|
|
110
|
+
*/
|
|
111
|
+
export type AcquireResult = {
|
|
112
|
+
kind: 'acquired';
|
|
113
|
+
pidPath: string;
|
|
114
|
+
info: DaemonLockInfo;
|
|
115
|
+
} | {
|
|
116
|
+
kind: 'taken';
|
|
117
|
+
existing: DaemonLockInfo | null;
|
|
118
|
+
pidPath: string;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Atomically create the daemon pidfile with its full record already in place.
|
|
122
|
+
* Returns either an `acquired` result (the caller is the daemon-elect and may
|
|
123
|
+
* construct a {@link Daemon}) or a `taken` result.
|
|
124
|
+
*
|
|
125
|
+
* must-fix 1 (issue #411 review): the lockfile must appear in ONE atomic step,
|
|
126
|
+
* already complete — never empty, even momentarily. The first attempt at this
|
|
127
|
+
* (`O_EXCL` create then a separate `writeSync`) left a microsecond window where
|
|
128
|
+
* the file existed but was empty; under concurrent daemon startup a third
|
|
129
|
+
* candidate could read that empty file, decode it as `null`, and `unlink` the
|
|
130
|
+
* winner's lock → two daemons (two watchers, two writers). The window was
|
|
131
|
+
* normally too small to hit, but the chokidar watcher's extra startup time made
|
|
132
|
+
* concurrent daemons overlap enough to reproduce it reliably.
|
|
133
|
+
*
|
|
134
|
+
* The fix writes the complete record to a private temp file, then hard-links it
|
|
135
|
+
* into place: `link()` is atomic AND exclusive (EEXIST if the target exists), so
|
|
136
|
+
* the pidfile becomes visible in one step already containing a full record.
|
|
137
|
+
* Whoever links first wins; everyone else gets EEXIST and reads a complete file.
|
|
138
|
+
* There is no empty-file window at all.
|
|
139
|
+
*/
|
|
140
|
+
export declare function tryAcquireDaemonLock(projectRoot: string): AcquireResult;
|
|
141
|
+
/**
|
|
142
|
+
* Remove a stale pidfile, but only if it still names a dead process. Re-reads
|
|
143
|
+
* the file immediately before unlinking so we never delete a lock that a live
|
|
144
|
+
* daemon (re)acquired in the meantime.
|
|
145
|
+
*
|
|
146
|
+
* must-fix 1 (issue #411 review): the original unconditionally `unlink`'d,
|
|
147
|
+
* which let a racing candidate delete a healthy daemon's lock. Passing
|
|
148
|
+
* `expectedDeadPid` (the pid the caller believed was dead) makes the clear a
|
|
149
|
+
* compare-and-delete: bail if the file now holds a different pid, or any live
|
|
150
|
+
* pid. Returns true when the stale lock is gone (or was already gone).
|
|
151
|
+
*/
|
|
152
|
+
export declare function clearStaleDaemonLock(pidPath: string, expectedDeadPid?: number): boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Probe whether `pid` is currently alive (signal-0). Treats EPERM as alive on
|
|
155
|
+
* every platform (the process exists, it's just not ours to signal) so we never
|
|
156
|
+
* mistake a live daemon for a dead one and clear its lock.
|
|
157
|
+
*/
|
|
158
|
+
export declare function isProcessAlive(pid: number): boolean;
|
|
159
|
+
/** Exported for test stubs that need to bound the hello-line read. */
|
|
160
|
+
export { MAX_HELLO_LINE_BYTES };
|
|
161
|
+
//# sourceMappingURL=daemon.d.ts.map
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP shared engine — the heavyweight, *shared* state for an MCP server:
|
|
3
|
+
* the project's {@link CodeGraph} instance, file watcher, and the
|
|
4
|
+
* {@link ToolHandler} cache for cross-project queries.
|
|
5
|
+
*
|
|
6
|
+
* One engine, many sessions:
|
|
7
|
+
* - direct mode (single stdio session) instantiates one engine + one session;
|
|
8
|
+
* - daemon mode instantiates one engine and a new session per socket
|
|
9
|
+
* connection. Every session reads from the same SQLite WAL and the same
|
|
10
|
+
* inotify watch set — that's the entire point of issue #411.
|
|
11
|
+
*/
|
|
12
|
+
import { ToolHandler } from './tools';
|
|
13
|
+
export interface MCPEngineOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Whether to start the file watcher when initializing. Daemon and direct
|
|
16
|
+
* modes both want this true; tests may set it false to keep the engine
|
|
17
|
+
* cheap. Honors {@link watchDisabledReason} regardless.
|
|
18
|
+
*/
|
|
19
|
+
watch?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Shared MCP engine. Thread-safe in the sense that multiple sessions can
|
|
23
|
+
* call its methods concurrently — internally it serializes initialization
|
|
24
|
+
* through a single promise so multiple sessions racing each other on first
|
|
25
|
+
* connect never double-open the SQLite file.
|
|
26
|
+
*/
|
|
27
|
+
export declare class MCPEngine {
|
|
28
|
+
private cg;
|
|
29
|
+
private toolHandler;
|
|
30
|
+
private projectPath;
|
|
31
|
+
private initPromise;
|
|
32
|
+
private watcherStarted;
|
|
33
|
+
private opts;
|
|
34
|
+
private closed;
|
|
35
|
+
constructor(opts?: MCPEngineOptions);
|
|
36
|
+
/**
|
|
37
|
+
* Convenience for {@link MCPServer} compatibility: pre-seed an explicit
|
|
38
|
+
* project path (from the `--path` CLI flag) without yet opening it. This
|
|
39
|
+
* keeps the synchronous constructor cheap; the actual open happens on the
|
|
40
|
+
* first `ensureInitialized` call.
|
|
41
|
+
*/
|
|
42
|
+
setProjectPathHint(projectPath: string): void;
|
|
43
|
+
/** Project root that the engine resolved on first init (null if none). */
|
|
44
|
+
getProjectPath(): string | null;
|
|
45
|
+
/** Shared ToolHandler — sessions delegate tool dispatch through this. */
|
|
46
|
+
getToolHandler(): ToolHandler;
|
|
47
|
+
/** Whether the default project's CodeGraph is open. */
|
|
48
|
+
hasDefaultCodeGraph(): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Walk up from `searchFrom` to find the nearest `.codegraph/` and open it.
|
|
51
|
+
* Idempotent: concurrent callers share one in-flight init; subsequent
|
|
52
|
+
* callers after success are no-ops.
|
|
53
|
+
*
|
|
54
|
+
* The original `MCPServer.tryInitializeDefault` carried the same retry-on-
|
|
55
|
+
* subsequent-tool-call semantics; we preserve them by NOT throwing when the
|
|
56
|
+
* search misses (just leaves `cg` null so the next call can retry).
|
|
57
|
+
*/
|
|
58
|
+
ensureInitialized(searchFrom: string): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Synchronous last-resort init used by the per-session retry loop when the
|
|
61
|
+
* background `ensureInitialized` already finished (or failed) and we need
|
|
62
|
+
* to pick up a project that appeared *after* the engine started.
|
|
63
|
+
*/
|
|
64
|
+
retryInitializeSync(searchFrom: string): void;
|
|
65
|
+
/**
|
|
66
|
+
* Close everything. Used on graceful daemon shutdown (SIGTERM/idle timeout)
|
|
67
|
+
* and on direct-mode stop. Idempotent.
|
|
68
|
+
*/
|
|
69
|
+
stop(): void;
|
|
70
|
+
private doInitialize;
|
|
71
|
+
/**
|
|
72
|
+
* Start file watching on the active CodeGraph instance. Idempotent — the
|
|
73
|
+
* watcher is per-engine, not per-session, which is why the daemon path
|
|
74
|
+
* collapses N inotify sets to one. The wording of the disabled-reason log
|
|
75
|
+
* exactly matches the prior in-tree implementation so log-driven dashboards
|
|
76
|
+
* keep working.
|
|
77
|
+
*/
|
|
78
|
+
private startWatching;
|
|
79
|
+
/**
|
|
80
|
+
* Reconcile the index with the current filesystem once, right after open —
|
|
81
|
+
* catches edits, adds, deletes, and `git pull`/`checkout` changes made while
|
|
82
|
+
* no watcher was running. Runs in the background, but the returned promise
|
|
83
|
+
* is pushed into the ToolHandler as a one-shot gate so the *first* tool
|
|
84
|
+
* call awaits completion before serving (without this, a tool call that
|
|
85
|
+
* races past sync returns rows for files that no longer exist on disk —
|
|
86
|
+
* and the per-file staleness banner can't help because `getPendingFiles()`
|
|
87
|
+
* is populated by the watcher, not by catch-up).
|
|
88
|
+
*/
|
|
89
|
+
private catchUpSync;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Parse and clamp the CODEGRAPH_WATCH_DEBOUNCE_MS env override.
|
|
93
|
+
*
|
|
94
|
+
* Issue #403: workspaces with bursty writes (formatter-on-save, multi-file
|
|
95
|
+
* refactors) sometimes want a longer quiet window before sync. Returns
|
|
96
|
+
* `undefined` for unset / empty / non-numeric / out-of-range values so the
|
|
97
|
+
* FileWatcher default (2000ms) takes over — never throws.
|
|
98
|
+
*
|
|
99
|
+
* Clamp range: 100ms (faster would mean a sync per keystroke) to 60s (longer
|
|
100
|
+
* and the watcher feels broken). Out-of-range values are treated as "ignore
|
|
101
|
+
* this misconfiguration" rather than capped, since silently capping a 0 or
|
|
102
|
+
* a typoed value would mask a real config bug.
|
|
103
|
+
*/
|
|
104
|
+
export declare function parseDebounceEnv(raw: string | undefined): number | undefined;
|
|
105
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CodeGraph MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Model Context Protocol server that exposes CodeGraph functionality
|
|
5
|
+
* as tools for AI assistants like Claude.
|
|
6
|
+
*
|
|
7
|
+
* @module mcp
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { MCPServer } from 'codegraph';
|
|
12
|
+
*
|
|
13
|
+
* const server = new MCPServer('/path/to/project');
|
|
14
|
+
* await server.start();
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* Runtime modes (decided in {@link MCPServer.start}):
|
|
18
|
+
*
|
|
19
|
+
* - **Direct** — one process serves one MCP client over stdio. The pre-#411
|
|
20
|
+
* behavior; used when the user opts out (`CODEGRAPH_NO_DAEMON=1`), no
|
|
21
|
+
* `.codegraph/` is reachable, or the daemon machinery fails for any reason.
|
|
22
|
+
* - **Proxy** — what an MCP host actually talks to when sharing is on: a thin
|
|
23
|
+
* stdio↔socket pipe to the shared daemon. The proxy carries the #277 PPID
|
|
24
|
+
* watchdog, so a SIGKILL'd host reaps its proxy promptly. See {@link ./proxy.ts}.
|
|
25
|
+
* - **Daemon** — a *detached* background process (its own session/process
|
|
26
|
+
* group) that serves N proxies over a Unix-domain socket / named pipe,
|
|
27
|
+
* sharing one CodeGraph + watcher + SQLite handle. Spawned on demand; never a
|
|
28
|
+
* child of any host, so it survives individual sessions and is reaped by
|
|
29
|
+
* client-refcount + idle timeout. See {@link ./daemon.ts} and issue #411.
|
|
30
|
+
*
|
|
31
|
+
* The detached-daemon + always-proxy split is the fix for the review finding
|
|
32
|
+
* that the original in-process daemon (a) was the first host's child, so closing
|
|
33
|
+
* that terminal severed every other client, and (b) disabled the PPID watchdog,
|
|
34
|
+
* regressing #277 (orphaned daemons on host SIGKILL).
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* MCP Server for CodeGraph
|
|
38
|
+
*
|
|
39
|
+
* Implements the Model Context Protocol to expose CodeGraph
|
|
40
|
+
* functionality as tools that can be called by AI assistants.
|
|
41
|
+
*
|
|
42
|
+
* Backwards-compatible constructor and `start()` signature with the
|
|
43
|
+
* pre-issue-#411 implementation: callers continue to do
|
|
44
|
+
* `new MCPServer(path).start()`. Internally we now pick from direct / proxy /
|
|
45
|
+
* daemon at start time.
|
|
46
|
+
*/
|
|
47
|
+
export declare class MCPServer {
|
|
48
|
+
private projectPath;
|
|
49
|
+
private session;
|
|
50
|
+
private engine;
|
|
51
|
+
private daemon;
|
|
52
|
+
private ppidWatchdog;
|
|
53
|
+
private originalPpid;
|
|
54
|
+
private hostPpid;
|
|
55
|
+
private stopped;
|
|
56
|
+
private mode;
|
|
57
|
+
constructor(projectPath?: string);
|
|
58
|
+
/**
|
|
59
|
+
* Start the MCP server.
|
|
60
|
+
*
|
|
61
|
+
* Decision order:
|
|
62
|
+
* 1. `CODEGRAPH_NO_DAEMON=1` → direct mode (unchanged pre-#411 behavior).
|
|
63
|
+
* 2. `CODEGRAPH_DAEMON_INTERNAL=1` → we ARE the detached daemon; listen.
|
|
64
|
+
* 3. No `.codegraph/` reachable → direct mode (the daemon's lockfile and
|
|
65
|
+
* socket both live under `.codegraph/`).
|
|
66
|
+
* 4. Otherwise connect to (or spawn) the shared daemon and proxy to it.
|
|
67
|
+
*
|
|
68
|
+
* On any unexpected failure in step 4 we transparently fall back to direct
|
|
69
|
+
* mode — a misbehaving daemon must never block a session from starting.
|
|
70
|
+
*/
|
|
71
|
+
start(): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Stop the server. In daemon mode this triggers graceful shutdown of every
|
|
74
|
+
* connected session; in direct mode it mirrors the pre-#411 behavior (close
|
|
75
|
+
* cg, exit). Proxy mode never routes through here — the proxy exits itself.
|
|
76
|
+
*/
|
|
77
|
+
stop(): void;
|
|
78
|
+
/** Single-process stdio MCP session — the pre-issue-#411 code path. */
|
|
79
|
+
private startDirect;
|
|
80
|
+
/**
|
|
81
|
+
* Run as the detached shared daemon (process spawned with
|
|
82
|
+
* `CODEGRAPH_DAEMON_INTERNAL=1`). Arbitrate the O_EXCL lock, then either
|
|
83
|
+
* become the daemon (bind the socket, serve forever) or — if a live daemon
|
|
84
|
+
* already holds the lock — exit so we don't leak a redundant process.
|
|
85
|
+
*
|
|
86
|
+
* No PPID watchdog and no stdin handlers: the daemon is detached on purpose
|
|
87
|
+
* and reaps itself via client-refcount + idle timeout (see {@link Daemon}).
|
|
88
|
+
*/
|
|
89
|
+
private startDaemonProcess;
|
|
90
|
+
/**
|
|
91
|
+
* Proxy mode (the common case). Serve the MCP handshake LOCALLY for instant
|
|
92
|
+
* tool registration, forwarding tool calls to the shared daemon — which is
|
|
93
|
+
* connected in the background (probed, then spawned + polled if absent) so the
|
|
94
|
+
* handshake never waits ~600ms on it. Runs until the host disconnects; the
|
|
95
|
+
* proxy falls back to an in-process engine if the daemon never binds, so this
|
|
96
|
+
* never wedges a session.
|
|
97
|
+
*/
|
|
98
|
+
private runProxyWithLocalHandshake;
|
|
99
|
+
/** Standard SIGINT/SIGTERM handlers that route to our `stop()` (direct mode). */
|
|
100
|
+
private installSignalHandlers;
|
|
101
|
+
/**
|
|
102
|
+
* PPID watchdog (#277) — direct mode only. Daemon mode is detached on purpose
|
|
103
|
+
* and reaps via idle timeout; proxy mode installs its own watchdog inside
|
|
104
|
+
* {@link runProxy}. So this only ever runs for an in-process direct session.
|
|
105
|
+
*/
|
|
106
|
+
private installPpidWatchdog;
|
|
107
|
+
}
|
|
108
|
+
export { StdioTransport } from './transport';
|
|
109
|
+
export { tools, ToolHandler } from './tools';
|
|
110
|
+
export { Daemon } from './daemon';
|
|
111
|
+
export { CodeGraphPackageVersion } from './version';
|
|
112
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP proxy mode — issue #411.
|
|
3
|
+
*
|
|
4
|
+
* The proxy is a near-transparent stdio↔socket pipe. Once it has verified
|
|
5
|
+
* the daemon's hello line (same major.minor.patch as ours), it does no
|
|
6
|
+
* protocol parsing of its own: every byte the MCP host writes to the proxy's
|
|
7
|
+
* stdin goes straight to the daemon socket, and every byte the daemon emits
|
|
8
|
+
* goes straight to the host's stdout. Server-initiated JSON-RPC requests
|
|
9
|
+
* (e.g. `roots/list`) flow through the same pipe transparently.
|
|
10
|
+
*
|
|
11
|
+
* Lifecycle expectations:
|
|
12
|
+
* - The proxy exits when *either* stream closes (host stdin closed →
|
|
13
|
+
* daemon socket end, or daemon-side socket close → host stdout end).
|
|
14
|
+
* - Closing the socket on the proxy side is what tells the daemon to
|
|
15
|
+
* decrement its connected-clients refcount.
|
|
16
|
+
* - On a parent-process death we can't detect via stdin close (e.g. SIGKILL
|
|
17
|
+
* of the MCP host), the proxy's PPID watchdog catches it — same logic
|
|
18
|
+
* the direct-mode server uses; see issue #277.
|
|
19
|
+
*/
|
|
20
|
+
import * as net from 'net';
|
|
21
|
+
import type { MCPEngine } from './engine';
|
|
22
|
+
export interface ProxyResult {
|
|
23
|
+
/**
|
|
24
|
+
* `proxied` — successfully attached to a same-version daemon and piped
|
|
25
|
+
* stdio. The proxy stays alive until either end closes.
|
|
26
|
+
* `fallback-needed` — the daemon rejected us (version mismatch / unreachable
|
|
27
|
+
* socket) and the caller should run the server in direct mode.
|
|
28
|
+
*/
|
|
29
|
+
outcome: 'proxied' | 'fallback-needed';
|
|
30
|
+
reason?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Attempt to connect to the daemon at `socketPath` and pipe stdio through it.
|
|
34
|
+
*
|
|
35
|
+
* Returns a promise that resolves when either:
|
|
36
|
+
* - the connection succeeded and one of stdin/socket has now closed
|
|
37
|
+
* (after which the process should exit), or
|
|
38
|
+
* - the connection failed early enough that the caller can still fall
|
|
39
|
+
* back to direct mode.
|
|
40
|
+
*
|
|
41
|
+
* The `expectedVersion` param defaults to the package's own version — daemon
|
|
42
|
+
* and proxy MUST match exactly. Mismatch resolves with
|
|
43
|
+
* `outcome: 'fallback-needed'` so the caller can transparently start its own
|
|
44
|
+
* server. (We accept the cost of two concurrent servers in this case as the
|
|
45
|
+
* price of never silently running a stale daemon against newer client code.)
|
|
46
|
+
*/
|
|
47
|
+
export declare function runProxy(socketPath: string, expectedVersion?: string): Promise<ProxyResult>;
|
|
48
|
+
/**
|
|
49
|
+
* Connect to a daemon at `socketPath` and verify its hello (exact version match).
|
|
50
|
+
* Returns the live socket (hello already consumed) or null if unreachable / stale
|
|
51
|
+
* / version-mismatched. Unlike {@link runProxy} it does NOT pipe — the caller
|
|
52
|
+
* owns the socket. Used by the local-handshake proxy's background connect.
|
|
53
|
+
*/
|
|
54
|
+
export declare function connectWithHello(socketPath: string, expectedVersion?: string): Promise<net.Socket | 'version-mismatch' | null>;
|
|
55
|
+
/** Dependencies the local-handshake proxy needs, injected by MCPServer (which
|
|
56
|
+
* owns the daemon-spawn machinery and the engine factory). */
|
|
57
|
+
export interface LocalHandshakeDeps {
|
|
58
|
+
/** Probe → spawn → retry → hello-verify; resolves a connected daemon socket,
|
|
59
|
+
* or null when the daemon path is genuinely unavailable (→ in-process fallback). */
|
|
60
|
+
getDaemonSocket(): Promise<net.Socket | null>;
|
|
61
|
+
/** Lazily create an in-process engine — used ONLY if the daemon never comes up,
|
|
62
|
+
* preserving the "a broken daemon never wedges a session" guarantee. */
|
|
63
|
+
makeEngine(): MCPEngine;
|
|
64
|
+
/** Project root for the fallback engine's lazy init. */
|
|
65
|
+
root: string;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Local-handshake proxy (the cold-start fix).
|
|
69
|
+
*
|
|
70
|
+
* Answers `initialize` + `tools/list` from STATIC constants the instant the
|
|
71
|
+
* client asks — tools register in ~process-startup time instead of waiting
|
|
72
|
+
* ~600ms for the daemon to spawn+bind, which is what produced the "No such tool
|
|
73
|
+
* available" race that made headless agents flail into grep/Read. Tool CALLS are
|
|
74
|
+
* forwarded to the shared daemon (connected in the background); the daemon's
|
|
75
|
+
* response to the forwarded `initialize` is suppressed (the client already got
|
|
76
|
+
* the local one). If the daemon never comes up (version mismatch / spawn fail),
|
|
77
|
+
* a lazily-created in-process engine serves the calls — so the handshake speedup
|
|
78
|
+
* never costs the old fall-back-to-direct robustness.
|
|
79
|
+
*/
|
|
80
|
+
export declare function runLocalHandshakeProxy(deps: LocalHandshakeDeps): Promise<void>;
|
|
81
|
+
//# sourceMappingURL=proxy.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-level instructions emitted in the MCP `initialize` response.
|
|
3
|
+
*
|
|
4
|
+
* MCP clients (Claude Code, Cursor, opencode, LangChain, OpenAI Agent
|
|
5
|
+
* SDK, …) surface this text in the agent's system prompt automatically,
|
|
6
|
+
* giving the agent a high-level playbook for the codegraph toolset
|
|
7
|
+
* before it sees individual tool descriptions.
|
|
8
|
+
*
|
|
9
|
+
* Goals when editing this:
|
|
10
|
+
* - Tool selection by intent (which tool for which question)
|
|
11
|
+
* - Common chains (refactor planning = X then Y)
|
|
12
|
+
* - Anti-patterns (don't grep when codegraph_search is faster)
|
|
13
|
+
*
|
|
14
|
+
* Keep it tight. The agent reads this every session — long instructions
|
|
15
|
+
* burn tokens. Reference only tools that exist on `main`; gate any
|
|
16
|
+
* conditional tools behind feature checks if/when they ship.
|
|
17
|
+
*/
|
|
18
|
+
export declare const SERVER_INSTRUCTIONS = "# Codegraph \u2014 code intelligence over an indexed knowledge graph\n\nCodegraph is a SQLite knowledge graph of every symbol, edge, and file\nin the workspace. Reads are sub-millisecond; the index lags writes by\nabout a second through the file watcher. Consult it BEFORE writing or\nediting code, not during.\n\n## Answer directly \u2014 don't delegate exploration\n\nFor \"how does X work\", architecture, trace, or where-is-X questions,\nanswer DIRECTLY \u2014 usually with ONE `codegraph_explore` call.\n`codegraph_explore` takes either a natural-language question or a bag of\nsymbol/file names and returns the verbatim source of the relevant symbols\ngrouped by file, so it is Read-equivalent and most often the ONLY\ncodegraph call you need. Codegraph IS the pre-built search index \u2014 so\ndelegating the lookup to a separate file-reading sub-task/agent, or\nrunning your own grep + read loop, repeats work codegraph already did and\ncosts more for the same answer. Reach for raw Read/Grep only to confirm a\nspecific detail codegraph didn't cover. A direct codegraph answer is\ntypically one to a few calls; a grep/read exploration is dozens.\n\n## Tool selection by intent\n\n- **Almost any question \u2014 \"how does X work\", architecture, a bug, \"what/where is X\", or surveying an area** \u2192 `codegraph_explore` (PRIMARY \u2014 call FIRST; ONE capped call returns the verbatim source of the relevant symbols grouped by file; most often the ONLY call you need)\n- **\"How does X reach/become Y? / the flow / the path from X to Y\"** \u2192 `codegraph_explore`, naming the symbols that span the flow (e.g. `mutateElement renderScene`) \u2014 it surfaces the call path among them, including dynamic-dispatch hops (callbacks, React re-render, JSX children) grep can't follow\n- **\"What is the symbol named X?\" (just its location)** \u2192 `codegraph_search`\n- **\"What calls this?\" / \"What does this call?\" / \"What would changing this break?\"** \u2192 `codegraph_callers` / `codegraph_callees` / `codegraph_impact`\n- **One specific symbol's full source (esp. a body `codegraph_explore` trimmed), or an OVERLOADED name** \u2192 `codegraph_node` (with `includeCode`): for an ambiguous name it returns EVERY matching definition's body in one call, so you never Read a file to find the right overload\n- **\"What's in directory X?\"** \u2192 `codegraph_files`\n- **\"Is the index ready / what's its size?\"** \u2192 `codegraph_status`\n\n## Common chains\n\n- **Flow / \"how does X reach Y\"**: ONE `codegraph_explore` with the symbol names spanning the flow \u2014 it surfaces the call path among them (riding dynamic-dispatch hops) AND returns their source. No need to reconstruct the path with `codegraph_search` + `codegraph_callers`.\n- **Onboarding / understanding any area**: ONE `codegraph_explore` is usually the whole answer. Only follow up \u2014 `codegraph_node` for a specific symbol \u2014 if something is still unclear.\n- **Refactor planning**: `codegraph_search` \u2192 `codegraph_callers` \u2192 `codegraph_impact`. The blast-radius answer comes from impact, not from walking callers manually.\n- **Debugging a regression**: `codegraph_callers` of the suspected symbol; widen with `codegraph_impact` if an unexpected call appears.\n\n## Anti-patterns\n\n- **Trust codegraph's results \u2014 don't re-verify them with grep.** They come from a full AST parse; re-checking with grep is slower, less accurate, and wastes context.\n- **Don't grep first** when looking up a symbol by name \u2014 `codegraph_search` is faster and returns kind + location + signature.\n- **Don't chain `codegraph_search` + `codegraph_node`** to understand an area \u2014 ONE `codegraph_explore` returns the relevant symbols' source together in a single round-trip.\n- **Don't loop `codegraph_node` over many symbols** \u2014 one `codegraph_explore` call returns them all grouped by file, while each separate call re-reads the whole context and costs far more. Use `codegraph_node` for a single symbol.\n- **After editing, check the staleness banner.** When a tool response starts with \"\u26A0\uFE0F Some files referenced below were edited since the last index sync\u2026\", the listed files are pending re-index \u2014 Read those specific files for accurate content. Every file NOT in that banner is fresh, so still trust codegraph. `codegraph_status` also lists pending files under \"Pending sync\".\n\n## Limitations\n\n- If a tool reports the project isn't initialized, `.codegraph/` doesn't exist yet \u2014 offer to run `codegraph init -i` to build the index.\n- Index lags file writes by ~1 second.\n- Cross-file resolution is best-effort name matching; ambiguous calls may return multiple candidates.\n- No live correctness validation \u2014 that's still the TypeScript compiler / test suite / linter's job. Codegraph supplements those with structural context they don't have.\n";
|
|
19
|
+
//# sourceMappingURL=server-instructions.d.ts.map
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP per-connection session — speaks the JSON-RPC protocol (initialize,
|
|
3
|
+
* tools/list, tools/call) over a single {@link JsonRpcTransport}. It owns
|
|
4
|
+
* per-client state only (which protocol version the client asked for, whether
|
|
5
|
+
* it advertised `roots`, the one-shot roots/list latch); the heavyweight
|
|
6
|
+
* resources (CodeGraph, watcher, ToolHandler) live in the shared
|
|
7
|
+
* {@link MCPEngine} so daemon mode can collapse N inotify sets / DB handles
|
|
8
|
+
* to one.
|
|
9
|
+
*
|
|
10
|
+
* The state-machine itself mirrors what `MCPServer` used to do inline before
|
|
11
|
+
* issue #411 split it out — the same regression tests in
|
|
12
|
+
* `__tests__/mcp-initialize.test.ts` still drive this code path.
|
|
13
|
+
*/
|
|
14
|
+
import { JsonRpcTransport } from './transport';
|
|
15
|
+
import { MCPEngine } from './engine';
|
|
16
|
+
/**
|
|
17
|
+
* MCP Server Info — kept on the session because some clients log it. The
|
|
18
|
+
* version tracks the real package version (was a hard-coded '0.1.0').
|
|
19
|
+
*/
|
|
20
|
+
export declare const SERVER_INFO: {
|
|
21
|
+
name: string;
|
|
22
|
+
version: string;
|
|
23
|
+
};
|
|
24
|
+
/** MCP Protocol Version (latest the server claims). */
|
|
25
|
+
export declare const PROTOCOL_VERSION = "2024-11-05";
|
|
26
|
+
export interface MCPSessionOptions {
|
|
27
|
+
/**
|
|
28
|
+
* Explicit project path from the `--path` CLI flag. When set, the session
|
|
29
|
+
* will not bother asking the client for `roots/list` — we already know
|
|
30
|
+
* where the project lives.
|
|
31
|
+
*/
|
|
32
|
+
explicitProjectPath?: string | null;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* One MCP client's view of the server. Created fresh per stdio launch
|
|
36
|
+
* (direct mode) or per socket connection (daemon mode).
|
|
37
|
+
*/
|
|
38
|
+
export declare class MCPSession {
|
|
39
|
+
private transport;
|
|
40
|
+
private engine;
|
|
41
|
+
private clientSupportsRoots;
|
|
42
|
+
private rootsAttempted;
|
|
43
|
+
private resolvePromise;
|
|
44
|
+
private explicitProjectPath;
|
|
45
|
+
constructor(transport: JsonRpcTransport, engine: MCPEngine, opts?: MCPSessionOptions);
|
|
46
|
+
/**
|
|
47
|
+
* Start handling messages from the transport. Returns immediately — the
|
|
48
|
+
* session lives for as long as the transport is open.
|
|
49
|
+
*/
|
|
50
|
+
start(): void;
|
|
51
|
+
/**
|
|
52
|
+
* Tear down the session. Does NOT touch the engine (the engine may serve
|
|
53
|
+
* other sessions) or call `process.exit` (the daemon decides when to exit).
|
|
54
|
+
*/
|
|
55
|
+
stop(): void;
|
|
56
|
+
/** Underlying transport — exposed for daemon-side close hooks. */
|
|
57
|
+
getTransport(): JsonRpcTransport;
|
|
58
|
+
private handleMessage;
|
|
59
|
+
private handleInitialize;
|
|
60
|
+
private handleToolsList;
|
|
61
|
+
private handleToolsCall;
|
|
62
|
+
/**
|
|
63
|
+
* Lazy default-project resolution. Three layers:
|
|
64
|
+
* 1. await the in-flight init kicked off from `handleInitialize` (if any);
|
|
65
|
+
* 2. if still uninitialized and we never asked the client for its roots,
|
|
66
|
+
* do so now (one-shot); fall back to cwd if the client lacks roots;
|
|
67
|
+
* 3. last-resort: re-walk from the best candidate — picks up projects
|
|
68
|
+
* that were `codegraph init`'d *after* the server started.
|
|
69
|
+
*/
|
|
70
|
+
private retryInitIfNeeded;
|
|
71
|
+
/**
|
|
72
|
+
* Ask the client for its workspace root via `roots/list` and open the
|
|
73
|
+
* first one. Falls back to `process.cwd()` on timeout or empty answer.
|
|
74
|
+
*/
|
|
75
|
+
private initFromRoots;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=session.d.ts.map
|