initial file integrations for dart tooling daemon
diff --git a/pkgs/dart_mcp/example/dash_client.dart b/pkgs/dart_mcp/example/dash_client.dart
index d0f6960..df8c096 100644
--- a/pkgs/dart_mcp/example/dash_client.dart
+++ b/pkgs/dart_mcp/example/dash_client.dart
@@ -25,12 +25,20 @@
final parsedArgs = argParser.parse(args);
final serverCommands = parsedArgs['server'] as List<String>;
- DashClient(
- serverCommands,
- geminiApiKey: geminiApiKey,
- auto: parsedArgs.flag('auto'),
- raw: parsedArgs.flag('raw'),
- verbose: parsedArgs.flag('verbose'),
+ runZonedGuarded(
+ () {
+ DashClient(
+ serverCommands,
+ geminiApiKey: geminiApiKey,
+ model: parsedArgs.option('model')!,
+ auto: parsedArgs.flag('auto'),
+ raw: parsedArgs.flag('raw'),
+ verbose: parsedArgs.flag('verbose'),
+ );
+ },
+ (e, s) {
+ stderr.writeln('$e\n$s');
+ },
);
}
@@ -41,6 +49,17 @@
abbr: 's',
help: 'A command to run to start an MCP server',
)
+ ..addOption(
+ 'model',
+ abbr: 'm',
+ help: 'The model to use',
+ defaultsTo: 'models/gemini-2.5-pro-preview-03-25',
+ allowed: [
+ 'models/gemini-2.5-pro-preview-03-25',
+ 'models/gemini-2.5-pro-exp-03-25',
+ 'gemini-2.0-flash',
+ ],
+ )
..addFlag(
'auto',
help:
@@ -65,7 +84,8 @@
final List<ServerConnection> serverConnections = [];
final Map<String, ServerConnection> connectionForFunction = {};
final List<gemini.Content> chatHistory = [];
- final gemini.GenerativeModel model;
+ final gemini.GenerativeModel proModel;
+ final gemini.GenerativeModel flashModel;
final bool auto;
final bool raw;
final bool verbose;
@@ -73,11 +93,16 @@
DashClient(
this.serverCommands, {
required String geminiApiKey,
+ required String model,
this.auto = false,
this.raw = false,
this.verbose = false,
- }) : model = gemini.GenerativeModel(
- // model: 'gemini-2.5-pro-exp-03-25',
+ }) : proModel = gemini.GenerativeModel(
+ model: model,
+ apiKey: geminiApiKey,
+ systemInstruction: systemInstructions,
+ ),
+ flashModel = gemini.GenerativeModel(
model: 'gemini-2.0-flash',
apiKey: geminiApiKey,
systemInstruction: systemInstructions,
@@ -114,8 +139,9 @@
final nextMessage = continuation ?? await stdinQueue.next;
continuation = null;
chatHistory.add(gemini.Content.text(nextMessage));
+ print('thinking...');
final modelResponse =
- (await model.generateContent(
+ (await proModel.generateContent(
chatHistory,
tools: serverTools,
)).candidates.single.content;
@@ -139,7 +165,7 @@
final dashSpeakResponse =
raw
? currentText
- : (await model.generateContent([
+ : (await flashModel.generateContent([
gemini.Content.text(
'Please rewrite the following message in your own voice',
),
@@ -195,7 +221,8 @@
}
}
await _chatToUser(response.toString());
- return null;
+ // After a function call, always ask gemini to keep going.
+ return 'Continue';
}
/// Analyzes a user [message] to see if it looks like they approved of the
@@ -203,7 +230,7 @@
Future<bool> _analyzeSentiment(String message) async {
if (message == 'y' || message == 'yes') return true;
final sentimentResult =
- (await model.generateContent([
+ (await flashModel.generateContent([
gemini.Content.text(
'Analyze the sentiment of the following response. If you are '
'highly confident that the user approves of running the previous '
diff --git a/pkgs/dart_tooling_mcp_server/lib/src/mixins/dtd.dart b/pkgs/dart_tooling_mcp_server/lib/src/mixins/dtd.dart
index 19bf087..358980b 100644
--- a/pkgs/dart_tooling_mcp_server/lib/src/mixins/dtd.dart
+++ b/pkgs/dart_tooling_mcp_server/lib/src/mixins/dtd.dart
@@ -101,6 +101,8 @@
FutureOr<InitializeResult> initialize(InitializeRequest request) async {
registerTool(connectTool, _connect);
registerTool(getRuntimeErrorsTool, runtimeErrors);
+ registerTool(readFileTool, _readFile);
+ registerTool(writeFileTool, _writeFile);
// TODO: these tools should only be registered for Flutter applications, or
// they should return an error when used against a pure Dart app (or a
@@ -449,6 +451,38 @@
);
}
+ /// Reads a file by uri using DTD.
+ Future<CallToolResult> _readFile(CallToolRequest request) async {
+ final dtd = _dtd;
+ if (dtd == null) return _dtdNotConnected;
+ final uriString = request.arguments?['uri'] as String?;
+ if (uriString == null) {
+ return _requiredUriParamMissing;
+ }
+
+ final uri = Uri.parse(uriString);
+ final contents = await dtd.readFileAsString(uri);
+ return CallToolResult(content: [TextContent(text: contents.content!)]);
+ }
+
+ /// Reads a file by uri using DTD.
+ Future<CallToolResult> _writeFile(CallToolRequest request) async {
+ final dtd = _dtd;
+ if (dtd == null) return _dtdNotConnected;
+ final uriString = request.arguments?['uri'] as String?;
+ if (uriString == null) {
+ return _requiredUriParamMissing;
+ }
+ final content = request.arguments?['content'] as String?;
+ if (content == null) {
+ return _requiredContentParamMissing;
+ }
+
+ final uri = Uri.parse(uriString);
+ await dtd.writeFileAsString(uri, content);
+ return CallToolResult(content: [TextContent(text: 'Success')]);
+ }
+
/// Calls [callback] on the first active debug session, if available.
Future<CallToolResult> _callOnVmService({
required Future<CallToolResult> Function(VmService) callback,
@@ -530,6 +564,39 @@
inputSchema: ObjectSchema(),
);
+ static final readFileTool = Tool(
+ name: 'read_file',
+ description:
+ 'Reads a file by URI as a utf8 String. Requires "${connectTool.name}" '
+ 'to be successfully called first.',
+ inputSchema: Schema.object(
+ properties: {
+ 'uri': Schema.string(title: 'uri', description: 'The file uri to read'),
+ },
+ required: ['uri'],
+ ),
+ );
+
+ static final writeFileTool = Tool(
+ name: 'write_file',
+ description:
+ 'Writes a file by URI. Requires "${connectTool.name}" to be '
+ 'successfully called first.',
+ inputSchema: Schema.object(
+ properties: {
+ 'uri': Schema.string(
+ title: 'uri',
+ description: 'The file uri to write',
+ ),
+ 'content': Schema.string(
+ title: 'content',
+ description: 'The utf8 String contents to write to the file',
+ ),
+ },
+ required: ['uri', 'content'],
+ ),
+ );
+
static final _dtdNotConnected = CallToolResult(
isError: true,
content: [
@@ -569,6 +636,16 @@
),
],
);
+
+ static final _requiredUriParamMissing = CallToolResult(
+ isError: true,
+ content: [TextContent(text: 'Missing required parameter `uri`.')],
+ );
+
+ static final _requiredContentParamMissing = CallToolResult(
+ isError: true,
+ content: [TextContent(text: 'Missing required parameter `content`.')],
+ );
}
/// Adds the [getDebugSessions] method to [DartToolingDaemon], so that calling