diff --git a/DEPS b/DEPS index 1d2b03c76d2..72575adac09 100644 --- a/DEPS +++ b/DEPS @@ -134,7 +134,6 @@ vars = { ### /third_party/pkg dependencies # 'tools/rev_sdk_deps.dart' will rev pkg dependencies to their latest; put an # EOL comment after a dependency to instead pin at the current revision. - "ai_rev": "9c96bfe5f091c9451eff5b59c9bffeb2e806b875", "core_rev": "be0b1531c445a185d3e93887b8d0355fc766c314", "dartdoc_rev": "77a52b6125ce8cc8a88a8a399d80701254e43838", "ecosystem_rev": "319ff812d463fc194999a0d4b682097450279332", @@ -338,8 +337,6 @@ deps = { Var('chromium_git') + '/external/github.com/mdn/browser-compat-data' + "@" + Var("browser-compat-data_tag"), - Var("dart_root") + "/third_party/pkg/ai": - Var("dart_git") + "ai.git" + "@" + Var("ai_rev"), Var("dart_root") + "/third_party/pkg/core": Var("dart_git") + "core.git" + "@" + Var("core_rev"), Var("dart_root") + "/third_party/pkg/dart_style": diff --git a/pkg/dart2native/lib/sdk.dart b/pkg/dart2native/lib/sdk.dart index fd478ced18b..d0972760f12 100644 --- a/pkg/dart2native/lib/sdk.dart +++ b/pkg/dart2native/lib/sdk.dart @@ -90,10 +90,6 @@ class Sdk { 'dart2wasm_product.snapshot', ); - String get dartMCPServerAotSnapshot => _snapshotPathFor( - 'dart_mcp_server_aot.dart.snapshot', - ); - String get ddsAotSnapshot => _snapshotPathFor( 'dds_aot.dart.snapshot', ); diff --git a/pkg/dartdev/lib/dartdev.dart b/pkg/dartdev/lib/dartdev.dart index a634332c630..10c3b64104f 100644 --- a/pkg/dartdev/lib/dartdev.dart +++ b/pkg/dartdev/lib/dartdev.dart @@ -11,6 +11,7 @@ import 'package:args/args.dart'; import 'package:args/command_runner.dart'; import 'package:cli_util/cli_logging.dart'; import 'package:dart_style/src/cli/format_command.dart'; +import 'package:dartdev/src/commands/dart_mcp_server.dart'; import 'package:meta/meta.dart'; import 'package:pub/pub.dart'; import 'package:unified_analytics/unified_analytics.dart'; @@ -20,7 +21,6 @@ import 'src/commands/build.dart'; import 'src/commands/compilation_server.dart'; import 'src/commands/compile.dart'; import 'src/commands/create.dart'; -import 'src/commands/dart_mcp_server.dart'; import 'src/commands/debug_adapter.dart'; import 'src/commands/development_service.dart'; import 'src/commands/devtools.dart'; diff --git a/pkg/dartdev/lib/src/commands/dart_mcp_server.dart b/pkg/dartdev/lib/src/commands/dart_mcp_server.dart index 634463c5398..0e4dc24bd05 100644 --- a/pkg/dartdev/lib/src/commands/dart_mcp_server.dart +++ b/pkg/dartdev/lib/src/commands/dart_mcp_server.dart @@ -4,14 +4,11 @@ import 'dart:async'; -import 'package:args/args.dart'; -import 'package:dart_mcp_server/arg_parser.dart' as dart_mcp_server; -import 'package:dartdev/src/utils.dart'; +import 'package:dartdev/src/commands/run.dart'; import '../core.dart'; -import '../sdk.dart'; -import '../vm_interop_handler.dart'; +/// This command is now just an alias for `dart run dart_mcp_server@`. class DartMCPServerCommand extends DartdevCommand { static const String cmdName = 'mcp-server'; @@ -20,12 +17,6 @@ A stdio based Model Context Protocol (MCP) server to aid in Dart and Flutter dev static const _experimentFlag = 'experimental-mcp-server'; - @override - ArgParser createArgParser() => dart_mcp_server.createArgParser( - usageLineLength: dartdevUsageLineLength, - includeHelp: false, - ); - DartMCPServerCommand({bool verbose = false}) : super(cmdName, cmdDescription, verbose, hidden: true) { argParser.addFlag( @@ -46,26 +37,33 @@ A stdio based Model Context Protocol (MCP) server to aid in Dart and Flutter dev @override Future run() async { - final parsedArgs = argResults!; + // We want the global arguments as we will be delegating back to the + // command runner to run the new command. + final forwardedArgs = globalResults!.arguments.toList(); // Strip out the experiment flag before forwarding on the args, this flag - // isn't supported by the actual binary. - final forwardedArgs = argResults!.arguments.toList(); - if (parsedArgs.wasParsed(_experimentFlag)) { + // isn't supported by the actual package. + // + // Have to check the local arg results here, as this flag only exists on the + // command arg parser and not the global one. + if (argResults!.wasParsed(_experimentFlag)) { forwardedArgs.removeWhere((arg) => arg.endsWith(_experimentFlag)); } - try { - VmInteropHandler.run(sdk.dartMCPServerAotSnapshot, [ - ...forwardedArgs, - ], useExecProcess: false); - return 0; - } catch (e, st) { - log.stderr('Error: launching Dart MCP server failed'); - log.stderr(e.toString()); - if (verbose) { - log.stderr(st.toString()); - } - return 255; + + // Find the index of the original command argument and replace it with + // `run dart_mcp_server@`. + final commandIndex = forwardedArgs.indexOf(cmdName); + if (commandIndex == -1) { + throw StateError( + 'Reached mcp-server command without `mcp-server` in arguments.', + ); } + forwardedArgs.replaceRange(commandIndex, commandIndex + 1, [ + RunCommand.cmdName, + 'dart_mcp_server@', + ]); + + // Finally, run the new command. + return await runner!.run(forwardedArgs) ?? 0; } } diff --git a/pkg/dartdev/pubspec.yaml b/pkg/dartdev/pubspec.yaml index 2cf727909bc..c5f6f38c004 100644 --- a/pkg/dartdev/pubspec.yaml +++ b/pkg/dartdev/pubspec.yaml @@ -19,7 +19,6 @@ dependencies: collection: any dart2native: any dart_data_home: any - dart_mcp_server: any dart_style: any dartdoc: any data_assets: any @@ -47,7 +46,6 @@ dependencies: # Use 'any' constraints here; we get our versions from the DEPS file. dev_dependencies: - dart_mcp: any expect: any lints: any pub_semver: any diff --git a/pkg/dartdev/test/commands/mcp_server_test.dart b/pkg/dartdev/test/commands/mcp_server_test.dart index b2c4d35b8b9..6adf057a096 100644 --- a/pkg/dartdev/test/commands/mcp_server_test.dart +++ b/pkg/dartdev/test/commands/mcp_server_test.dart @@ -2,52 +2,70 @@ // 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. -import 'dart:io'; - -import 'package:dart_mcp/client.dart'; -import 'package:dart_mcp/stdio.dart'; +import 'dart:async'; +import 'package:args/command_runner.dart'; +import 'package:dartdev/src/commands/dart_mcp_server.dart'; import 'package:test/test.dart'; void main() { - group('dart mcp-server', () { - for (var withExperiment in const [true, false]) { - test( - 'can be connected with a client with${withExperiment ? '' : 'out'} the experiment flag', - () async { - final client = TestMCPClient(); - addTearDown(client.shutdown); - final process = await Process.start(Platform.resolvedExecutable, [ - 'mcp-server', - if (withExperiment) '--experimental-mcp-server', - ]); + group('DartMCPServerCommand', () { + late FakeRunner runner; + late DartMCPServerCommand command; - final connection = client.connectServer( - stdioChannel(input: process.stdout, output: process.stdin), - ); - connection.done.then((_) => process.kill()); + setUp(() { + runner = FakeRunner(); + command = DartMCPServerCommand(); + runner.addCommand(command); + }); - final initializeResult = await connection.initialize( - InitializeRequest( - protocolVersion: ProtocolVersion.latestSupported, - capabilities: client.capabilities, - clientInfo: client.implementation, - ), - ); + test('delegates to `run dart_mcp_server@`', () async { + await runner.run(['mcp-server']); + expect(runner.capturedArgs, equals(['run', 'dart_mcp_server@'])); + }); - expect( - initializeResult.protocolVersion, - ProtocolVersion.latestSupported, - ); - connection.notifyInitialized(); - - expect(await connection.listTools(ListToolsRequest()), isNotEmpty); - }, + test('forwards command arguments', () async { + await runner.run(['mcp-server', 'foo', 'bar']); + expect( + runner.capturedArgs, + equals(['run', 'dart_mcp_server@', 'foo', 'bar']), ); - } + }); + + test('strips experimental flag', () async { + await runner.run(['mcp-server', '--experimental-mcp-server']); + expect(runner.capturedArgs, equals(['run', 'dart_mcp_server@'])); + }); + + test('strips experimental flag and keeps other args', () async { + await runner.run(['mcp-server', '--experimental-mcp-server', 'foo']); + expect(runner.capturedArgs, equals(['run', 'dart_mcp_server@', 'foo'])); + }); + + test('forwards global arguments', () async { + runner.argParser.addFlag('global-flag', negatable: false); + await runner.run(['--global-flag', 'mcp-server']); + expect( + runner.capturedArgs, + equals(['--global-flag', 'run', 'dart_mcp_server@']), + ); + }); }); } -base class TestMCPClient extends MCPClient { - TestMCPClient() - : super(Implementation(name: 'test client', version: '0.1.0')); +class FakeRunner extends CommandRunner { + List? capturedArgs; + bool isFirstCall = true; + + FakeRunner() : super('dart', 'dart command runner'); + + @override + Future run(Iterable args) async { + if (isFirstCall) { + isFirstCall = false; + return await super.run(args); + } else { + capturedArgs = args.toList(); + return 0; + } + } } diff --git a/pubspec.yaml b/pubspec.yaml index 852d4cad6e8..f30d2cb7423 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -140,10 +140,6 @@ dependency_overrides: path: third_party/pkg/tools/pkgs/csslib dart_flutter_team_lints: path: third_party/pkg/ecosystem/pkgs/dart_flutter_team_lints - dart_mcp: - path: third_party/pkg/ai/pkgs/dart_mcp - dart_mcp_server: - path: third_party/pkg/ai/pkgs/dart_mcp_server dart_style: path: third_party/pkg/dart_style dartdoc: diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn index 7c205e32541..39a5e6939ca 100644 --- a/sdk/BUILD.gn +++ b/sdk/BUILD.gn @@ -150,11 +150,6 @@ if (dart_target_arch != "ia32" && dart_target_arch != "x86") { "../utils/dtd:dtd_aot", "dart_tooling_daemon_aot", ], - [ - "dart_mcp_server_aot_product", - "../utils/dart_mcp_server:dart_mcp_server_aot", - "dart_mcp_server_aot", - ], ] if (include_experimental_vm_service) { _platform_sdk_snapshots += [ [ diff --git a/utils/dart_mcp_server/BUILD.gn b/utils/dart_mcp_server/BUILD.gn deleted file mode 100644 index 06c3bc2b7ce..00000000000 --- a/utils/dart_mcp_server/BUILD.gn +++ /dev/null @@ -1,31 +0,0 @@ -# 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. - -import("../../runtime/runtime_args.gni") -import("../aot_snapshot.gni") - -group("dart_mcp_server_aot") { - public_deps = [ - ":dart_mcp_server_aot_product_snapshot", - ":dart_mcp_server_aot_snapshot", - ] -} - -aot_snapshot("dart_mcp_server_aot_snapshot") { - main_dart = "dart_mcp_server.dart" - output = "$root_gen_dir/dart_mcp_server_aot.dart.snapshot" -} - -aot_snapshot("dart_mcp_server_aot_product_snapshot") { - main_dart = "dart_mcp_server.dart" - output = "$root_gen_dir/dart_mcp_server_aot_product.dart.snapshot" - - # dartaotruntime has dart_product_config applied to it, - # so it is built in product mode in both release and - # product builds, and is only built in debug mode in debug - # builds. The following line ensures that the dartaotruntime - # and dart_mcp_server_aot snapshot in an SDK build are - # always compatible with each other. - force_product_mode = !dart_debug -} diff --git a/utils/dart_mcp_server/dart_mcp_server.dart b/utils/dart_mcp_server/dart_mcp_server.dart deleted file mode 100644 index ba17c565013..00000000000 --- a/utils/dart_mcp_server/dart_mcp_server.dart +++ /dev/null @@ -1,36 +0,0 @@ -// 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. - -/// A custom entrypoint used by `dart mcp-server`. Injects an analytics instance -/// using the [DashTool.dartTool] tool. -library; - -import 'dart:io'; - -import 'package:dart_mcp_server/dart_mcp_server.dart'; -import 'package:unified_analytics/unified_analytics.dart'; - -void main(List args) async { - DashTool? tool; - if (Platform.environment['DASH__TOOL'] case final toolEnv?) { - try { - tool = DashTool.fromLabel(toolEnv); - } catch (e) { - // Ignore errors, but don't track analytics for unrecognized tools. - } - } else { - // We default to the `dart` tool if none specified. - tool = DashTool.dartTool; - } - - final analytics = tool != null - ? Analytics( - tool: tool, - // The actual version is the part up to the first space. - dartVersion: Platform.version.split(' ').first, - ) - : null; - - exitCode = await DartMCPServer.run(args, analytics: analytics); -}