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
2 changes: 2 additions & 0 deletions pkgs/dart_mcp_server/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
- Add `--tools=dart|all` argument to allow enabling only vanilla Dart tools for
non-flutter projects.
- Include the device name and target platform in the list_devices tool.
- Fix erroneous SDK version error messages when connecting to a VM Service
instead of DTD URI.

# 0.1.1 (Dart SDK 3.10.0)

Expand Down
31 changes: 30 additions & 1 deletion pkgs/dart_mcp_server/lib/src/mixins/dtd.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'dart:convert';
import 'package:dart_mcp/server.dart';
import 'package:dds_service_extensions/dds_service_extensions.dart';
import 'package:dtd/dtd.dart';
import 'package:json_rpc_2/json_rpc_2.dart';
import 'package:meta/meta.dart';
import 'package:unified_analytics/unified_analytics.dart' as ua;
import 'package:vm_service/vm_service.dart';
Expand Down Expand Up @@ -231,9 +232,23 @@ base mixin DartToolingDaemonSupport
}

try {
_dtd = await DartToolingDaemon.connect(
final dtd = _dtd = await DartToolingDaemon.connect(
Uri.parse(request.arguments![ParameterNames.uri] as String),
);
try {
await dtd.call(null, 'getVM');
// If the call above succeeds, we were connected to the vm service, and
// should error.
await _resetDtd();
return _gotVmServiceUri;
} on RpcException catch (e) {
// Double check the failure was a method not found failure, if not
// rethrow it.
if (e.code != RpcErrorCodes.kMethodNotFound) {
await _resetDtd();
rethrow;
}
}
unawaited(_dtd!.done.then((_) async => await _resetDtd()));

await _listenForServices();
Expand Down Expand Up @@ -1098,6 +1113,20 @@ base mixin DartToolingDaemonSupport
isError: true,
)..failureReason = CallToolFailureReason.flutterDriverNotEnabled;

static final _gotVmServiceUri = CallToolResult(
content: [
Content.text(
text:
'Connected to a VM Service but expected to connect to a Dart '
'Tooling Daemon service. When launching apps from an IDE you '
'should have a "Copy DTD URI to clipboard" command pallete option, '
'or when directly launching apps from a terminal you can pass the '
'"--print-dtd" command line option in order to get the DTD URI.',
),
],
isError: true,
);

static final runtimeErrorsScheme = 'runtime-errors';

static const _defaultTimeoutMs = 5000;
Expand Down
17 changes: 11 additions & 6 deletions pkgs/dart_mcp_server/test/test_harness.dart
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,16 @@ class TestHarness {
await AppDebugSession.kill(session.appProcess, session.isFlutter);
}

/// Connects the MCP server to the dart tooling daemon at the `dtdUri` from
/// [fakeEditorExtension] using the "connectDartToolingDaemon" tool function.
/// Connects the MCP server to the dart tooling daemon at the [dtdUri] using
/// the "connectDartToolingDaemon" tool function.
///
/// By default the DTD Uri will come from the [fakeEditorExtension].
///
/// This mimics a user using the "copy DTD Uri from clipboard" action.
Future<void> connectToDtd() async {
Future<CallToolResult> connectToDtd({
String? dtdUri,
bool expectError = false,
}) async {
final tools = (await mcpServerConnection.listTools()).tools;

final connectTool = tools.singleWhere(
Expand All @@ -157,11 +162,11 @@ class TestHarness {
final result = await callToolWithRetry(
CallToolRequest(
name: connectTool.name,
arguments: {ParameterNames.uri: fakeEditorExtension.dtdUri},
arguments: {ParameterNames.uri: dtdUri ?? fakeEditorExtension.dtdUri},
),
expectError: expectError,
);

expect(result.isError, isNot(true), reason: result.content.join('\n'));
return result;
}

/// Helper to send [request] to [mcpServerConnection].
Expand Down
19 changes: 19 additions & 0 deletions pkgs/dart_mcp_server/test/tools/dtd_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,25 @@ void main() {
expect(log.characters, 10);
});
});

test('connect_to_dtd will reject a vm service URI', () async {
final testHarness = await TestHarness.start(inProcess: true);
final debugSession = await testHarness.startDebugSession(
dartCliAppsPath,
'bin/infinite_wait.dart',
isFlutter: false,
);
final connectResult = await testHarness.connectToDtd(
dtdUri: debugSession.vmServiceUri,
expectError: true,
);
expect(
(connectResult.content.first as TextContent).text,
contains('Connected to a VM Service'),
);
final retryResult = await testHarness.connectToDtd();
expect(retryResult.isError, isNot(true));
});
}

extension on Iterable<Resource> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

void main() async {
while (true) {
await Future.delayed(const Duration(seconds: 1));
}
}