Flutter Inspector Templates#
커스텀 인스펙터 도구 등록 및 디버깅 패턴 템플릿입니다.
Template A: Custom BLoC Inspector Tool#
BLoC 상태를 검사하는 커스텀 도구 등록
lib/debug/bloc_inspector_tool.dart#
import 'package:flutter/foundation.dart';
import 'package:mcp_toolkit/mcp_toolkit.dart';
/// BLoC 상태 검사 도구 등록
void registerBlocInspectorTools() {
if (!kDebugMode) return;
// 모든 BLoC 목록 조회
addMcpTool(MCPCallEntry.tool(
handler: (params) {
final blocs = _getAllRegisteredBlocs();
return MCPCallResult(
message: '${blocs.length}개의 BLoC이 등록되어 있습니다',
parameters: {
'blocs': blocs.map((b) => {
'name': b.runtimeType.toString(),
'state': b.state.runtimeType.toString(),
}).toList(),
},
);
},
definition: MCPToolDefinition(
name: 'bloc_get_all',
description: '모든 등록된 BLoC/Cubit 인스턴스 반환',
inputSchema: {'type': 'object', 'properties': {}},
),
));
// 특정 BLoC 상태 조회
addMcpTool(MCPCallEntry.tool(
handler: (params) {
final blocName = params['blocName'] as String?;
if (blocName == null) {
return MCPCallResult(
message: 'blocName 파라미터가 필요합니다',
parameters: {'error': true},
);
}
final bloc = _findBlocByName(blocName);
if (bloc == null) {
return MCPCallResult(
message: '$blocName을 찾을 수 없습니다',
parameters: {'error': true},
);
}
return MCPCallResult(
message: '$blocName 상태 조회 완료',
parameters: {
'blocName': blocName,
'state': _serializeState(bloc.state),
},
);
},
definition: MCPToolDefinition(
name: 'bloc_get_state',
description: '특정 BLoC의 현재 상태 반환',
inputSchema: {
'type': 'object',
'properties': {
'blocName': {
'type': 'string',
'description': 'BLoC 클래스 이름',
},
},
'required': ['blocName'],
},
),
));
}
Template B: Navigation Inspector Tool#
GoRouter 네비게이션 검사 도구
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';
/// 네비게이션 인스펙터 도구 등록
void registerNavInspectorTools(GoRouter router) {
if (!kDebugMode) return;
// 현재 라우트 조회
addMcpTool(MCPCallEntry.tool(
handler: (params) {
final config = router.routerDelegate.currentConfiguration;
final location = config.uri.toString();
return MCPCallResult(
message: '현재 라우트: $location',
parameters: {
'route': {
'path': location,
'pathParameters': config.pathParameters,
'queryParameters': config.uri.queryParameters,
},
},
);
},
definition: MCPToolDefinition(
name: 'nav_get_current_route',
description: '현재 활성화된 라우트 정보 반환',
inputSchema: {'type': 'object', 'properties': {}},
),
));
// 딥링크 테스트
addMcpTool(MCPCallEntry.tool(
handler: (params) {
final uri = params['uri'] as String?;
if (uri == null) {
return MCPCallResult(
message: 'uri 파라미터가 필요합니다',
parameters: {'error': true},
);
}
try {
final parsedUri = Uri.parse(uri);
final match = router.configuration.findMatch(parsedUri);
return MCPCallResult(
message: '딥링크 매칭 성공',
parameters: {
'uri': uri,
'matched': match != null,
'route': match?.route.path,
'params': match?.pathParameters,
},
);
} catch (e) {
return MCPCallResult(
message: '딥링크 파싱 실패: $e',
parameters: {'error': true},
);
}
},
definition: MCPToolDefinition(
name: 'nav_test_deep_link',
description: '딥링크 URI를 테스트하고 매칭되는 라우트 반환',
inputSchema: {
'type': 'object',
'properties': {
'uri': {
'type': 'string',
'description': '테스트할 딥링크 URI',
},
},
'required': ['uri'],
},
),
));
}
Template C: Network Inspector Tool#
HTTP 요청 로깅 도구
lib/debug/network_inspector_tool.dart#
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:mcp_toolkit/mcp_toolkit.dart';
/// 네트워크 로그 저장소
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 인터셉터
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,
});
}
}
/// 네트워크 인스펙터 도구 등록
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}개의 네트워크 로그',
parameters: {'logs': logs},
);
},
definition: MCPToolDefinition(
name: 'network_get_logs',
description: 'HTTP 요청/응답 로그 조회',
inputSchema: {
'type': 'object',
'properties': {
'limit': {'type': 'integer', 'description': '최대 개수'},
'status': {'type': 'string', 'description': '상태 필터 (2xx, 4xx, 5xx)'},
},
},
),
));
addMcpTool(MCPCallEntry.tool(
handler: (params) {
NetworkLogStore.clear();
return MCPCallResult(
message: '네트워크 로그가 초기화되었습니다',
parameters: {'success': true},
);
},
definition: MCPToolDefinition(
name: 'network_clear_logs',
description: '네트워크 로그 초기화',
inputSchema: {'type': 'object', 'properties': {}},
),
));
}
Template D: Image Cache Inspector Tool#
이미지 캐시 분석 도구
lib/debug/image_inspector_tool.dart#
import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart';
import 'package:mcp_toolkit/mcp_toolkit.dart';
/// 이미지 캐시 인스펙터 도구 등록
void registerImageInspectorTools() {
if (!kDebugMode) return;
addMcpTool(MCPCallEntry.tool(
handler: (params) {
final cache = PaintingBinding.instance.imageCache;
return MCPCallResult(
message: '이미지 캐시 통계',
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: '이미지 캐시 통계 반환',
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: '이미지 캐시가 정리되었습니다 (type: $type)',
parameters: {'success': true},
);
},
definition: MCPToolDefinition(
name: 'img_clear_cache',
description: '이미지 캐시 정리',
inputSchema: {
'type': 'object',
'properties': {
'type': {
'type': 'string',
'enum': ['all', 'live'],
'description': '정리 유형',
},
},
},
),
));
}
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#
폼 상태 검사 도구
lib/debug/form_inspector_tool.dart#
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:mcp_toolkit/mcp_toolkit.dart';
/// 폼 레지스트리 (앱에서 폼 등록 필요)
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;
}
/// 폼 인스펙터 도구 등록
void registerFormInspectorTools() {
if (!kDebugMode) return;
addMcpTool(MCPCallEntry.tool(
handler: (params) {
final forms = FormRegistry.all;
return MCPCallResult(
message: '${forms.length}개의 폼이 등록되어 있습니다',
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: '등록된 모든 폼 목록 반환',
inputSchema: {'type': 'object', 'properties': {}},
),
));
addMcpTool(MCPCallEntry.tool(
handler: (params) {
final formKey = params['formKey'] as String?;
if (formKey == null) {
return MCPCallResult(
message: 'formKey 파라미터가 필요합니다',
parameters: {'error': true},
);
}
final form = FormRegistry.get(formKey);
if (form == null) {
return MCPCallResult(
message: '$formKey 폼을 찾을 수 없습니다',
parameters: {'error': true},
);
}
final isValid = form.currentState?.validate() ?? false;
return MCPCallResult(
message: '$formKey 유효성 검사 완료',
parameters: {
'formKey': formKey,
'isValid': isValid,
},
);
},
definition: MCPToolDefinition(
name: 'form_validate',
description: '폼 유효성 검사 실행',
inputSchema: {
'type': 'object',
'properties': {
'formKey': {
'type': 'string',
'description': '폼 키',
},
},
'required': ['formKey'],
},
),
));
}
Template F: App Initialization#
모든 인스펙터 도구 초기화
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 초기화
MCPToolkitBinding.instance
..initialize()
..initializeFlutterToolkit();
// 디버그 모드에서만 인스펙터 도구 등록
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 → 도구 확인
2. runClientResource uri="visual://localhost/app/errors/latest"
3. runClientTool name="log_get_errors" args={"includeStackTrace": true}
4. runClientTool name="bloc_get_all" → 상태 확인
5. 원인 분석 후 수정
6. hot_reload_flutter → 변경 적용
Pattern 2: Network Debugging#
1. runClientTool name="network_get_logs" args={"status": "4xx"}
2. runClientTool name="auth_get_tokens" → 토큰 확인
3. runClientTool name="config_get_value" args={"key": "apiBaseUrl"}
4. 문제 수정
5. runClientTool name="network_clear_logs"
6. 재테스트
Pattern 3: UI Performance#
1. runClientResource uri="visual://localhost/view/screenshots"
2. runClientTool name="ui_find_overflow" → 오버플로우 확인
3. runClientTool name="img_get_cache_stats" → 이미지 캐시 확인
4. runClientTool name="img_analyze_warnings"
5. 최적화 적용
6. hot_reload_flutter
pubspec.yaml Configuration#
dependencies:
mcp_toolkit: ^0.1.0 # MCP toolkit for debug tools
dev_dependencies:
# 없음 - mcp_toolkit은 kDebugMode로 자동 분리