@bdayadev/flutter-ultra-mcp 0.0.0 → 1.0.1
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/marketplace.json +35 -0
- package/.claude-plugin/plugin.json +14 -14
- package/.mcp.json +67 -67
- package/CODE_OF_CONDUCT.md +83 -83
- package/CONTRIBUTING.md +108 -108
- package/LICENSE +201 -201
- package/README.md +77 -77
- package/SECURITY.md +19 -19
- package/dart/ultra_flutter/CHANGELOG.md +34 -34
- package/dart/ultra_flutter/EXTENSIONS.md +61 -61
- package/dart/ultra_flutter/README.md +109 -109
- package/dart/ultra_flutter/pubspec.yaml +37 -37
- package/dart/ultra_flutter_devtools/README.md +7 -7
- package/dart/ultra_flutter_devtools/pubspec.yaml +27 -27
- package/docs/UPSTREAM-PATROL-PRS.md +5 -5
- package/docs/UPSTREAM-SENTRY-PR.md +62 -62
- package/docs/discovery-empirics.md +435 -435
- package/examples/counter-app/README.md +24 -24
- package/examples/counter-app/pubspec.yaml +23 -23
- package/examples/oidc-app/README.md +48 -48
- package/examples/oidc-app/pubspec.yaml +24 -24
- package/package.json +82 -82
- package/packages/flutter-ultra-browser/README.md +29 -29
- package/packages/flutter-ultra-browser/package.json +39 -39
- package/packages/flutter-ultra-build/README.md +60 -60
- package/packages/flutter-ultra-build/package.json +38 -38
- package/packages/flutter-ultra-devtools/README.md +7 -7
- package/packages/flutter-ultra-devtools/package.json +36 -36
- package/packages/flutter-ultra-gesture/README.md +51 -51
- package/packages/flutter-ultra-gesture/package.json +58 -58
- package/packages/flutter-ultra-native-desktop/README.md +131 -131
- package/packages/flutter-ultra-native-desktop/package.json +81 -81
- package/packages/flutter-ultra-native-mobile/README.md +103 -103
- package/packages/flutter-ultra-native-mobile/package.json +72 -72
- package/packages/flutter-ultra-patrol/README.md +40 -40
- package/packages/flutter-ultra-patrol/package.json +38 -38
- package/packages/flutter-ultra-runtime/README.md +63 -63
- package/packages/flutter-ultra-runtime/package.json +69 -69
- package/shared/contracts/package.json +31 -31
- package/shared/device-router/README.md +51 -51
- package/shared/device-router/package.json +62 -62
- package/shared/keyring/README.md +7 -7
- package/shared/keyring/package.json +24 -24
- package/shared/mcp-runtime/README.md +116 -116
- package/shared/mcp-runtime/package.json +58 -58
- package/shared/state-store/README.md +66 -66
- package/shared/state-store/package.json +60 -60
- package/shared/vm-service-client/README.md +135 -135
- package/shared/vm-service-client/package.json +62 -62
- package/skills/_internal-on-tool-failure/SKILL.md +13 -13
- package/skills/_internal-session-bootstrap/SKILL.md +18 -18
- package/skills/debug/SKILL.md +20 -20
- package/skills/devtools/SKILL.md +21 -21
- package/skills/drive/SKILL.md +20 -20
- package/skills/scaffold/SKILL.md +21 -21
- package/skills/setup/SKILL.md +26 -26
- package/skills/test/SKILL.md +19 -19
- package/skills/tour/SKILL.md +22 -22
|
@@ -1,51 +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
|
|
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
|
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@flutter-ultra/device-router",
|
|
3
|
-
"version": "0.0.1",
|
|
4
|
-
"private":
|
|
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
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@flutter-ultra/device-router",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
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
|
+
}
|
package/shared/keyring/README.md
CHANGED
|
@@ -1,7 +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.
|
|
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.
|
|
@@ -1,24 +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
|
-
}
|
|
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
|
+
}
|
|
@@ -1,116 +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).
|
|
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).
|
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@flutter-ultra/mcp-runtime",
|
|
3
|
-
"version": "0.0.1",
|
|
4
|
-
"private":
|
|
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
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@flutter-ultra/mcp-runtime",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
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
|
+
}
|