rxjs-spy-mcp
Enables AI agents to debug RxJS 7 streams in the browser through Chrome DevTools MCP by tagging observables and querying subscription graphs, live values, and buffered logs as structured JSON.
README
rxjs-spy-mcp
Experimental RxJS runtime debugging prototype for Chrome DevTools MCP.
This repository modernizes the idea behind Nicholas Jamieson's rxjs-spy for an AI-assisted debugging workflow: RxJS runtime events are captured into a structured heap registry, exposed through a small debug API, and made readable by Chrome DevTools MCP agents.
Main contributor note
ChatGPT is the main contributor to this project.
The architecture, TypeScript starter implementation, RxJS custom debug operators, MVU demo, time-travel heap registry, and Chrome DevTools MCP bridge were generated and refined with ChatGPT from the user's RxJS debugging requirements.
Project goal
The goal is not to replace rxjs-spy completely yet. This is a first experimental implementation of an MCP-friendly RxJS debugging model.
The current prototype focuses on:
- typed RxJS debug operators
- an Elm-like MVU demo
- time-travel state history
- passive heap-based inspection
- safe snapshotting and redaction
- Chrome DevTools third-party tool discovery
- AI-readable debug frames
The long-term direction is a modern rxjs-spy-mcp runtime that can inspect:
- tagged streams
- notifications:
next,error,complete - subscriptions and unsubscriptions
- MVU transitions:
Msg -> Model - inner subscription behavior from
switchMap,mergeMap,concatMap,exhaustMap - scheduler-aware timing traces
Mental model
Observable = static dataflow description
Subscription = runtime execution
Notification = runtime event: next | error | complete
Scheduler = runtime time policy
Heap registry = durable debug memory
Chrome MCP = AI-readable inspection bridge
The debugger turns fast asynchronous RxJS events into durable debug frames:
Msg / next / error / complete / unsubscribe
↓
spyOnHeap / spyOnMvuLoop
↓
window.__RXJS_SPY_MCP__
↓
Chrome DevTools MCP / console / debug panel
Installation
npm install
Run the demo
npm run dev
Open the local Vite URL printed in the terminal, usually:
http://127.0.0.1:5173
Sample: using the debug feature manually
- Start the app with
npm run dev. - Open the browser DevTools console.
- The app should already have recorded an initial
INITtransition. - Inspect the tracked streams:
window.__RXJS_SPY_MCP__.listStreams()
- Inspect the main MVU state stream:
window.__RXJS_SPY_MCP__.inspectStream('main-app-state')
- Read only the timeline frames:
window.__RXJS_SPY_MCP__.getTimeline('main-app-state', 10)
- Read the compact runtime story:
window.__RXJS_SPY_MCP__.story('main-app-state', 20)
As a table:
console.table(window.__RXJS_SPY_MCP__.story('main-app-state', 20))
The story output turns raw debug frames into rows like:
INIT -> query="", active="", loading=false, results=0
SET_QUERY -> query="rxjs", active="", loading=false, results=0
START_SEARCH -> query="rxjs", active="rxjs", loading=true, results=0
SEARCH_SUCCESS -> query="rxjs", active="rxjs", loading=false, results=3
- Type a search query, for example
rxjs, and press Search. - Simulate a failing async effect by typing:
error
Then press Search.
- Inspect the story again:
console.table(window.__RXJS_SPY_MCP__.story('main-app-state', 20))
You should see a sequence similar to:
INIT
SET_QUERY
START_SEARCH
SEARCH_FAILURE
Each mvu-transition frame stores:
{
action: Msg,
resultingState: Model
}
This gives a readable runtime story:
The user changed the query.
A search request started.
The async effect failed.
The model moved into an error state.
The view rendered the error.
If getTimeline('main-app-state', 20) returns []
Run this first:
window.__RXJS_SPY_MCP__.diagnose()
Then run:
window.__RXJS_SPY_MCP__.listStreams()
Expected after a fresh page load:
streamCount >= 1
streamTags includes "main-app-state"
mainStateHistorySize >= 1
Also check that you use the exact global name with two underscores before and after RXJS_SPY_MCP:
window.__RXJS_SPY_MCP__
not:
window._RXJS_SPY_MCP_
If the timeline is still empty:
git pull
npm install
npm run dev
Then hard-refresh the browser tab and run:
window.__RXJS_SPY_MCP__.diagnose()
window.__RXJS_SPY_MCP__.getTimeline('main-app-state', 20)
The current implementation uses a seeded BehaviorSubject<Msg> for the MVU message source, so an INIT transition should be recorded immediately when runtime.appState$ is subscribed in main.ts.
Sample: visual time-travel
The debug panel on the right shows the heap timeline.
Click any frame to visually rewind the UI to the resultingState stored in that frame.
You can also jump from the DevTools console:
window.jumpToStep(2)
Important: this is currently visual rewind, not full replay-based state restoration. The internal scan accumulator is not rewound. A future version can add true event replay.
Sample: using the custom operators
Generic stream inspection
import { interval, map, take } from 'rxjs';
import { spyOnHeap } from './debug/operators';
const counter$ = interval(1000).pipe(
take(5),
map(n => ({ count: n })),
spyOnHeap('counter-stream', { maxFrames: 10 })
);
counter$.subscribe();
Then inspect it in the console:
window.__RXJS_SPY_MCP__.inspectStream('counter-stream')
MVU transition inspection
const msg$ = new BehaviorSubject<Msg>({ type: 'INIT' });
const transition$ = msg$.pipe(
scan(
(acc, msg) => ({ msg, model: update(acc.model, msg) }),
{ msg: { type: 'INIT' }, model: initialModel }
),
spyOnMvuLoop('main-app-state', { maxFrames: 80 })
);
This is the key teaching/debugging use case:
Msg flows in over time.
update calculates the next Model.
spyOnMvuLoop stores Msg + Model as a debug frame.
Sample: Chrome DevTools MCP workflow
This project registers a Chrome DevTools third-party developer tools bridge through the page-level devtoolstooldiscovery event.
When Chrome DevTools MCP is connected with the experimental third-party tools category enabled, an AI agent can discover tools such as:
rxjs_list_streams
rxjs_inspect_stream
rxjs_get_timeline
rxjs_story
A typical AI-agent prompt:
Inspect the active browser tab with Chrome DevTools MCP. Use the rxjs-spy-mcp tools to list RxJS streams, read the main-app-state story, and explain why the latest search failed.
Expected agent behavior:
1. list_3p_developer_tools
2. execute_3p_developer_tool: rxjs_list_streams
3. execute_3p_developer_tool: rxjs_story { tag: 'main-app-state', limit: 20 }
4. Explain the Msg -> Model transition that caused the bad state.
A fallback MCP approach is script evaluation:
() => globalThis.__RXJS_SPY_MCP__.story('main-app-state', 20)
Corrections applied to the original prototype
| Dimension | Correction applied |
|---|---|
| Concept | Reframed MCP as an inspection bridge, not a replacement for RxJS runtime instrumentation. |
| MVU time-travel teaching value | Added explicit Msg -> Model transition tracking and a visual timeline panel. |
| TypeScript correctness | Split app and debug types, fixed invalid imports, removed any-based INITIALIZE, added strict typed operators. |
| Chrome MCP API correctness | Replaced the invented navigator.developerTools.registerTool idea with a devtoolstooldiscovery bridge for Chrome DevTools third-party tools. |
| rxjs-spy replacement completeness | Added a foundation for tagged streams, notification frames, subscription IDs, teardown tracking, and stream summaries. Still not a full rxjs-spy replacement. |
| AI-agent usability | Added JSON-friendly diagnose, listStreams, inspectStream, getTimeline, and story methods. |
| Production safety | Dev-only installation, redaction for secret-like keys, safe snapshot serialization, circular-value tolerance, and size-limited snapshots. |
Current limitations
This is an experimental prototype. It does not yet implement full rxjs-spy behavior.
Missing or future work:
- monkey-patch-free tagging API comparable to
rxjs-spytags - global Observable subscription graph
- parent/child subscription graph
- higher-order operator visualization
- dedicated debug operators for
switchMap,mergeMap,concatMap,exhaustMap - scheduler-aware traces for
asyncScheduler,animationFrameScheduler, virtual time, and drift - true replay-based time travel
- tests
- package publishing
Safety notes
The debug registry exposes runtime state on window.__RXJS_SPY_MCP__ in development mode. Do not expose sensitive production data through debug streams.
The snapshot layer redacts common secret-like keys and limits serialized payload size, but this is not a complete security boundary.
License
MIT
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
Neon Database
MCP server for interacting with Neon Management API and databases
E2B
Using MCP to run code via e2b.
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.