From flutter-mcp-toolkit
Registers custom MCP tools and resources in Flutter apps via mcp_toolkit dynamic registry. Guides MCPCallEntry usage, tool vs resource choice, Map handlers, schemas, discovery, and lifecycle pitfalls.
npx claudepluginhub arenukvern/mcp_flutter --plugin flutter-mcp-toolkitThis skill uses the workspace's default tool permissions.
<!-- @FMT_MODE_PRELUDE -->
Entry point for AI-driven inspection, control, debugging, and custom tooling of running Flutter apps; performs preflight check and routes to task-specific skills.
Configures Flutter Driver extension for app interaction and converts MCP actions into permanent integration tests using integration_test package. Use for adding integration testing, UI exploration via MCP, or automating user flows.
Guides adding WebMCP to web applications for AI accessibility, LLM UI tools, and MCP browser automation. Covers design principles, tool architecture, and testing workflows.
Share bugs, ideas, or general feedback.
Use this when bundled MCP tools (screenshot, semantic snapshot, tap, …) are not enough and you need app-specific read surfaces or actions — e.g. cart totals, feature flags, curated debug snapshots of internal state. Entries are registered in the Flutter process and exposed to the agent through the dynamic registry.
| Need | Use |
|---|---|
| One-off read of a simple value | fmt_evaluate_dart_expression (no app code change). |
| Stable read-only payload (diagnostics, JSON snapshot, “current route”) | MCPCallEntry.resource + fmt_client_resource. Prefer resources when the contract is “GET-like” and idempotent. |
| Parameterized or mutating action, or reusable named operation | MCPCallEntry.tool + fmt_client_tool. |
MCPCallHandler is FutureOr<MCPCallResult> Function(ServiceExtensionRequestMap request) where ServiceExtensionRequestMap is Map<String, String>.
request['n'], request['userId'], then parse (int.tryParse, double.tryParse, jsonDecode for nested blobs if the wire format sends JSON-as-string).request.arguments — that is not the app-side API.import 'package:mcp_toolkit/mcp_toolkit.dart';
final tool = MCPCallEntry.tool(
handler: (request) async {
final userId = request['userId'] ?? '';
final cart = CartRepository.instance.forUser(userId);
return MCPCallResult(
message: 'ok',
parameters: {
'total': cart.total,
'items': cart.items.map((i) => i.toJson()).toList(),
},
);
},
definition: MCPToolDefinition(
name: 'cart_get_snapshot',
description: 'Return current cart total and items for a user.',
inputSchema: {
'type': 'object',
'additionalProperties': false,
'properties': {
'userId': {'type': 'string'},
},
'required': ['userId'],
},
),
);
await MCPToolkitBinding.instance.addEntries(entries: {tool});
Prefer MCPToolkitBinding.instance.bootstrapFlutter(additionalEntries: { ... }, runApp: ...) so tools/resources register in one place with zone/error setup — same entries shape as above.
Register after initialize() / bootstrapFlutter wiring, once at bootstrap — not inside build, not per-widget initState.
Resources are for read-only MCP surfaces: diagnostics, config summaries, or JSON blobs the agent polls without treating them as imperative actions.
MCPCallEntry.resource(
definition: MCPResourceDefinition(
name: 'app_cart_digest',
description: 'Compact cart summary for agents (read-only).',
mimeType: 'application/json',
),
handler: (request) async => MCPCallResult(
message: 'Cart digest',
parameters: {
'itemCount': CartRepository.instance.visibleCount,
'currency': CartRepository.instance.currencyCode,
},
),
),
name must be snake_case (letters, digits, underscores). resourceUri maps it to a visual://localhost/... URI (underscore segments become path segments). Agents consume it via fmt_client_resource using that URI / listing from fmt_list_client_tools_and_resources.mimeType honestly (application/json vs text/plain) so clients know how to interpret payloads.The MCP server enforces strict JSON Schema:
additionalProperties: false unless you intentionally accept arbitrary keys. Unknown keys fail validation — good for catching agent typos.required for anything the handler reads unconditionally.enum over unconstrained strings.parameters in MCPCallResult must be JSON-serializable; non-serializable objects degrade to toString().fmt_list_client_tools_and_resources — enumerate app-registered tools and resources.fmt_client_tool — invoke a tool by name with JSON args (CLI: flutter-mcp-toolkit exec --name fmt_client_tool --args '...' per your transport).fmt_client_resource — fetch a registered resource (URI from listing / resourceUri convention).If something should appear but does not: confirm addEntries completed (await), then hot restart — reload does not always replay discovery cleanly.
addEntries from widget code → duplicate registrations. Register once in main() / bootstrap, not in build.bootstrapFlutter / main run again on boot — correct pattern survives restart.cart_, flags_, nav_) to avoid collisions with builtins or other domains.mcp_toolkit is in pubspec.yaml.lib/mcp_tools/<domain>_surfaces.dart exporting registerXSurfaces() that returns Set<MCPCallEntry> or performs addEntries once.registerXSurfaces() from bootstrapFlutter(..., additionalEntries: ...) or call addEntries immediately after initializeFlutterToolkit inside bootstrapFlutter’s chain — never from StatefulWidget lifecycle.additionalProperties: false, explicit required).fmt_list_client_tools_and_resources before first fmt_client_tool / fmt_client_resource call.request.arguments — wrong shape; use request['key'] on Map<String, String>.await on addEntries → race before discovery lists your surface.Future instances inside parameters → useless serialization; await inside the handler.inputSchema out of sync with the handler → agents trust the schema; update both.flutter-mcp-toolkit-guide → flutter-mcp-toolkit-inspect / flutter-mcp-toolkit-control.ARCHITECTURE.md → “Dynamic Registry Architecture”.