Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkgs/dart_mcp_server/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
3 changes: 2 additions & 1 deletion pkgs/dart_mcp_server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down
66 changes: 64 additions & 2 deletions pkgs/dart_mcp_server/lib/src/mixins/dtd.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<CallToolResult> 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.
Expand Down Expand Up @@ -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: {
Expand All @@ -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',
Expand Down
21 changes: 21 additions & 0 deletions pkgs/dart_mcp_server/test/tools/dtd_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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', () {
Expand Down