Skip to content

MCP Integration Overview

View raw .md

How dowel exposes its commands to AI clients as MCP tools: what MCP is, the ten substrate tools, the tools plugins contribute and the state file that ties a tool call to a running app.

On this page

What is MCP#

The Model Context Protocol (MCP) is an open standard that defines how AI clients (Claude Code, Cursor, Windsurf, and similar) discover and invoke tools exposed by a local or remote server over a JSON-RPC transport. A client connects once, calls initialize to receive the full tool catalogue, and then invokes individual tools by name. The server handles routing, argument validation, and error formatting, returning structured content the client model can reason over. MCP decouples the tool surface (what the agent can do) from the tool implementation (how it is done), so the same server can serve multiple AI clients without code duplication.


Why MCP in dowel#

LLM agents working on Flutter codebases traditionally had one interaction mode: read source files and shell out to CLI commands. That works for static analysis and compilation, but it breaks down the moment the agent needs to observe or control a running Flutter application. Capturing a Semantics snapshot, tailing HTTP traffic, evaluating an expression in the live isolate, or hot-reloading after a code change all require a live connection to the Dart VM Service. Passing raw VM Service WebSocket URIs and protocol bytes through shell commands is fragile and opaque.

dowel's MCP integration solves this by surfacing the running Flutter app's capabilities as first-class MCP tools. The agent calls dowel_start to launch the app, dowel_reload to push a hot reload, and plugin-contributed tools to snap the widget tree or tail HTTP requests, all without copy-pasting commands or parsing raw output. The MCP server handles VM Service discovery, lazy reconnect across stop-restart cycles, and structured error formatting so the model can self-correct.


10 Substrate Tools#

The MCP server always registers the following ten tools, derived from the dowel CLI's built-in command set. Tool names are normalised from cmd:name to cmd_name so they satisfy the MCP identifier constraint ([a-zA-Z][a-zA-Z0-9_]*). These tools run in-process via the dowel registry; nine of them require no VM Service connection (so dowel_start works even before any Flutter app is running), while dowel_tinker dispatches over the VM Service and lazy-reconnects on first call.

Tool Maps to CLI command Purpose
dowel_start start Launch the Flutter app via flutter run and write state.json
dowel_stop stop Stop the running Flutter process group, wait until it is gone, and delete state.json
dowel_status status Read state.json and return the current process metadata as JSON
dowel_logs logs Return the log captured from flutter run (follow tails it)
dowel_restart restart Stop the running app and start it again, carrying the prior session's device and ports across (not the R hot restart, which is dowel_hot_restart)
dowel_reload reload Hot reload (sends r to flutter run) without losing widget state
dowel_hot_restart hot-restart Hot restart (sends R to flutter run), resetting ephemeral state
dowel_doctor doctor Run preflight checks (flutter --version, dart --version, port 3100 free, Flutter SDK 3.30.0 or newer for --cdp-port) and report each as pass or fail
dowel_list list List all registered dowel commands with their signatures
dowel_tinker tinker Evaluate Dart expression in running app via VM Service. Maps to: tinker command. Requires running app (dowel_start first).

Commands intentionally excluded from the MCP allowlist: interactive commands (help), codegen commands (make:*, *:refresh), installer commands (plugin:*, install), and MCP meta commands (mcp:*). They either require a TTY, mutate source files better handled by the client's own file tools, or recurse into the MCP server itself.


Plugin-Contributed Tools#

Beyond the ten substrate tools, plugins extend the MCP catalogue by overriding DowelServiceProvider.mcpTools(). The default implementation returns an empty list; plugins return a list of McpToolDescriptor instances that the MCP server collects at initialise time and registers alongside the substrate tools. The same McpFilterConfig allow/deny rules apply uniformly, so a deny rule against a plugin tool name works identically to tools.deny: [dowel_start].

Plugin tools dispatch through the VM Service (not in-process), so they require a running Flutter app. The MCP server lazy-reconnects on each dispatch call, meaning the agent can call dowel_start first and the very next plugin tool call picks up the new app automatically.

