Add the --platform and --empty arguments to the flutter create tool (#144)
diff --git a/pkgs/dart_mcp_server/lib/src/mixins/dash_cli.dart b/pkgs/dart_mcp_server/lib/src/mixins/dash_cli.dart
index 605ba1f..be32e73 100644
--- a/pkgs/dart_mcp_server/lib/src/mixins/dash_cli.dart
+++ b/pkgs/dart_mcp_server/lib/src/mixins/dash_cli.dart
@@ -102,6 +102,29 @@
),
);
}
+ final platforms =
+ ((args[ParameterNames.platform] as List?)?.cast<String>() ?? [])
+ .toSet();
+ if (projectType == 'flutter') {
+ // Platforms are ignored for Dart, so no need to validate them.
+ final invalidPlatforms = platforms.difference(_allowedFlutterPlatforms);
+ if (invalidPlatforms.isNotEmpty) {
+ final plural =
+ invalidPlatforms.length > 1
+ ? 'are not valid platforms'
+ : 'is not a valid platform';
+ errors.add(
+ ValidationError(
+ ValidationErrorType.itemInvalid,
+ path: [ParameterNames.platform],
+ details:
+ '${invalidPlatforms.join(',')} $plural. Platforms '
+ '${_allowedFlutterPlatforms.map((e) => '`$e`').join(', ')} '
+ 'are the only allowed values for the platform list argument.',
+ ),
+ );
+ }
+ }
if (errors.isNotEmpty) {
return CallToolResult(
@@ -117,6 +140,13 @@
final commandArgs = [
'create',
if (template != null && template.isNotEmpty) ...['--template', template],
+ if (projectType == 'flutter' && platforms.isNotEmpty)
+ '--platform=${platforms.join(',')}',
+ // Create an "empty" project by default so the LLM doesn't have to deal
+ // with all the boilerplate and comments.
+ if (projectType == 'flutter' &&
+ (args[ParameterNames.empty] as bool? ?? true))
+ '--empty',
directory,
];
@@ -188,8 +218,30 @@
description:
'The project template to use (e.g., "console-full", "app").',
),
+ ParameterNames.platform: Schema.list(
+ items: Schema.string(),
+ description:
+ 'The list of platforms this project supports. Only valid '
+ 'for Flutter projects. The allowed values are '
+ '${_allowedFlutterPlatforms.map((e) => '`$e`').join(', ')}. '
+ 'Defaults to creating a project for all platforms.',
+ ),
+ ParameterNames.empty: Schema.bool(
+ description:
+ 'Whether or not to create an "empty" project with minimized '
+ 'boilerplate and example code. Defaults to true.',
+ ),
},
required: [ParameterNames.directory, ParameterNames.projectType],
),
);
+
+ static const _allowedFlutterPlatforms = {
+ 'web',
+ 'linux',
+ 'macos',
+ 'windows',
+ 'android',
+ 'ios',
+ };
}
diff --git a/pkgs/dart_mcp_server/lib/src/utils/cli_utils.dart b/pkgs/dart_mcp_server/lib/src/utils/cli_utils.dart
index 9310546..44e2389 100644
--- a/pkgs/dart_mcp_server/lib/src/utils/cli_utils.dart
+++ b/pkgs/dart_mcp_server/lib/src/utils/cli_utils.dart
@@ -292,10 +292,11 @@
);
final rootSchema = Schema.string(
- title: 'The URI of the project root to run this tool in.',
+ title: 'The file URI of the project root to run this tool in.',
description:
'This must be equal to or a subdirectory of one of the roots '
- 'returned by a call to "listRoots".',
+ 'allowed by the client. Must be a URI with a `file:` '
+ 'scheme (e.g. file:///absolute/path/to/root).',
);
/// Very thin extension type for a pubspec just containing what we need.
diff --git a/pkgs/dart_mcp_server/lib/src/utils/constants.dart b/pkgs/dart_mcp_server/lib/src/utils/constants.dart
index f6fc543..a5a2729 100644
--- a/pkgs/dart_mcp_server/lib/src/utils/constants.dart
+++ b/pkgs/dart_mcp_server/lib/src/utils/constants.dart
@@ -9,10 +9,12 @@
static const column = 'column';
static const command = 'command';
static const directory = 'directory';
+ static const empty = 'empty';
static const line = 'line';
static const name = 'name';
static const packageName = 'packageName';
static const paths = 'paths';
+ static const platform = 'platform';
static const position = 'position';
static const projectType = 'projectType';
static const query = 'query';
diff --git a/pkgs/dart_mcp_server/lib/src/utils/process_manager.dart b/pkgs/dart_mcp_server/lib/src/utils/process_manager.dart
index af6786c..e8c52c1 100644
--- a/pkgs/dart_mcp_server/lib/src/utils/process_manager.dart
+++ b/pkgs/dart_mcp_server/lib/src/utils/process_manager.dart
@@ -16,5 +16,5 @@
/// implement this class and use [processManager] instead of making direct calls
/// to dart:io's [Process] class.
abstract interface class ProcessManagerSupport {
- LocalProcessManager get processManager;
+ ProcessManager get processManager;
}
diff --git a/pkgs/dart_mcp_server/test/tools/dart_cli_test.dart b/pkgs/dart_mcp_server/test/tools/dart_cli_test.dart
index 98caf8d..1473741 100644
--- a/pkgs/dart_mcp_server/test/tools/dart_cli_test.dart
+++ b/pkgs/dart_mcp_server/test/tools/dart_cli_test.dart
@@ -217,6 +217,7 @@
'create',
'--template',
'app',
+ '--empty',
'new_app',
],
workingDirectory: exampleFlutterAppRoot.path,
@@ -224,6 +225,66 @@
]);
});
+ test('creates a non-empty Flutter project', () async {
+ testHarness.mcpClient.addRoot(exampleFlutterAppRoot);
+ final request = CallToolRequest(
+ name: createProjectTool.name,
+ arguments: {
+ ParameterNames.root: exampleFlutterAppRoot.uri,
+ ParameterNames.directory: 'new_full_app',
+ ParameterNames.projectType: 'flutter',
+ ParameterNames.template: 'app',
+ ParameterNames.empty:
+ false, // Explicitly create a non-empty project
+ },
+ );
+ await testHarness.callToolWithRetry(request);
+
+ expect(testProcessManager.commandsRan, [
+ equalsCommand((
+ command: [
+ endsWith('flutter'),
+ 'create',
+ '--template',
+ 'app',
+ // Note: --empty is NOT present
+ 'new_full_app',
+ ],
+ workingDirectory: exampleFlutterAppRoot.path,
+ )),
+ ]);
+ });
+
+ test('fails with invalid platform for Flutter project', () async {
+ testHarness.mcpClient.addRoot(exampleFlutterAppRoot);
+ final request = CallToolRequest(
+ name: createProjectTool.name,
+ arguments: {
+ ParameterNames.root: exampleFlutterAppRoot.uri,
+ ParameterNames.directory: 'my_app_invalid_platform',
+ ParameterNames.projectType: 'flutter',
+ ParameterNames.platform: ['atari_jaguar', 'web'], // One invalid
+ },
+ );
+ final result = await testHarness.callToolWithRetry(
+ request,
+ expectError: true,
+ );
+
+ expect(result.isError, isTrue);
+ expect(
+ (result.content.first as TextContent).text,
+ allOf(
+ contains('atari_jaguar is not a valid platform.'),
+ contains(
+ 'Platforms `web`, `linux`, `macos`, `windows`, `android`, `ios` '
+ 'are the only allowed values',
+ ),
+ ),
+ );
+ expect(testProcessManager.commandsRan, isEmpty);
+ });
+
test('fails if projectType is missing', () async {
testHarness.mcpClient.addRoot(dartCliAppRoot);
final request = CallToolRequest(