/** * Autoload name this server reserves in a target project's `project.godot`. * Exported so `run_project`'s pre-flight scan can recognize its own injected * bridge and skip scanning it. */ export declare const BRIDGE_AUTOLOAD_NAME: "McpBridge"; /** * Thrown when project.godot already registers an autoload named `McpBridge` * at a path this server does not own. Distinct from the incidental filesystem * failures `inject` can raise, because the caller must surface this one to the * user: it is the only inject failure they can act on, and proceeding would * leave them staring at a bridge timeout instead. */ export declare class BridgeAutoloadCollisionError extends Error { readonly registeredPath: string; constructor(message: string, registeredPath: string); } /** * Thrown when `inject` is called for attach mode (a baked token supplied) and * another live attach session already owns this project. At most one attach * owner is allowed per project, because attach mode bakes port and token into * the one shared script — two attach owners would stomp each other's baked * values on every inject. */ export declare class BridgeAttachConflictError extends Error { readonly conflictingOwner: BridgeOwnerInfo; constructor(message: string, conflictingOwner: BridgeOwnerInfo); } export type BridgeSessionMode = 'spawned' | 'attached'; /** * One registry entry: a live session's claim on the shared bridge artifacts * for a project. Written to its own file under `bridge/owners/` by `inject`, * removed by that same instance's `cleanup`. `token` is present only for * `mode: 'attached'`, since attach mode has no env-var channel to re-deliver * it and it is already on disk in the rendered script anyway. */ export interface BridgeOwnerInfo { pid: number; instanceId: string; hostname: string; mode: BridgeSessionMode; startedAt: string; port: number; token?: string; } /** Constructor-injectable seams for `BridgeManager`, used by tests to fake a * dead process, a foreign hostname, or a specific pid without touching the * real OS. Production call sites omit `options` entirely. */ export interface BridgeManagerOptions { isProcessAlive?: (pid: number) => boolean; hostname?: () => string; pid?: () => number; } /** * Owns the McpBridge autoload artifact: the script copy under * `.mcp/godot-runtime/bridge/` in the target project, the owner registry * under `.mcp/godot-runtime/bridge/owners/`, the `[autoload]` entry in * project.godot, the `.mcp/.gdignore` marker, and the `.gitignore` * augmentation. GodotRunner delegates to this for inject/cleanup during * run_project / attach_project / stop_project flows. Path composition lives * in `utils/artifact-paths.ts`. * * Designed for N concurrent server processes sharing one project. Every entry * point reads disk state rather than trusting in-memory bookkeeping, because * a sibling process's inject/cleanup can change that state at any time. The * shared script and autoload entry are created on the first live session's * inject and removed only by the last live session's cleanup, tracked via one * owner file per live session (see `BridgeOwnerInfo`). An owner is "live" * when its hostname doesn't match this host (unknowable, so treated * conservatively as live) or its pid answers a liveness probe; dead owner * files are pruned opportunistically on every registry read. * * Accepted gap on `project.godot`: two servers doing read-modify-write on it * in the same instant can still lose one edit — there is no file lock. * Writing the owner file before touching project.godot (see `inject`) makes * that window tiny, and the next `inject` from either side restores the * entry if it was lost. * * Accepted gap on cross-version coexistence: an older server version running * concurrently on the same project writes no owner file, so this instance * cannot see it and treats the project as unowned once its own owner count * hits zero. * * `McpBridge` is reserved by this server, but the name is not reserved by * Godot. An entry under that name whose registered path is not server-owned * (see `isServerOwnedBridgePath`) belongs to the user: inject refuses rather * than rewriting it, and cleanup leaves it alone. */ export declare class BridgeManager { private bridgeScriptPath; private repairedProjects; private readonly instanceId; private readonly pid; private readonly hostnameFn; private readonly isProcessAliveFn; constructor(bridgeScriptPath: string, options?: BridgeManagerOptions); /** * @param bakedToken Session token to bake into the on-disk script, for * attach-mode sessions where Node cannot set the env var on a Godot * process the user launched themselves. Spawned sessions deliver the * token via `MCP_SESSION_TOKEN` instead and should omit this so the * rendered script carries no baked attach owner (fail-open only when no * token is configured at all). * @throws {BridgeAutoloadCollisionError} if an `[autoload]` entry named * McpBridge already exists and points at a path this server does not own * (a name collision with user code). Callers must not swallow this one. * @throws {BridgeAttachConflictError} if `bakedToken` is supplied and * another live attach session already owns this project. Thrown before * any write. */ inject(projectPath: string, port: number, bakedToken?: string): void; /** * Leave this instance's session. Deletes only this instance's owner file, * then re-reads the registry: if other live owners remain, the shared * script and autoload entry are left in place (the script is re-rendered, * which resets baked attach values to the template defaults when the * leaving session was the attach owner and no other attach owner exists). * Only when no live owners remain at all does the shared script and * autoload entry get removed. * * Must stay fully synchronous and never throw: `GodotRunner * .cleanupBridgeArtifactsSync` calls this from a `process.on('exit')` * handler, where there is no event loop left and nowhere to report a * failure to. Every step is independently try/caught and best-effort, as * the prior single-owner implementation was. */ cleanup(projectPath: string): void; /** * Clear bridge artifacts stranded by an earlier process. Two cases: * project.godot still has an `McpBridge=` line but the script is gone (the * autoload would crash every subsequent headless op), or the script is still * on disk from a server that was hard-killed before it could clean up. * "Stranded" means these artifacts are present with no live registered * owner (see `BridgeOwnerInfo`) — not merely "not injected by this process", * since another live session on this project is a legitimate reason for the * artifacts to exist. * * Trigger surface, deliberately wide: this runs from `executeOperation`, so * the first headless op of any kind against a project — `validate` included, * nothing launched — can clear a stray entry or script. What it is allowed to * delete is narrowed by `removeBridgeArtifacts`, which never touches an * autoload path this server does not own. * * Cached per project: once a path has been checked clean, skip the file * reads on subsequent ops in the same session. `inject`/`cleanup` clear the * cache for a project they touch, so a later check re-reads the disk. * * Accepted gap: an older server version running concurrently on this * project writes no owner file, so it is invisible here and its artifacts * can be misclassified as stranded. */ repairOrphaned(projectPath: string): void; /** * Live owners of this project other than this instance, pruning dead ones * as a side effect (via the registry read below). Used by * `GodotRunner.otherLiveSessionsOnProject` to power the cross-server edit * guard. */ listOtherLiveOwners(projectPath: string): BridgeOwnerInfo[]; /** * True when `project.godot` currently registers the `McpBridge` autoload * pointing at this server's script path. Used by the bridge-not-ready * timeout diagnostic to distinguish "the game started with no bridge at * all" from "the bridge autoload is present but never became ready". */ isBridgeAutoloadRegistered(projectPath: string): boolean; /** * Render the bridge script template with the given attach owner's port and * token baked in, or return the template unchanged when there is none. * Spawned sessions never bake — they deliver their port via the * `MCP_BRIDGE_PORT` env var — so the rendered script is identical for every * spawned session regardless of who wrote it, which is what makes the * "write only when different" check in `writeRenderedScriptIfChanged` * meaningful across sibling processes. */ private renderScript; private writeRenderedScriptIfChanged; /** This instance's owner file for `projectPath`. Stable across calls within * one `BridgeManager` instance's lifetime, so a restart-and-reinject * overwrites its own previous file rather than orphaning it. */ private ownerFilePath; private ownerFileName; private isSelfOwnerFile; private isOwnerLive; /** * Read every owner file in the registry, pruning (best-effort unlink) any * that is unparseable or dead. Returns only the live entries. This is the * single point that mutates the on-disk registry by pruning, so every * public method that needs "who is live" goes through it. */ private readLiveOwners; /** * The live attach-mode owner on this project, or undefined. `excludeSelf` * is true for the conflict check in `inject` (self has not written its own * file yet at that point, but excluding is still correct if it somehow * had), and false when rendering the script, since after `inject` writes * its own owner file self may legitimately be the attach owner to bake. */ private liveAttachOwner; /** * Remove the shared bridge artifacts: the autoload entry, the namespaced * script and its `.uid`, the legacy project-root script and its `.uid`, the * `owners/` directory once empty, and the `bridge/` directory once empty. * Each step is independently try/caught and best-effort. * * Callers (`cleanup`, `repairOrphaned`) are responsible for confirming no * live owner remains before calling this — it does not check the registry * itself, so it must never be called while another session might still be * relying on these artifacts. * * Never touches `screenshots/`, `scripts/`, `validate/`, the * `.mcp/godot-runtime/` directory itself, or `.mcp/.gdignore` — handed-out * screenshot paths must stay resolvable and the audit trail must survive. * Legacy `.mcp/screenshots/` and `.mcp/scripts/` from older versions are left * in place, neither migrated nor deleted. * * When the `McpBridge` entry points somewhere this server does not own, the * entry and the project-root script are both left alone: that combination is * a user's own autoload sharing a reserved name, not our artifact. * * Accepted gap: with no `McpBridge` entry at all there is nothing to test * ownership against, so a project-root file named exactly `mcp_bridge.gd` * (plus its `.uid`) is removed on the assumption it is ours. The blast * radius is that one filename at the project root and nothing else. */ private removeBridgeArtifacts; /** * Registered path of the `McpBridge` autoload, normalized to `res://` form, * or undefined when project.godot is missing, unreadable, or has no such * entry. */ private findBridgeAutoload; private unlinkQuietly; private ensureMcpGdignore; private ensureGitignored; } //# sourceMappingURL=bridge-manager.d.ts.map