Tapir MCP Setup
Setting up the tapir MCP tools means writing the .mcp.json entry of the dowel MCP server and registering TapirDowelProvider.
On this page
The tapir tools ride on the substrate MCP server shipped inside
dowel. Install the substrate, register
TapirDowelProvider, and every tapir_* tool surfaces automatically.
dart run tapir mcp:install
Prerequisites#
Before wiring any client config:
- The consumer's
pubspec.yamldepends ondowelandtapir. - The consumer's
bin/dispatcher.dart(the generated scaffold fromdart run tapir install) picksTapirDowelProviderup from thelib/app/_plugins.g.dartbarrel, whichdart run tapir plugin:install tapirfills anddart run tapir plugins:refreshregenerates.dart run tapir tapir:installruns both for you. dart run tapir listshows the tapir commands (tapir:snap,tapir:tap, etc.). When they are missing, the provider is not registered.
The MCP server stays online with zero apps running. Plugin tool calls lazy-reconnect to the
VM Service URI recorded in the project's dowel session file
(~/.dowel/sessions/<hash>/state.json) on first call.
Automated install (recommended)#
dowel ships a built-in MCP installer that writes the canonical .mcp.json
entry for you. Once tapir is in pubspec.yaml and the provider is registered, the same
single command wires Claude Code, Cursor, Windsurf, and any other client that
reads .mcp.json at the project root:
dart run tapir mcp:install
The installer is idempotent: pre-existing entries are preserved and the dowel key is
replaced in-place on re-run. Override the target path per client:
# Cursor (per-project)
dart run tapir mcp:install --path .cursor/mcp.json
To remove the entry later:
dart run tapir mcp:uninstall
Canonical .mcp.json entry#
When writing the file by hand (or wiring a client whose path is not yet covered by
mcp:install --path):
{
"mcpServers": {
"dowel": {
"command": "dart",
"args": ["run", "tapir", "mcp:serve"],
"cwd": "."
}
}
}
The cwd field must point at the project root (the directory that contains the consumer's
pubspec.yaml). dart run tapir mcp:serve starts the substrate's MCP server with
TapirDowelProvider already registered, so the tapir descriptors ride on it.
Fallback invocations#
mcp:install picks the .mcp.json command/args payload based on the consumer's scaffold state. Three precedence levels, highest first:
-
./bin/dowel mcp:serve: when the fastcli scaffold is present and the platform is POSIX. Fastest startup (a pre-compiled binary). This is the default afterdart run tapir tapir:install.json { "command": "./bin/dowel", "args": ["mcp:serve"], "cwd": "." } -
dart run tapir mcp:serve: when fastcli is absent (Windows or scaffold skipped). Auto-selected: the tapir wrapper injects--invocation=tapirbefore forwardingmcp:installto the substrate, so the correct payload is written without any manual intervention. About 3s startup per call. This path surfaces everytapir_*tool without a scaffold, because the wrapper forcescollectMcpTools: truewhen dispatchingmcp:serve.json { "command": "dart", "args": ["run", "tapir", "mcp:serve"], "cwd": "." } -
dart run :dispatcher mcp:serve: fallback whenmcp:installruns without a plugin invocation hint (for exampledart run dowel mcp:install, which skips the tapir wrapper). Prefer level 2 for tapir consumers; this form boots without tapir plugin tools unless the generated dispatcher already registers the provider.
Trade-off: the fastcli path (level 1) requires the ./bin/dowel AOT binary to be pre-compiled and present on the file system, but offers the fastest MCP server startup. The dart run path (level 2) works everywhere Dart is on PATH with no pre-compilation, at the cost of about 3s cold-start per agent session.
Per-client install#
| Client | Config path | After edit |
|---|---|---|
| Claude Code | .mcp.json (project) or ~/.claude.json (user) |
/mcp reconnect dowel |
| Cursor | .cursor/mcp.json (project) or ~/.cursor/mcp.json (user) |
Auto-reload |
| VS Code | code --add-mcp '{"name":"dowel","type":"stdio","command":"dart","args":["run","tapir","mcp:serve"]}' |
Reload window |
| Continue | .continue/config.json mcpServers block |
Restart Continue panel |
| Windsurf | Windsurf MCP settings panel | Reload Cascade panel |
| Claude Desktop | ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) / %APPDATA%\Claude\claude_desktop_config.json (Windows) |
Fully quit + relaunch (close-window != quit) |
For Claude Code via CLI:
claude mcp add dowel -- dart run tapir mcp:serve
# or project-scoped:
claude mcp add --scope project dowel -- dart run tapir mcp:serve
Reconnect ritual#
Claude Code, Continue, and Windsurf do NOT auto-reconnect when .mcp.json or
.dowel/mcp.json (the visibility filter) changes. After every edit:
- Claude Code: run
/mcp reconnect dowelin the chat. The slash command re-initialises only the named server; other MCP entries stay connected. - VS Code / Continue: reload the editor window or restart the panel.
- Windsurf: reload the Cascade panel.
- Cursor: picks up changes on its own; no manual action needed.
The substrate MCP server stays online during a reconnect; the client simply re-runs
initialize and reads the refreshed tool catalogue.
Troubleshooting#
The server is online but no app is running. This is normal. The substrate MCP server
starts without requiring the session file. Every tapir_* tool call returns an
actionable error (No Flutter app detected) until you run dart run tapir start. Once the
session file exists the next tool call lazy-reconnects.
tapir_* tools missing from the catalogue. The provider is not registered with the
executable .mcp.json starts. Check that executable, not the wrapper: when .mcp.json
starts ./bin/dowel, run ./bin/dowel list, because dart run tapir list registers
TapirDowelProvider itself and shows the tapir:* block even when the scaffolded
./bin/dowel lacks the provider (for example after a failed Phase 2 of tapir:install).
When the block is missing, run dart run tapir plugin:install tapir, then
./bin/dowel list again. dart run tapir plugins:refresh regenerates the
lib/app/_plugins.g.dart auto-discovery barrel. When .mcp.json starts
dart run tapir mcp:serve, dart run tapir list is the right check.
Lazy-reconnect on first tools/call is slow (~1s). Expected; the substrate opens a
WebSocket to the VM Service URI on demand. Subsequent calls reuse the cached connection.
Tool visibility filter. Hide a tool surface without uninstalling: edit
.dowel/mcp.json to add the tool name under tools.deny. Deny always wins over allow.
Run the reconnect ritual after editing.
Related Documentation#
- overview.md: tool catalogue, dispatch surfaces (
ext.tapir.*vs.dowel:tapir:*), lifecycle. - tool-reference.md: per-tool input schema and example payloads.
- Substrate MCP setup: the full per-client install matrix for the underlying server.