From 5e5b0dbb8fc1c2bd38f3eb9aa28330b97c85118e Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sat, 15 Aug 2026 16:51:54 -0700 Subject: [PATCH] FN-9098: bridge scoped Fusion tools into Cursor Publish engine-owned Fusion tools to Cursor through a crash-safe, worktree-scoped MCP bridge. - preserve operator MCP configuration with locking, journaling, quarantine, and lease reconciliation - enforce identity-scoped fn_* provenance so injected custom and MCP tools are never exposed - secure loopback dispatch with per-session tokens, heartbeats, cleanup, and normalized tool events - document the Cursor contract and cover bridge lifecycle, config hygiene, and failure handling Files changed: .changeset/fn-9098-cursor-mcp-bridge.md | 7 + docs/cursor-cli-contract.md | 140 ++------------- docs/mcp.md | 4 + .../src/__tests__/agent-session-helpers.test.ts | 24 +++ .../src/__tests__/step-session-executor.test.ts | 16 ++ .../src/__tests__/web-fetch-universal.test.ts | 4 +- packages/engine/src/agent-heartbeat.ts | 3 +- packages/engine/src/agents/agent-runtime.ts | 9 + .../engine/src/agents/agent-session-helpers.ts | 26 +-- packages/engine/src/execution/reviewer.ts | 1 + .../engine/src/execution/step-session-executor.ts | 31 ++-- .../engine/src/executor/execute-workflow-step.ts | 4 +- packages/engine/src/merger.ts | 4 +- plugins/fusion-plugin-cursor-runtime/README.md | 18 +- plugins/fusion-plugin-cursor-runtime/package.json | 2 +- .../src/__tests__/cursor-mcp-config.test.ts | 100 +++++++++++ .../cursor-mcp-server-failure.stream.jsonl | 3 + .../fixtures/cursor-mcp-tool-call.stream.jsonl | 4 + .../src/__tests__/runtime-adapter.test.ts | 57 +++++- .../src/__tests__/worktree-hygiene.test.ts | 52 ++++++ .../src/cursor-mcp-config.ts | 196 +++++++++++++++++++++ .../src/mcp-schema-server.cjs | 155 ++++++++++++++++ .../src/prompt-transport.ts | 4 +- .../src/runtime-adapter.ts | 67 +++++-- .../src/tool-bridge.ts | 48 +++++ .../src/tool-mapping.ts | 11 ++ plugins/fusion-plugin-cursor-runtime/src/types.ts | 6 +- .../src/worktree-hygiene.ts | 117 ++++++++++++ 28 files changed, 934 insertions(+), 179 deletions(-) Fusion-Task-Id: FN-9098 Fusion-Task-Lineage: 11b6cb10-ce0e-4f33-9007-c83f2bbf82ea Co-authored-by: Fusion (runfusion.ai) --- .changeset/fn-9098-cursor-mcp-bridge.md | 7 + docs/cursor-cli-contract.md | 140 ++----------- docs/mcp.md | 4 + .../__tests__/agent-session-helpers.test.ts | 24 +++ .../__tests__/step-session-executor.test.ts | 16 ++ .../src/__tests__/web-fetch-universal.test.ts | 4 +- packages/engine/src/agent-heartbeat.ts | 3 +- packages/engine/src/agents/agent-runtime.ts | 9 + .../src/agents/agent-session-helpers.ts | 26 ++- packages/engine/src/execution/reviewer.ts | 1 + .../src/execution/step-session-executor.ts | 31 +-- .../src/executor/execute-workflow-step.ts | 4 +- packages/engine/src/merger.ts | 4 +- .../fusion-plugin-cursor-runtime/README.md | 18 +- .../fusion-plugin-cursor-runtime/package.json | 2 +- .../src/__tests__/cursor-mcp-config.test.ts | 100 +++++++++ .../cursor-mcp-server-failure.stream.jsonl | 3 + .../cursor-mcp-tool-call.stream.jsonl | 4 + .../src/__tests__/runtime-adapter.test.ts | 57 ++++- .../src/__tests__/worktree-hygiene.test.ts | 52 +++++ .../src/cursor-mcp-config.ts | 196 ++++++++++++++++++ .../src/mcp-schema-server.cjs | 155 ++++++++++++++ .../src/prompt-transport.ts | 4 +- .../src/runtime-adapter.ts | 67 ++++-- .../src/tool-bridge.ts | 48 +++++ .../src/tool-mapping.ts | 11 + .../fusion-plugin-cursor-runtime/src/types.ts | 6 +- .../src/worktree-hygiene.ts | 117 +++++++++++ 28 files changed, 934 insertions(+), 179 deletions(-) create mode 100644 .changeset/fn-9098-cursor-mcp-bridge.md create mode 100644 plugins/fusion-plugin-cursor-runtime/src/__tests__/cursor-mcp-config.test.ts create mode 100644 plugins/fusion-plugin-cursor-runtime/src/__tests__/fixtures/cursor-mcp-server-failure.stream.jsonl create mode 100644 plugins/fusion-plugin-cursor-runtime/src/__tests__/fixtures/cursor-mcp-tool-call.stream.jsonl create mode 100644 plugins/fusion-plugin-cursor-runtime/src/__tests__/worktree-hygiene.test.ts create mode 100644 plugins/fusion-plugin-cursor-runtime/src/cursor-mcp-config.ts create mode 100644 plugins/fusion-plugin-cursor-runtime/src/mcp-schema-server.cjs create mode 100644 plugins/fusion-plugin-cursor-runtime/src/tool-bridge.ts create mode 100644 plugins/fusion-plugin-cursor-runtime/src/tool-mapping.ts create mode 100644 plugins/fusion-plugin-cursor-runtime/src/worktree-hygiene.ts diff --git a/.changeset/fn-9098-cursor-mcp-bridge.md b/.changeset/fn-9098-cursor-mcp-bridge.md new file mode 100644 index 0000000000..82c9336e5e --- /dev/null +++ b/.changeset/fn-9098-cursor-mcp-bridge.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": minor +--- + +summary: Bridge Fusion task tools into Cursor CLI sessions safely. +category: feature +dev: Adds tokenized bridge env vars, baseline-first journaled `.cursor/mcp.json` leases, exclusion-before-creation, operator-edit quarantine/recovery, tracked-config refusal, and awaited disposal. diff --git a/docs/cursor-cli-contract.md b/docs/cursor-cli-contract.md index 7f5d10333d..855fc923a7 100644 --- a/docs/cursor-cli-contract.md +++ b/docs/cursor-cli-contract.md @@ -1,139 +1,25 @@ -# Cursor CLI Contract (FN-3396 Step 0) - -Date: 2026-05-07 -**Update history:** 2026-07-08 — corrected the model-discovery and auth-status contract from FN-3396's assumed `--json` commands to the verified `cursor-agent models` / `cursor-agent status --format json` contract captured and implemented in FN-7697. +## MCP staging and cleanup -## Research method +Fusion creates a unique `fusion-custom-tools-` server key per Cursor session. The `.cursor/.fusion-mcp-state.json` manifest retains the complete `{ command, args, env }` entry for every lease, allowing one process to recompose a peer process's live entry. Operator content is taken from current bytes; Fusion content is taken from that manifest. -- Local runtime inspection in the task environment (`which`, direct command execution). -- Local binary wrapper inspection (`cursor`, `cursor-agent` launch scripts and install layout). -- Bounded `fn_research_run` was attempted but failed in this environment with: `table research_runs has no column named projectId`. +Before staging in a git worktree, Fusion writes its marker block to `info/exclude`, then creates `.cursor/` and its lock directory. The marker covers `mcp.json`, the state record, and the lock so step-boundary `git add -A` cannot capture session files. The first stage persists the byte-exact baseline before it writes config bytes. Later changes journal the intended output before atomic config replacement, then promote the record, so a crash can resolve either the intended or previous byte sequence without mistaking Fusion output for an operator edit. -## Confirmed invocation and binary detection +A tracked `.cursor/mcp.json` is refused. Byte-different operator edits are preserved rather than restored over. If an operator edit makes the file unparsable, Fusion quarantines the worktree: no process writes that config, the exclusion remains, and further staging is refused. Reconciliation clears the quarantine only after the config is deleted or has valid JSON with no `fusion-custom-tools-*` keys. -- **Primary executable aliases found on PATH:** - - `cursor` - - `cursor-agent` -- **Not found on PATH:** - - `cursor-cli` -- `cursor` is a wrapper that can delegate to agent mode and emits a targeted message when IDE install is missing. -- `cursor-agent` is the direct CLI runtime entrypoint and is symlinked to a versioned install under: - - `~/.local/share/cursor-agent/versions//cursor-agent` +### Worktree safety protocol -### Detection strategy +The lease manifest records each `serverEntry` as `{ command, args, env? }`; entries are always recomposed from the durable manifest while non-Fusion content comes from the current on-disk JSON. A peer can therefore dispose without dropping another process's bridge. The first stage commits the raw baseline before its first config write. Every subsequent mutation writes `pending { kind, raw, seq }`, atomically replaces the config, then promotes the pending record. Recovery compares bytes against the pending result and last confirmed result: match pending promotes, match prior discards, and any other bytes latch an operator edit. -1. If the global `cursorCliBinaryPath` setting is a non-empty string, probe that configured binary first. -2. Probe `cursor-agent` from PATH. -3. Probe `cursor` from PATH. -4. Deduplicate candidates when the configured value is exactly `cursor-agent` or `cursor`. -5. Persist the resolved path and executable name in probe results. -6. Report explicit failure reason when neither exists. +Bootstrap is deliberately outside the main lock because that lock lives in `.cursor/`: resolve git shape, serialize the `info/exclude` marker under the git-dir bootstrap lock, observe/create `.cursor/` with an `EEXIST`-safe ownership observation, then acquire the main lock. On final cleanup the inverse is used: the exclusion marker is the last in-lock removal, the main lock is released immediately, and only then may Fusion make one non-recursive `rmdir` attempt. A failed `rmdir` is a benign peer/operator race and is retried only by a later reconciliation. -### Manual binary path override +Lock owners persist PID, hostname, and acquisition time. Contenders retry briefly and may reclaim only a dead same-host owner or an expired critical-section TTL. Leases heartbeat independently for long turns. The synchronous process-exit backstop makes one free-lock attempt only; it never bootstraps, takes over a stale lock, or writes when a peer owns the lock. Reconciliation is the crash-recovery owner. - - -Settings → Authentication → Cursor CLI exposes an optional binary path field. Leave it blank to use PATH auto-detection. When populated, Fusion validates the configured path by running the same `--version` probe used for status/enable, saves it only if that configured candidate itself succeeds, and then uses it for status, enable validation, and Cursor model discovery before falling back to PATH candidates. - -If the configured path fails during ordinary status/model-discovery probes but a PATH candidate succeeds, Fusion remains usable and reports the PATH candidate as the effective `binaryPath`; bounded diagnostics include the configured-path failure. If saving a new non-empty override fails or only succeeds via PATH fallback, the Settings save returns a 400 diagnostic and does not persist the path. - -Windows paths with spaces, for example `C:\Users\A User\AppData\Roaming\npm\cursor-agent.cmd`, are treated as one operator-provided string. Users should not quote or split the path in the UI. - -### Windows PATH shim invocation - - - -On Windows, `cursor-agent`, `cursor`, and manual override paths can resolve to `.cmd` / `.bat` wrappers rather than native executables. Node.js direct `spawn(binary, args)` does not execute those wrappers reliably; Fusion's Cursor command runner therefore sets shell execution only when `process.platform === "win32"`. - -The Windows shell-backed path applies to every Cursor CLI command Fusion currently runs through the shared runner: - -- Configured binary / `cursor-agent --version` / `cursor --version` probe attempts. -- Auth-status probe against the effective probe-selected binary: `cursor-agent status --format json`. -- Model discovery against the effective probe-selected binary: `cursor-agent models` (plain text, no `--json` flag). - -Non-Windows probes and discovery continue to use direct spawn. Spawn errors such as `ENOENT` or `EACCES` are included in the unavailable probe reason in bounded diagnostic form so a working terminal command is distinguishable from known Cursor runtime/auth states; Fusion does not dump PATH, environment variables, or unbounded stdout/stderr. - -## Confirmed error/auth/runtime signals - -Observed command behavior in this environment: - -- `cursor --help` (without IDE install): - - `Error: No Cursor IDE installation found. Use 'cursor agent' or 'agent' to run the agent.` -- `cursor-agent --help` and `cursor agent --help` (with locked keychain): - - `Error: Your macOS login keychain is locked.` - - `Run security unlock-keychain and try again.` - -### Auth/readiness implications - -- Keychain-locked is a distinct, expected failure mode and must be surfaced as an auth/runtime-blocked state (not as unknown crash). -- Missing IDE install is a distinct expected failure mode from missing binary. - -## Structured output and model discovery - -- **Confirmed:** `cursor-agent models` is the model-list command. Output is plain text — passing an unsupported JSON output flag (e.g. appending `--json` to the `models` subcommand) fails with `error: unknown option '--json'`. -- Output shape: an `Available models` header line, a blank line, then one model per line formatted as ` -