Skip to content

Commit 62234f7

Browse files
committed
Add the --platform and --empty arguments to the flutter create tool
1 parent e04e501 commit 62234f7

4 files changed

Lines changed: 123 additions & 2 deletions

File tree

pkgs/dart_mcp_server/lib/src/mixins/dash_cli.dart

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,29 @@ base mixin DashCliSupport on ToolsSupport, LoggingSupport, RootsTrackingSupport
9898
),
9999
);
100100
}
101+
final platforms =
102+
((args[ParameterNames.platform] as List?)?.cast<String>() ?? [])
103+
.toSet();
104+
if (projectType == 'flutter') {
105+
// Platforms are ignored for Dart, so no need to validate them.
106+
final invalidPlatforms = platforms.difference(_allowedFlutterPlatforms);
107+
if (invalidPlatforms.isNotEmpty) {
108+
final plural =
109+
invalidPlatforms.length > 1
110+
? 'are not valid platforms'
111+
: 'is not a valid platform';
112+
errors.add(
113+
ValidationError(
114+
ValidationErrorType.itemInvalid,
115+
path: [ParameterNames.platform],
116+
details:
117+
'${invalidPlatforms.join(',')} $plural. Platforms '
118+
'${_allowedFlutterPlatforms.map((e) => '`$e`').join(', ')} '
119+
'are the only allowed values for the platform list argument.',
120+
),
121+
);
122+
}
123+
}
101124

102125
if (errors.isNotEmpty) {
103126
return CallToolResult(
@@ -113,6 +136,13 @@ base mixin DashCliSupport on ToolsSupport, LoggingSupport, RootsTrackingSupport
113136
final commandArgs = [
114137
'create',
115138
if (template != null && template.isNotEmpty) ...['--template', template],
139+
if (projectType == 'flutter' && platforms.isNotEmpty)
140+
'--platform=${platforms.join(',')}',
141+
// Create an "empty" project by default so the LLM doesn't have to deal
142+
// with all the boilerplate and comments.
143+
if (projectType == 'flutter' &&
144+
(args[ParameterNames.empty] as bool? ?? true))
145+
'--empty',
116146
directory,
117147
];
118148

@@ -176,8 +206,30 @@ base mixin DashCliSupport on ToolsSupport, LoggingSupport, RootsTrackingSupport
176206
description:
177207
'The project template to use (e.g., "console-full", "app").',
178208
),
209+
ParameterNames.platform: Schema.list(
210+
items: Schema.string(),
211+
description:
212+
'The list of platforms this Flutter project supports. Only valid '
213+
'for Flutter projects. The allowed values are '
214+
'${_allowedFlutterPlatforms.map((e) => '`$e`').join(', ')}. '
215+
'Defaults to creating a project for all platforms.',
216+
),
217+
ParameterNames.empty: Schema.bool(
218+
description:
219+
'Whether or not to create an "empty" project with minimized '
220+
'boilerplate and example code. Defaults to true.',
221+
),
179222
},
180223
required: [ParameterNames.directory, ParameterNames.projectType],
181224
),
182225
);
226+
227+
static const _allowedFlutterPlatforms = {
228+
'web',
229+
'linux',
230+
'macos',
231+
'windows',
232+
'android',
233+
'ios',
234+
};
183235
}

pkgs/dart_mcp_server/lib/src/utils/constants.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ extension ParameterNames on Never {
99
static const column = 'column';
1010
static const command = 'command';
1111
static const directory = 'directory';
12+
static const empty = 'empty';
1213
static const line = 'line';
1314
static const name = 'name';
1415
static const packageName = 'packageName';
1516
static const paths = 'paths';
17+
static const platform = 'platform';
1618
static const position = 'position';
1719
static const projectType = 'projectType';
1820
static const query = 'query';

pkgs/dart_mcp_server/lib/src/utils/process_manager.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ import 'package:process/process.dart';
1616
/// implement this class and use [processManager] instead of making direct calls
1717
/// to dart:io's [Process] class.
1818
abstract interface class ProcessManagerSupport {
19-
LocalProcessManager get processManager;
19+
ProcessManager get processManager;
2020
}

pkgs/dart_mcp_server/test/tools/dart_cli_test.dart

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,12 +201,79 @@ dependencies:
201201

202202
expect(testProcessManager.commandsRan, [
203203
equalsCommand((
204-
command: ['flutter', 'create', '--template', 'app', 'new_app'],
204+
command: [
205+
'flutter',
206+
'create',
207+
'--template',
208+
'app',
209+
'--empty',
210+
'new_app',
211+
],
205212
workingDirectory: exampleFlutterAppRoot.path,
206213
)),
207214
]);
208215
});
209216

217+
test('creates a non-empty Flutter project', () async {
218+
testHarness.mcpClient.addRoot(exampleFlutterAppRoot);
219+
final request = CallToolRequest(
220+
name: createProjectTool.name,
221+
arguments: {
222+
ParameterNames.root: exampleFlutterAppRoot.uri,
223+
ParameterNames.directory: 'new_full_app',
224+
ParameterNames.projectType: 'flutter',
225+
ParameterNames.template: 'app',
226+
ParameterNames.empty:
227+
false, // Explicitly create a non-empty project
228+
},
229+
);
230+
await testHarness.callToolWithRetry(request);
231+
232+
expect(testProcessManager.commandsRan, [
233+
equalsCommand((
234+
command: [
235+
'flutter',
236+
'create',
237+
'--template',
238+
'app',
239+
// Note: --empty is NOT present
240+
'new_full_app',
241+
],
242+
workingDirectory: exampleFlutterAppRoot.path,
243+
)),
244+
]);
245+
});
246+
247+
test('fails with invalid platform for Flutter project', () async {
248+
testHarness.mcpClient.addRoot(exampleFlutterAppRoot);
249+
final request = CallToolRequest(
250+
name: createProjectTool.name,
251+
arguments: {
252+
ParameterNames.root: exampleFlutterAppRoot.uri,
253+
ParameterNames.directory: 'my_app_invalid_platform',
254+
ParameterNames.projectType: 'flutter',
255+
ParameterNames.platform: ['atari_jaguar', 'web'], // One invalid
256+
},
257+
);
258+
final result = await testHarness.callToolWithRetry(
259+
request,
260+
expectError: true,
261+
);
262+
263+
expect(result.isError, isTrue);
264+
expect(
265+
(result.content.first as TextContent).text,
266+
allOf(
267+
contains('atari_jaguar is not a valid platform.'),
268+
contains(
269+
'Platforms `web`, `linux`, `macos`, `windows`, `android`, `ios` '
270+
'are the only allowed values',
271+
),
272+
),
273+
);
274+
expect(testProcessManager.commandsRan, isEmpty);
275+
});
276+
210277
test('fails if projectType is missing', () async {
211278
testHarness.mcpClient.addRoot(dartCliAppRoot);
212279
final request = CallToolRequest(

0 commit comments

Comments
 (0)