ADR 0001 — Capability kernel and tool-name prefix model
- Status: Accepted
- Shipped: v3.0.0
- Authoritative source:
mcp_capability_kernel/lib/
Context
Pre-v3.0, the dependency graph was upside-down at the server boundary:
flutter_live_edit_toolkit → mcp_toolkitwas correct (app-side).mcp_server_dart → flutter_live_edit_toolkitwas a leak: the server statically depended on Flutter, which it never renders. This was the source of the long-standinguses-material-designwarnings in server builds and tests.- Every binary advertised live-edit's tool surface unconditionally; there was no way to compose a custom server with a different capability set.
- Tool names were flat across the MCP boundary —
tap_widgetfrom core and a hypothetical futuretap_widgetfrom another capability would collide silently.
Decision
Introduce a capability kernel (mcp_capability_kernel): a Dart package
that defines the contracts a unit of MCP functionality must satisfy. Each
capability is its own package, registered into a host (server or CLI) at
compile time. The kernel applies a <capabilityId>_ prefix to every tool and
resource name the capability exposes.
The shape of the contract:
abstract interface class Capability {
/// Stable id for the tool-name prefix (`<id>_<tool>`).
/// Must match ^[a-z][a-z0-9_]*$. Reserved: 'app'.
String get id;
String get description;
String get version;
Future<void> register(CapabilityContext context);
Future<void> dispose();
}
abstract interface class CapabilityContext {
CapabilityConfig get config;
void registerTool(ToolRegistration registration);
void registerResource(ResourceRegistration registration);
T require<T extends HostService>();
}
HostService is a marker interface for capabilities to request host machinery
(today: CommandRunner for the shared-core executor). Host services are
provided by the host at registration time; missing services throw
HostServiceUnavailableError.
Prefix enforcement (kernel-only)
All rules apply at registration time and throw on violation:
- Capability id must match
^[a-z][a-z0-9_]*$(InvalidCapabilityIdError). - Capability id must not be
app— reserved for unscoped dynamic registrations (InvalidCapabilityIdError). - Tool name passed to
registerToolmust not start with the capability's prefix (PrePrefixedToolNameError). Capabilities pass bare names; the kernel composes the public name. - Two static registrations resolving to the same fully-qualified name throw
ToolNameCollisionErrorat host startup. - A dynamic entry whose name collides with a static registration is rejected at the bridge with a structured error.
Hard cut at v3.0.0
No deprecation aliases. No soft errors. No --no-use-capability-kernel opt-out.
Reasoning: a capability rename is rare, and a clean tool_not_found is easier
to debug than a silent fallback. Migration tooling would have outlived the
migration window by years.
Consequences
What changed:
mcp_server_dart's static dep tree contains zero Flutter packages.uses-material-designwarnings in server builds disappeared.- All MCP tool names from the bundled
fmtcapability are prefixed:tap_widget→fmt_tap_widget,hot_reload_and_capture→fmt_hot_reload_and_capture, etc. Unprefixed names returntool_not_found. The locked surface is intool/contracts/expected_tool_surface.txt. - The dynamic-registry host trio is exposed as
fmt_list_client_tools_and_resources,fmt_client_tool, andfmt_client_resourceon MCP and in the CLI catalog, matching thefmt_naming used intools/list. - CLI catalog names remain bare for intrinsic fmt tools (
flutter-mcp-toolkit exec --name tap_widget, …); the MCP kernel still publishes those asfmt_tap_widget. Dynamic-registry commands are the exception: their catalog name already includes thefmt_prefix. mcp_server_dartandflutter-mcp-toolkitbecame thin shells. Their defaultmain.dartregisters the bundled capability set; power users write their ownmain.dartto compose a different set.
What we paid:
- Every existing client-side configuration referencing a tool by name had to be updated. v3.0.0 ships the breaking change without a shim.
- Future capability work (live-edit re-integration, network introspection) has to shape itself as a capability rather than as a server feature.
Non-goals
- Plugin-discovery / runtime capability loading. Capabilities register at compile time. A user wanting a different surface composes a different binary.
- Cross-process or remote capabilities.
- Per-capability semantic versioning beyond what the
versionfield surfaces indoctor.
Open follow-ups (optional v3.x)
- Capability-level configuration via a config file (today: CLI flags only).
- A
flutter-mcp-toolkit capabilities listsubcommand. DynamicRegistryBridgehost service interface — designed but not yet implemented; planned alongside live-edit re-integration.
Related work
- The post-v3.0 live-edit re-integration plan, which is the first new
capability beyond
core, is tracked intodo/live_edit_reintegration.md. - Network introspection (P3), the other deferred capability-shaped feature,
is specified in
todo/p3_network_introspection.md.