L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Développement · SKILL.md

flutter-mcp-toolkit-custom-tools

flutter-mcp-toolkit-custom-tools

Use this skill when the agent exposes app-specific surfaces by registering custom MCP tools and resources inside the Flutter app (mcp_toolkit dynamic registry — AgentCallEntry, addEntries). Covers tool vs resource vs evaluate-expression, Map-based handlers, schema strictness, discovery via fmt_list_client_tools_and_resources, fmt_client_tool, fmt_client_res…

Independent GitHub snapshot · Automated classification · Original documentation governs

Documentation de la Skill

GitHub

name: flutter-mcp-toolkit-custom-tools description: Use this skill when the agent exposes app-specific surfaces by registering custom MCP tools and resources inside the Flutter app (mcp_toolkit dynamic registry — AgentCallEntry, addEntries). Covers tool vs resource vs evaluate-expression, Map-based handlers, schema strictness, discovery via fmt_list_client_tools_and_resources, fmt_client_tool, fmt_client_resource, and lifecycle pitfalls.

Custom MCP Toolkit Tools & Resources (Dynamic Registry)

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.

Migration: The legacy call-entry type was removed in intentcall Phase 6b. Use AgentCallEntry or mcpToolkitTool / mcpToolkitResource. See flutter-mcp-toolkit-intentcall-migration.

Boundary: Change app discovery, Flutter VM-service extensions, or app-owned debug surfaces here. Change canonical registry/session semantics, schema policy, platform projection, or publish behavior in the upstream IntentCall repository.

Pick the right primitive

NeedUse
One-off read of a simple valuefmt_evaluate_dart_expression (no app code change).
Stable read-only payload (diagnostics, JSON snapshot, “current route”)AgentCallEntry.resource + fmt_client_resource. Prefer resources when the contract is “GET-like” and idempotent.
Parameterized or mutating action, or reusable named operationAgentCallEntry.tool + fmt_client_tool.

Handler signatures

Native AgentCallEntry (preferred for new code)

Handlers receive AgentArguments (Map<String, Object?>) and return AgentResult:

import 'package:mcp_toolkit/mcp_toolkit.dart';

final tool = AgentCallEntry.tool(
  namespace: 'app',
  name: 'cart_get_snapshot',
  description: 'Return current cart total and items for a user.',
  inputSchema: const {
    'type': 'object',
    'additionalProperties': false,
    'properties': {
      'userId': {'type': 'string'},
    },
    'required': ['userId'],
  },
  handler: (final args) async {
    final userId = args['userId']?.toString() ?? '';
    final cart = CartRepository.instance.forUser(userId);
    return AgentResult.success(
      message: 'ok',
      data: {
        'total': cart.total,
        'items': cart.items.map((final i) => i.toJson()).toList(),
      },
    );
  },
);

await MCPToolkitBinding.instance.addEntries(entries: {tool});

Legacy MCPToolDefinition + MCPCallResult (bridge)

Built-in toolkits still use mcpToolkitTool / mcpToolkitResource to adapt Map<String, String> service-extension handlers:

final tool = mcpToolkitTool(
  namespace: 'app',
  handler: (final request) async {
    final userId = request['userId'] ?? '';
    return MCPCallResult(
      message: 'ok',
      parameters: {'userId': userId},
    );
  },
  definition: MCPToolDefinition(
    name: 'cart_get_snapshot',
    description: 'Return current cart total and items for a user.',
    inputSchema: ObjectSchema(
      properties: {
        'userId': StringSchema(),
      },
      required: ['userId'],
    ),
  ),
);
  • Tool arguments on the wire are strings keyed by schema property names — parse with int.tryParse, jsonDecode, etc.
  • Do not use request.arguments on the app side.

Prefer MCPToolkitBinding.instance.addEntries(entries: { ... }) before bootstrapFlutter(runApp: ...) so tools/resources register in one place with zone/error setup.

Register once at bootstrap — not inside build, not per-widget initState.

Custom resources

final resource = AgentCallEntry.resource(
  namespace: 'app',
  name: 'app_cart_digest',
  description: 'Compact cart summary for agents (read-only).',
  mimeType: 'application/json',
  handler: (final args) async => AgentResult.success(
    message: 'Cart digest',
    data: {
      'itemCount': CartRepository.instance.visibleCount,
      'currency': CartRepository.instance.currencyCode,
    },
  ),
);

Or via bridge:

mcpToolkitResource(
  namespace: 'app',
  definition: MCPResourceDefinition(
    name: 'app_cart_digest',
    description: 'Compact cart summary for agents (read-only).',
    mimeType: 'application/json',
  ),
  handler: (final request) async => MCPCallResult(
    message: 'Cart digest',
    parameters: {'itemCount': 3},
  ),
),
  • name must be snake_case. Published resource URIs follow visual://localhost/... conventions; agents use fmt_client_resource and listings from fmt_list_client_tools_and_resources.

Schema rules (tools)

The MCP server enforces strict JSON Schema:

  • Prefer additionalProperties: false unless you intentionally accept arbitrary keys.
  • Mark required for anything the handler reads unconditionally.
  • AgentResult.data / MCPCallResult.parameters must be JSON-serializable.

Discovery from the agent side

  1. fmt_list_client_tools_and_resources — enumerate app-registered tools and resources.
  2. fmt_client_tool — invoke a dynamic tool by name with JSON args.
  3. fmt_client_resource — fetch a registered resource URI from the listing.

If something should appear but does not: confirm addEntries completed (await), then hot restart.

Lifecycle gotchas

  • Hot reload + addEntries from widget code → duplicate registrations. Register once in main() / bootstrap.
  • Debug mode only — release builds do not expose VM service extensions.
  • Naming: the app uses a flat global namespace. Prefix tools/resources (cart_, flags_, nav_). If a built-in entry already uses a name, the custom entry is skipped without warning; the built-in handles the call and the custom handler does not run.
  • Naming the running instance is MCPToolkitBinding.instance.setAppIdentity(label: 'Fleet app · staging'), called again whenever the name changes (sign-in, workspace switch). Discovery reads that label, and a tool of your own called app_identity is one of the collisions above.

When the agent authors surfaces for the user’s app

  1. Ensure mcp_toolkit is in pubspec.yaml.
  2. Add lib/mcp_tools/<domain>_surfaces.dart returning Set<AgentCallEntry> or calling addEntries once.
  3. Wire from addEntries(...) before bootstrapFlutter(...) — never from StatefulWidget lifecycle.
  4. Tight schemas; hot restart; then fmt_list_client_tools_and_resources before first client invoke.

Safety and scope

  • Treat handlers as powerful debug hooks: avoid secrets, unchecked IO.
  • Keep handlers thin: delegate to existing app services.

Common traps

  • Mixing AgentArguments with Map<String, String> — pick one API (native vs mcpToolkitTool).
  • Missing await on addEntries → race before discovery.
  • inputSchema out of sync with the handler → agents trust the schema; update both.

Related

  • flutter-mcp-toolkit-intentcall-migration — CLI migrator, breaking upgrade
  • flutter-mcp-toolkit-guide → inspect / control / debug skills
  • Repository ARCHITECTURE.md → “Dynamic Registry Architecture”
Skills enregistrées