Skip to content
Debugging
Skill

/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,

BOOST
From plugin
mcp-flutter
38922 skills1 agent
Install
$ npx -y skills add Arenukvern/mcp_flutter --skill flutter-mcp-toolkit-custom-tools --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/flutter-mcp-toolkit-custom-tools

Context preview

The summary Claude sees to decide when to auto-load this skill.

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,

SKILL.md

flutter-mcp-toolkit-custom-tools.SKILL.md
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.

<!-- @FMT_MODE_PRELUDE -->

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

| 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”) | **`AgentCallEntry.resource`** + **`fmt_client_resource`**. Prefer resources when the contract is “GET-like” and idempotent. | | Parameterized or mutating action, or reusable named operation | **`AgentCallEntry.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 skippe
Read more
Ships withmcp-flutter

Inspect and drive a running Flutter app from your AI assistant. 📖 Docs: docs.page/arenukvern/mcp_flutter 🤝 Contribute: guide · contributors · code of conduct · security flutter-mcp-toolkit is a Dart MCP server + Flutter package for AI agents (Codex, Zed,

Get the whole plugin

Other skills on mcp-flutter.