MCP Toolkit โ Cocode Dynamic Tool Templates#
Reference implementations for the cocode-standard dynamic tools listed in SKILL.md. Ship these in a shared internal package (e.g.
packages/dev_tooling/mcp_entries) so every app gets them by adding one import.
1. Binding bootstrap#
// lib/main_dev_tools.dart
import 'package:flutter/foundation.dart';
import 'package:mcp_toolkit/mcp_toolkit.dart';
import 'package:dev_tooling/mcp_entries.dart';
Future<void> registerCocodeMcpTools() async {
if (!kDebugMode) return;
final binding = MCPToolkitBinding.instance;
binding.initializeFlutterToolkit();
binding.addEntries(entries: cocodeMcpEntries);
}
Call registerCocodeMcpTools() early in main().
2. Cocode entry set#
// packages/dev_tooling/lib/mcp_entries.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:mcp_toolkit/mcp_toolkit.dart';
import 'package:my_app/router.dart';
import 'tools/bloc.dart';
import 'tools/db.dart';
import 'tools/auth.dart';
import 'tools/flags.dart';
import 'tools/network.dart';
import 'tools/locale.dart';
import 'tools/analytics.dart';
import 'tools/nav.dart';
final Iterable<MCPCallEntry> cocodeMcpEntries = [
...blocEntries,
...dbEntries,
...authEntries,
...flagsEntries,
...networkEntries,
...localeEntries,
...analyticsEntries,
...navEntries,
];
3. bloc.dump_state example#
// packages/dev_tooling/lib/tools/bloc.dart
import 'dart:convert';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:mcp_toolkit/mcp_toolkit.dart';
final blocEntries = <MCPCallEntry>[
MCPCallEntry.tool(
name: 'bloc.dump_state',
description: 'Returns JSON snapshot of one or all registered BLoCs.',
inputSchema: {
'type': 'object',
'properties': {
'type': {'type': 'string', 'description': 'BLoC runtimeType name; omit for all'},
},
},
handler: (args) async {
final typeFilter = args?['type'] as String?;
final snapshot = <String, Object?>{};
for (final entry in CocodeBlocRegistry.instance.all) {
if (typeFilter != null && entry.runtimeType.toString() != typeFilter) {
continue;
}
snapshot[entry.runtimeType.toString()] =
_redact(entry.state.toJson());
}
return MCPCallResult.text(jsonEncode(snapshot));
},
),
MCPCallEntry.tool(
name: 'bloc.add_event',
description: 'Dispatch an event into a registered BLoC.',
inputSchema: {
'type': 'object',
'required': ['type', 'event'],
'properties': {
'type': {'type': 'string'},
'event': {'type': 'object'},
},
},
handler: (args) async {
final typeName = args!['type'] as String;
final eventJson = args['event'] as Map<String, dynamic>;
final entry = CocodeBlocRegistry.instance.byTypeName(typeName);
if (entry == null) {
return MCPCallResult.error('No BLoC registered as $typeName');
}
entry.addJsonEvent(eventJson);
return MCPCallResult.text('dispatched');
},
),
];
Map<String, Object?> _redact(Map<String, Object?> input) {
// Centralise PII redaction here.
const piiKeys = {'email', 'phone', 'auth_token', 'access_token'};
return {
for (final entry in input.entries)
entry.key: piiKeys.contains(entry.key) ? '<redacted>' : entry.value,
};
}
Requires a small
CocodeBlocRegistryhelper that keeps weak refs to currently-instantiated BLoCs and serialises their state. Implementation is project-specific.
4. db.seed_fixture example#
// packages/dev_tooling/lib/tools/db.dart
import 'package:get_it/get_it.dart';
import 'package:mcp_toolkit/mcp_toolkit.dart';
import 'package:my_app/data/local/app_database.dart';
import 'package:my_app/data/local/fixtures.dart';
final dbEntries = <MCPCallEntry>[
MCPCallEntry.tool(
name: 'db.seed_fixture',
description: 'Seed the embedded Drift database with a named fixture.',
inputSchema: {
'type': 'object',
'required': ['name'],
'properties': {
'name': {
'type': 'string',
'enum': fixtureRegistry.keys.toList(),
},
},
},
handler: (args) async {
final name = args!['name'] as String;
final fixture = fixtureRegistry[name];
if (fixture == null) {
return MCPCallResult.error('Unknown fixture: $name');
}
final db = GetIt.I<AppDatabase>();
await fixture.apply(db);
return MCPCallResult.text('seeded $name (${fixture.recordCount} rows)');
},
),
MCPCallEntry.tool(
name: 'db.reset',
description: 'Drop all data and reapply default seed.',
handler: (_) async {
final db = GetIt.I<AppDatabase>();
await db.resetForTesting();
await defaultSeed.apply(db);
return MCPCallResult.text('reset complete');
},
),
];
5. nav.go_named example#
// packages/dev_tooling/lib/tools/nav.dart
import 'package:get_it/get_it.dart';
import 'package:go_router/go_router.dart';
import 'package:mcp_toolkit/mcp_toolkit.dart';
import 'package:my_app/router.dart';
final navEntries = <MCPCallEntry>[
MCPCallEntry.tool(
name: 'nav.go_named',
description: 'Navigate via GoRouter by route name.',
inputSchema: {
'type': 'object',
'required': ['name'],
'properties': {
'name': {'type': 'string'},
'pathParameters': {'type': 'object'},
'queryParameters': {'type': 'object'},
},
},
handler: (args) async {
final router = GetIt.I<GoRouter>();
router.goNamed(
args!['name'] as String,
pathParameters: Map<String, String>.from(args['pathParameters'] ?? {}),
queryParameters: Map<String, dynamic>.from(args['queryParameters'] ?? {}),
);
return MCPCallResult.text('navigated');
},
),
];
6. network.offline toggle#
// packages/dev_tooling/lib/tools/network.dart
import 'package:dio/dio.dart';
import 'package:get_it/get_it.dart';
import 'package:mcp_toolkit/mcp_toolkit.dart';
final networkEntries = <MCPCallEntry>[
MCPCallEntry.tool(
name: 'network.offline',
description: 'Toggle a global interceptor that fails every HTTP request.',
inputSchema: {
'type': 'object',
'required': ['value'],
'properties': {'value': {'type': 'boolean'}},
},
handler: (args) async {
final on = args!['value'] as bool;
final dio = GetIt.I<Dio>();
dio.interceptors.removeWhere((i) => i is OfflineSimulationInterceptor);
if (on) {
dio.interceptors.add(OfflineSimulationInterceptor());
}
return MCPCallResult.text('offline=$on');
},
),
];
class OfflineSimulationInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
handler.reject(
DioException(
requestOptions: options,
type: DioExceptionType.connectionError,
message: 'offline simulation',
),
);
}
}
7. .mcp.json (per-project)#
{
" mcpServers " : {
" mcp_flutter " : {
" command " : " mcp_server_dart " ,
" args " : [ " --images " ] // enable screenshot tool
}
}
}
8. Smoke prompt#
Use mcp_toolkit dynamic tools to:
1. db.reset
2. db.seed_fixture { name: " feed_with_10 " }
3. nav.go_named { name: " feed " }
4. bloc.dump_state { type: " FeedBloc " }
Then call cc-flutter-pixel-loop:capture --route=/feed --label=feed-after-seed.
Confirm bloc.state.posts.length == 10 and the screenshot shows 10 cards.