Flutter Inspector Templates#
Templates for custom inspector tool registration and debugging patterns.
Template A: Custom BLoC Inspector Tool#
Register a custom tool for inspecting BLoC state
lib/debug/bloc_inspector_tool.dart#
import 'package:flutter/foundation.dart';
import 'package:mcp_toolkit/mcp_toolkit.dart';
/// Register BLoC state inspection tools
void registerBlocInspectorTools() {
if (!kDebugMode) return;
// Query all BLoC list
addMcpTool(MCPCallEntry.tool(
handler: (params) {
final blocs = _getAllRegisteredBlocs();
return MCPCallResult(
message: '${blocs.length} BLoCs are registered',
parameters: {
'blocs': blocs.map((b) => {
'name': b.runtimeType.toString(),
'state': b.state.runtimeType.toString(),
}).toList(),
},
);
},
definition: MCPToolDefinition(
name: 'bloc_get_all',
description: 'Return all registered BLoC/Cubit instances',
inputSchema: {'type': 'object', 'properties': {}},
),
));
// Query specific BLoC state
addMcpTool(MCPCallEntry.tool(
handler: (params) {
final blocName = params['blocName'] as String?;
if (blocName == null) {
return MCPCallResult(
message: 'blocName parameter is required',
parameters: {'error': true},
);
}
final bloc = _findBlocByName(blocName);
if (bloc == null) {
return MCPCallResult(
message: 'Cannot find $blocName',
parameters: {'error': true},
);
}
return MCPCallResult(
message: '$blocName state query complete',
parameters: {
'blocName': blocName,
'state': _serializeState(bloc.state),
},
);
},
definition: MCPToolDefinition(
name: 'bloc_get_state',
description: 'Return current state of a specific BLoC',
inputSchema: {
'type': 'object',
'properties': {
'blocName': {
'type': 'string',
'description': 'BLoC class name',
},
},
'required': ['blocName'],
},
),
));
}
Template B: Navigation Inspector Tool#
GoRouter navigation inspection tool
lib/debug/nav_inspector_tool.dart#
import 'package:flutter/foundation.dart';
import 'package:go_router/go_router.dart';
import 'package:mcp_toolkit/mcp_toolkit.dart';
/// Register navigation inspector tools
void registerNavInspectorTools(GoRouter router) {
if (!kDebugMode) return;
// Query current route
addMcpTool(MCPCallEntry.tool(
handler: (params) {
final config = router.routerDelegate.currentConfiguration;
final location = config.uri.toString();
return MCPCallResult(
message: 'Current route: $location',
parameters: {
'route': {
'path': location,
'pathParameters': config.pathParameters,
'queryParameters': config.uri.queryParameters,
},
},
);
},
definition: MCPToolDefinition(
name: 'nav_get_current_route',
description: 'Return currently active route info',
inputSchema: {'type': 'object', 'properties': {}},
),
));
// Deep link test
addMcpTool(MCPCallEntry.tool(
handler: (params) {
final uri = params['uri'] as String?;
if (uri == null) {
return MCPCallResult(
message: 'uri parameter is required',
parameters: {'error': true},
);
}
try {
final parsedUri = Uri.parse(uri);
final match = router.configuration.findMatch(parsedUri);
return MCPCallResult(
message: 'Deep link matching successful',
parameters: {
'uri': uri,
'matched': match != null,
'route': match?.route.path,
'params': match?.pathParameters,
},
);
} catch (e) {
return MCPCallResult(
message: 'Deep link parsing failed: $e',
parameters: {'error': true},
);
}
},
definition: MCPToolDefinition(
name: 'nav_test_deep_link',
description: 'Test a deep link URI and return matching route',
inputSchema: {
'type': 'object',
'properties': {
'uri': {
'type': 'string',
'description': 'Deep link URI to test',
},
},
'required': ['uri'],
},
),
));
}
Template C: Network Inspector Tool#
HTTP request logging tool
lib/debug/network_inspector_tool.dart#
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:mcp_toolkit/mcp_toolkit.dart';
/// Network log store
class NetworkLogStore {
static final List<Map<String, dynamic>> _logs = [];
static const int maxLogs = 100;
static void addLog(Map<String, dynamic> log) {
_logs.add(log);
if (_logs.length > maxLogs) {
_logs.removeAt(0);
}
}
static List<Map<String, dynamic>> getLogs({
int? limit,
String? statusFilter,
}) {
var result = _logs.reversed.toList();
if (statusFilter != null) {
result = result.where((log) {
final status = log['status'] as int?;
if (status == null) return false;
return status.toString().startsWith(statusFilter[0]);
}).toList();
}
if (limit != null) {
result = result.take(limit).toList();
}
return result;
}
static void clear() => _logs.clear();
}
/// Dio interceptor
class NetworkInspectorInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
options.extra['startTime'] = DateTime.now();
handler.next(options);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
_logRequest(response.requestOptions, response.statusCode, response.data);
handler.next(response);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
_logRequest(err.requestOptions, err.response?.statusCode, err.message);
handler.next(err);
}
void _logRequest(RequestOptions options, int? status, dynamic data) {
final startTime = options.extra['startTime'] as DateTime?;
final duration = startTime != null
? DateTime.now().difference(startTime).inMilliseconds
: null;
NetworkLogStore.addLog({
'timestamp': DateTime.now().toIso8601String(),
'method': options.method,
'url': options.uri.toString(),
'status': status,
'duration': duration,
});
}
}
/// Register network inspector tools
void registerNetworkInspectorTools() {
if (!kDebugMode) return;
addMcpTool(MCPCallEntry.tool(
handler: (params) {
final limit = params['limit'] as int? ?? 50;
final status = params['status'] as String?;
final logs = NetworkLogStore.getLogs(limit: limit, statusFilter: status);
return MCPCallResult(
message: '${logs.length} network logs',
parameters: {'logs': logs},
);
},
definition: MCPToolDefinition(
name: 'network_get_logs',
description: 'HTTP request/response log query',
inputSchema: {
'type': 'object',
'properties': {
'limit': {'type': 'integer', 'description': 'Maximum count'},
'status': {'type': 'string', 'description': 'Status filter (2xx, 4xx, 5xx)'},
},
},
),
));
addMcpTool(MCPCallEntry.tool(
handler: (params) {
NetworkLogStore.clear();
return MCPCallResult(
message: 'Network logs have been cleared',
parameters: {'success': true},
);
},
definition: MCPToolDefinition(
name: 'network_clear_logs',
description: 'Clear network logs',
inputSchema: {'type': 'object', 'properties': {}},
),
));
}
Template D: Image Cache Inspector Tool#
Image cache analysis tool
lib/debug/image_inspector_tool.dart#
import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart';
import 'package:mcp_toolkit/mcp_toolkit.dart';
/// Register image cache inspector tools
void registerImageInspectorTools() {
if (!kDebugMode) return;
addMcpTool(MCPCallEntry.tool(
handler: (params) {
final cache = PaintingBinding.instance.imageCache;
return MCPCallResult(
message: 'Image cache statistics',
parameters: {
'cache': {
'currentSize': cache.currentSize,
'currentSizeFormatted': _formatBytes(cache.currentSize),
'maximumSize': cache.maximumSize,
'maximumSizeFormatted': _formatBytes(cache.maximumSize),
'usagePercent': (cache.currentSize / cache.maximumSize * 100).round(),
'liveImageCount': cache.liveImageCount,
'pendingImageCount': cache.pendingImageCount,
},
},
);
},
definition: MCPToolDefinition(
name: 'img_get_cache_stats',
description: 'Return image cache statistics',
inputSchema: {'type': 'object', 'properties': {}},
),
));
addMcpTool(MCPCallEntry.tool(
handler: (params) {
final type = params['type'] as String? ?? 'all';
final cache = PaintingBinding.instance.imageCache;
switch (type) {
case 'all':
cache.clear();
break;
case 'live':
cache.clearLiveImages();
break;
}
return MCPCallResult(
message: 'Image cache has been cleared (type: $type)',
parameters: {'success': true},
);
},
definition: MCPToolDefinition(
name: 'img_clear_cache',
description: 'Clear image cache',
inputSchema: {
'type': 'object',
'properties': {
'type': {
'type': 'string',
'enum': ['all', 'live'],
'description': 'Clear type',
},
},
},
),
));
}
String _formatBytes(int bytes) {
if (bytes < 1024) return '$bytes B';
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
}
Template E: Form Inspector Tool#
Form state inspection tool
lib/debug/form_inspector_tool.dart#
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:mcp_toolkit/mcp_toolkit.dart';
/// Form registry (app must register forms)
class FormRegistry {
static final Map<String, GlobalKey<FormState>> _forms = {};
static void register(String key, GlobalKey<FormState> formKey) {
_forms[key] = formKey;
}
static void unregister(String key) {
_forms.remove(key);
}
static GlobalKey<FormState>? get(String key) => _forms[key];
static Map<String, GlobalKey<FormState>> get all => _forms;
}
/// Register form inspector tools
void registerFormInspectorTools() {
if (!kDebugMode) return;
addMcpTool(MCPCallEntry.tool(
handler: (params) {
final forms = FormRegistry.all;
return MCPCallResult(
message: '${forms.length} forms are registered',
parameters: {
'forms': forms.entries.map((e) {
final state = e.value.currentState;
return {
'key': e.key,
'isValid': state?.validate() ?? false,
};
}).toList(),
},
);
},
definition: MCPToolDefinition(
name: 'form_list',
description: 'Return all registered form list',
inputSchema: {'type': 'object', 'properties': {}},
),
));
addMcpTool(MCPCallEntry.tool(
handler: (params) {
final formKey = params['formKey'] as String?;
if (formKey == null) {
return MCPCallResult(
message: 'formKey parameter is required',
parameters: {'error': true},
);
}
final form = FormRegistry.get(formKey);
if (form == null) {
return MCPCallResult(
message: 'Cannot find form $formKey',
parameters: {'error': true},
);
}
final isValid = form.currentState?.validate() ?? false;
return MCPCallResult(
message: '$formKey validation complete',
parameters: {
'formKey': formKey,
'isValid': isValid,
},
);
},
definition: MCPToolDefinition(
name: 'form_validate',
description: 'Run form validation',
inputSchema: {
'type': 'object',
'properties': {
'formKey': {
'type': 'string',
'description': 'Form key',
},
},
'required': ['formKey'],
},
),
));
}
Template F: App Initialization#
Initialize all inspector tools
lib/main.dart#
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:mcp_toolkit/mcp_toolkit.dart';
import 'debug/bloc_inspector_tool.dart';
import 'debug/form_inspector_tool.dart';
import 'debug/image_inspector_tool.dart';
import 'debug/nav_inspector_tool.dart';
import 'debug/network_inspector_tool.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
// MCP Toolkit initialization
MCPToolkitBinding.instance
..initialize()
..initializeFlutterToolkit();
// Register inspector tools only in debug mode
if (kDebugMode) {
registerBlocInspectorTools();
registerNavInspectorTools(appRouter);
registerNetworkInspectorTools();
registerImageInspectorTools();
registerFormInspectorTools();
debugPrint('Flutter Inspector tools registered');
}
runApp(const MyApp());
}
Debugging Workflow Patterns#
Pattern 1: Crash Investigation#
1. listClientToolsAndResources - > Check tools
2. runClientResource uri= " visual://localhost/app/errors/latest "
3. runClientTool name= " log_get_errors " args={ " includeStackTrace " : true}
4. runClientTool name= " bloc_get_all " - > Check state
5. Analyze cause and fix
6. hot_reload_flutter - > Apply changes
Pattern 2: Network Debugging#
1. runClientTool name= " network_get_logs " args={ " status " : " 4xx " }
2. runClientTool name= " auth_get_tokens " - > Check tokens
3. runClientTool name= " config_get_value " args={ " key " : " apiBaseUrl " }
4. Fix the issue
5. runClientTool name= " network_clear_logs "
6. Re-test
Pattern 3: UI Performance#
1. runClientResource uri= " visual://localhost/view/screenshots "
2. runClientTool name= " ui_find_overflow " - > Check overflow
3. runClientTool name= " img_get_cache_stats " - > Check image cache
4. runClientTool name= " img_analyze_warnings "
5. Apply optimizations
6. hot_reload_flutter
pubspec.yaml Configuration#
dependencies:
mcp_toolkit: ^0.1.0 # MCP toolkit for debug tools
dev_dependencies:
# None - mcp_toolkit auto-separates via kDebugMode