@bdayadev/flutter-ultra-mcp 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +14 -0
- package/.mcp.json +67 -0
- package/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +108 -0
- package/LICENSE +201 -0
- package/README.md +77 -0
- package/SECURITY.md +19 -0
- package/dart/ultra_flutter/CHANGELOG.md +34 -0
- package/dart/ultra_flutter/EXTENSIONS.md +61 -0
- package/dart/ultra_flutter/README.md +109 -0
- package/dart/ultra_flutter/pubspec.yaml +37 -0
- package/dart/ultra_flutter_devtools/README.md +7 -0
- package/dart/ultra_flutter_devtools/pubspec.yaml +27 -0
- package/docs/UPSTREAM-PATROL-PRS.md +5 -0
- package/docs/UPSTREAM-SENTRY-PR.md +62 -0
- package/docs/discovery-empirics.md +435 -0
- package/examples/counter-app/README.md +24 -0
- package/examples/counter-app/pubspec.yaml +23 -0
- package/examples/oidc-app/README.md +48 -0
- package/examples/oidc-app/pubspec.yaml +24 -0
- package/package.json +82 -0
- package/packages/flutter-ultra-browser/README.md +29 -0
- package/packages/flutter-ultra-browser/package.json +39 -0
- package/packages/flutter-ultra-build/README.md +60 -0
- package/packages/flutter-ultra-build/package.json +38 -0
- package/packages/flutter-ultra-devtools/README.md +7 -0
- package/packages/flutter-ultra-devtools/package.json +36 -0
- package/packages/flutter-ultra-gesture/README.md +51 -0
- package/packages/flutter-ultra-gesture/package.json +58 -0
- package/packages/flutter-ultra-native-desktop/README.md +131 -0
- package/packages/flutter-ultra-native-desktop/package.json +81 -0
- package/packages/flutter-ultra-native-mobile/README.md +103 -0
- package/packages/flutter-ultra-native-mobile/package.json +72 -0
- package/packages/flutter-ultra-patrol/README.md +40 -0
- package/packages/flutter-ultra-patrol/package.json +38 -0
- package/packages/flutter-ultra-runtime/README.md +63 -0
- package/packages/flutter-ultra-runtime/package.json +69 -0
- package/shared/contracts/package.json +31 -0
- package/shared/device-router/README.md +51 -0
- package/shared/device-router/package.json +62 -0
- package/shared/keyring/README.md +7 -0
- package/shared/keyring/package.json +24 -0
- package/shared/mcp-runtime/README.md +116 -0
- package/shared/mcp-runtime/package.json +58 -0
- package/shared/state-store/README.md +66 -0
- package/shared/state-store/package.json +60 -0
- package/shared/vm-service-client/README.md +135 -0
- package/shared/vm-service-client/package.json +62 -0
- package/skills/_internal-on-tool-failure/SKILL.md +13 -0
- package/skills/_internal-session-bootstrap/SKILL.md +18 -0
- package/skills/debug/SKILL.md +20 -0
- package/skills/devtools/SKILL.md +21 -0
- package/skills/drive/SKILL.md +20 -0
- package/skills/scaffold/SKILL.md +21 -0
- package/skills/setup/SKILL.md +26 -0
- package/skills/test/SKILL.md +19 -0
- package/skills/tour/SKILL.md +22 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flutter-ultra/flutter-ultra-native-mobile",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "MCP server for native mobile overlays: Android UIAutomator via adb, iOS XCUITest via xcrun + go-ios. Solves Chrome Custom Tabs / SafariViewController OAuth via Playwright + deep-link intent dispatch.",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"homepage": "https://github.com/Bdaya-Dev/flutter-ultra-mcp/tree/main/packages/flutter-ultra-native-mobile",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Bdaya-Dev/flutter-ultra-mcp.git",
|
|
11
|
+
"directory": "packages/flutter-ultra-native-mobile"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/Bdaya-Dev/flutter-ultra-mcp/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"flutter-ultra",
|
|
19
|
+
"android",
|
|
20
|
+
"ios",
|
|
21
|
+
"adb",
|
|
22
|
+
"uiautomator",
|
|
23
|
+
"xcuitest",
|
|
24
|
+
"go-ios",
|
|
25
|
+
"oauth",
|
|
26
|
+
"chrome-custom-tabs"
|
|
27
|
+
],
|
|
28
|
+
"type": "module",
|
|
29
|
+
"main": "dist/index.js",
|
|
30
|
+
"module": "dist/index.js",
|
|
31
|
+
"types": "dist/index.d.ts",
|
|
32
|
+
"bin": {
|
|
33
|
+
"flutter-ultra-native-mobile": "dist/bin.js"
|
|
34
|
+
},
|
|
35
|
+
"exports": {
|
|
36
|
+
".": {
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"import": "./dist/index.js"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"sideEffects": false,
|
|
42
|
+
"files": [
|
|
43
|
+
"dist",
|
|
44
|
+
"README.md"
|
|
45
|
+
],
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsc -b",
|
|
48
|
+
"lint": "eslint src",
|
|
49
|
+
"test": "vitest run",
|
|
50
|
+
"typecheck": "tsc -b --noEmit",
|
|
51
|
+
"clean": "rimraf dist .turbo *.tsbuildinfo"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@flutter-ultra/mcp-runtime": "^0.0.1",
|
|
55
|
+
"@flutter-ultra/state-store": "^0.0.1",
|
|
56
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
57
|
+
"fast-xml-parser": "^4.5.0",
|
|
58
|
+
"playwright-core": "^1.49.0",
|
|
59
|
+
"zod": "^3.23.8"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"rimraf": "^6.0.0",
|
|
63
|
+
"typescript": "^5.6.0",
|
|
64
|
+
"vitest": "^2.1.0"
|
|
65
|
+
},
|
|
66
|
+
"engines": {
|
|
67
|
+
"node": ">=20"
|
|
68
|
+
},
|
|
69
|
+
"publishConfig": {
|
|
70
|
+
"access": "public"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# @flutter-ultra/flutter-ultra-patrol
|
|
2
|
+
|
|
3
|
+
MCP server orchestrating **Patrol E2E tests** across web, Android, iOS, and desktop. Wraps `patrol_cli` and bundles the [Bdaya patrol fork](https://github.com/Bdaya-Dev/patrol) as a submodule at `vendor/patrol/` so external contributors get the fork automatically with `git clone --recurse-submodules`.
|
|
4
|
+
|
|
5
|
+
## Tool catalogue (13 tools per plan §17B.1)
|
|
6
|
+
|
|
7
|
+
| Tool | Purpose |
|
|
8
|
+
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
9
|
+
| `list_tests` | Walk `integration_test/` + `patrol_test/`, return `[{file, testNames[], tags[]}]`. |
|
|
10
|
+
| `start_patrol_test` | MARATHON: spawn `patrol test ...`, return taskId immediately. |
|
|
11
|
+
| `poll_patrol_job` | Non-blocking status + rolling log tail for any marathon job. |
|
|
12
|
+
| `get_patrol_result` | Final structured `{passed, failed, skipped, durations, failures[]}` for a completed test job. |
|
|
13
|
+
| `cancel_patrol_job` | SIGTERM + SIGKILL grace period. Idempotent. |
|
|
14
|
+
| `start_patrol_develop` | MARATHON: warm `patrol develop` session for repeat runs. |
|
|
15
|
+
| `patrol_develop_run` | Dispatch named test inside the warm session (much faster than a fresh `patrol test`). |
|
|
16
|
+
| `patrol_hot_reload` | `r` / `R` to the develop session's stdin. |
|
|
17
|
+
| `take_patrol_screenshot` | CDP screenshot via the develop session (Bdaya `f26306f6`). |
|
|
18
|
+
| `start_patrol_recording` | Bdaya CDP-based GIF/webm recorder. |
|
|
19
|
+
| `stop_patrol_recording` | Finalize the active recording. |
|
|
20
|
+
| `get_patrol_browser_errors` | CDP-captured browser console errors (Bdaya `b591a390`). Defaults to warm session, falls back to most recent test job, or pass `taskId`. |
|
|
21
|
+
| `get_patrol_web_debugger_port` | Surfaces the CDP port the Bdaya fork prints at web target startup so Playwright / DevTools can co-attach. |
|
|
22
|
+
|
|
23
|
+
## Environment contract (from plugin `.mcp.json`)
|
|
24
|
+
|
|
25
|
+
| Var | Used for |
|
|
26
|
+
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
27
|
+
| `FLUTTER_ULTRA_PATROL_FORK` | Absolute path to `vendor/patrol/`. |
|
|
28
|
+
| `FLUTTER_ULTRA_STATE_DIR` | Future on-disk job persistence (in-memory in v1.0). |
|
|
29
|
+
| `PATROL_WEB_BROWSER_ARGS` | Comma-separated Chromium flags merged into every `--web-browser-args`. Default: `--enable-unsafe-swiftshader,--disable-renderer-backgrounding,--disable-background-timer-throttling`. |
|
|
30
|
+
| `FLUTTER_ULTRA_LOG_LEVEL` | `debug`/`info`/`warn`/`error`. Default `info`. |
|
|
31
|
+
|
|
32
|
+
## Invocation strategy
|
|
33
|
+
|
|
34
|
+
For each call we pick (in order):
|
|
35
|
+
|
|
36
|
+
1. `useRawCli: true` → skip wrapper detection.
|
|
37
|
+
2. `./scripts/run_patrol_web.ps1` (Windows) or `./scripts/run_patrol_web.sh` (Unix) at the project root → use it. Some projects standardize on this wrapper to pre-apply env vars + flags.
|
|
38
|
+
3. Otherwise spawn `dart run patrol_cli` from the project root, which resolves through `pubspec_overrides.yaml` to `vendor/patrol/packages/patrol_cli`.
|
|
39
|
+
|
|
40
|
+
Web invocations always set `--web-init-timeout 180000` by default (vs upstream's 60000 hardcode — see Bdaya fork PR #9 in `docs/UPSTREAM-PATROL-PRS.md`).
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flutter-ultra/flutter-ultra-patrol",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "MCP server for orchestrating patrol_cli E2E tests (web + Android + iOS) using the bundled Bdaya patrol fork.",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"bin": {
|
|
11
|
+
"flutter-ultra-patrol": "dist/bin.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc -b",
|
|
19
|
+
"lint": "eslint src",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"test:watch": "vitest",
|
|
22
|
+
"typecheck": "tsc -b --noEmit",
|
|
23
|
+
"clean": "rimraf dist .turbo *.tsbuildinfo"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
27
|
+
"zod": "^3.23.8"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^22.0.0",
|
|
31
|
+
"rimraf": "^6.0.0",
|
|
32
|
+
"typescript": "^5.6.0",
|
|
33
|
+
"vitest": "^2.1.0"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=20"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# @flutter-ultra/flutter-ultra-runtime
|
|
2
|
+
|
|
3
|
+
MCP server for Flutter **runtime control**: discover + attach to `flutter run`
|
|
4
|
+
debug sessions over DDS, introspect the widget tree (read-only), hot
|
|
5
|
+
reload/restart, evaluate Dart, capture HTTP/gRPC traffic, tail logs.
|
|
6
|
+
|
|
7
|
+
Part of the [flutter-ultra-mcp](https://github.com/Bdaya-Dev/flutter-ultra-mcp)
|
|
8
|
+
plugin. See plan §5.2 for the full catalogue.
|
|
9
|
+
|
|
10
|
+
## Tools (28)
|
|
11
|
+
|
|
12
|
+
| Group | Tools |
|
|
13
|
+
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
14
|
+
| Discovery + lifecycle | `discover_sessions`, `attach`, `detach`, `list_sessions`, `launch_app`, `poll_launch_app`, `stop_app`, `hot_reload`, `hot_restart` |
|
|
15
|
+
| Inspection (read-only) | `get_widget_tree`, `get_widget_details`, `get_selected_widget`, `set_selected_widget`, `widget_exists` (rev 23), `find_widget` (rev 23), `count_widget_tree_nodes`, `screenshot`, `dump_render_tree`, `dump_layer_tree`, `dump_semantics_tree`, `evaluate` |
|
|
16
|
+
| Toggles | `toggle_debug_paint`, `toggle_perf_overlay`, `set_time_dilation`, `set_platform_override` |
|
|
17
|
+
| Logs | `get_logs`, `start_tail_logs`, `poll_tail_logs`, `stop_tail_logs`, `get_runtime_errors`, `log_buffer_stats` |
|
|
18
|
+
| HTTP / gRPC capture | `start_http_capture`, `get_http_events`, `stop_http_capture`, `decode_grpc_message` |
|
|
19
|
+
|
|
20
|
+
All inspect tools are read-only side-effect-free; `widget_exists` / `find_widget`
|
|
21
|
+
walk `ext.flutter.inspector.getRootWidgetSummaryTree` per AC-R5.
|
|
22
|
+
|
|
23
|
+
## Session model
|
|
24
|
+
|
|
25
|
+
Sessions persist to `${CLAUDE_PLUGIN_DATA}/state/sessions.json` so the
|
|
26
|
+
gesture, devtools, and patrol servers can read the WS URI without IPC.
|
|
27
|
+
This server is the sole writer. Each session is a `VmServiceClient`
|
|
28
|
+
behind a reference-counted `SessionResource` — parallel tool calls share
|
|
29
|
+
one WebSocket per plan §17.10.
|
|
30
|
+
|
|
31
|
+
DDS multi-client coexistence with VS Code's Dart debugger is automatic;
|
|
32
|
+
our connection sets `setClientName('flutter-ultra/runtime/<pid>')` per
|
|
33
|
+
plan §7.2.
|
|
34
|
+
|
|
35
|
+
## Discovery ladder
|
|
36
|
+
|
|
37
|
+
Implemented per worker-P's empirical report
|
|
38
|
+
([`docs/discovery-empirics.md`](../../docs/discovery-empirics.md)):
|
|
39
|
+
|
|
40
|
+
1. Process scan for `dart` / `flutter` / `dartvm` / `chrome` with
|
|
41
|
+
`--enable-vm-service=<port>` in cmdline.
|
|
42
|
+
2. HTTP GET the raw VM port — the body redirects us to the DDS URI.
|
|
43
|
+
3. Convert `http://...` → `ws://.../ws`.
|
|
44
|
+
4. Probe `getVM` to confirm the URI is alive before returning.
|
|
45
|
+
|
|
46
|
+
## Acceptance criteria covered
|
|
47
|
+
|
|
48
|
+
- **AC-R1**: `hot_reload` completes < 5 s; `get_widget_tree` returns the
|
|
49
|
+
post-reload tree without re-attach.
|
|
50
|
+
- **AC-R2**: WS drop triggers exp-backoff reconnect inside
|
|
51
|
+
`@flutter-ultra/vm-service-client` (0.5/1/2/4/8/14.5 s).
|
|
52
|
+
- **AC-R3**: Per-session `SessionResource<VmServiceClient>` keeps two
|
|
53
|
+
attached sessions strictly isolated.
|
|
54
|
+
- **AC-R4**: `screenshot` returns a PNG ≥ 200 bytes.
|
|
55
|
+
- **AC-R5 (rev 23)**: `widget_exists({kind:'key', value:'X'})` returns
|
|
56
|
+
`{exists, count, bounds?}` in < 300 ms on a 500-node tree without
|
|
57
|
+
triggering setState / hot-reload / route navigation side effects
|
|
58
|
+
(verified via the summary tree extension which is explicitly
|
|
59
|
+
side-effect-free).
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
|
|
63
|
+
Apache-2.0.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flutter-ultra/flutter-ultra-runtime",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "MCP server for Flutter runtime: discover + attach to flutter run sessions over DDS, introspect VM Service, hot reload, screenshots, HTTP/gRPC capture.",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"homepage": "https://github.com/Bdaya-Dev/flutter-ultra-mcp/tree/main/packages/flutter-ultra-runtime",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Bdaya-Dev/flutter-ultra-mcp.git",
|
|
11
|
+
"directory": "packages/flutter-ultra-runtime"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/Bdaya-Dev/flutter-ultra-mcp/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"flutter",
|
|
19
|
+
"dart",
|
|
20
|
+
"vm-service",
|
|
21
|
+
"dds",
|
|
22
|
+
"hot-reload"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "dist/index.js",
|
|
26
|
+
"module": "dist/index.js",
|
|
27
|
+
"types": "dist/index.d.ts",
|
|
28
|
+
"bin": {
|
|
29
|
+
"flutter-ultra-runtime": "dist/bin.js"
|
|
30
|
+
},
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"import": "./dist/index.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"sideEffects": false,
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"README.md"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsc -b",
|
|
44
|
+
"lint": "eslint src",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"typecheck": "tsc -b --noEmit",
|
|
47
|
+
"clean": "rimraf dist .turbo *.tsbuildinfo"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@flutter-ultra/device-router": "^0.0.1",
|
|
51
|
+
"@flutter-ultra/mcp-runtime": "^0.0.1",
|
|
52
|
+
"@flutter-ultra/state-store": "^0.0.1",
|
|
53
|
+
"@flutter-ultra/vm-service-client": "^0.0.1",
|
|
54
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
55
|
+
"protobufjs": "^7.4.0",
|
|
56
|
+
"zod": "^3.23.8"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"rimraf": "^6.0.0",
|
|
60
|
+
"typescript": "^5.6.0",
|
|
61
|
+
"vitest": "^2.1.0"
|
|
62
|
+
},
|
|
63
|
+
"engines": {
|
|
64
|
+
"node": ">=20"
|
|
65
|
+
},
|
|
66
|
+
"publishConfig": {
|
|
67
|
+
"access": "public"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flutter-ultra/contracts",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "JSON Schema contract definitions for ext.flutter.ultra.* wire format — single source of truth for TS↔Dart interop.",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"ext-flutter-ultra",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -b",
|
|
17
|
+
"lint": "eslint src",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:contract": "vitest run",
|
|
20
|
+
"typecheck": "tsc -b --noEmit",
|
|
21
|
+
"clean": "rimraf dist .turbo *.tsbuildinfo"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"ajv": "^8.20.0",
|
|
25
|
+
"ajv-formats": "^3.0.1",
|
|
26
|
+
"zod": "^3.24.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"vitest": "^3.2.4"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# @flutter-ultra/device-router
|
|
2
|
+
|
|
3
|
+
Device abstraction for flutter-ultra-mcp: run commands on local, WSL, or SSH-remote targets from a single interface.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { DeviceRouter } from '@flutter-ultra/device-router';
|
|
9
|
+
|
|
10
|
+
const router = new DeviceRouter();
|
|
11
|
+
|
|
12
|
+
// List available devices (local + WSL distros + SSH hosts)
|
|
13
|
+
const devices = await router.listAvailable();
|
|
14
|
+
|
|
15
|
+
// Connect to a WSL distro
|
|
16
|
+
const { device, probe } = await router.connect({ kind: 'wsl', distro: 'Ubuntu-22.04' });
|
|
17
|
+
|
|
18
|
+
// Run a command on the device
|
|
19
|
+
const result = await device.exec(['flutter', 'build', 'linux', '--release']);
|
|
20
|
+
|
|
21
|
+
// Forward a TCP port from the remote device to local
|
|
22
|
+
const fwd = await device.forwardTcpPort('localhost', 8080);
|
|
23
|
+
console.log(`VM service available at ws://localhost:${fwd.localPort}/ws`);
|
|
24
|
+
|
|
25
|
+
// Clean up
|
|
26
|
+
await fwd.close();
|
|
27
|
+
await router.disconnect(device.id);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Device types
|
|
31
|
+
|
|
32
|
+
| Kind | Transport | Platform | Use case |
|
|
33
|
+
| ------- | --------------------- | ----------- | --------------------------------------- |
|
|
34
|
+
| `local` | `child_process` | Host OS | Default — same machine |
|
|
35
|
+
| `wsl` | `wsl.exe -d <distro>` | Linux | Flutter Linux builds/tests from Windows |
|
|
36
|
+
| `ssh` | SSH ControlMaster | Linux/macOS | Flutter macOS builds via remote Mac |
|
|
37
|
+
|
|
38
|
+
## Legacy adapter
|
|
39
|
+
|
|
40
|
+
Existing code using worker-J's v1 Device shape (`label/isLocal/exec/uploadFile/fileExists/openRpcStream`) can use the `LegacyDeviceAdapter`:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { LegacyDeviceAdapter } from '@flutter-ultra/device-router';
|
|
44
|
+
|
|
45
|
+
const legacy = new LegacyDeviceAdapter(device);
|
|
46
|
+
// legacy.label, legacy.isLocal, legacy.exec(), legacy.fileExists(), legacy.openRpcStream()
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
|
|
51
|
+
Apache-2.0
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flutter-ultra/device-router",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Device abstraction for flutter-ultra-mcp: run commands on local, WSL, or SSH-remote targets from a single interface.",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"homepage": "https://github.com/Bdaya-Dev/flutter-ultra-mcp/tree/main/shared/device-router",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Bdaya-Dev/flutter-ultra-mcp.git",
|
|
11
|
+
"directory": "shared/device-router"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/Bdaya-Dev/flutter-ultra-mcp/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"flutter",
|
|
18
|
+
"mcp",
|
|
19
|
+
"device",
|
|
20
|
+
"wsl",
|
|
21
|
+
"ssh",
|
|
22
|
+
"remote"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "dist/index.js",
|
|
26
|
+
"module": "dist/index.js",
|
|
27
|
+
"types": "dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"sideEffects": false,
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"README.md"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc -b",
|
|
41
|
+
"lint": "eslint src",
|
|
42
|
+
"test": "vitest run",
|
|
43
|
+
"typecheck": "tsc -b --noEmit",
|
|
44
|
+
"clean": "rimraf dist .turbo *.tsbuildinfo"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"execa": "^9.5.1",
|
|
48
|
+
"ssh-config": "^5.0.0",
|
|
49
|
+
"zod": "^3.25.76"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"rimraf": "^6.0.0",
|
|
53
|
+
"typescript": "^5.6.0",
|
|
54
|
+
"vitest": "^2.1.0"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=20"
|
|
58
|
+
},
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# @flutter-ultra/keyring
|
|
2
|
+
|
|
3
|
+
OS-keyring-backed secret storage for plugin-managed credentials: signing certs, OIDC tokens, Sentry DSNs.
|
|
4
|
+
|
|
5
|
+
**Status:** scaffold stub. Implementation owner: shared infra (wave 2).
|
|
6
|
+
|
|
7
|
+
Cross-platform via native APIs — Windows Credential Manager, macOS Keychain, Linux Secret Service. Plain-filesystem fallback is intentionally absent to preserve the plan §19 security guarantee.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flutter-ultra/keyring",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "OS-keyring-backed secret storage for plugin-managed credentials (signing certs, OIDC tokens, Sentry DSNs).",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc -b",
|
|
16
|
+
"lint": "eslint src",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"typecheck": "tsc -b --noEmit",
|
|
19
|
+
"clean": "rimraf dist .turbo *.tsbuildinfo"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@napi-rs/keyring": "^1.3.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# @flutter-ultra/mcp-runtime
|
|
2
|
+
|
|
3
|
+
Shared MCP server scaffolding for the flutter-ultra-mcp plugin's 8 servers.
|
|
4
|
+
Encapsulates the recurring patterns — stdio transport boot, Zod-validated
|
|
5
|
+
tools, watchdog/timeout enforcement, keep-alive against
|
|
6
|
+
[Claude Code #58004](https://github.com/anthropics/claude-code/issues/58004),
|
|
7
|
+
the cross-server session model, and the canonical FinderSpec for widget
|
|
8
|
+
lookups — so each server's `src/index.ts` stays focused on its tool catalogue.
|
|
9
|
+
|
|
10
|
+
## Quick start
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { createServer } from '@flutter-ultra/mcp-runtime';
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
|
|
16
|
+
const server = createServer({
|
|
17
|
+
info: { name: 'flutter-ultra-runtime', version: '0.0.1' },
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
server.defineTool(
|
|
21
|
+
{
|
|
22
|
+
name: 'list_sessions',
|
|
23
|
+
description: 'List active Flutter sessions attached by this server.',
|
|
24
|
+
timeoutClass: 'instant',
|
|
25
|
+
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
26
|
+
},
|
|
27
|
+
async () => ({
|
|
28
|
+
sessions: [
|
|
29
|
+
/* ... */
|
|
30
|
+
],
|
|
31
|
+
}),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
server.defineTool(
|
|
35
|
+
{
|
|
36
|
+
name: 'hot_reload',
|
|
37
|
+
description: 'Trigger a hot reload on the given session.',
|
|
38
|
+
inputShape: { sessionId: z.string().min(8) },
|
|
39
|
+
timeoutClass: 'quick',
|
|
40
|
+
ceilingMs: 60_000,
|
|
41
|
+
},
|
|
42
|
+
async ({ sessionId }, { signal, sendProgress }) => {
|
|
43
|
+
sendProgress({ progress: 0, message: 'Sending reloadSources to VM service' });
|
|
44
|
+
// ... handler body with signal-aware aborts
|
|
45
|
+
return { ok: true, sessionId };
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
await server.start();
|
|
50
|
+
process.on('SIGTERM', () => server.stop());
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Components
|
|
54
|
+
|
|
55
|
+
| Module | Exports |
|
|
56
|
+
| -------------- | ------------------------------------------------------------------------------------------------------ |
|
|
57
|
+
| `server.ts` | `createServer`, `defineTool` — high-level builder over `@modelcontextprotocol/sdk` |
|
|
58
|
+
| `watchdog.ts` | `runWithWatchdog`, `TimeoutClass`, `DEFAULT_CEILINGS_MS` — per-tool hard cap + AbortSignal propagation |
|
|
59
|
+
| `keepAlive.ts` | `startKeepAlive` — periodic `notifications/message` debug ping (plan §17.9) |
|
|
60
|
+
| `logger.ts` | `createLogger` — JSON-lines stderr logger with per-tool child loggers |
|
|
61
|
+
| `session.ts` | `Session`, `SessionResource`, `SessionsFile` — cross-server session model |
|
|
62
|
+
| `finder.ts` | `FinderSchema`, `FinderSpec`, `matchesText` — shared discriminated union for widget lookups |
|
|
63
|
+
| `errors.ts` | `ToolWatchdogTimeoutError`, `SessionNotFoundError`, etc. |
|
|
64
|
+
|
|
65
|
+
## Timeout classes (plan §17.2)
|
|
66
|
+
|
|
67
|
+
| Class | Ceiling | When to use |
|
|
68
|
+
| ---------- | ------- | ------------------------------------------------------------------ |
|
|
69
|
+
| `instant` | 10 s | < 1 s p95: `list_sessions`, `get_widget_tree` on small trees |
|
|
70
|
+
| `quick` | 30 s | 1–5 s p95: `hot_reload`, `screenshot`, `tap` |
|
|
71
|
+
| `long` | 55 s | 5–55 s p95: `dump_render_tree`, `wait_for` (always emit progress) |
|
|
72
|
+
| `marathon` | 55 s | > 55 s: MUST be split-tool (`start_*` / `poll_*` / `get_*_result`) |
|
|
73
|
+
|
|
74
|
+
Per-tool override via env var: `FLUTTER_ULTRA_TOOL_TIMEOUT_<NAME_UPPER>=120000`.
|
|
75
|
+
|
|
76
|
+
## Session model
|
|
77
|
+
|
|
78
|
+
Sessions live as JSON in `${CLAUDE_PLUGIN_DATA}/state/sessions.json` (path
|
|
79
|
+
resolved by `@flutter-ultra/state-store`). The runtime server is the sole
|
|
80
|
+
writer; gesture / devtools / patrol read-only. Each session carries:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
{ id, uri, source, clientName, attachedAt, lastSeenAt,
|
|
84
|
+
status, pid?, projectRoot?, device?, isolateIds?, appName? }
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`SessionResource<T>` reference-counts an expensive per-session resource
|
|
88
|
+
(e.g. the `VmServiceClient` WebSocket) so multiple parallel tool calls
|
|
89
|
+
share one connection per plan §17.10.
|
|
90
|
+
|
|
91
|
+
## FinderSpec
|
|
92
|
+
|
|
93
|
+
Discriminated union used by both runtime (`widget_exists`, `find_widget`)
|
|
94
|
+
and gesture (`tap`, `enter_text`, `wait_for`) so an agent's "is the widget
|
|
95
|
+
in the tree?" check uses the exact same matcher as the subsequent tap:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
type FinderSpec =
|
|
99
|
+
| { kind: 'key'; value: string }
|
|
100
|
+
| {
|
|
101
|
+
kind: 'text';
|
|
102
|
+
value: string;
|
|
103
|
+
matchType?: 'exact' | 'contains' | 'regex';
|
|
104
|
+
caseInsensitive?: boolean;
|
|
105
|
+
}
|
|
106
|
+
| { kind: 'type'; value: string }
|
|
107
|
+
| { kind: 'coords'; x: number; y: number }
|
|
108
|
+
| { kind: 'semanticsLabel'; value: string; matchType?: 'exact' | 'contains' | 'regex' }
|
|
109
|
+
| { kind: 'tooltip'; value: string; matchType?: 'exact' | 'contains' | 'regex' };
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`matchesText(candidate, spec)` is the shared comparator both consumers use.
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
Apache-2.0. See [LICENSE](../../LICENSE).
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flutter-ultra/mcp-runtime",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Shared MCP server scaffolding: stdio transport, watchdog/timeout, structured logging, session model, finder spec.",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"homepage": "https://github.com/Bdaya-Dev/flutter-ultra-mcp/tree/main/shared/mcp-runtime",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Bdaya-Dev/flutter-ultra-mcp.git",
|
|
11
|
+
"directory": "shared/mcp-runtime"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/Bdaya-Dev/flutter-ultra-mcp/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"model-context-protocol",
|
|
19
|
+
"flutter-ultra"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "dist/index.js",
|
|
23
|
+
"module": "dist/index.js",
|
|
24
|
+
"types": "dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"sideEffects": false,
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"README.md"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsc -b",
|
|
38
|
+
"lint": "eslint src",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"typecheck": "tsc -b --noEmit",
|
|
41
|
+
"clean": "rimraf dist .turbo *.tsbuildinfo"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
45
|
+
"zod": "^3.23.8"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"rimraf": "^6.0.0",
|
|
49
|
+
"typescript": "^5.6.0",
|
|
50
|
+
"vitest": "^2.1.0"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=20"
|
|
54
|
+
},
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public"
|
|
57
|
+
}
|
|
58
|
+
}
|