Update mcp-server command to be an alias for 'run dart_mcp_server@'

Strips the dart-lang/ai dependency from the SDK entirely, as well as the snapshot.

Bug: https://github.com/dart-lang/ai/issues/479
Change-Id: Id919c6a8fbf5fedeffb37e5181bc2fbd85adf986
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/507220
Reviewed-by: Ben Konyi <bkonyi@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
Auto-Submit: Jake Macdonald <jakemac@google.com>
This commit is contained in:
Jake Macdonald
2026-05-28 12:06:25 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 527d529493
commit 06bdbd777f
10 changed files with 82 additions and 151 deletions
-3
View File
@@ -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":
-4
View File
@@ -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',
);
+1 -1
View File
@@ -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';
@@ -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<int> 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;
}
}
-2
View File
@@ -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
+56 -38
View File
@@ -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<int> {
List<String>? capturedArgs;
bool isFirstCall = true;
FakeRunner() : super('dart', 'dart command runner');
@override
Future<int?> run(Iterable<String> args) async {
if (isFirstCall) {
isFirstCall = false;
return await super.run(args);
} else {
capturedArgs = args.toList();
return 0;
}
}
}
-4
View File
@@ -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:
-5
View File
@@ -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 += [ [
-31
View File
@@ -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
}
@@ -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<String> 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);
}