blob: 2979db4578d71c83c6f320613eb7d2ec4e5ca4ce [file] [log] [blame]
// 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 'dart:async';
import 'package:dart_mcp/server.dart';
import 'package:file/file.dart';
import 'package:file/local.dart';
import 'package:meta/meta.dart';
import 'package:process/process.dart';
import 'package:unified_analytics/unified_analytics.dart';
import 'mixins/analyzer.dart';
import 'mixins/dash_cli.dart';
import 'mixins/dtd.dart';
import 'mixins/pub.dart';
import 'mixins/pub_dev_search.dart';
import 'mixins/roots_fallback_support.dart';
import 'utils/analytics.dart';
import 'utils/file_system.dart';
import 'utils/process_manager.dart';
import 'utils/sdk.dart';
/// An MCP server for Dart and Flutter tooling.
final class DartMCPServer extends MCPServer
with
LoggingSupport,
ToolsSupport,
ResourcesSupport,
RootsTrackingSupport,
RootsFallbackSupport,
DartAnalyzerSupport,
DashCliSupport,
PubSupport,
PubDevSupport,
DartToolingDaemonSupport
implements
AnalyticsSupport,
ProcessManagerSupport,
FileSystemSupport,
SdkSupport {
DartMCPServer(
super.channel, {
required this.sdk,
this.analytics,
@visibleForTesting this.processManager = const LocalProcessManager(),
@visibleForTesting this.fileSystem = const LocalFileSystem(),
this.forceRootsFallback = false,
super.protocolLogSink,
}) : super.fromStreamChannel(
implementation: Implementation(
name: 'dart and flutter tooling',
version: '0.1.0',
),
instructions:
'This server helps to connect Dart and Flutter developers to '
'their development tools and running applications.\n'
'IMPORTANT: Prefer using an MCP tool provided by this server '
'over using tools directly in a shell.',
);
@override
final LocalProcessManager processManager;
@override
final FileSystem fileSystem;
@override
final bool forceRootsFallback;
@override
final Sdk sdk;
@override
final Analytics? analytics;
@override
/// Automatically logs all tool calls via analytics by wrapping the [impl],
/// if [analytics] is not `null`.
void registerTool(
Tool tool,
FutureOr<CallToolResult> Function(CallToolRequest) impl,
) {
// For type promotion.
final analytics = this.analytics;
super.registerTool(
tool,
analytics == null
? impl
: (CallToolRequest request) async {
final watch = Stopwatch()..start();
CallToolResult? result;
try {
return result = await impl(request);
} finally {
watch.stop();
try {
analytics.send(
Event.dartMCPEvent(
client: clientInfo.name,
clientVersion: clientInfo.version,
serverVersion: implementation.version,
type: AnalyticsEvent.callTool.name,
additionalData: CallToolMetrics(
tool: request.name,
success: result != null && result.isError != true,
elapsedMilliseconds: watch.elapsedMilliseconds,
),
),
);
} catch (e) {
log(LoggingLevel.warning, 'Error sending analytics event: $e');
}
}
},
);
}
}