MCP Tool Reference
Catalogue of every MCP tool surfaced by dowel directly. The MCP server exposes
10 substrate tools (always available) plus plugin-contributed tools when the matching
provider is registered in the consumer's bin/dispatcher.dart. Plugin tool catalogues live on
each plugin's own documentation site (see Plugin Tools below).
On this page
Tool Naming Convention#
Substrate tools#
Substrate tools are built-in dowel commands exposed by McpServer directly. Their names
follow the pattern dowel_<command> where the dowel command name is transformed:
:(namespace separator) becomes_-(word separator) becomes_
Examples: start maps to dowel_start; hot-restart maps to dowel_hot_restart.
Plugin tools#
Plugin tools are contributed via DowelServiceProvider.mcpTools(). Each plugin package
owns a prefix that matches the package name or domain:
| Plugin package | Tool prefix | Reference |
|---|---|---|
tapir |
tapir_ |
anilcancakir.com/tapir/docs/mcp/tool-reference |
crumbs |
crumbs_ |
anilcancakir.com/crumbs/docs/mcp/tool-reference |
The same : and - transformation applies inside the suffix part.
Exclusions from the allowlist#
Interactive (help), codegen (make:*, *:refresh), installer (plugin:*,
install), and MCP meta (mcp:*) commands are intentionally excluded. They
either require a TTY, mutate source on disk (better served by the client's own file tools),
or recurse into the MCP server itself. tinker was previously excluded as interactive but
is now absorbed into the substrate as dowel_tinker (the --eval one-shot path drives
the MCP surface).
Substrate Tools#
The 10 substrate tools are always present when the MCP server starts. They are served
in-process: the server builds a MapInput from the MCP arguments, runs the dowel command
via BufferedOutput, and returns the combined stdout + exit code as MCP text content.
dowel_start#
Maps to dowel command: start
Starts the Flutter app in debug mode and records the process PID and VM Service URI in this project's session file. Once it exists the plugin tools (tapir, crumbs, tinker) can connect to the running VM.
| Parameter | Type | Default | Description |
|---|---|---|---|
device |
string | chrome |
Flutter device id. Common values: chrome (web), macos (desktop), <adb-serial> (Android), or any id from flutter devices. |
port |
string | 3100 |
Web port for the chrome device (numeric string). Ignored for non-web devices. |
vm-service-port |
string | 8181 |
Port the VM Service binds to on the host. Change when 8181 is already taken. |
dds |
boolean | false |
Enable the Dart Development Service (DDS) proxy. Set true when a tool requires DDS-only features. |
profile-static |
boolean | false |
Run a device (Android, iOS, desktop) with flutter run --profile: release-like performance, no hot reload. On chrome it only tags the session, because a web profile build has no VM Service. |
dowel_stop#
Maps to dowel command: stop
Sends SIGTERM to the process group of the recorded Flutter tool, so frontend_server and
the other children stop with it, and waits up to 5 seconds for the group. A member still
there is sent SIGKILL and waited on for up to 5 seconds more; then, on a web session, the
web port gets up to 5 seconds to come free. A CDP session's Chrome is stopped the same way,
with its CDP port. A pid that now belongs to a process started after the session was recorded
is left alone. The call returns once the app is gone (about 15 seconds at worst, 30 with
Chrome), so a dowel_start straight after it does not race the old app for its port. Then it deletes this project's session; an app that outlives SIGKILL
keeps its session and fails the call, so it can be retried. On an Android device it also runs
adb -s <serial> shell am force-stop <applicationId> (the id comes from
android/app/build.gradle*), because signalling the flutter tool leaves the app running.
After this call, plugin tools will fail until dowel_start is called again.
No parameters.
dowel_status#
Maps to dowel command: status
Reads this project's session and returns the current process state as JSON: PID, device, VM Service URI, start timestamp, and whether the process is still alive.
No parameters.
dowel_logs#
Maps to dowel command: logs
Returns the log captured from the Flutter process since this project's session started. Use
this to see compilation errors, framework warnings, and print() output from the app.
| Parameter | Type | Default | Description |
|---|---|---|---|
follow |
boolean | false |
When true, tail the live log stream until the client disconnects (-f on the CLI). When false, return the whole captured log and exit immediately. |
dowel_restart#
Maps to dowel command: restart
Stops the running Flutter app and starts it again (stop plus start, not a hot restart). The
relaunch carries the prior session's device, web port, VM Service port, CDP port, --flutter-arg
values and profile build mode across. The Dart process is new, so all in-memory state is lost.
For the R keypress, use dowel_hot_restart.
No parameters.
dowel_reload#
Maps to dowel command: reload
Sends r to the stdin of the running flutter run process (through the FIFO the session
records), so it works on every device target. Equivalent to pressing r in the flutter run
terminal. Updates code in place; widget state is preserved.
Faster than dowel_restart but does not pick up changes to initState or constructor
bodies.
No parameters.
dowel_hot_restart#
Maps to dowel command: hot-restart
Sends R to the stdin of the running flutter run process (the same FIFO as dowel_reload):
tears down the widget tree and rebuilds from main() while keeping the Dart VM alive. Faster than a full process restart; slower than hot reload. Use
when hot reload cannot pick up the change (e.g. changes to StatefulWidget.createState,
didChangeDependencies, or provider boot logic).
No parameters.
dowel_doctor#
Maps to dowel command: doctor
Runs four preflight checks, each reported as pass or fail: flutter --version,
dart --version, port 3100 free, and a Flutter SDK of 3.30.0 or newer (needed for
--cdp-port). It also prints advisory warnings for a stale .mcp.json entry and an SDK too
old for CDP commands; those never change the exit code. Run this first when dowel_start
fails unexpectedly.
No parameters.
dowel_list#
Maps to dowel command: list
Lists every registered dowel command with its signature and description. Output mirrors
dart run dowel list. Useful for discovering commands contributed by plugin providers
registered in the consumer's bin/dispatcher.dart.
No parameters.
dowel_tinker#
Maps to dowel command: tinker
Evaluate a Dart expression inside the running Flutter app via the VM Service evaluate RPC.
Takes required eval argument (Dart expression). Returns the formatted evaluation result
as text. Dispatch follows the same lazy-reconnect path as plugin VM-extension tools; the
expression runs in the foreground isolate's root library scope and is passed through the
Tinker.casters chain before return.
| Parameter | Type | Default | Description |
|---|---|---|---|
eval |
string | (required) | Dart expression to evaluate in the running app's root library (e.g. User.current.name, MonitorController.instance.refresh(), 1+1). Runs synchronously in the foreground isolate; the formatted result returns as text. |
Plugin Tools#
Plugin tools are contributed by sibling packages via DowelServiceProvider.mcpTools().
They surface in the MCP server only when the corresponding provider is registered in the
consumer's bin/dispatcher.dart and the MCP server is restarted (run ./bin/dowel mcp:serve or
dart run :dispatcher mcp:serve on Windows or when bin/dowel is absent).
Each plugin tool dispatches over a ext.<domain>.* VM Service extension registered by the
plugin inside the running Flutter app.
tapir#
E2E interaction driver. Tools operate over ext.tapir.* VM Service extensions installed by
tapir inside the running app. Tool names use the tapir_ prefix.
The plugin's MCP tool catalogue evolves alongside the package; the canonical reference lives at anilcancakir.com/tapir/docs/mcp/tool-reference (per-tool input schema, required parameters, return shape, example invocations).
crumbs#
Runtime inspector. Tools read ring buffers populated by ext.crumbs.* VM Service
extensions installed by crumbs inside the running app. Tool names use the
crumbs_ prefix.
The plugin's MCP tool catalogue evolves alongside the package; the canonical reference lives at anilcancakir.com/crumbs/docs/mcp/tool-reference (per-tool input schema, required parameters, return shape, example invocations).
Tinker (absorbed into the substrate)#
The connected REPL is no longer a plugin tool: it ships as the substrate's
dowel_tinker (see above). magic_tinker is still consumed by the host app for the
interactive REPL CLI (dart run dowel tinker), but its MCP surface has been removed
to avoid duplicating the substrate path. Use dowel_tinker from any MCP client for
live state inspection, controller calls, or facade events.
Filter Configuration#
The MCP server applies a three-layer filter (file, then env vars, then CLI flags) to control which plugin tools surface. Each layer uses Cargo-style replacement for allow lists and union for deny lists.
Precedence rules#
- Allow lists: CLI replaces env, which replaces file. A non-null allow list at a higher layer REPLACES the lower layer entirely. Null means "no opinion at this layer."
- Deny lists: union of all three layers. A tool denied in any layer stays denied.
- Deny wins over allow: when a name appears in both the effective allow set and the effective deny set, the deny wins.
Layer 1: .dowel/mcp.json#
Place this file in the project root (next to bin/dispatcher.dart):
{
"packages": {
"allow": null,
"deny": []
},
"tools": {
"allow": null,
"deny": []
}
}
Field reference:
| Key | Type | Meaning |
|---|---|---|
packages.allow |
null or string[] |
null = allow all packages; array = allow only named packages |
packages.deny |
string[] |
Package names always excluded (wins over allow) |
tools.allow |
null or string[] |
null = allow all tool names; array = allow only named tools |
tools.deny |
string[] |
Tool names always excluded (wins over allow) |
Package names match DowelServiceProvider.providerName: dowel (substrate),
tapir, crumbs. Tool names match McpToolDescriptor.name exactly
(e.g. dowel_tinker, or any name listed on each plugin's tool-reference site).
Layer 2: environment variables#
| Variable | Effect |
|---|---|
DOWEL_MCP_PACKAGES_ALLOW |
CSV of package names to allow |
DOWEL_MCP_PACKAGES_DENY |
CSV of package names to deny |
DOWEL_MCP_TOOLS_ALLOW |
CSV of tool names to allow |
DOWEL_MCP_TOOLS_DENY |
CSV of tool names to deny |
Layer 3: CLI flags#
Pass flags directly to the MCP server invocation:
./bin/dowel mcp:serve \
--include-package=tapir \
--exclude-tool=dowel_stop
CLI flags replace the env + file allow lists and are unioned into the deny sets.
Example: expose tapir tools only
{ "packages": { "allow": ["tapir"], "deny": [] }, "tools": { "allow": null, "deny": [] } }
Example: hide destructive substrate tools
{ "packages": { "allow": null, "deny": [] }, "tools": { "allow": null, "deny": ["dowel_stop"] } }
Related Documentation#
- MCP Overview: architecture, transport, and server entry points
- MCP Setup: consumer registration,
.mcp.json, and first-run checklist