Skip to content

Commit d33ed02

Browse files
authored
[Feat] Add hot restart tool (#297)
1 parent 59db320 commit d33ed02

File tree

4 files changed

+88
-3
lines changed

4 files changed

+88
-3
lines changed

pkgs/dart_mcp_server/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
* Fix a bug in hot_reload ([#290](https://github.com/dart-lang/ai/issues/290)).
1919
* Add the `list_devices`, `launch_app`, `get_app_logs`, and `list_running_apps`
2020
tools for running Flutter apps.
21+
* Add the `hot_restart` tool for restarting running Flutter apps.
2122

2223
# 0.1.0 (Dart SDK 3.9.0)
2324

pkgs/dart_mcp_server/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,8 @@ For more information, see the official VS Code documentation for
147147
| `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. |
148148
| `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. |
149149
| `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. |
150-
| `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. |
150+
| `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. |
151+
| `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. |
151152
| `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. |
152153
| `launch_app` | | Launches a Flutter application and returns its DTD URI. |
153154
| `list_devices` | | Lists available Flutter devices. |

pkgs/dart_mcp_server/lib/src/mixins/dtd.dart

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ base mixin DartToolingDaemonSupport
157157
// Flutter app that does not support the operation, e.g. hot reload is not
158158
// supported in profile mode).
159159
if (enableScreenshots) registerTool(screenshotTool, takeScreenshot);
160+
registerTool(hotRestartTool, hotRestart);
160161
registerTool(hotReloadTool, hotReload);
161162
registerTool(getWidgetTreeTool, widgetTree);
162163
registerTool(getSelectedWidgetTool, selectedWidget);
@@ -351,6 +352,51 @@ base mixin DartToolingDaemonSupport
351352
);
352353
}
353354

355+
/// Performs a hot restart on the currently running app.
356+
///
357+
/// If more than one debug session is active, then it just uses the first
358+
/// one.
359+
// TODO: support passing a debug session id when there is more than one
360+
// debug session.
361+
Future<CallToolResult> hotRestart(CallToolRequest request) async {
362+
return _callOnVmService(
363+
callback: (vmService) async {
364+
final appListener = await _AppListener.forVmService(vmService, this);
365+
appListener.errorLog.clear();
366+
367+
final vm = await vmService.getVM();
368+
var success = false;
369+
try {
370+
final hotRestartMethodName =
371+
(await appListener.waitForServiceRegistration('hotRestart')) ??
372+
'hotRestart';
373+
374+
/// If we haven't seen a specific one, we just call the default one.
375+
final result = await vmService.callMethod(
376+
hotRestartMethodName,
377+
isolateId: vm.isolates!.first.id,
378+
);
379+
final resultType = result.json?['type'];
380+
success = resultType == 'Success';
381+
} catch (e) {
382+
// Handle potential errors during the process
383+
return CallToolResult(
384+
isError: true,
385+
content: [TextContent(text: 'Hot restart failed: $e')],
386+
);
387+
}
388+
return CallToolResult(
389+
isError: !success ? true : null,
390+
content: [
391+
TextContent(
392+
text: 'Hot restart ${success ? 'succeeded' : 'failed'}.',
393+
),
394+
],
395+
);
396+
},
397+
);
398+
}
399+
354400
/// Performs a hot reload on the currently running app.
355401
///
356402
/// If more than one debug session is active, then it just uses the first one.
@@ -902,8 +948,10 @@ base mixin DartToolingDaemonSupport
902948
name: 'hot_reload',
903949
description:
904950
'Performs a hot reload of the active Flutter application. '
905-
'This is to apply the latest code changes to the running application. '
906-
'Requires "${connectTool.name}" to be successfully called first.',
951+
'This will apply the latest code changes to the running application, '
952+
'while maintaining application state. Reload will not update const '
953+
'definitions of global values. Requires "${connectTool.name}" to be '
954+
'successfully called first.',
907955
annotations: ToolAnnotations(title: 'Hot reload', destructiveHint: true),
908956
inputSchema: Schema.object(
909957
properties: {
@@ -918,6 +966,20 @@ base mixin DartToolingDaemonSupport
918966
),
919967
);
920968

969+
@visibleForTesting
970+
static final hotRestartTool = Tool(
971+
name: 'hot_restart',
972+
description:
973+
'Performs a hot restart of the active Flutter application. '
974+
'This applies the latest code changes to the running application, '
975+
'including changes to global const values, while resetting '
976+
'application state. Requires "${connectTool.name}" to be '
977+
"successfully called first. Doesn't work for Non-Flutter Dart CLI "
978+
'programs.',
979+
annotations: ToolAnnotations(title: 'Hot restart', destructiveHint: true),
980+
inputSchema: Schema.object(properties: {}, required: []),
981+
);
982+
921983
@visibleForTesting
922984
static final getWidgetTreeTool = Tool(
923985
name: 'get_widget_tree',

pkgs/dart_mcp_server/test/tools/dtd_test.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,27 @@ void main() {
7575
TextContent(text: 'Hot reload succeeded.'),
7676
]);
7777
});
78+
79+
test('can perform a hot restart', () async {
80+
await testHarness.startDebugSession(
81+
counterAppPath,
82+
'lib/main.dart',
83+
isFlutter: true,
84+
);
85+
final tools =
86+
(await testHarness.mcpServerConnection.listTools()).tools;
87+
final hotRestartTool = tools.singleWhere(
88+
(t) => t.name == DartToolingDaemonSupport.hotRestartTool.name,
89+
);
90+
final hotRestartResult = await testHarness.callToolWithRetry(
91+
CallToolRequest(name: hotRestartTool.name),
92+
);
93+
94+
expect(hotRestartResult.isError, isNot(true));
95+
expect(hotRestartResult.content, [
96+
TextContent(text: 'Hot restart succeeded.'),
97+
]);
98+
});
7899
});
79100

80101
group('dart cli tests', () {

0 commit comments

Comments
 (0)