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