Skip to content

Commit 71a0b00

Browse files
authored
detect VM service connections and return a nicer error (#316)
Closes #256 Detect VM Service connections by calling `getVM` - if that _succeeds_ then we return a better error instead of the misleading error about needing a newer SDK. If it failes with a method not found exception, we continue.
1 parent 2c1b352 commit 71a0b00

File tree

5 files changed

+71
-7
lines changed

5 files changed

+71
-7
lines changed

pkgs/dart_mcp_server/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
- Add `--tools=dart|all` argument to allow enabling only vanilla Dart tools for
44
non-flutter projects.
55
- Include the device name and target platform in the list_devices tool.
6+
- Fix erroneous SDK version error messages when connecting to a VM Service
7+
instead of DTD URI.
68

79
# 0.1.1 (Dart SDK 3.10.0)
810

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'dart:convert';
88
import 'package:dart_mcp/server.dart';
99
import 'package:dds_service_extensions/dds_service_extensions.dart';
1010
import 'package:dtd/dtd.dart';
11+
import 'package:json_rpc_2/json_rpc_2.dart';
1112
import 'package:meta/meta.dart';
1213
import 'package:unified_analytics/unified_analytics.dart' as ua;
1314
import 'package:vm_service/vm_service.dart';
@@ -231,9 +232,23 @@ base mixin DartToolingDaemonSupport
231232
}
232233

233234
try {
234-
_dtd = await DartToolingDaemon.connect(
235+
final dtd = _dtd = await DartToolingDaemon.connect(
235236
Uri.parse(request.arguments![ParameterNames.uri] as String),
236237
);
238+
try {
239+
await dtd.call(null, 'getVM');
240+
// If the call above succeeds, we were connected to the vm service, and
241+
// should error.
242+
await _resetDtd();
243+
return _gotVmServiceUri;
244+
} on RpcException catch (e) {
245+
// Double check the failure was a method not found failure, if not
246+
// rethrow it.
247+
if (e.code != RpcErrorCodes.kMethodNotFound) {
248+
await _resetDtd();
249+
rethrow;
250+
}
251+
}
237252
unawaited(_dtd!.done.then((_) async => await _resetDtd()));
238253

239254
await _listenForServices();
@@ -1098,6 +1113,20 @@ base mixin DartToolingDaemonSupport
10981113
isError: true,
10991114
)..failureReason = CallToolFailureReason.flutterDriverNotEnabled;
11001115

1116+
static final _gotVmServiceUri = CallToolResult(
1117+
content: [
1118+
Content.text(
1119+
text:
1120+
'Connected to a VM Service but expected to connect to a Dart '
1121+
'Tooling Daemon service. When launching apps from an IDE you '
1122+
'should have a "Copy DTD URI to clipboard" command pallete option, '
1123+
'or when directly launching apps from a terminal you can pass the '
1124+
'"--print-dtd" command line option in order to get the DTD URI.',
1125+
),
1126+
],
1127+
isError: true,
1128+
);
1129+
11011130
static final runtimeErrorsScheme = 'runtime-errors';
11021131

11031132
static const _defaultTimeoutMs = 5000;

pkgs/dart_mcp_server/test/test_harness.dart

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,16 @@ class TestHarness {
158158
await AppDebugSession.kill(session.appProcess, session.isFlutter);
159159
}
160160

161-
/// Connects the MCP server to the dart tooling daemon at the `dtdUri` from
162-
/// [fakeEditorExtension] using the "connectDartToolingDaemon" tool function.
161+
/// Connects the MCP server to the dart tooling daemon at the [dtdUri] using
162+
/// the "connectDartToolingDaemon" tool function.
163+
///
164+
/// By default the DTD Uri will come from the [fakeEditorExtension].
163165
///
164166
/// This mimics a user using the "copy DTD Uri from clipboard" action.
165-
Future<void> connectToDtd() async {
167+
Future<CallToolResult> connectToDtd({
168+
String? dtdUri,
169+
bool expectError = false,
170+
}) async {
166171
final tools = (await mcpServerConnection.listTools()).tools;
167172

168173
final connectTool = tools.singleWhere(
@@ -172,11 +177,11 @@ class TestHarness {
172177
final result = await callToolWithRetry(
173178
CallToolRequest(
174179
name: connectTool.name,
175-
arguments: {ParameterNames.uri: fakeEditorExtension.dtdUri},
180+
arguments: {ParameterNames.uri: dtdUri ?? fakeEditorExtension.dtdUri},
176181
),
182+
expectError: expectError,
177183
);
178-
179-
expect(result.isError, isNot(true), reason: result.content.join('\n'));
184+
return result;
180185
}
181186

182187
/// Helper to send [request] to [mcpServerConnection].

pkgs/dart_mcp_server/test/tools/dtd_test.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,25 @@ void main() {
794794
expect(log.characters, 10);
795795
});
796796
});
797+
798+
test('connect_to_dtd will reject a vm service URI', () async {
799+
final testHarness = await TestHarness.start(inProcess: true);
800+
final debugSession = await testHarness.startDebugSession(
801+
dartCliAppsPath,
802+
'bin/infinite_wait.dart',
803+
isFlutter: false,
804+
);
805+
final connectResult = await testHarness.connectToDtd(
806+
dtdUri: debugSession.vmServiceUri,
807+
expectError: true,
808+
);
809+
expect(
810+
(connectResult.content.first as TextContent).text,
811+
contains('Connected to a VM Service'),
812+
);
813+
final retryResult = await testHarness.connectToDtd();
814+
expect(retryResult.isError, isNot(true));
815+
});
797816
}
798817

799818
extension on Iterable<Resource> {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
void main() async {
6+
while (true) {
7+
await Future.delayed(const Duration(seconds: 1));
8+
}
9+
}

0 commit comments

Comments
 (0)