Skip to content

feat: named server registry and --server flag for CLI commands#38

Open
vinifig wants to merge 6 commits intoai-dashboad:mainfrom
vinifig:main
Open

feat: named server registry and --server flag for CLI commands#38
vinifig wants to merge 6 commits intoai-dashboad:mainfrom
vinifig:main

Conversation

@vinifig
Copy link
Copy Markdown

@vinifig vinifig commented Apr 2, 2026

Idea

This PR introduces a named server registry to flutter_skill, drawing inspiration from how the Playwright CLI handles browser server instances — where you can start a server independently and then target it by name/endpoint from any command or test. The same pattern is applied here: instead of each command needing to discover and connect to a Flutter VM Service URI on its own, you attach once (flutter_skill connect), give the session a name, and every subsequent command just passes --server=<name>.

This makes flutter_skill feel more like a proper CLI tool for automation pipelines: stateless commands, named sessions, parallel execution across multiple apps.

Changes

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 commands

  • flutter_skill connect --id=myapp --port=50000 — attach to a running Flutter app and give it a persistent name
  • flutter_skill server list — table of all running named servers
  • flutter_skill server stop --id=myapp — stop a named server
  • flutter_skill server status --id=myapp — show port, PID, project path, VM URI
  • flutter_skill servers — shorthand for server list
  • flutter_skill ping --server=myapp — health check a named server
  • flutter_skill tap "Login" --server=myapp — run any action via a named server
  • flutter_skill screenshot --server=app-a,app-b — parallel execution across multiple servers

Architecture

File Role
lib/src/server_registry.dart Reads/writes ~/.flutter_skill/servers/<id>.json; PID-alive filtering, collision guard, path-traversal validation
lib/src/skill_server.dart JSON-RPC 2.0 server over TCP (+ Unix socket fast-path on macOS/Linux)
lib/src/skill_client.dart Resolves named server → port, sends JSON-RPC requests
lib/src/cli/connect.dart flutter_skill connect command
lib/src/cli/server_cmd.dart flutter_skill server list/stop/status
lib/src/cli/ping_cmd.dart flutter_skill ping command
lib/src/cli/output_format.dart isCiEnvironment() helper; --output=json|human flag; callServersParallel()

Modified files (additive only — no breaking changes)

  • lib/src/cli/launch.dart — new --id=<name> and --detach flags; auto-registers a SkillServer when the VM URI is discovered
  • lib/src/cli/inspect.dart — new --server=<id>[,...] flag for parallel forwarding; --output flag
  • lib/src/cli/act.dart — same --server pattern for all act subcommands
  • bin/flutter_skill.dart — routes new commands while keeping server alone as the MCP server

CI output

When CI, GITHUB_ACTIONS, CIRCLECI, TRAVIS, or BUILDKITE env vars are set, output defaults to JSON. Always overridable with --output=json or --output=human.

Reference

The full development history, code review rounds, and discussion are in vinifig#1.

Test plan

  • flutter_skill connect --id=myapp --port=50000 registers entry in ~/.flutter_skill/servers/myapp.json
  • flutter_skill server list shows the registered server
  • flutter_skill tap "Submit" --server=myapp forwards tap to the named server
  • flutter_skill screenshot --server=a,b runs in parallel and writes screenshot_a.png / screenshot_b.png
  • flutter_skill server stop --id=myapp removes the registry entry and stops the server process
  • flutter_skill ping --server=myapp exits 0 when reachable, 1 when not
  • All existing commands (launch, inspect, act without --server) behave identically to before
  • Unit tests pass: dart test test/server_registry_test.dart test/skill_server_client_test.dart

vinifig added 6 commits April 1, 2026 14:27
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
- 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
- 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
- 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
@vinifig
Copy link
Copy Markdown
Author

vinifig commented Apr 2, 2026

@charliewwdev and @carlye0304 may you guys take a look please?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant