flutter-mcp-toolkit-custom-tools
- Repo stars 0
- Author repo skills-registry
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.
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”) | 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. |
Handler signature (tools and resources)
MCPCallHandler is FutureOr<MCPCallResult> Function(ServiceExtensionRequestMap request) where ServiceExtensionRequestMap is Map<String, String>.
- Tool arguments arrive as string values keyed by schema property names — mirror the README pattern:
request['n'],request['userId'], then parse (int.tryParse,double.tryParse,jsonDecodefor nested blobs if the wire format sends JSON-as-string). - Do not use
request.arguments— that is not the app-side API.
Minimal tool registration
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.
Custom resources
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,
},
),
),
namemust besnake_case(letters, digits, underscores).resourceUrimaps it to avisual://localhost/...URI (underscore segments become path segments). Agents consume it viafmt_client_resourceusing that URI / listing fromfmt_list_client_tools_and_resources.- Set
mimeTypehonestly (application/jsonvstext/plain) so clients know how to interpret payloads.
Schema rules (tools)
The MCP server enforces strict JSON Schema:
- Prefer
additionalProperties: falseunless you intentionally accept arbitrary keys. Unknown keys fail validation — good for catching agent typos. - Mark
requiredfor anything the handler reads unconditionally. - Prefer primitives and
enumover unconstrained strings. parametersinMCPCallResultmust be JSON-serializable; non-serializable objects degrade totoString().
Discovery from the agent side
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 /resourceUriconvention).
If something should appear but does not: confirm addEntries completed (await), then hot restart — reload does not always replay discovery cleanly.
Lifecycle gotchas
- Hot reload +
addEntriesfrom widget code → duplicate registrations. Register once inmain()/ bootstrap, not inbuild. - Hot restart clears VM state; registrations tied to
bootstrapFlutter/mainrun again on boot — correct pattern survives restart. - Debug mode only — release builds do not expose these VM service extensions.
- Naming: flat global namespace per app — prefix tools/resources (
cart_,flags_,nav_) to avoid collisions with builtins or other domains.
When the agent authors surfaces for the user’s app
- Ensure
mcp_toolkitis inpubspec.yaml. - Add
lib/mcp_tools/<domain>_surfaces.dartexportingregisterXSurfaces()that returnsSet<MCPCallEntry>or performsaddEntriesonce. - Wire
registerXSurfaces()frombootstrapFlutter(..., additionalEntries: ...)or calladdEntriesimmediately afterinitializeFlutterToolkitinsidebootstrapFlutter’s chain — never fromStatefulWidgetlifecycle. - Tight schemas (
additionalProperties: false, explicitrequired). - Hot restart, then
fmt_list_client_tools_and_resourcesbefore firstfmt_client_tool/fmt_client_resourcecall.
Safety and scope
- Treat handlers as powerful debug hooks: avoid exposing secrets, full databases, or unchecked filesystem/network IO.
- Keep handlers thin: delegate to domain/services already used by the app (same DI/getters), don’t duplicate business logic in MCP-only paths unless intentional.
Common traps
request.arguments— wrong shape; userequest['key']onMap<String, String>.- Missing
awaitonaddEntries→ race before discovery lists your surface. - Returning
Futureinstances insideparameters→ useless serialization;awaitinside the handler. inputSchemaout of sync with the handler → agents trust the schema; update both.
Related
- Driving the live app (snapshot / tap / reload):
flutter-mcp-toolkit-guide→flutter-mcp-toolkit-inspect/flutter-mcp-toolkit-control. - Repository
ARCHITECTURE.md→ “Dynamic Registry Architecture”.
<!-- tomevault:4.0:skill_md:2026-05-22 -->Source: Arenukvern/mcp_flutter — distributed by TomeVault.
- Fluxly category
- AI
- Author-declared agents
- No explicit declaration found; this is not inferred or tested compatibility
- Static check
- 88 / 100 · heuristic scan, not runtime safety proof
- Author / version / license
- @tomevault-io · no license declared
- Fluxly token estimate
- Lean
- Fluxly setup estimate
- Guided setup
- External API key
- No requirement detected
- Detected OS requirements
- Unspecified
- Runtime requirements
- Unspecified
- Detected file/system behavior
-
- Read-only
- Write / modify
- Shell exec
- Detected network behavior
- External requests
- Install commands
- None (reference only)
Profile is derived at build time from SKILL.md and install vectors. Subject to drift from author intent.
Heads up: 未限定 allowed-tools,默认拥有全部工具权限。
The current SKILL.md does not define a fixed output example. Need · Use One-off read of a simple value · fmtevaluatedartexpression (no app code change). Stable read-only payload (diagnostics, JSON snapshot, “current route”) · MCPCallEntry.resource + fmtclientresource. Prefer resources when the contract is “GET-like” and…
MCPCallHandler is FutureOr<MCPCallResult> Function(ServiceExtensionRequestMap request) where ServiceExtensionRequestMap is Map<String, String>. Tool arguments arrive as string values keyed by schema property names — mirror the README pattern: request['n'],…
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 —…
Resources are for read-only MCP surfaces: diagnostics, config summaries, or JSON blobs the agent polls without treating them as imperative actions. name must be snakecase (letters, digits, underscores). resourceUri maps it to a visual://localhost/... URI…
The MCP server enforces strict JSON Schema: Prefer additionalProperties: false unless you intentionally accept arbitrary keys. Unknown keys fail validation — good for catching agent typos. Mark required for anything the handler reads unconditionally.
fmtlistclienttoolsandresources — enumerate app-registered tools and resources. fmtclienttool — invoke a tool by name with JSON args (CLI: flutter-mcp-toolkit exec --name fmtclienttool --args '...' per your transport). fmtclientresource — fetch a registered…
<!-- @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**.
## 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”) | **`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`**. |
## Handler signature (tools and resources)
[`MCPCallHandler`](https://github.com/Arenukvern/mcp_flutter/blob/main/mcp_toolkit/lib/src/mcp_models.dart) is `FutureOr<MCPCallResult> Function(ServiceExtensionRequestMap request)` where **`ServiceExtensionRequestMap` is `Map<String, String>`**.
- Tool arguments arrive as **string values** keyed by schema property names — mirror the README pattern: `request['n']`, `request['userId']`, then parse (`int.tryParse`, `double.tryParse`, `jsonDecode` for nested blobs if the wire format sends JSON-as-string).
- Do **not** use `request.arguments` — that is not the app-side API.
## Minimal tool registration
```dart
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(
… Author text anchors workflow facts; Fluxly only indexes current sections, terms, files, and commands.
sections -> Pick the right primitive → Handler signature (tools and resources) → Minimal tool registration → Custom resources → Schema rules (tools) → Discovery from the agent side
terms -> app-specific · in the Flutter process · dynamic registry · fmtevaluatedartexpression · read-only · MCPCallEntry.resource · fmtclientresource · MCPCallEntry.tool
files/cmd -> fmtevaluatedartexpression · MCPCallEntry.resource · fmtclientresource · MCPCallEntry.tool · fmtclienttool · MCPCallHandler · FutureOr<MCPCallResult> Function(ServiceExtensionRequestMap request) · ServiceExtensionRequestMap
body sha256 -> ebe00b8b1a8f
Decide Fit First
Design Intent
How To Use It
Boundaries And Review