@wa008/ui-audit-mcp 1.0.2
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/LICENSE +21 -0
- package/README.md +75 -0
- package/dist/device/adapter.d.ts +45 -0
- package/dist/device/adapter.d.ts.map +1 -0
- package/dist/device/adapter.js +137 -0
- package/dist/device/adapter.js.map +1 -0
- package/dist/evaluation/checklist.d.ts +14 -0
- package/dist/evaluation/checklist.d.ts.map +1 -0
- package/dist/evaluation/checklist.js +57 -0
- package/dist/evaluation/checklist.js.map +1 -0
- package/dist/evaluation/scorer.d.ts +7 -0
- package/dist/evaluation/scorer.d.ts.map +1 -0
- package/dist/evaluation/scorer.js +35 -0
- package/dist/evaluation/scorer.js.map +1 -0
- package/dist/examples/agent-demo.d.ts +2 -0
- package/dist/examples/agent-demo.d.ts.map +1 -0
- package/dist/examples/agent-demo.js +79 -0
- package/dist/examples/agent-demo.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +75 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/evaluation-logger.d.ts +20 -0
- package/dist/logger/evaluation-logger.d.ts.map +1 -0
- package/dist/logger/evaluation-logger.js +93 -0
- package/dist/logger/evaluation-logger.js.map +1 -0
- package/dist/maestro/adapter.d.ts +37 -0
- package/dist/maestro/adapter.d.ts.map +1 -0
- package/dist/maestro/adapter.js +139 -0
- package/dist/maestro/adapter.js.map +1 -0
- package/dist/src/device/adapter.d.ts +45 -0
- package/dist/src/device/adapter.d.ts.map +1 -0
- package/dist/src/device/adapter.js +137 -0
- package/dist/src/device/adapter.js.map +1 -0
- package/dist/src/evaluation/checklist.d.ts +14 -0
- package/dist/src/evaluation/checklist.d.ts.map +1 -0
- package/dist/src/evaluation/checklist.js +57 -0
- package/dist/src/evaluation/checklist.js.map +1 -0
- package/dist/src/evaluation/scorer.d.ts +7 -0
- package/dist/src/evaluation/scorer.d.ts.map +1 -0
- package/dist/src/evaluation/scorer.js +35 -0
- package/dist/src/evaluation/scorer.js.map +1 -0
- package/dist/src/index.d.ts +23 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +78 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/logger/evaluation-logger.d.ts +20 -0
- package/dist/src/logger/evaluation-logger.d.ts.map +1 -0
- package/dist/src/logger/evaluation-logger.js +93 -0
- package/dist/src/logger/evaluation-logger.js.map +1 -0
- package/dist/src/tools/evaluate-style.d.ts +30 -0
- package/dist/src/tools/evaluate-style.d.ts.map +1 -0
- package/dist/src/tools/evaluate-style.js +57 -0
- package/dist/src/tools/evaluate-style.js.map +1 -0
- package/dist/src/tools/get-checklist.d.ts +27 -0
- package/dist/src/tools/get-checklist.d.ts.map +1 -0
- package/dist/src/tools/get-checklist.js +57 -0
- package/dist/src/tools/get-checklist.js.map +1 -0
- package/dist/src/tools/get-log.d.ts +25 -0
- package/dist/src/tools/get-log.d.ts.map +1 -0
- package/dist/src/tools/get-log.js +33 -0
- package/dist/src/tools/get-log.js.map +1 -0
- package/dist/src/tools/launch-app.d.ts +19 -0
- package/dist/src/tools/launch-app.d.ts.map +1 -0
- package/dist/src/tools/launch-app.js +24 -0
- package/dist/src/tools/launch-app.js.map +1 -0
- package/dist/src/tools/list-apps.d.ts +28 -0
- package/dist/src/tools/list-apps.d.ts.map +1 -0
- package/dist/src/tools/list-apps.js +75 -0
- package/dist/src/tools/list-apps.js.map +1 -0
- package/dist/src/tools/submit-evaluation.d.ts +57 -0
- package/dist/src/tools/submit-evaluation.d.ts.map +1 -0
- package/dist/src/tools/submit-evaluation.js +107 -0
- package/dist/src/tools/submit-evaluation.js.map +1 -0
- package/dist/src/tools/swipe.d.ts +28 -0
- package/dist/src/tools/swipe.d.ts.map +1 -0
- package/dist/src/tools/swipe.js +26 -0
- package/dist/src/tools/swipe.js.map +1 -0
- package/dist/src/tools/take-screenshot.d.ts +26 -0
- package/dist/src/tools/take-screenshot.d.ts.map +1 -0
- package/dist/src/tools/take-screenshot.js +32 -0
- package/dist/src/tools/take-screenshot.js.map +1 -0
- package/dist/src/tools/tap.d.ts +22 -0
- package/dist/src/tools/tap.d.ts.map +1 -0
- package/dist/src/tools/tap.js +24 -0
- package/dist/src/tools/tap.js.map +1 -0
- package/dist/src/types.d.ts +57 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +5 -0
- package/dist/src/types.js.map +1 -0
- package/dist/test-agent.d.ts +2 -0
- package/dist/test-agent.d.ts.map +1 -0
- package/dist/test-agent.js.map +1 -0
- package/dist/tools/evaluate-style.d.ts +30 -0
- package/dist/tools/evaluate-style.d.ts.map +1 -0
- package/dist/tools/evaluate-style.js +57 -0
- package/dist/tools/evaluate-style.js.map +1 -0
- package/dist/tools/get-checklist.d.ts +27 -0
- package/dist/tools/get-checklist.d.ts.map +1 -0
- package/dist/tools/get-checklist.js +57 -0
- package/dist/tools/get-checklist.js.map +1 -0
- package/dist/tools/get-log.d.ts +25 -0
- package/dist/tools/get-log.d.ts.map +1 -0
- package/dist/tools/get-log.js +33 -0
- package/dist/tools/get-log.js.map +1 -0
- package/dist/tools/launch-app.d.ts +19 -0
- package/dist/tools/launch-app.d.ts.map +1 -0
- package/dist/tools/launch-app.js +24 -0
- package/dist/tools/launch-app.js.map +1 -0
- package/dist/tools/submit-evaluation.d.ts +57 -0
- package/dist/tools/submit-evaluation.d.ts.map +1 -0
- package/dist/tools/submit-evaluation.js +107 -0
- package/dist/tools/submit-evaluation.js.map +1 -0
- package/dist/tools/swipe.d.ts +28 -0
- package/dist/tools/swipe.d.ts.map +1 -0
- package/dist/tools/swipe.js +26 -0
- package/dist/tools/swipe.js.map +1 -0
- package/dist/tools/take-screenshot.d.ts +26 -0
- package/dist/tools/take-screenshot.d.ts.map +1 -0
- package/dist/tools/take-screenshot.js +32 -0
- package/dist/tools/take-screenshot.js.map +1 -0
- package/dist/tools/tap.d.ts +22 -0
- package/dist/tools/tap.d.ts.map +1 -0
- package/dist/tools/tap.js +24 -0
- package/dist/tools/tap.js.map +1 -0
- package/dist/types.d.ts +57 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wa008
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# UI Audit MCP
|
|
2
|
+
|
|
3
|
+
An MCP server for iOS app UI evaluation and testing.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
- **Device Control**: Launch apps, take screenshots, tap, and swipe on iOS Simulators.
|
|
7
|
+
- **UI Evaluation**: Structured checklists for quality and style consistency.
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
- **Node.js 18+**
|
|
11
|
+
- **Xcode CLI Tools**: `xcode-select --install` (for `xcrun simctl`)
|
|
12
|
+
- **idb**: Required for tap/swipe.
|
|
13
|
+
```bash
|
|
14
|
+
brew install idb-companion
|
|
15
|
+
pip3 install fb-idb
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Install (via npx)
|
|
19
|
+
Add this to your MCP client config (e.g., `claude_desktop_config.json`):
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"mcpServers": {
|
|
23
|
+
"ui-audit": {
|
|
24
|
+
"command": "npx",
|
|
25
|
+
"args": ["-y", "@wa007/ui-audit-mcp"]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
*(Note: Requires the package to be published to NPM. For local development, use the Setup steps below.)*
|
|
31
|
+
|
|
32
|
+
## Manual Setup
|
|
33
|
+
```bash
|
|
34
|
+
git clone https://github.com/wa008/ui-audit-mcp.git
|
|
35
|
+
cd ui-audit-mcp
|
|
36
|
+
npm install
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Manual Config
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"mcpServers": {
|
|
43
|
+
"ui-audit": {
|
|
44
|
+
"command": "node",
|
|
45
|
+
"args": ["/absolute/path/to/ui-audit-mcp/dist/src/index.js"]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Tools
|
|
52
|
+
| Tool | Purpose |
|
|
53
|
+
|---|---|
|
|
54
|
+
| `launch_app` | Launch app by Bundle ID |
|
|
55
|
+
| `list_apps` | keys to find Bundle IDs |
|
|
56
|
+
| `take_screenshot` | Capture screen & metadata |
|
|
57
|
+
| `tap` / `swipe` | Interact with UI (ratio coordinates 0-1) |
|
|
58
|
+
| `get_checklist` | Start evaluation session (screen/style) |
|
|
59
|
+
| `submit_evaluation` | Submit scores (1-5) & get pass/fail result |
|
|
60
|
+
| `evaluate_style_consistency` | Compare multiple screens |
|
|
61
|
+
| `get_evaluation_log` | Review past results |
|
|
62
|
+
|
|
63
|
+
## Typical Workflow
|
|
64
|
+
1. `list_apps(all: false)` → Find Bundle ID
|
|
65
|
+
2. `launch_app("com.example.app")`
|
|
66
|
+
3. `take_screenshot()`
|
|
67
|
+
4. `get_checklist(type: "screen")` → Get session ID
|
|
68
|
+
5. Agent analyzes UI → `submit_evaluation(sessionId, scores)`
|
|
69
|
+
|
|
70
|
+
## Data
|
|
71
|
+
Logs: `~/.ui-audit-mcp/logs/`
|
|
72
|
+
Screenshots: `~/.ui-audit-mcp/screenshots/`
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
MIT
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device adapter using idb + xcrun simctl.
|
|
3
|
+
*
|
|
4
|
+
* Dependencies:
|
|
5
|
+
* - xcrun simctl (ships with Xcode CLI tools) → launch app, screenshot
|
|
6
|
+
* - idb (Facebook's iOS Development Bridge) → tap, swipe
|
|
7
|
+
*
|
|
8
|
+
* Install idb:
|
|
9
|
+
* brew install idb-companion
|
|
10
|
+
* pip3 install fb-idb
|
|
11
|
+
*
|
|
12
|
+
* Coordinates use the iOS points system. The adapter accepts ratio
|
|
13
|
+
* coordinates (0-1) and converts them using the screen dimensions
|
|
14
|
+
* obtained from the screenshot.
|
|
15
|
+
*/
|
|
16
|
+
/** Check that required CLI tools are available. */
|
|
17
|
+
export declare function preflight(): Promise<{
|
|
18
|
+
ok: boolean;
|
|
19
|
+
missing: string[];
|
|
20
|
+
}>;
|
|
21
|
+
/** Launch an app by bundle ID on the booted simulator. */
|
|
22
|
+
export declare function launchApp(appId: string): Promise<{
|
|
23
|
+
success: boolean;
|
|
24
|
+
error?: string;
|
|
25
|
+
}>;
|
|
26
|
+
/** Take a screenshot and return base64 PNG + dimensions. */
|
|
27
|
+
export declare function takeScreenshot(): Promise<{
|
|
28
|
+
imageBase64: string;
|
|
29
|
+
width: number;
|
|
30
|
+
height: number;
|
|
31
|
+
filePath: string;
|
|
32
|
+
}>;
|
|
33
|
+
/** Tap at ratio coordinates (0-1). Converts to points using screen size. */
|
|
34
|
+
export declare function tap(ratioX: number, ratioY: number): Promise<{
|
|
35
|
+
success: boolean;
|
|
36
|
+
pointX: number;
|
|
37
|
+
pointY: number;
|
|
38
|
+
error?: string;
|
|
39
|
+
}>;
|
|
40
|
+
/** Swipe using ratio coordinates (0-1). */
|
|
41
|
+
export declare function swipe(startX: number, startY: number, endX: number, endY: number): Promise<{
|
|
42
|
+
success: boolean;
|
|
43
|
+
error?: string;
|
|
44
|
+
}>;
|
|
45
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/device/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AA+BH,mDAAmD;AACnD,wBAAsB,SAAS,IAAI,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAa7E;AAID,0DAA0D;AAC1D,wBAAsB,SAAS,CAC3B,KAAK,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAQ/C;AAED,4DAA4D;AAC5D,wBAAsB,cAAc,IAAI,OAAO,CAAC;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CACpB,CAAC,CAcD;AAED,4EAA4E;AAC5E,wBAAsB,GAAG,CACrB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAe/E;AAED,2CAA2C;AAC3C,wBAAsB,KAAK,CACvB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAmB/C"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device adapter using idb + xcrun simctl.
|
|
3
|
+
*
|
|
4
|
+
* Dependencies:
|
|
5
|
+
* - xcrun simctl (ships with Xcode CLI tools) → launch app, screenshot
|
|
6
|
+
* - idb (Facebook's iOS Development Bridge) → tap, swipe
|
|
7
|
+
*
|
|
8
|
+
* Install idb:
|
|
9
|
+
* brew install idb-companion
|
|
10
|
+
* pip3 install fb-idb
|
|
11
|
+
*
|
|
12
|
+
* Coordinates use the iOS points system. The adapter accepts ratio
|
|
13
|
+
* coordinates (0-1) and converts them using the screen dimensions
|
|
14
|
+
* obtained from the screenshot.
|
|
15
|
+
*/
|
|
16
|
+
import * as fs from "node:fs";
|
|
17
|
+
import * as path from "node:path";
|
|
18
|
+
import * as os from "node:os";
|
|
19
|
+
import { execFile } from "node:child_process";
|
|
20
|
+
import { promisify } from "node:util";
|
|
21
|
+
import { randomUUID } from "node:crypto";
|
|
22
|
+
const execFileAsync = promisify(execFile);
|
|
23
|
+
const SCREENSHOT_DIR = path.join(os.homedir(), ".ui-critic-mcp", "screenshots");
|
|
24
|
+
function ensureDir(dir) {
|
|
25
|
+
if (!fs.existsSync(dir))
|
|
26
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
/** Read PNG width and height from the IHDR chunk (bytes 16-23). */
|
|
29
|
+
function readPngDimensions(filePath) {
|
|
30
|
+
const buf = Buffer.alloc(24);
|
|
31
|
+
const fd = fs.openSync(filePath, "r");
|
|
32
|
+
fs.readSync(fd, buf, 0, 24, 0);
|
|
33
|
+
fs.closeSync(fd);
|
|
34
|
+
return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) };
|
|
35
|
+
}
|
|
36
|
+
const IDB_INSTALL_HINT = "idb is required for tap/swipe. Install it with:\n" +
|
|
37
|
+
" brew install idb-companion\n" +
|
|
38
|
+
" pip3 install fb-idb\n" +
|
|
39
|
+
"See https://fbidb.io for details.";
|
|
40
|
+
/** Check that required CLI tools are available. */
|
|
41
|
+
export async function preflight() {
|
|
42
|
+
const missing = [];
|
|
43
|
+
try {
|
|
44
|
+
await execFileAsync("xcrun", ["simctl", "list", "devices", "booted"]);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
missing.push("xcrun simctl (install Xcode Command Line Tools)");
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
await execFileAsync("idb", ["--help"]);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
missing.push(`idb (${IDB_INSTALL_HINT})`);
|
|
54
|
+
}
|
|
55
|
+
return { ok: missing.length === 0, missing };
|
|
56
|
+
}
|
|
57
|
+
// ─── Public API ──────────────────────────────────────────
|
|
58
|
+
/** Launch an app by bundle ID on the booted simulator. */
|
|
59
|
+
export async function launchApp(appId) {
|
|
60
|
+
try {
|
|
61
|
+
await execFileAsync("xcrun", ["simctl", "launch", "booted", appId]);
|
|
62
|
+
return { success: true };
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
const e = err;
|
|
66
|
+
return { success: false, error: e.stderr ?? e.message ?? String(err) };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Take a screenshot and return base64 PNG + dimensions. */
|
|
70
|
+
export async function takeScreenshot() {
|
|
71
|
+
ensureDir(SCREENSHOT_DIR);
|
|
72
|
+
const filePath = path.join(SCREENSHOT_DIR, `${randomUUID()}.png`);
|
|
73
|
+
try {
|
|
74
|
+
await execFileAsync("xcrun", ["simctl", "io", "booted", "screenshot", filePath]);
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
const e = err;
|
|
78
|
+
throw new Error(`Screenshot failed: ${e.stderr ?? e.message}`);
|
|
79
|
+
}
|
|
80
|
+
const { width, height } = readPngDimensions(filePath);
|
|
81
|
+
const imageBase64 = fs.readFileSync(filePath).toString("base64");
|
|
82
|
+
return { imageBase64, width, height, filePath };
|
|
83
|
+
}
|
|
84
|
+
/** Tap at ratio coordinates (0-1). Converts to points using screen size. */
|
|
85
|
+
export async function tap(ratioX, ratioY) {
|
|
86
|
+
const { width, height } = await getScreenSize();
|
|
87
|
+
const pointX = Math.round(ratioX * width);
|
|
88
|
+
const pointY = Math.round(ratioY * height);
|
|
89
|
+
try {
|
|
90
|
+
await execFileAsync("idb", ["ui", "tap", String(pointX), String(pointY)]);
|
|
91
|
+
return { success: true, pointX, pointY };
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
const e = err;
|
|
95
|
+
if (e.code === "ENOENT") {
|
|
96
|
+
return { success: false, pointX, pointY, error: IDB_INSTALL_HINT };
|
|
97
|
+
}
|
|
98
|
+
return { success: false, pointX, pointY, error: e.stderr ?? e.message ?? String(err) };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** Swipe using ratio coordinates (0-1). */
|
|
102
|
+
export async function swipe(startX, startY, endX, endY) {
|
|
103
|
+
const { width, height } = await getScreenSize();
|
|
104
|
+
const x1 = Math.round(startX * width);
|
|
105
|
+
const y1 = Math.round(startY * height);
|
|
106
|
+
const x2 = Math.round(endX * width);
|
|
107
|
+
const y2 = Math.round(endY * height);
|
|
108
|
+
try {
|
|
109
|
+
await execFileAsync("idb", [
|
|
110
|
+
"ui", "swipe", String(x1), String(y1), String(x2), String(y2),
|
|
111
|
+
]);
|
|
112
|
+
return { success: true };
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
const e = err;
|
|
116
|
+
if (e.code === "ENOENT") {
|
|
117
|
+
return { success: false, error: IDB_INSTALL_HINT };
|
|
118
|
+
}
|
|
119
|
+
return { success: false, error: e.stderr ?? e.message ?? String(err) };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// ─── Internal ────────────────────────────────────────────
|
|
123
|
+
let cachedScreenSize = null;
|
|
124
|
+
/** Get screen size by taking a quick screenshot and reading its dimensions. */
|
|
125
|
+
async function getScreenSize() {
|
|
126
|
+
if (cachedScreenSize)
|
|
127
|
+
return cachedScreenSize;
|
|
128
|
+
const result = await takeScreenshot();
|
|
129
|
+
cachedScreenSize = { width: result.width, height: result.height };
|
|
130
|
+
// Clean up the temp screenshot — it was only needed for dimensions
|
|
131
|
+
try {
|
|
132
|
+
fs.unlinkSync(result.filePath);
|
|
133
|
+
}
|
|
134
|
+
catch { /* ignore */ }
|
|
135
|
+
return cachedScreenSize;
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../src/device/adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evaluation checklist definitions.
|
|
3
|
+
*
|
|
4
|
+
* Screen evaluation: 4 items (overlap, layout, info clarity, ambiguity)
|
|
5
|
+
* Style consistency: 3 items (color, component, typography)
|
|
6
|
+
*/
|
|
7
|
+
import { ChecklistItem } from "../types.js";
|
|
8
|
+
/** Single-screen UI quality checklist */
|
|
9
|
+
export declare const SCREEN_CHECKLIST: ChecklistItem[];
|
|
10
|
+
/** Multi-screen style-consistency checklist */
|
|
11
|
+
export declare const STYLE_CHECKLIST: ChecklistItem[];
|
|
12
|
+
/** Default passing score for each checklist item (on a 1-5 scale) */
|
|
13
|
+
export declare const DEFAULT_PASSING_SCORE = 3;
|
|
14
|
+
//# sourceMappingURL=checklist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checklist.d.ts","sourceRoot":"","sources":["../../src/evaluation/checklist.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,yCAAyC;AACzC,eAAO,MAAM,gBAAgB,EAAE,aAAa,EAiC3C,CAAC;AAEF,+CAA+C;AAC/C,eAAO,MAAM,eAAe,EAAE,aAAa,EAyB1C,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,qBAAqB,IAAI,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evaluation checklist definitions.
|
|
3
|
+
*
|
|
4
|
+
* Screen evaluation: 4 items (overlap, layout, info clarity, ambiguity)
|
|
5
|
+
* Style consistency: 3 items (color, component, typography)
|
|
6
|
+
*/
|
|
7
|
+
/** Single-screen UI quality checklist */
|
|
8
|
+
export const SCREEN_CHECKLIST = [
|
|
9
|
+
{
|
|
10
|
+
id: "overlap",
|
|
11
|
+
name: "Element Overlap Detection",
|
|
12
|
+
description: "Check whether any UI elements (text, buttons, images, icons) overlap or obscure each other on the current screen.",
|
|
13
|
+
scoringGuide: "1 = severe overlap affecting usability, 2 = noticeable overlap, 3 = minor overlap, 4 = almost no overlap, 5 = no overlap at all",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: "layout",
|
|
17
|
+
name: "Layout & Alignment",
|
|
18
|
+
description: "Check whether button sizes are reasonable, elements are properly aligned, spacing is consistent, and the overall layout is harmonious.",
|
|
19
|
+
scoringGuide: "1 = chaotic layout, 2 = obvious layout issues, 3 = acceptable with minor flaws, 4 = good layout, 5 = polished layout",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "info_clarity",
|
|
23
|
+
name: "Information Clarity",
|
|
24
|
+
description: "Check whether the user can quickly identify key information on this screen, whether the information hierarchy is clear, and whether the primary call-to-action (CTA) is prominent.",
|
|
25
|
+
scoringGuide: "1 = key information is impossible to find, 2 = confusing, 3 = readable but not ideal, 4 = clear, 5 = excellent hierarchy",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "ambiguity",
|
|
29
|
+
name: "Expression Ambiguity",
|
|
30
|
+
description: "Check whether any copy, icons, or button labels are ambiguous or misleading, and whether the intended action is clearly communicated to the user.",
|
|
31
|
+
scoringGuide: "1 = severely ambiguous / misleading, 2 = multiple ambiguities, 3 = minor ambiguity, 4 = clear, 5 = precise and unambiguous",
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
/** Multi-screen style-consistency checklist */
|
|
35
|
+
export const STYLE_CHECKLIST = [
|
|
36
|
+
{
|
|
37
|
+
id: "color_consistency",
|
|
38
|
+
name: "Color Scheme Consistency",
|
|
39
|
+
description: "Compare all provided screenshots and check whether the primary colors, accent colors, and background colors are consistent across screens.",
|
|
40
|
+
scoringGuide: "1 = completely different styles, 2 = noticeable inconsistencies, 3 = mostly consistent, 4 = well unified, 5 = perfectly unified",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "component_consistency",
|
|
44
|
+
name: "Component Style Consistency",
|
|
45
|
+
description: "Compare buttons, navigation bars, cards, and other reusable components across all screenshots to verify they share the same visual style (corner radius, shadow, spacing, etc.).",
|
|
46
|
+
scoringGuide: "1 = every screen looks different, 2 = noticeable differences, 3 = mostly consistent, 4 = well unified, 5 = strict design system",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: "typography_consistency",
|
|
50
|
+
name: "Typography Consistency",
|
|
51
|
+
description: "Compare heading sizes, body text sizes, font weights, and line spacing across all screenshots to verify consistency.",
|
|
52
|
+
scoringGuide: "1 = chaotic typography, 2 = noticeable differences, 3 = mostly consistent, 4 = well unified, 5 = strictly unified",
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
/** Default passing score for each checklist item (on a 1-5 scale) */
|
|
56
|
+
export const DEFAULT_PASSING_SCORE = 3;
|
|
57
|
+
//# sourceMappingURL=checklist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checklist.js","sourceRoot":"","sources":["../../src/evaluation/checklist.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,yCAAyC;AACzC,MAAM,CAAC,MAAM,gBAAgB,GAAoB;IAC7C;QACI,EAAE,EAAE,SAAS;QACb,IAAI,EAAE,2BAA2B;QACjC,WAAW,EACP,mHAAmH;QACvH,YAAY,EACR,iIAAiI;KACxI;IACD;QACI,EAAE,EAAE,QAAQ;QACZ,IAAI,EAAE,oBAAoB;QAC1B,WAAW,EACP,wIAAwI;QAC5I,YAAY,EACR,sHAAsH;KAC7H;IACD;QACI,EAAE,EAAE,cAAc;QAClB,IAAI,EAAE,qBAAqB;QAC3B,WAAW,EACP,oLAAoL;QACxL,YAAY,EACR,0HAA0H;KACjI;IACD;QACI,EAAE,EAAE,WAAW;QACf,IAAI,EAAE,sBAAsB;QAC5B,WAAW,EACP,mJAAmJ;QACvJ,YAAY,EACR,4HAA4H;KACnI;CACJ,CAAC;AAEF,+CAA+C;AAC/C,MAAM,CAAC,MAAM,eAAe,GAAoB;IAC5C;QACI,EAAE,EAAE,mBAAmB;QACvB,IAAI,EAAE,0BAA0B;QAChC,WAAW,EACP,4IAA4I;QAChJ,YAAY,EACR,iIAAiI;KACxI;IACD;QACI,EAAE,EAAE,uBAAuB;QAC3B,IAAI,EAAE,6BAA6B;QACnC,WAAW,EACP,kLAAkL;QACtL,YAAY,EACR,iIAAiI;KACxI;IACD;QACI,EAAE,EAAE,wBAAwB;QAC5B,IAAI,EAAE,wBAAwB;QAC9B,WAAW,EACP,sHAAsH;QAC1H,YAAY,EACR,mHAAmH;KAC1H;CACJ,CAAC;AAEF,qEAAqE;AACrE,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scoring logic — determines pass / fail for each checklist item
|
|
3
|
+
* and computes the overall evaluation outcome.
|
|
4
|
+
*/
|
|
5
|
+
import { ChecklistItem, EvaluationOutcome, EvaluationType, ScoreEntry } from "../types.js";
|
|
6
|
+
export declare function evaluateScores(sessionId: string, type: EvaluationType, screenName: string, checklist: ChecklistItem[], scores: ScoreEntry[], passingScore: number, attemptNumber: number): EvaluationOutcome;
|
|
7
|
+
//# sourceMappingURL=scorer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scorer.d.ts","sourceRoot":"","sources":["../../src/evaluation/scorer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACH,aAAa,EACb,iBAAiB,EAEjB,cAAc,EACd,UAAU,EACb,MAAM,aAAa,CAAC;AAErB,wBAAgB,cAAc,CAC1B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,cAAc,EACpB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,aAAa,EAAE,EAC1B,MAAM,EAAE,UAAU,EAAE,EACpB,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,GACtB,iBAAiB,CAgCnB"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scoring logic — determines pass / fail for each checklist item
|
|
3
|
+
* and computes the overall evaluation outcome.
|
|
4
|
+
*/
|
|
5
|
+
export function evaluateScores(sessionId, type, screenName, checklist, scores, passingScore, attemptNumber) {
|
|
6
|
+
const results = checklist.map((item) => {
|
|
7
|
+
const entry = scores.find((s) => s.id === item.id);
|
|
8
|
+
const score = entry?.score ?? 0;
|
|
9
|
+
const passed = score >= passingScore;
|
|
10
|
+
return {
|
|
11
|
+
id: item.id,
|
|
12
|
+
score,
|
|
13
|
+
passed,
|
|
14
|
+
reason: entry?.reason,
|
|
15
|
+
suggestion: entry?.suggestion,
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
const failedItems = results.filter((r) => !r.passed).map((r) => r.id);
|
|
19
|
+
const totalScore = results.reduce((sum, r) => sum + r.score, 0);
|
|
20
|
+
const averageScore = results.length > 0
|
|
21
|
+
? parseFloat((totalScore / results.length).toFixed(2))
|
|
22
|
+
: 0;
|
|
23
|
+
return {
|
|
24
|
+
sessionId,
|
|
25
|
+
type,
|
|
26
|
+
screenName,
|
|
27
|
+
overallPassed: failedItems.length === 0,
|
|
28
|
+
averageScore,
|
|
29
|
+
results,
|
|
30
|
+
failedItems,
|
|
31
|
+
timestamp: new Date().toISOString(),
|
|
32
|
+
attemptNumber,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=scorer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scorer.js","sourceRoot":"","sources":["../../src/evaluation/scorer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAUH,MAAM,UAAU,cAAc,CAC1B,SAAiB,EACjB,IAAoB,EACpB,UAAkB,EAClB,SAA0B,EAC1B,MAAoB,EACpB,YAAoB,EACpB,aAAqB;IAErB,MAAM,OAAO,GAAuB,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACvD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC;QACnD,MAAM,KAAK,GAAG,KAAK,EAAE,KAAK,IAAI,CAAC,CAAC;QAChC,MAAM,MAAM,GAAG,KAAK,IAAI,YAAY,CAAC;QACrC,OAAO;YACH,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,KAAK;YACL,MAAM;YACN,MAAM,EAAE,KAAK,EAAE,MAAM;YACrB,UAAU,EAAE,KAAK,EAAE,UAAU;SAChC,CAAC;IACN,CAAC,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACtE,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAChE,MAAM,YAAY,GACd,OAAO,CAAC,MAAM,GAAG,CAAC;QACd,CAAC,CAAC,UAAU,CAAC,CAAC,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACtD,CAAC,CAAC,CAAC,CAAC;IAEZ,OAAO;QACH,SAAS;QACT,IAAI;QACJ,UAAU;QACV,aAAa,EAAE,WAAW,CAAC,MAAM,KAAK,CAAC;QACvC,YAAY;QACZ,OAAO;QACP,WAAW;QACX,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,aAAa;KAChB,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-demo.d.ts","sourceRoot":"","sources":["../../examples/agent-demo.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
// Connect to the local MCP server
|
|
5
|
+
async function run() {
|
|
6
|
+
console.log("Starting MCP Client...");
|
|
7
|
+
const transport = new StdioClientTransport({
|
|
8
|
+
command: "node",
|
|
9
|
+
args: [path.join(process.cwd(), "dist/src/index.js")],
|
|
10
|
+
});
|
|
11
|
+
const client = new Client({
|
|
12
|
+
name: "test-agent",
|
|
13
|
+
version: "1.0.0",
|
|
14
|
+
}, {
|
|
15
|
+
capabilities: {},
|
|
16
|
+
});
|
|
17
|
+
try {
|
|
18
|
+
await client.connect(transport);
|
|
19
|
+
console.log("Connected to MCP server.");
|
|
20
|
+
// 1. Launch App
|
|
21
|
+
console.log("Launching Preferences app...");
|
|
22
|
+
await client.callTool({
|
|
23
|
+
name: "launch_app",
|
|
24
|
+
arguments: { appId: "com.apple.Preferences" },
|
|
25
|
+
});
|
|
26
|
+
// 2. Take Screenshot
|
|
27
|
+
console.log("Taking screenshot...");
|
|
28
|
+
const screenshotResult = await client.callTool({
|
|
29
|
+
name: "take_screenshot",
|
|
30
|
+
arguments: {},
|
|
31
|
+
});
|
|
32
|
+
if (screenshotResult.imageBase64) {
|
|
33
|
+
console.log(`Screenshot captured (${screenshotResult.width}x${screenshotResult.height})`);
|
|
34
|
+
}
|
|
35
|
+
// 3. Get Checklist (Screen)
|
|
36
|
+
console.log("Getting checklist for 'SettingsScreen'...");
|
|
37
|
+
const checklistResult = await client.callTool({
|
|
38
|
+
name: "get_checklist",
|
|
39
|
+
arguments: { type: "screen", screenName: "SettingsScreen" },
|
|
40
|
+
});
|
|
41
|
+
const checklistData = JSON.parse(checklistResult.content[0].text);
|
|
42
|
+
const sessionId = checklistData.sessionId;
|
|
43
|
+
const items = checklistData.checklist;
|
|
44
|
+
console.log(`Session ID: ${sessionId}`);
|
|
45
|
+
console.log(`Checklist items: ${items.map((i) => i.id).join(", ")}`);
|
|
46
|
+
// 4. Submit Evaluation (Simulate passing)
|
|
47
|
+
console.log("Submitting passing evaluation...");
|
|
48
|
+
const scores = items.map((item) => ({
|
|
49
|
+
id: item.id,
|
|
50
|
+
score: 5,
|
|
51
|
+
}));
|
|
52
|
+
const evalResult = await client.callTool({
|
|
53
|
+
name: "submit_evaluation",
|
|
54
|
+
arguments: {
|
|
55
|
+
sessionId: sessionId,
|
|
56
|
+
scores: scores,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
console.log("Evaluation result:", JSON.parse(evalResult.content[0].text));
|
|
60
|
+
// 5. Get Logs
|
|
61
|
+
console.log("Fetching logs...");
|
|
62
|
+
const logResult = await client.callTool({
|
|
63
|
+
name: "get_evaluation_log",
|
|
64
|
+
arguments: { limit: 1 },
|
|
65
|
+
});
|
|
66
|
+
console.log("Logs retrieved.");
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
console.error("Error running test agent:", error);
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
// transport.close() is not exposed directly on StdioClientTransport in some versions,
|
|
73
|
+
// but client.close() should handle it?
|
|
74
|
+
// Actually, just exit process.
|
|
75
|
+
process.exit(0);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
run();
|
|
79
|
+
//# sourceMappingURL=agent-demo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-demo.js","sourceRoot":"","sources":["../../examples/agent-demo.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* UI Critic MCP Server
|
|
4
|
+
*
|
|
5
|
+
* An MCP server for iOS app UI evaluation and testing.
|
|
6
|
+
* Uses idb + xcrun simctl for device operations (launch, screenshot, tap, swipe)
|
|
7
|
+
* and provides a structured checklist-based evaluation workflow.
|
|
8
|
+
*
|
|
9
|
+
* Tools:
|
|
10
|
+
* Device operations:
|
|
11
|
+
* - launch_app Launch an iOS app by bundle ID
|
|
12
|
+
* - take_screenshot Capture the current screen as base64 PNG
|
|
13
|
+
* - tap Tap at ratio coordinates (0-1)
|
|
14
|
+
* - swipe Swipe between ratio coordinates (0-1)
|
|
15
|
+
*
|
|
16
|
+
* Evaluation:
|
|
17
|
+
* - get_checklist Get the evaluation checklist and create a session
|
|
18
|
+
* - submit_evaluation Submit scores and get pass/fail result
|
|
19
|
+
* - evaluate_style_consistency Compare multiple screenshots for style consistency
|
|
20
|
+
* - get_evaluation_log Query past evaluation results
|
|
21
|
+
*/
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;;;;;GAmBG"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* UI Critic MCP Server
|
|
4
|
+
*
|
|
5
|
+
* An MCP server for iOS app UI evaluation and testing.
|
|
6
|
+
* Uses idb + xcrun simctl for device operations (launch, screenshot, tap, swipe)
|
|
7
|
+
* and provides a structured checklist-based evaluation workflow.
|
|
8
|
+
*
|
|
9
|
+
* Tools:
|
|
10
|
+
* Device operations:
|
|
11
|
+
* - launch_app Launch an iOS app by bundle ID
|
|
12
|
+
* - take_screenshot Capture the current screen as base64 PNG
|
|
13
|
+
* - tap Tap at ratio coordinates (0-1)
|
|
14
|
+
* - swipe Swipe between ratio coordinates (0-1)
|
|
15
|
+
*
|
|
16
|
+
* Evaluation:
|
|
17
|
+
* - get_checklist Get the evaluation checklist and create a session
|
|
18
|
+
* - submit_evaluation Submit scores and get pass/fail result
|
|
19
|
+
* - evaluate_style_consistency Compare multiple screenshots for style consistency
|
|
20
|
+
* - get_evaluation_log Query past evaluation results
|
|
21
|
+
*/
|
|
22
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
23
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
24
|
+
import { launchAppSchema, launchApp } from "./tools/launch-app.js";
|
|
25
|
+
import { takeScreenshotSchema, takeScreenshot } from "./tools/take-screenshot.js";
|
|
26
|
+
import { tapSchema, tap } from "./tools/tap.js";
|
|
27
|
+
import { swipeSchema, swipe } from "./tools/swipe.js";
|
|
28
|
+
import { getChecklistSchema, getChecklist } from "./tools/get-checklist.js";
|
|
29
|
+
import { submitEvaluationSchema, submitEvaluation } from "./tools/submit-evaluation.js";
|
|
30
|
+
import { evaluateStyleSchema, evaluateStyleConsistency } from "./tools/evaluate-style.js";
|
|
31
|
+
import { getEvaluationLogSchema, getEvaluationLog } from "./tools/get-log.js";
|
|
32
|
+
const server = new McpServer({
|
|
33
|
+
name: "ui-critic-mcp",
|
|
34
|
+
version: "1.0.0",
|
|
35
|
+
});
|
|
36
|
+
// ─── Device operation tools ────────────────────────────────
|
|
37
|
+
server.tool("launch_app", "Launch an iOS app on the simulator by its bundle ID.", launchAppSchema.shape, async (args) => launchApp(args));
|
|
38
|
+
server.tool("take_screenshot", "Capture the current simulator screen as a base64 PNG image. " +
|
|
39
|
+
"Returns the image for visual inspection along with screen metadata.", takeScreenshotSchema.shape, async (args) => takeScreenshot(args));
|
|
40
|
+
server.tool("tap", "Tap at a specific position on the screen. " +
|
|
41
|
+
"Uses ratio coordinates (0-1): x=0 is left edge, x=1 is right edge, y=0 is top, y=1 is bottom.", tapSchema.shape, async (args) => tap(args));
|
|
42
|
+
server.tool("swipe", "Swipe from one point to another on the screen. " +
|
|
43
|
+
"Uses ratio coordinates (0-1) for both start and end positions.", swipeSchema.shape, async (args) => swipe(args));
|
|
44
|
+
// ─── Evaluation tools ──────────────────────────────────────
|
|
45
|
+
server.tool("get_checklist", "Get the UI evaluation checklist and create an evaluation session. " +
|
|
46
|
+
"Use type='screen' for single-screen quality evaluation (4 items: overlap, layout, info clarity, ambiguity). " +
|
|
47
|
+
"Use type='style' for multi-screen style consistency evaluation (3 items: color, component, typography).", getChecklistSchema.shape, async (args) => getChecklist(args));
|
|
48
|
+
server.tool("submit_evaluation", "Submit scores for each checklist item. Determines pass/fail (passing score >= 3) " +
|
|
49
|
+
"and persists the result. Failed items include reasons and suggestions for improvement.", submitEvaluationSchema.shape, async (args) => submitEvaluation(args));
|
|
50
|
+
server.tool("evaluate_style_consistency", "Compare multiple screenshots for visual style consistency. " +
|
|
51
|
+
"Provide at least 2 screenshots. Returns all images alongside a style consistency checklist. " +
|
|
52
|
+
"After analyzing, call submit_evaluation with the returned sessionId and your scores.", evaluateStyleSchema.shape, async (args) => evaluateStyleConsistency(args));
|
|
53
|
+
server.tool("get_evaluation_log", "Query past evaluation results and summary statistics. " +
|
|
54
|
+
"Use to review improvement across attempts or generate a final report.", getEvaluationLogSchema.shape, async (args) => getEvaluationLog(args));
|
|
55
|
+
import { preflight } from "./device/adapter.js";
|
|
56
|
+
// ─── Start server ──────────────────────────────────────────
|
|
57
|
+
async function main() {
|
|
58
|
+
// Check for required CLI tools (non-blocking — warn only)
|
|
59
|
+
const check = await preflight();
|
|
60
|
+
if (!check.ok) {
|
|
61
|
+
console.error("[ui-critic-mcp] ⚠️ Missing dependencies:");
|
|
62
|
+
for (const m of check.missing) {
|
|
63
|
+
console.error(` - ${m}`);
|
|
64
|
+
}
|
|
65
|
+
console.error("[ui-critic-mcp] Some tools (tap, swipe) may not work.");
|
|
66
|
+
}
|
|
67
|
+
const transport = new StdioServerTransport();
|
|
68
|
+
await server.connect(transport);
|
|
69
|
+
console.error("[ui-critic-mcp] Server started on stdio transport.");
|
|
70
|
+
}
|
|
71
|
+
main().catch((err) => {
|
|
72
|
+
console.error("[ui-critic-mcp] Fatal error:", err);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
});
|
|
75
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|