feat: add MCP server for Claude Code integration - #1598
Conversation
Add `reactotron-mcp` package that exposes Reactotron's debug data as an MCP server. Claude Code connects directly via HTTP to read timeline events, app state, network requests, and send commands to connected apps. - New `lib/reactotron-mcp/` package with MCP resources and tools - MCP toggle button in desktop app footer (green dot status indicator) - Resources: timeline, state, network, apps, benchmarks - Tools: dispatch_action, request_state, swap_state, send_custom_command - Uses @modelcontextprotocol/sdk with StreamableHTTP transport - No changes to reactotron-core-server or client packages
…externals via whiteListedModules
- Replace react-native-builder-bob with tsup (this is a Node.js server, not a React Native lib) - Bundle @modelcontextprotocol/sdk into the output to avoid CJS/ESM resolution issues - Use the new McpServer.registerResource/registerTool API instead of deprecated Server - Use zod schemas for tool input validation - Remove stale .d.ts files from src/
…acks state plugin Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tools:
- dispatch_action: polls for confirmation (state.action.complete)
- request_state: filters response by clientId, faster timeout
- send_custom_command: description guides to list_custom_commands first
- New: list_custom_commands — parses customCommand.register events
- New: clear_timeline — clears command buffer
- New: show_overlay — sends image overlay to app with file→base64
conversion, PNG/JPEG/GIF dimension extraction, and all defaults
matching the desktop app's behavior
Resources:
- Improved descriptions to guide Claude (read timeline first, etc.)
- State resource clarifies staleness, points to request_state tool
Also:
- Fix dynamic import("fs") crash in Electron renderer — use static imports
- Add file logging to /tmp/reactotron-mcp.log for debugging
- Add uncaughtException/unhandledRejection handlers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resources: - reactotron://state/subscriptions — shows active subscriptions and state.values.change events from subscribed paths - reactotron://asyncstorage — shows all AsyncStorage mutations (setItem, removeItem, etc.) captured from the app Tools: - subscribe_state — subscribe to a state path via server.stateValuesSubscribe() - unsubscribe_state — unsubscribe via server.stateValuesUnsubscribe() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add 20 integration tests covering MCP lifecycle, all resources, and all tools (using real relay server + mock app WebSocket) - Fix zod v3/v4 type mismatch: add zod ^4.3.0 as direct dependency, import from zod/v4 (matching the MCP SDK's usage) - Flatten dispatch_action schema (actionType/actionPayload instead of nested object) for zod v4 compatibility - Remove unused ReactotronMcpOptions interface - Remove process.on listener leak (was adding per start() call) - Update CLAUDE.md to reflect current implementation status Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Security: - Bind HTTP server to 127.0.0.1 only (was 0.0.0.0, exposed on LAN) - Remove wildcard CORS headers (Claude Code doesn't need them) Correctness: - start() returns Promise, rejects on port bind failure - mcpStatus only set to "started" after listen succeeds, "error" on failure - Clean up MCP server on Electron window close (useEffect cleanup) - Set started=true synchronously to prevent double-start race - MCP port configurable via electron-store (mcpPort, default 4567) - Fix opacity description (0.25 → 0.5 to match actual default) - Add JPEG header magic byte validation in dimension parser - Resource json() helper now passes through correct URI per MCP spec Cleanup: - Remove all file-based debug logging (/tmp/reactotron-mcp.log) - Remove toolLog and appendFileSync imports - Remove unused ReactotronMcpOptions interface Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Feature documentation belongs in docs/, not as a CLAUDE.md in the project root. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Match the style of existing Reactotron docs — Docusaurus frontmatter, conversational tone, code examples, and practical usage guide. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
reactotron-mcp is an internal MCP server loaded at runtime by the desktop app — it should not be published to npm. This marks it private and updates the validation and release scripts to skip private packages gracefully. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Pushed two commits to fix CI failures:
The Footer component gained Note: I used I'll be testing things locally from here. Planning to tighten up error handling since the MCP client can send arbitrary payloads to the tools/resources, and we should validate defensively at that boundary. |
Wraps all timeline commands in an error boundary via buildTimelineCommand. If a command component throws during render (e.g. unexpected payload shape), the boundary catches it and renders a RENDER ERROR timeline entry showing the error message and raw payload, instead of crashing the desktop app. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Store custom command registrations in the Reactotron server so they survive MCP server restarts. Previously list_custom_commands returned empty if the MCP server started after apps connected, since the registration events were missed. Now the server tracks commands per clientId and the MCP tool reads from that instead of the event buffer. Also adds three new custom commands to the example app (showAlert, setAsyncStorage, getAsyncStorage) for testing MCP integration. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add @unknown default case to Calendar.Identifier switch in LocalizationModule.swift, which became non-exhaustive in newer SDKs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Four commits from integration testing:
Tested all MCP tools/resources, multi-app targeting, dispatch round-trips, custom command flow, desktop timeline regression (37 events, 6 types, no render errors). |
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
joshuayoes
left a comment
There was a problem hiding this comment.
This is looking really good! Thank you for this contribution.
I'll get this released later this week after I record a cool demo video since this is a great new feature.
Great to hear! Let me know when the video's up, curious to see what you've made! |
|
We are going to do some internal testing this week first, so I'm planning to get this released Monday. |
Updated the instructions in mcp.md to specify that the MCP server is off by default and needs to be toggled on before connecting with Claude Code. This enhances user understanding of the setup process.
Large apps were blowing past Claude Code's token limits — Tyler hit 3.3M chars on request_state. This adds response size protection: - Compact JSON (no pretty printing) saves 30-50% on nested data - 800K character cap with truncation + actionable guidance messages - Timeline returns summarized events (type/timestamp/preview) instead of full payloads, with a new timeline_by_type resource template for drill-down - Network resource truncates request/response bodies to 500-char previews - New request_state_keys tool for lightweight state tree exploration - request_state description now strongly pushes toward using path param - Stress test buttons added to example app LoggingScreen Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Response size protection for MCP serverAddresses feedback where a noisy app exceeded Claude Code's token limits (3.3M chars on Changes:
Docs: Clarified MCP is off by default, documented new tools, updated example conversation. Tests: 43 passing (20 new), including integration tests for oversized state truncation. |
|
Released in 3.9.0! |

Please verify the following:
yarn build-and-test:localpassesREADME.md(or relevant documentation) has been updated with your changesDescribe your PR
Adds a built-in MCP server to Reactotron, allowing AI coding assistants like Claude Code to read debug events and send commands to connected React Native / React apps.
What it does
New package:
reactotron-mcp(lib/reactotron-mcp/)A self-contained MCP server that receives the
reactotron-core-serverinstance and exposes its data as MCP resources and tools over HTTP.Resources (read-only debug data):
Tools (interact with running app):
dispatch_action— dispatch Redux actions with confirmation pollingrequest_state— request fresh state snapshot (1.5s timeout)swap_state— hot-swap entire state treesend_custom_command— trigger registered custom commandslist_custom_commands— discover available custom commandsshow_overlay— image overlay with local file→base64 conversion and dimension extractionsubscribe_state/unsubscribe_state— watch state paths for changesclear_timeline— clear MCP event bufferArchitecture
@modelcontextprotocol/sdk(its CJS exports are broken)Desktop app changes
reactotron-mcpis NOT whitelisted in electron-webpack — loaded via Node.js at runtimeMulti-app support
When multiple apps are connected, resources include
_metahints guiding Claude to ask the user which app they're working on. Single-app connections are auto-selected.Setup
Tests
20 integration tests covering resources, tools, multi-app handling, and edge cases.
Documentation
Added
docs/mcp.mdwith getting started guide, feature overview, and architecture docs.