Plugin registration is explicit. The consumer's bin/dispatcher.dart wrapper must list each provider in the baseProviders list it passes to runDowel(...) (auto-discovery via lib/app/_plugins.g.dart handles this for plugin:install-registered packages). Consumers invoke ./bin/dowel mcp:serve (post-install via mcp:install) or dart run :dispatcher mcp:serve (Windows / no bin/dowel fallback) to start the MCP server with plugins collected. The bin/mcp.dart entry point (dart run dowel:mcp) loads only the substrate commands without plugin tools.

The two sibling packages that ship production plugin tools are:

Package MCP tool reference
tapir anilcancakir.com/tapir/docs/mcp/tool-reference
crumbs anilcancakir.com/crumbs/docs/mcp/tool-reference

Each package's provider is wired by the consumer (TapirDowelProvider, CrumbsDowelProvider) registered in the wrapper's baseProviders list. See the per-package CLAUDE.md for registration details and each plugin's tool reference site for the current tool catalogue. magic_tinker remains the CLI REPL host for dart run dowel tinker, but its MCP surface is now the substrate's dowel_tinker.


State File Contract#

dowel start writes a JSON file per project at ~/.dowel/sessions/<hash>/state.json (the path is machine-local and user-scoped). The MCP server reads this file during initialize to discover the running Flutter app's VM Service WebSocket URI. If the file is absent or lacks a vmServiceUri key, the server stays online and registers all tools, but VM Service dispatch calls return an actionable error until the app is started.

The state file holds one record per project: starting a second app in the same project overwrites the previous record, while another project gets a session of its own. It is never committed to version control (.gitignore / .pubignore exclude .dowel/).

Fields (from lib/src/state/state_file.dart):

Field Type Notes
pid int PID of the flutter run process
vmServiceUri string Canonical ws://host:port/<token>/ws used for VM Service connection
webPort int --web-port value passed to flutter run
vmServicePort int Informational; defaults to 8181
startedAt string ISO 8601 UTC timestamp
profile string debug or static
projectRoot string Absolute path to the consumer project
device string chrome, macos, linux, windows, or a device UDID
chromePid int or null Chrome process PID when --device=chrome (D6 capture)
tmpProfileDir string or null Temp Chrome profile directory path (D6 capture)

Architecture Diagram#

text
  Claude Code (MCP client)
        |
        | stdio JSON-RPC  (initialize / tools/call)
        |
  ./bin/dowel mcp:serve
  (or: dart run :dispatcher mcp:serve)
        |
   McpServer (mcp:serve)
        |
        +-- substrate tools (dowel_start, dowel_stop, ...)
        |         |
        |         v
        |   DowelRegistry  (in-process command dispatch)
        |
        +-- plugin tools (tapir_*, crumbs_*) + dowel_tinker
                  |
                  | VM Service WebSocket
                  | ws://localhost:PORT/<token>/ws
                  |
             VmServiceClient
                  |
                  | ext.tapir.* / ext.crumbs.* / VM evaluate RPC
                  |
          Flutter app isolate (debug mode)
                  |
          +-------+-------+
          |               |
  TapirIntegration   CrumbsIntegration
  (widget tree)     (HTTP + logs + exceptions)

Flow summary:

  1. The MCP client (Claude Code) connects to the server over stdio and calls initialize.
  2. McpServer reads this project's session and opens a VM Service WebSocket to the running Flutter app.
  3. All registered tools become available to the client model.
  4. Substrate tool calls (prefix dowel:) route in-process through DowelRegistry without VM Service involvement, so dowel_start works even when no app is running.
  5. Plugin tool calls dispatch through VmServiceClient.callServiceExtension, reaching the Flutter app's registered extension handlers in the debug isolate.
  6. Results return as CallToolResult text content; errors carry isError: true with an actionable message so the model can self-correct without a human in the loop.

  • Setup guide: install the MCP server, configure .dowel/mcp.json, and wire provider registration in bin/dispatcher.dart.
  • Tool reference: per-tool input schema, example calls, and error codes for all ten substrate tools, plus links to each plugin's own MCP tool reference site.
esc

Search index did not load.

No match for "".

Try a class like rounded-lg or a widget like WButton.