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.
How this skill is triggered — by the user, by Claude, or both
Slash command
/flutter-mcp-toolkit:flutter-mcp-toolkit-custom-toolsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
<!-- @FMT_MODE_PRELUDE -->
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”.npx claudepluginhub arenukvern/mcp_flutter --plugin flutter-mcp-toolkitEntry point for AI-driven inspection, control, debugging, and custom tooling of running Flutter apps; performs preflight check and routes to task-specific skills.
Scaffolds an MCP App tool and UI resource pair for interactive tool UIs. Use when you need to build a visual/interactive tool that a human will actively interact with in real time.
Exposes API Platform resources as MCP tools and resources for AI agents. Use when building LLM-accessible APIs via Model Context Protocol.