feat: standalone Electron debugger with MCP integration#57
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wraps globalThis.fetch to capture auth-related HTTP requests as HarEntry objects, with idempotent install/uninstall and static-asset skip logic. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements StandaloneClient with singleton guard via globalThis.__wolfcola_ws, handshake on connect, and send helpers for SDK/network events and clear command. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Orchestrates StandaloneClient, installFetchInterceptor, and ensureRunning into a single public-facing function for app developers to call. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements WsServer Effect service that accepts WebSocket connections, processes HANDSHAKE messages to create/reconnect sessions via SessionManager, and routes SDK_EVENT/NETWORK_EVENT/CLEAR messages through the session handler. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds createMcpTools() wrapping SessionManager operations as async tool functions suitable for MCP registration. Includes list-sessions, get-events, get-flow-summary, get-diagnosis, get-event-detail, search-events, clear-flow, export-json, export-markdown, and set-clear-on-reconnect tools. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d script, and HTML shell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…g, type safety - Bind WS server to 127.0.0.1 only (was open to all interfaces) - Guard RegExp construction from MCP input with try-catch fallback - Wrap all interceptors (fetch/XHR/Node HTTP) in try-catch to never break user's app - Guard XHR responseText access for non-text responseTypes - Hide Session.runtime from public API (split Session/SessionInternal) - Extract makeSession factory to eliminate construction duplication - Add connected status and logging to attachDebugger return handle - Add spawn error listener in launchDebugger before unref - Close WebSocket on error in ensureRunning retry loop - Fix --port arg parsing to not match --portfolio etc - Cross-platform binary lookup (which/where) - Add missing SWITCH_SESSION IPC handler - Wire event forwarding from WS server to renderer via IPC - Remove unnecessary Effect.provide wrappers in WS server Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use Effect Schema.parseJson to compose JSON parsing + schema validation into a single decode step. This gives unified error messages for both malformed JSON and schema validation failures, and eliminates raw JSON.parse from all production code. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace hand-rolled ws + Effect.async with NodeSocketServer.makeWebSocket from @effect/platform-node. This properly handles: - Server binding lifecycle (listening event wait via Effect.async) - Connection handling via FiberSet (each socket runs in its own fiber) - Resource cleanup via Effect.acquireRelease and Scope - Message processing stays in Effect fiber context (no Effect.runPromise inside callbacks) Also fixes: - Build output format: CJS (.cjs) to work with Electron + bundled deps - Asset path resolution: __dirname instead of import.meta.dirname - Remove --packages=external so ws is bundled into the main binary Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace bag-of-functions createMcpTools with proper @effect/ai integration: - Define tools via Tool.make with Effect Schema parameters/success types - Group into WolfcolaToolkit with handlers that use SessionManager - Wire McpServer.layerStdio for --mcp headless mode - Add --mcp flag to main.ts entry point (skips Electron, runs stdio) - Dynamic import of Electron so --mcp mode works without a display Tools: list-sessions, get-events, get-flow-summary, get-diagnosis, get-event-detail, search-events, clear-flow, export-json, export-markdown, set-clear-on-reconnect Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- tsconfig.spec.json: extend tsconfig.base.json with proper compiler options, matching monorepo pattern (was extending solution-style config) - search-events: add 200-char length limit on regex patterns to mitigate ReDoS risk from MCP input; falls back to substring match for long patterns - standalone-client: log connection errors in onerror and catch blocks so developers can diagnose why the debugger didn't connect Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previous tests only tested SessionManager directly — they never touched the MCP layer. New tests: - Register tools via McpServer.toolkit + WolfcolaToolkitLive - Call tools through McpServer.callTool (same path as real MCP clients) - Verify CallToolResult shape (isError, content[0].text) - Test tool registration (10 tools with names and descriptions) - Test non-existent tool returns InvalidParams error - Test export-json/export-markdown return proper format Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- session-manager.ts: annotate Effect.map return as SessionManagerShape to fix implicit-any on lambda parameters - mcp/tools.ts: restructure WolfcolaToolkitLive to close over mgr via Effect.gen in toLayer, so handlers have R=never - ipc-bridge.ts: fix readonly array cast using Parameters<typeof fn>[0] - node-http-interceptor.ts: fix overloaded write forwarding - xhr-interceptor.ts: fix overloaded open forwarding Both packages now pass tsc --noEmit cleanly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause fixes: - Decompose SessionManagerShape.handleMessage into typed methods: getState(), clearSession(), ingestEvent() — eliminates 8 casts - Export IncomingMessage type from devtools-core - Extract exportAsJson/exportAsMarkdown shared helpers — deduplicates ipc-bridge and mcp/tools export logic - XHR interceptor: replace unsafe this-casting with WeakMap for storing method/url metadata across open/send calls - Node HTTP interceptor: use Function.prototype.apply.bind for clean overloaded write forwarding Result: 1 narrowing cast remains in production code (arguments -> Parameters<typeof originalOpen> for XHR monkey-patch) vs 17 type escapes before this commit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Caution
This PR has two critical issues that should block merge: ManagedRuntime leak on disconnect (every disconnect/reconnect cycle leaks a runtime, unbounded under HMR reload) and unredacted OAuth token exposure in MCP data-retrieval tools (get-events, get-event-detail, search-events return raw tokens while export-json/export-markdown correctly apply redactFlowState). Also notable: race conditions in reconnect/remove and error message leakage to WebSocket clients.
TL;DR — Adds a standalone Electron desktop app for OIDC/OAuth2 debugging plus an enhanced attachDebugger() API in the bridge package, with WebSocket-based event transport and an optional @effect/ai MCP server for headless use. The architecture is well-structured and the test coverage is strong, but several correctness and security gaps need addressing.
Key changes
- New
@wolfcola/devtools-standalonepackage — Electron app with WebSocket server, per-sessionManagedRuntime, IPC bridge, Elm renderer, and MCP server with 10 tools - Enhanced
@wolfcola/devtools-bridge—attachDebugger()API with fetch/XHR/Node HTTP interceptors, WebSocket client with HMR singleton guard, auto-launch via binary discovery - Protocol schemas — Typed
Schema.parseJson-based WebSocket message protocol for handshake, SDK events, network events, and clear - Core export —
IncomingMessagetype made public fromdevtools-core
Reuses devtools-types, devtools-core, and devtools-ui unchanged.
Summary | 42 files | 20 commits | base: main ← feat/standalone-debugger
|
Addressed all 7 review comments from review #4295525192:
Tests: 169 passing (48 standalone + 121 bridge), Task list (10/10 completed)
|
|
Addressed all 7 review comments from review #4295525192:
Task list (10/11 completed)
|
The build step copies devtools-ui's elm.js into packages/devtools-standalone/assets/. ESLint then tries to lint this 50k+ line compiled Elm output and fails. Fix: - Add .gitignore for build artifacts in devtools-standalone - Add **/assets/elm.js to eslint ignore list Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Reviewed the following changes:
- Added CLAUDE.md with project guidance for agentic workers
- Added
docs/superpowers/plans/implementation plan anddocs/superpowers/specs/design document - Added unreferenced
packages/devtools-types/src/lib/test.tsplaceholder (see inline comment)
|
Addressed the single review comment: removed unreferenced Task list (5/5 completed)
|
Both tools were linting esbuild output (renderer.js) which uses double quotes, conflicting with the project's single-quote rule. Broadens the existing elm.js-only eslint ignore to cover all compiled assets. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add minor changeset for devtools-bridge and devtools-core - Add README for the new devtools-standalone package covering modes, WebSocket protocol, MCP tools, architecture, and Effect services - Update bridge README with standalone debugger section, attachDebugger API docs, Node HTTP interceptor, and updated architecture diagram Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@pullfrog approve or request further changes. |
There was a problem hiding this comment.
No new issues. Reviewed the following changes:
- Added changeset with minor bumps for
devtools-bridgeanddevtools-core - Updated bridge README with standalone debugger docs and
attachDebugger()API section - Added comprehensive standalone package README (quick start, modes, protocol, MCP tools, architecture)
All 8 prior review findings remain resolved. No new code changes introduced.
There was a problem hiding this comment.
TL;DR — Adds a standalone Electron desktop app for OIDC/OAuth2 debugging with a new @wolfcola/devtools-standalone package, a public attachDebugger() API in devtools-bridge, and headless --mcp mode. Apps connect via WebSocket; events flow through the same pipeline as the Chrome extension.
Key changes
- New
@wolfcola/devtools-standalonepackage — Electron app with@effect/platformNodeSocketServer.makeWebSocketfor WS, session management with per-sessionManagedRuntime, IPC bridge to Elm UI, and@effect/aiMCP server with 10 tools attachDebugger()API indevtools-bridge— WebSocket client with HMR singleton guard, fetch/XHR/Node HTTP interceptors filtered byisAuthRelated, auto-launch via binary discovery with exponential backoff- MCP integration — 10 tools registered via
@effect/aiToolkit with Effect Schema parameters. Headless--mcpmode viaMcpServer.layerStdio - Minimal core changes —
IncomingMessagetype exported fromdevtools-core;devtools-bridgegainsdevtools-coredependency forisAuthRelated
Summary | 51 files | 26 commits | base: main ← feat/standalone-debugger
| const existingSession = yield* Ref.modify(sessionsRef, (ss) => { | ||
| const existing = ss.find((s) => s.name === opts.name); | ||
| if (existing) { | ||
| const newRuntime: ManagedRuntime.ManagedRuntime<EventStoreService, never> = |
There was a problem hiding this comment.
When reconnecting, a new ManagedRuntime is created and assigned to the session, but the old runtime (existing.runtime) is never disposed. In the normal WS flow (connect → disconnect on socket close → reconnect), the old runtime is already disposed by disconnect, so this is safe. But if reconnect is called while a session is still connected (e.g., two concurrent WS connections with the same name), the old runtime is orphaned and its resources leak until process exit. Consider disposing the old runtime before replacing it.

Summary
Adds a standalone Electron desktop app for OIDC/OAuth2 debugging, following the react-devtools model. Apps opt in by calling
attachDebugger()from@wolfcola/devtools-bridge— the debugger spawns automatically and receives events over WebSocket.@wolfcola/devtools-standalone— Electron app with WebSocket server (@effect/platformNodeSocketServer), session management (per-session ManagedRuntime), IPC bridge, and MCP server (@effect/aiToolkit)@wolfcola/devtools-bridge—attachDebugger()API with fetch/XHR/Node HTTP interceptors, WebSocket client with HMR singleton guard, auto-launch via binary discovery@effect/aiToolkit with Effect Schema parameters. Headless--mcpmode for stdio transportdevtools-types,devtools-core,devtools-ui(Elm)Architecture
Key decisions
NodeSocketServer.makeWebSocketfor the WS server — proper fiber lifecycle, scoped cleanup, FiberSet for concurrent connectionsTool.make+Toolkit+McpServer.layerStdiofor MCP — typed schemas, Layer composition, no manual JSON-RPC handlingSchema.parseJsonfor WebSocket protocol — single decode step combining JSON parsing + schema validationSession/SessionInternalsplit — public API never exposesManagedRuntimeSessionManagerShapewithgetState(),clearSession(),ingestEvent()instead ofhandleMessage(unknown) → unknownTest plan
tsc --noEmitclean on both packagesMcpServer.callTool— all 10 tools verified--mcpstdio mode with Claude CodeattachDebugger()from a real app with HMR🤖 Generated with Claude Code