A Claude Code plugin that bundles a local Miro MCP server, giving Claude tools to create and manage boards, sticky notes, shapes, frames, connectors, and tags for EventStorming, brainstorming, and diagramming workflows.
This is the marketplace's first plugin to ship its own MCP server. The server is a
single self-contained Node artifact (server/dist/index.min.js) invoked over local stdio, so
enabling the plugin adds the Miro tools with no separate install, no registry token,
and no npx dependency (a bundled node <server> sidesteps the Windows bare-npx
spawn bug, anthropics/claude-code#58510).
The plugin installs disabled (defaultEnabled: false). A bundled MCP server that
connects to an external, credentialed service is opt-in, not on by default. Enable it
with claude plugin enable miro or the /plugin interface. Set a token before first use:
| Option | Storage | Purpose |
|---|---|---|
miro_api_token |
Claude Code secure credential storage (never settings.json) |
Miro REST API token. The server starts without it; until it is set, every Miro tool call returns an error that names the option and the configure command. |
Get a token from https://miro.com/app/settings/user-profile/apps. Claude Code does not prompt for it
at install, and an unset token does not put the plugin in the /plugin Errors view. Enter it
with /plugin configure miro@melodic-software (masked input); until then each tool call fails
with that instruction. Setting MIRO_API_TOKEN in the server's environment also works, and an
empty value or the unexpanded ${user_config.miro_api_token} text counts as unset. Sensitive values use the macOS Keychain, or
~/.claude/.credentials.json on platforms where no supported keychain is available;
the token is substituted into the server's
MIRO_API_TOKEN environment variable at launch.
Run /miro:setup to check enablement and MCP availability or, with explicit confirmation,
perform a minimal read-only API credential check. The setup skill never reads or exposes the
token and never invokes a mutating Miro tool.
Once set, a sensitive userConfig value has no dedicated reconfigure entry in the /plugin
detail view, and the /mcp server menu's "Clear authentication" applies to OAuth-based servers
only. It is not the rotation path for a token supplied through userConfig (the bundled stdio
server receives miro_api_token as its MIRO_API_TOKEN environment variable, never through an
OAuth flow). To change or clear the token at any time, run:
/plugin configure miro@<marketplace>
That reopens the same configuration screen shown at first enable, letting you overwrite or blank the stored token. Prefer it regardless. It masks input, where a token passed on the command line lands in shell history and the process table.
The older claim here, that --config is ignored once the plugin is installed, was never
version-stamped. Headless --config against an already-installed plugin writes a
non-sensitive option; whether that holds for a sensitive option such as
miro_api_token has not been verified, so do not rely on it for a credential. Do not
uninstall to rotate: that drops this plugin's entire stored pluginConfigs entry,
resetting every option in the Options reference table below to its manifest default.
The verified-version record lives in the
plugin-reconfiguration convention.
If your machine resolves vendor API keys from a secret store at launch time instead of typing
them into app UIs (see
melodic-software/dotfiles ADR 0005),
you can keep miro_api_token out of Claude Code's secure credential storage and let vault-exec
hand the token to the server at launch instead. This is entirely opt-in. Skip it and the plugin
behaves exactly as described above.
Leave miro_api_token unset. The plugin's own server then starts without a token and answers every
tool call with the configure instruction, which is why the recipe below disables it per project
and launches a different server process instead.
-
Enable the plugin.
-
Locate this plugin's cached server bundle. Claude Code copies an installed plugin into a version-keyed cache directory,
~/.claude/plugins/cache/<marketplace>/miro/<resolved-version>/server/dist/index.min.js. The exact path changes on everymiroupdate, so re-check it after one. -
Add a user-scope stdio MCP server whose command wraps that path with
vault-exec:claude mcp add --transport stdio --scope user miro \ -- vault-exec --env MIRO_API_TOKEN=<your-secret-name> \ -- node /path/to/cache/miro/<resolved-version>/server/dist/index.min.js
The
vault-exec.ps1PowerShell wrapper resolves the same secret on Windows, but the shell a wrapped stdio command like this launches through on Windows is unverified. Confirm it starts before relying on it. -
Because that command differs from the plugin's own
node ${CLAUDE_PLUGIN_ROOT}/...entry, Claude Code's endpoint-based deduplication does not collapse the two servers the way it does for an HTTP server at an identical URL (see thedometrainplugin's README for that case). Disable the plugin's ownmiroserver with/mcpso only the wrapped one runs./mcp's disabled-server choice is stored per project, so repeat this once in every project where you usemiro.
Tool names move from mcp__plugin_miro_miro__<tool> (plugin-provided) to mcp__miro__<tool>
(directly configured) once the override is active. Update any permission rule written against the
old prefix. Basis: MCP server configuration, "Plugin MCP
tool names" and "Server deduplication," verified 2026-09-23.
event-storming 0.6.14 and later detect Miro under either prefix, so its live-board path keeps
working with this override. Earlier versions probe only mcp__plugin_miro_miro__* and fall back
to markdown while the override is active.
The server registers Miro operations grouped by concern: boards, sticky notes, frames,
tags, connectors, bulk create, and overlap detection. Read-only tools annotate
readOnlyHint so Claude can parallelize them; mutating tools serialize.
Stdio MCP server (@modelcontextprotocol/sdk)
on Node ≥ 24. Cross-platform, no per-OS path divergence at the stdio boundary. Tool
definitions are thin wrappers over the @mirohq/miro-api
client; the request/response and error-shaping logic lives in server/src/.
The TypeScript in server/src/ is the single source of truth. server/dist/index.min.js is
generated build output: an esbuild single-file bundle of the
source and all runtime dependencies. Plugin install runs no build step, so the bundle
is committed; CI rebuilds it from source with the pinned toolchain and fails on any
drift, so the committed artifact is always exactly what the source produces.
The whole Node project (package.json, the lockfile, src/, dist/, and the tool
configs) lives under server/ rather than at the plugin root. Claude Code runs
npm ci --ignore-scripts inside a consumer's plugin cache whenever the plugin root
holds both a package.json and a supported lockfile, and that install cannot be turned
off; it would materialize this project's devDependencies (the TypeScript, biome, esbuild
and vitest toolchain) on every install even though the bundle needs none of them at
runtime. Keeping the project one level down leaves the plugin root without a lockfile,
so nothing is installed, while CI and Dependabot still pin and rebuild from the same
lockfile. Basis: plugins-reference.md,
"Node.js package dependencies", verified 2026-09-11; recheck when that section changes.
cd plugins/miro/server
npm install
npm run typecheck # tsc --noEmit
npm test # vitest (with coverage + typecheck)
npm run lint # biome check
npm run bundle # regenerate dist/index.min.js from src/
npm run verify-bundle # fail if dist/index.min.js drifts from src/After editing server/src/, run npm run bundle and commit the regenerated
server/dist/index.min.js alongside the source change.
Generated from this plugin's .claude-plugin/plugin.json. Every option Claude Code
will prompt for when the plugin is enabled, with the environment variable each hook
reads it from.
| Option | Type | Default | Environment variable | Description |
|---|---|---|---|---|
miro_api_token |
string | (none) | CLAUDE_PLUGIN_OPTION_MIRO_API_TOKEN |
Sensitive: stored in the OS keychain or protected credentials file. Miro REST API token from https://miro.com/app/settings/user-profile/apps. Until it is set, every Miro tool call returns an error that names this option and the configure command. Stored by Claude Code in secure credential storage, never settings.json. |
Three supported routes, in the order most people want them:
-
Interactively. Claude Code prompts for declared options when you enable the plugin. To change them later:
/plugin configure miro@<marketplace>. -
Headless. Repeat
--configfor each option. Replace<marketplace>with the marketplace you installed this plugin from:claude plugin install miro@<marketplace> -s <scope> --config miro_api_token=<value>
Route 1 is the rotation path for this plugin, not this one. Every option here is
sensitive, and/plugin configuremasks input. A secret passed on the command line lands in shell history and the process table. Do not rely on this command to rotate a credential; the verified-version record lives in the plugin-reconfiguration convention. Do notclaude plugin uninstallto reconfigure either: uninstalling drops this plugin's whole storedpluginConfigsentry, resetting every option in the table above to its default. -
By hand, in settings. Add the value under
pluginConfigsin your user settings (~/.claude/settings.json):{ "pluginConfigs": { "miro@<marketplace>": { "options": { "miro_api_token": <value> } } } }Plugin option values are read from user,
--settings, and managed settings only, not from a project's.claude/settings.json. To vary behavior per repository, enable or disable the plugin in that project'senabledPluginsinstead of setting an option there.
Do not set the CLAUDE_PLUGIN_OPTION_* variables yourself. They are how Claude Code
hands a configured value to a hook process; the value comes from the routes above.
- User configuration: the
userConfigschema and theCLAUDE_PLUGIN_OPTION_<KEY>export - Plugin install options: the
--configflag's reference entry - Plugins and skills settings:
enabledPlugins,extraKnownMarketplaces,pluginConfigs - Settings files and who they affect: user vs project vs local precedence
- Manage installed plugins: enabling, disabling,
/plugin list