diff --git a/pkgs/dart_mcp_server/CHANGELOG.md b/pkgs/dart_mcp_server/CHANGELOG.md index 3802d73..fb0318f 100644 --- a/pkgs/dart_mcp_server/CHANGELOG.md +++ b/pkgs/dart_mcp_server/CHANGELOG.md @@ -18,6 +18,7 @@ * Fix a bug in hot_reload ([#290](https://github.com/dart-lang/ai/issues/290)). * Add the `list_devices`, `launch_app`, `get_app_logs`, and `list_running_apps` tools for running Flutter apps. +* Add the `hot_restart` tool for restarting running Flutter apps. # 0.1.0 (Dart SDK 3.9.0) diff --git a/pkgs/dart_mcp_server/README.md b/pkgs/dart_mcp_server/README.md index 6759d68..8431c3e 100644 --- a/pkgs/dart_mcp_server/README.md +++ b/pkgs/dart_mcp_server/README.md @@ -147,7 +147,8 @@ For more information, see the official VS Code documentation for | `get_runtime_errors` | Get runtime errors | Retrieves the most recent runtime errors that have occurred in the active Dart or Flutter application. Requires "connect_dart_tooling_daemon" to be successfully called first. | | `get_selected_widget` | Get selected widget | Retrieves the selected widget from the active Flutter application. Requires "connect_dart_tooling_daemon" to be successfully called first. | | `get_widget_tree` | Get widget tree | Retrieves the widget tree from the active Flutter application. Requires "connect_dart_tooling_daemon" to be successfully called first. | -| `hot_reload` | Hot reload | Performs a hot reload of the active Flutter application. This is to apply the latest code changes to the running application. Requires "connect_dart_tooling_daemon" to be successfully called first. | +| `hot_reload` | Hot reload | Performs a hot reload of the active Flutter application. This will apply the latest code changes to the running application, while maintaining application state. Reload will not update const definitions of global values. Requires "connect_dart_tooling_daemon" to be successfully called first. | +| `hot_restart` | Hot restart | Performs a hot restart of the active Flutter application. This applies the latest code changes to the running application, including changes to global const values, while resetting application state. Requires "connect_dart_tooling_daemon" to be successfully called first. Doesn't work for Non-Flutter Dart CLI programs. | | `hover` | Hover information | Get hover information at a given cursor position in a file. This can include documentation, type information, etc for the text at that position. | | `launch_app` | | Launches a Flutter application and returns its DTD URI. | | `list_devices` | | Lists available Flutter devices. | diff --git a/pkgs/dart_mcp_server/lib/src/mixins/dtd.dart b/pkgs/dart_mcp_server/lib/src/mixins/dtd.dart index 693823b..f808fc5 100644 --- a/pkgs/dart_mcp_server/lib/src/mixins/dtd.dart +++ b/pkgs/dart_mcp_server/lib/src/mixins/dtd.dart @@ -157,6 +157,7 @@ base mixin DartToolingDaemonSupport // Flutter app that does not support the operation, e.g. hot reload is not // supported in profile mode). if (enableScreenshots) registerTool(screenshotTool, takeScreenshot); + registerTool(hotRestartTool, hotRestart); registerTool(hotReloadTool, hotReload); registerTool(getWidgetTreeTool, widgetTree); registerTool(getSelectedWidgetTool, selectedWidget); @@ -351,6 +352,51 @@ base mixin DartToolingDaemonSupport ); } + /// Performs a hot restart on the currently running app. + /// + /// If more than one debug session is active, then it just uses the first + /// one. + // TODO: support passing a debug session id when there is more than one + // debug session. + Future hotRestart(CallToolRequest request) async { + return _callOnVmService( + callback: (vmService) async { + final appListener = await _AppListener.forVmService(vmService, this); + appListener.errorLog.clear(); + + final vm = await vmService.getVM(); + var success = false; + try { + final hotRestartMethodName = + (await appListener.waitForServiceRegistration('hotRestart')) ?? + 'hotRestart'; + + /// If we haven't seen a specific one, we just call the default one. + final result = await vmService.callMethod( + hotRestartMethodName, + isolateId: vm.isolates!.first.id, + ); + final resultType = result.json?['type']; + success = resultType == 'Success'; + } catch (e) { + // Handle potential errors during the process + return CallToolResult( + isError: true, + content: [TextContent(text: 'Hot restart failed: $e')], + ); + } + return CallToolResult( + isError: !success ? true : null, + content: [ + TextContent( + text: 'Hot restart ${success ? 'succeeded' : 'failed'}.', + ), + ], + ); + }, + ); + } + /// Performs a hot reload on the currently running app. /// /// If more than one debug session is active, then it just uses the first one. @@ -902,8 +948,10 @@ base mixin DartToolingDaemonSupport name: 'hot_reload', description: 'Performs a hot reload of the active Flutter application. ' - 'This is to apply the latest code changes to the running application. ' - 'Requires "${connectTool.name}" to be successfully called first.', + 'This will apply the latest code changes to the running application, ' + 'while maintaining application state. Reload will not update const ' + 'definitions of global values. Requires "${connectTool.name}" to be ' + 'successfully called first.', annotations: ToolAnnotations(title: 'Hot reload', destructiveHint: true), inputSchema: Schema.object( properties: { @@ -918,6 +966,20 @@ base mixin DartToolingDaemonSupport ), ); + @visibleForTesting + static final hotRestartTool = Tool( + name: 'hot_restart', + description: + 'Performs a hot restart of the active Flutter application. ' + 'This applies the latest code changes to the running application, ' + 'including changes to global const values, while resetting ' + 'application state. Requires "${connectTool.name}" to be ' + "successfully called first. Doesn't work for Non-Flutter Dart CLI " + 'programs.', + annotations: ToolAnnotations(title: 'Hot restart', destructiveHint: true), + inputSchema: Schema.object(properties: {}, required: []), + ); + @visibleForTesting static final getWidgetTreeTool = Tool( name: 'get_widget_tree', diff --git a/pkgs/dart_mcp_server/test/tools/dtd_test.dart b/pkgs/dart_mcp_server/test/tools/dtd_test.dart index 79ef04c..8543f35 100644 --- a/pkgs/dart_mcp_server/test/tools/dtd_test.dart +++ b/pkgs/dart_mcp_server/test/tools/dtd_test.dart @@ -75,6 +75,27 @@ void main() { TextContent(text: 'Hot reload succeeded.'), ]); }); + + test('can perform a hot restart', () async { + await testHarness.startDebugSession( + counterAppPath, + 'lib/main.dart', + isFlutter: true, + ); + final tools = + (await testHarness.mcpServerConnection.listTools()).tools; + final hotRestartTool = tools.singleWhere( + (t) => t.name == DartToolingDaemonSupport.hotRestartTool.name, + ); + final hotRestartResult = await testHarness.callToolWithRetry( + CallToolRequest(name: hotRestartTool.name), + ); + + expect(hotRestartResult.isError, isNot(true)); + expect(hotRestartResult.content, [ + TextContent(text: 'Hot restart succeeded.'), + ]); + }); }); group('dart cli tests', () {