feat: CLI server commands with named instances and --server flag#1
feat: CLI server commands with named instances and --server flag#1
Conversation
Introduces a TCP IPC layer so any MCP action can be invoked against a named, running server instance from plain CLI commands. New files: - lib/src/server_registry.dart — ServerRegistry + ServerEntry (reads/writes ~/.flutter_skill/servers/<id>.json) - lib/src/skill_server.dart — SkillServer: JSON-RPC 2.0 over TCP (+ optional Unix socket on macOS/Linux) - lib/src/skill_client.dart — SkillClient: resolves named servers and sends JSON-RPC requests - lib/src/cli/connect.dart — `flutter_skill connect --id=<name> [--port|--uri]` command - lib/src/cli/server_cmd.dart — `flutter_skill server list/stop/status` subcommands - lib/src/cli/output_format.dart — isCiEnvironment() + OutputFormat helpers Modified files: - lib/src/cli/launch.dart — adds --id=<name> and --detach flags; registers SkillServer on URI discovery - lib/src/cli/inspect.dart — adds --server=<id>[,<id2>,...] for parallel forwarding; --output flag - lib/src/cli/act.dart — adds --server=<id>[,<id2>,...] for parallel forwarding; --output flag - bin/flutter_skill.dart — routes `connect`, `servers`, and `server list/stop/status` to new handlers Usage examples: flutter_skill connect --id=myapp --port=50000 flutter_skill server list flutter_skill server stop --id=myapp flutter_skill tap "Login" --server=myapp flutter_skill screenshot --server=app-a,app-b # parallel
Code Review — InitialSummaryThe PR delivers the right architecture — TCP registry, Changed Files
🔴 CRITICAL — 3 issues[CRITICAL] Correctness —
[CRITICAL] Correctness —
[CRITICAL] Correctness —
🟠 MAJOR — 7 issues[MAJOR] Architecture —
[MAJOR] Architecture —
[MAJOR] Architecture —
[MAJOR] Correctness —
[MAJOR] Correctness —
[MAJOR] Security —
[MAJOR] Correctness —
🟡 MINOR — 5 issues[MINOR] Correctness —
[MINOR] Correctness —
[MINOR] Design —
[MINOR] Correctness —
[MINOR] Naming —
⚪ NIT — 3 issues[NIT] Redundancy —
[NIT] Correctness —
[NIT] Unused —
VerdictCHANGES REQUESTED — Two critical bugs (zombie processes, 365-day park hack) plus the major tool surface gap and |
- CRITICAL-1: add shutdown case to _dispatch in skill_server.dart; move ServerRegistry.unregister into catch block only in server_cmd.dart - CRITICAL-2: replace Future.delayed park hack with Completer in connect.dart - CRITICAL-3: fix Windows _isPidAlive false-positive with word-boundary matching - MAJOR-1: add hot_restart and scroll_to cases to _dispatch; add phase-1 comment - MAJOR-2: fix _spawnDetachedServer to detect dart run context and construct correct invocation - MAJOR-3: fix scroll direction hardcoded to 'up'; split scroll/scroll_to RPC cases - MAJOR-4: deduplicate _parseServerIds -> parseServerIds in output_format.dart; extract ServerCallResult replacing _ActResult/_ServerResult - MAJOR-5: add ID validation in ServerRegistry.register to prevent path traversal - MINOR-1: add https -> wss normalization in connect.dart - MINOR-2: replace as dynamic with proper type check in _vmServiceUri - MINOR-3: rename stripOutputFlag to stripOutputFormatFlag (keep old name as shim) - NIT-1: remove _padRight wrapper in server_cmd.dart; use .padRight() directly - NIT-2: remove unused findFreePort from skill_server.dart
Code Review — commit 5f6293fSummaryArchitecture is sound and first-pass fixes were applied correctly. However, two CRITICAL runtime failures remain ( 🔴 CRITICAL — 2 issues[CRITICAL] Correctness —
[CRITICAL] Correctness —
🟠 MAJOR — 6 issues[MAJOR] Correctness —
[MAJOR] Correctness —
[MAJOR] Correctness —
[MAJOR] Architecture —
[MAJOR] Architecture —
[MAJOR] Security —
🟡 MINOR — 4 issues[MINOR] State Management —
[MINOR] Correctness —
[MINOR] Naming —
[MINOR] Naming —
⚪ NIT — 2 issues[NIT] Code Duplication —
[NIT] Testing
VerdictCHANGES REQUESTED — Two CRITICAL bugs ( |
- CRITICAL-1: Add go_back case to _dispatch in skill_server.dart - CRITICAL-2: Fix scroll semantic mismatch in act.dart _buildRpcCall (scroll_to with key) - MAJOR-1: Add assert_visible, assert_gone, wait_for_element, get_text, find_element to _dispatch - MAJOR-2: Implement hot_restart with FlutterSkillClient.hotRestart() and hotReload fallback - MAJOR-3: Throw on null screenshot instead of silently returning null - MAJOR-4: Extract prune() from listAll() in server_registry.dart; update server_cmd.dart to call prune() before listing - MAJOR-5: Add ID validation (regex) to SkillClient.byId, ServerRegistry.get, ServerRegistry.unixSocketPath - MAJOR-6: Pass process to _attachServer in launch.dart; stop skill server when flutter run exits - MINOR-1: Replace _nextId instance field with local const 1 in SkillClient.call - MINOR-2: Add stopping guard in connect.dart doShutdown to prevent double-stop on concurrent signals - MINOR-3: Replace doc-comment deprecation with @deprecated annotation on stripOutputFlag - MINOR-4: Rename id to requestId in _handleLine to avoid shadowing the server id field - NIT-1: Extract callServersParallel helper into output_format.dart; refactor act.dart and inspect.dart to use it
Code Review — commit 4c647daSummaryCore architecture clean and well-motivated. Two CRITICAL bugs remain: 🔴 CRITICAL — 2 issues[CRITICAL] Correctness —
[CRITICAL] Correctness —
🟠 HIGH — 4 issues[HIGH] Correctness —
[HIGH] Correctness —
[HIGH] Correctness —
[HIGH] Testing
🟡 MEDIUM — 5 issues[MEDIUM] Correctness —
[MEDIUM] Correctness —
[MEDIUM] Architecture —
[MEDIUM] Correctness —
[MEDIUM] Security —
🔵 LOW — 4 issues[LOW] Correctness —
[LOW] Naming —
[LOW] Architecture —
[LOW] Code Duplication —
VerdictCHANGES REQUESTED — Two CRITICAL bugs ( |
- CRITICAL-1: scroll_to now calls FlutterSkillClient.scrollTo() with swipe fallback - CRITICAL-2: hotRestart throws UnsupportedError (was silently calling reloadSources) - HIGH-1: _buildRpcCall covers assert_visible/gone, wait_for_element, get_text, find_element, hot_reload, hot_restart - HIGH-2: multi-server screenshot derives per-server filenames via _deriveServerPath - HIGH-3: SkillServer exposes onShutdownRequested stream; shutdown case no longer calls exit(0); connect.dart listens to it for orderly cleanup - MEDIUM-1: _sendResult/_sendError catch socket write errors and destroy broken sockets - MEDIUM-2: StreamSubscription cancelled once response matched in skill_client.dart - MEDIUM-3: ServerRegistry.register rejects collision with a live (pid-alive) server - MEDIUM-4: flutter_skill ping subcommand added (ping_cmd.dart) with docs - LOW-1: URI normalisation in connect.dart uses Uri.parse to strip path before adding /ws - LOW-2: removed deprecated stripOutputFlag alias - LOW-3: launch.dart uses Platform.executable with .dart-script detection - LOW-4: _elementMatches static helper extracted; assert/wait/find cases use it
Code Review — commit 5c14c9fSummaryArchitecture sound, most prior feedback addressed. Five issues remain before merge: inconsistent JSON schemas between single/multi-server paths, 🟠 HIGH — 3 issues[HIGH] API Contract —
[HIGH] Correctness —
[HIGH] Correctness —
🟡 MEDIUM — 3 issues[MEDIUM] Correctness —
[MEDIUM] State Management —
[MEDIUM] Testing
🔵 LOW — 2 issues[LOW] Correctness —
[LOW] Architecture —
VerdictCHANGES REQUESTED — Inconsistent JSON schema and ignored |
Code Review — commit 91f240a (Round 5 / Final)SummaryWell-structured feature with solid security mitigations (path traversal, ID validation, collision guard, Windows PID word-boundary matching) and the 🟡 MEDIUM — 3 issues[MEDIUM] Correctness —
[MEDIUM] Correctness —
[MEDIUM] Correctness —
🔵 LOW — 7 issues[LOW] Correctness —
[LOW] Error Handling —
[LOW] API Contract —
[LOW] Code Duplication —
[LOW] Naming —
[LOW] Testing
[LOW] State Management —
Verdict✅ APPROVED WITH COMMENTS Architecture is correct, security mitigations are solid, and all previous CRITICAL/HIGH blockers are fully resolved. The two medium items (subscription not cancelled on timeout, |
Summary
Adds a named server registry + TCP IPC layer so every MCP action can also be invoked as a plain CLI command targeting a named, running server instance.
✨ New capabilities
flutter_skill connect --id=myapp --port=50000— attach to a running Flutter app and give it a persistent nameflutter_skill server list— table of all running named serversflutter_skill server stop --id=myapp— stop a named serverflutter_skill server status --id=myapp— show port, PID, project path, VM URIflutter_skill servers— shorthand forserver listflutter_skill ping --server=myapp— health check a named serverflutter_skill tap "Login" --server=myapp— run any action via a named serverflutter_skill screenshot --server=app-a,app-b— parallel execution across multiple servers🏗️ Architecture
lib/src/server_registry.dart~/.flutter_skill/servers/<id>.json; PID-alive filtering, collision guard, path-traversal validationlib/src/skill_server.dartlib/src/skill_client.dartlib/src/cli/connect.dartflutter_skill connectcommandlib/src/cli/server_cmd.dartflutter_skill server list/stop/statuslib/src/cli/ping_cmd.dartflutter_skill pingcommandlib/src/cli/output_format.dartisCiEnvironment()helper;--output=json|humanflag;callServersParallel()📝 Modified files (additive only — no breaking changes)
lib/src/cli/launch.dart— new--id=<name>and--detachflags; auto-registers a SkillServer when the VM URI is discovered; monitorsflutter runexit to stop the serverlib/src/cli/inspect.dart— new--server=<id>[,...]flag for parallel forwarding;--outputflag; consistent JSON schema with direct-VM pathlib/src/cli/act.dart— same--serverpattern for all act subcommands;--output=jsonrespected on both direct-VM and server-forwarding pathsbin/flutter_skill.dart— routesconnect,ping,servers,server list/stop/statusto new handlers while keepingserveralone as the MCP server🤖 CI output
When
CI,GITHUB_ACTIONS,CIRCLECI,TRAVIS, orBUILDKITEenv vars are set, output defaults to JSON. Always overridable with--output=jsonor--output=human.Single-server JSON output matches the same schema as the direct-VM path. Multi-server output wraps results in an array with per-server metadata.
Test plan
flutter_skill connect --id=myapp --port=50000registers entry in~/.flutter_skill/servers/myapp.jsonflutter_skill server listshows the registered serverflutter_skill tap "Submit" --server=myappforwards tap to the named serverflutter_skill screenshot --server=a,bruns in parallel and writesscreenshot_a.png/screenshot_b.pngflutter_skill server stop --id=myappremoves the registry entry and stops the server processflutter_skill ping --server=myappexits 0 when reachable, 1 when notlaunch,inspect,actwithout--server) behave identically to beforedart analyze lib/src/...reports no issues on new filesdart test test/server_registry_test.dart test/skill_server_client_test.dart