Skip to content

Case study, open source, npm package

Flutter Lamp

Live eyes for your AI on a running Flutter app.

Role
Author & maintainer
Period
Aug 2026 to present
Status
Actively developed
License
MIT
Latest
v0.21.0 on npm

Source on GitHub (opens in a new tab)npm package (opens in a new tab)

Overview

An MCP server that connects Claude Code, Cursor, Codex and Gemini to a running Flutter app over the Dart VM Service. It streams exceptions, logs, network calls, frame timings and memory as structured data, runs an evidence-first root-cause diagnosis, and serves a live dashboard.

22
MCP tools, each annotated read-only or mutating
8
golden incidents replayed from real device sessions
70%
confidence floor. Below it, the answer is “Unknown”
0
dependencies in the live dashboard

Problem

Debugging Flutter with an AI assistant means copy-pasting stack traces, flutter run output and DevTools screenshots. The assistant is blind between messages and rebuilds what happened from whatever text it was handed.

The failure worth preventing is a confident wrong conclusion. An honest “unknown” is the better outcome.

What I built

  1. Collectors for exceptions with reconstructed stack traces, console and dart:developer logs, dart:io HTTP, frame timings with jank detection, navigation, widget rebuilds, Riverpod, Provider and Bloc activity, memory and the VM timeline.
  2. One normalised, capped event store. The MCP tools and the browser dashboard are two readers of the same store in the same process, with no bridge between them that could drift or disagree.
  3. diagnose_runtime correlates evidence into a summary, root cause, cited evidence ids, confidence, alternatives and fixes, including which categories nothing could observe.
  4. A live dashboard at 127.0.0.1:7373 over native HTTP + WebSocket: logs, network, exceptions, timeline, performance charts, and every tool call the agent made with its latency and response size.
  5. An evaluation suite that replays recorded device sessions and scores the diagnosis against apps that actually misbehaved.

Architecture

Collectors read the Dart VM Service into one capped event store, redacting secrets before anything is stored. The MCP tools, the diagnosis engine and the live dashboard all read that store in the same process.

How runtime evidence flows
  1. Flutter app

    debug or profile build

    Runs on a device or emulator over USB or TCP. Release builds are out of scope: the VM Service, Inspector and dart:io profiling don’t exist there.

  2. Dart VM Service

    JSON-RPC over WebSocket

    Official protocol only. DevTools is never scraped. A cable-free Android path (ensure_tcp_device) keeps the tunnel alive when the USB cable moves.

  3. Collectors

    logs, exceptions, frames, network

    Each runtime source implements one Collector interface and registers itself. Adding a source changes no other layer.

  4. Redaction

    at capture, before storage

    Authorization, cookies, token-shaped headers, sensitive query params and JWT/Bearer strings are removed before they are stored. The VM Service credential, which authorises arbitrary Dart execution, is scrubbed unconditionally.

  5. RuntimeStore

    one capped event stream

    Every event carries a timestamp, source, severity and category. Each category keeps a fixed window (3,000 logs … 500 navigation); runtime_status reports what was evicted so a truncated window is never presented as a whole session.

  6. Diagnosis engine

    evidence to cause

    Correlates evidence into a stable cause label with cited evidence ids, alternatives and coverage. Below 70% confidence it says “Unknown” instead of guessing.

  7. MCP tools

    stdio → Claude Code, Cursor, Codex

    22 tools, each carrying an MCP readOnlyHint. The recommended agent flow is runtime_health → what_changed → a targeted get_* → diagnose_runtime.

  8. Dashboard

    HTTP + WebSocket on loopback

    Streams the same store to a browser. The WebSocket handshake needs a per-process token and a loopback Origin, because browsers exempt WebSocket from the same-origin policy.

  9. Eval replay

    8 golden incidents

    Recorded sessions are hydrated back into a store with their ids intact, the diagnosers re-run, and the output is scored for false confidence.

Decisions

  1. Score false confidence, not accuracy

    Each golden incident is a real exported session plus the right answer, argued in prose from the events. The loader rejects a short justification: a golden pasted from today’s output would be a test that can never fail.

  2. Redact at capture, not at display

    Secrets never enter the event store, so no consumer (the AI model or the dashboard) can leak what was never stored. Withheld header names are listed so a partial picture is visible as partial.

  3. Loopback is not a security boundary

    Any page open in the browser could connect to ws://127.0.0.1:7373 without a check. The handshake requires a per-process token inlined into the served page; /health returns liveness only, never the VM Service URI.

  4. Assumptions carry their provenance

    The 16.67 ms frame budget is one value that reports where it came from. Deriving the refresh rate from batched event arrival was measured and rejected, so the rate is reported as unknown, and a developer can supply it.

  5. One process, two readers

    Because the dashboard and the MCP tools read the same store in the same process, the dashboard can show exactly what the agent asked for and what it cost, and guarantee the human and the agent see identical evidence.

Result

Published to npm and run with npx -y flutter-lamp, with a bundled Claude Code skill that runs connect → gather → diagnose without asking for pasted logs.

Versions 0.1.0 to 0.20.0 shipped between 22 and 28 August 2026, each documented in a Keep a Changelog history, including a fix for a tool-accounting bug that was silently reporting zero errors.

Known limitations

  • Debug and profile builds only.
  • Network capture is pull-on-demand: Dart exposes no push stream for dart:io HTTP.
  • Dart-side HTTP only. Calls from Kotlin, Swift or a WebView don’t appear.
  • CPU is not observable: the VM Service does not sample it.

Stack

TypeScript, Node.js, MCP, Dart VM Service, WebSocket, Claude Code, GitHub Actions