{"_id":"@drmxrcy/appscreen-mcp","name":"@drmxrcy/appscreen-mcp","dist-tags":{"latest":"1.1.0"},"versions":{"1.1.0":{"name":"@drmxrcy/appscreen-mcp","version":"1.1.0","description":"MCP server for controlling the AppScreen App Store Screenshot Generator.","type":"module","private":false,"license":"MIT","author":{"name":"Kaan Gönüldinc","email":"contact@appsolves.dev","url":"https://appsolves.dev"},"homepage":"https://drmxrcy.github.io/appscreen-mcp/","repository":{"type":"git","url":"git+https://github.com/DrMxrcy/appscreen-mcp.git","directory":"mcp-server"},"bugs":{"url":"https://github.com/DrMxrcy/appscreen-mcp/issues"},"bin":{"appscreen-mcp":"dist/index.js"},"scripts":{"build":"tsc -p tsconfig.json","start":"node dist/index.js","dev":"tsx src/index.ts","typecheck":"tsc -p tsconfig.json --noEmit","prepack":"npm run typecheck && npm run build","prepublishOnly":"npm run typecheck && npm run build"},"keywords":["mcp","model-context-protocol","appscreen","app-store","screenshots","screenshot-generator","playwright","codex","claude"],"publishConfig":{"access":"public"},"engines":{"node":">=18.18"},"dependencies":{"@modelcontextprotocol/sdk":"^1.29.0","playwright":"^1.56.0","zod":"^3.25.0"},"devDependencies":{"@types/node":"latest","tsx":"latest","typescript":"latest"},"_id":"@drmxrcy/appscreen-mcp@1.1.0","gitHead":"631d3aff58b80512abbb32d53e25af38a1f8fb9a","_nodeVersion":"24.0.0","_npmVersion":"11.3.0","dist":{"integrity":"sha512-27AO1euqeAOWhBnX3Iv9EdYFn122vHiFc0LSwgqo3KIY1SGcjdtTewVN8zkibzCtJll6LHrTOGxGz2UCGfLUFw==","shasum":"be331c5c44958c2de77097570550ba882fc25789","tarball":"https://registry.npmjs.org/@drmxrcy/appscreen-mcp/-/appscreen-mcp-1.1.0.tgz","fileCount":6,"unpackedSize":88966,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCICLNCiqmkE4c5GZsCrJLdG8UY8dkKZO66I+zayy1qCwFAiEAhe2t/lkx7f285uEGm/rpULUeOZWARDloteyutCankRc="}]},"_npmUser":{"name":"drmxrcy","email":"drmxrcy@gmail.com"},"directories":{},"maintainers":[{"name":"drmxrcy","email":"drmxrcy@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/appscreen-mcp_1.1.0_1786508550329_0.49425058211465456"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-12T04:22:30.222Z","1.1.0":"2026-08-12T04:22:30.474Z","modified":"2026-08-12T04:22:30.640Z"},"maintainers":[{"name":"drmxrcy","email":"drmxrcy@gmail.com"}],"description":"MCP server for controlling the AppScreen App Store Screenshot Generator.","homepage":"https://drmxrcy.github.io/appscreen-mcp/","keywords":["mcp","model-context-protocol","appscreen","app-store","screenshots","screenshot-generator","playwright","codex","claude"],"repository":{"type":"git","url":"git+https://github.com/DrMxrcy/appscreen-mcp.git","directory":"mcp-server"},"author":{"name":"Kaan Gönüldinc","email":"contact@appsolves.dev","url":"https://appsolves.dev"},"bugs":{"url":"https://github.com/DrMxrcy/appscreen-mcp/issues"},"license":"MIT","readme":"# AppScreen MCP Server\n\n> Forked from [AppSolves/appscreen-mcp](https://github.com/AppSolves/appscreen-mcp).\n\nThis package exposes the AppScreen App Store Screenshot Generator as a Model Context Protocol server.\n\nIt uses a clean in-app automation bridge (`../mcp-bridge.js`) and Playwright. The MCP server does not scrape random UI selectors. It loads the app, calls `window.AppScreenMCP`, and saves exported PNG/ZIP artifacts to disk when requested.\n\n## npm package\n\nThe MCP server is published on npm as:\n\n```bash\n@drmxrcy/appscreen-mcp\n```\n\nFor most users, no repository clone is required. MCP clients can run the server directly with `npx`:\n\n```bash\nnpx -y @drmxrcy/appscreen-mcp@latest\n```\n\nThe package controls the hosted AppScreen frontend by default:\n\n```txt\nhttps://drmxrcy.github.io/appscreen-mcp/\n```\n\nYou only need to clone this repository if you want to develop the MCP server or run a local copy of the frontend.\n\n## Local development install\n\n```bash\ncd mcp-server\nnpm install\nnpx playwright install chromium\nnpm run build\n```\n\n## Run locally\n\nFor local development, start the AppScreen frontend from the repository root first:\n\n```bash\npython3 -m http.server 8000\n```\n\nThen run the MCP server:\n\n```bash\ncd mcp-server\nAPPSCREEN_URL=http://localhost:8000 APPSCREEN_OUTPUT_DIR=./outputs npm start\n```\n\nFor the hosted fork:\n\n```bash\ncd mcp-server\nAPPSCREEN_URL=https://drmxrcy.github.io/appscreen-mcp/ npm start\n```\n\n## Environment variables\n\n| Variable | Required | Default | Description |\n|---|---:|---|---|\n| `APPSCREEN_URL` | No | `https://drmxrcy.github.io/appscreen-mcp/` | URL of the AppScreen frontend that the MCP server should control. Use `http://localhost:8000` for local development or your hosted GitHub Pages URL for the public app. |\n| `APPSCREEN_OUTPUT_DIR` | No | `~/AppScreenMCP/outputs` | Directory where exported PNG/ZIP artifacts are saved when `saveToFile` is enabled. Relative paths are resolved from the MCP server process working directory, so absolute paths are recommended for predictable behavior. |\n| `APPSCREEN_HEADLESS` | No | `true` | Controls whether Playwright runs Chromium hidden or visible. Set to `false` to see the browser while debugging or watching an agent control the app. |\n| `APPSCREEN_BROWSER_TIMEOUT_MS` | No | `60000` | Timeout in milliseconds for browser navigation, bridge initialization, and Playwright operations. Increase this if the hosted app or large screenshot sets load slowly. |\n| `APPSCREEN_BROWSER_PROFILE_DIR` | No | `~/AppScreenMCP/browser-profile` | Persistent Chromium profile directory used by Playwright. This preserves browser storage such as IndexedDB between MCP runs. Use a stable absolute path if you want AppScreen projects to persist after closing the browser. |\n\n### Transport variables\n\nOnly relevant when running the server over HTTP instead of stdio. See [Self-hosting](#self-hosting).\n\n| Variable | Required | Default | Description |\n|---|---:|---|---|\n| `MCP_TRANSPORT` | No | `stdio` | Set to `http` to serve the Streamable HTTP transport instead of stdio. The `--http` CLI flag does the same thing. |\n| `PORT` | No | `3000` | Port for the HTTP transport. |\n| `MCP_AUTH_TOKEN` | **Yes for HTTP** | _unset_ | Bearer token required on every `/mcp` request. If unset, the server prints a warning and binds to `127.0.0.1` only, so it is unreachable from other machines. |\n| `MCP_BIND` | No | `0.0.0.0` with a token, `127.0.0.1` without | Interface to bind. Only set this to override the safe default, and only behind a trusted network or reverse proxy. |\n| `MCP_ALLOWED_ORIGINS` | No | _empty_ | Comma-separated list of browser origins allowed to call `/mcp`. Empty means any request carrying an `Origin` header is rejected, which blocks DNS-rebinding attacks. Normal MCP clients do not send `Origin`, so you almost never need this. |\n| `MCP_MAX_BODY_BYTES` | No | `67108864` (64 MB) | Maximum request body on an established session. Tool calls carry base64 screenshots and a set can hold ten in one request, so raise this if you upload unusually large images. Oversize requests get a `413`. |\n| `MCP_SESSION_IDLE_MS` | No | `1800000` (30 min) | How long an idle session is kept before its slot is reclaimed. Clients that disconnect without sending `DELETE /mcp` would otherwise hold a slot until restart. |\n\n### Recommended defaults\n\nFor most users, only `APPSCREEN_URL` and optionally `APPSCREEN_HEADLESS` are needed:\n\n```toml\n[mcp_servers.appscreen.env]\nAPPSCREEN_URL = \"https://drmxrcy.github.io/appscreen-mcp/\"\nAPPSCREEN_HEADLESS = \"true\"\n```\n\nIf you want predictable export and browser persistence paths, set absolute directories:\n\n```toml\n[mcp_servers.appscreen.env]\nAPPSCREEN_URL = \"https://drmxrcy.github.io/appscreen-mcp/\"\nAPPSCREEN_OUTPUT_DIR = \"C:/Users/YourName/AppScreenMCP/outputs\"\nAPPSCREEN_BROWSER_PROFILE_DIR = \"C:/Users/YourName/AppScreenMCP/browser-profile\"\nAPPSCREEN_HEADLESS = \"false\"\n```\n\n### Notes\n\n- `APPSCREEN_OUTPUT_DIR` controls exported files only. It does not control browser storage.\n- `APPSCREEN_BROWSER_PROFILE_DIR` controls Chromium's persistent profile, including IndexedDB.\n- `APPSCREEN_HEADLESS = \"false\"` is useful during development because you can watch the agent control the app in real time.\n- Avoid using `./outputs` in shared documentation unless you intentionally want outputs relative to the MCP server process working directory.\n\n## Codex example\n\nAdd this to your Codex MCP config:\n\n```toml\n[mcp_servers.appscreen]\ncommand = \"npx\"\nargs = [\"-y\", \"@drmxrcy/appscreen-mcp@latest\"]\n\n[mcp_servers.appscreen.env]\nAPPSCREEN_URL = \"https://drmxrcy.github.io/appscreen-mcp/\"\nAPPSCREEN_HEADLESS = \"true\"\n```\n\nFor visible browser debugging and explicit output paths:\n\n```toml\n[mcp_servers.appscreen]\ncommand = \"npx\"\nargs = [\"-y\", \"@drmxrcy/appscreen-mcp@latest\"]\n\n[mcp_servers.appscreen.env]\nAPPSCREEN_URL = \"https://drmxrcy.github.io/appscreen-mcp/\"\nAPPSCREEN_OUTPUT_DIR = \"C:/Users/YourName/AppScreenMCP/outputs\"\nAPPSCREEN_BROWSER_PROFILE_DIR = \"C:/Users/YourName/AppScreenMCP/browser-profile\"\nAPPSCREEN_HEADLESS = \"false\"\n```\n\n## Claude Desktop example\n\nTo use the published npm package:\n\n```json\n{\n  \"mcpServers\": {\n    \"appscreen\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@drmxrcy/appscreen-mcp@latest\"],\n      \"env\": {\n        \"APPSCREEN_URL\": \"https://drmxrcy.github.io/appscreen-mcp/\",\n        \"APPSCREEN_HEADLESS\": \"true\"\n      }\n    }\n  }\n}\n```\n\nFor local development from a cloned repository:\n\n```json\n{\n  \"mcpServers\": {\n    \"appscreen\": {\n      \"command\": \"node\",\n      \"args\": [\"/absolute/path/to/appscreen-mcp/mcp-server/dist/index.js\"],\n      \"env\": {\n        \"APPSCREEN_URL\": \"http://localhost:8000\",\n        \"APPSCREEN_OUTPUT_DIR\": \"/absolute/path/to/appscreen-mcp/mcp-server/outputs\",\n        \"APPSCREEN_HEADLESS\": \"false\"\n      }\n    }\n  }\n}\n```\n\n## Main tools\n\n- `appscreen_initialize`\n- `appscreen_get_usage_guide`\n- `appscreen_get_capabilities`\n- `appscreen_get_state`\n- `appscreen_create_project`\n- `appscreen_switch_project`\n- `appscreen_rename_project`\n- `appscreen_duplicate_project`\n- `appscreen_delete_current_project`\n- `appscreen_set_output_size`\n- `appscreen_set_languages`\n- `appscreen_select_screenshot`\n- `appscreen_add_blank_screenshot`\n- `appscreen_add_screenshot`\n- `appscreen_set_localized_screenshot_image`\n- `appscreen_remove_screenshot`\n- `appscreen_duplicate_screenshot`\n- `appscreen_set_background`\n- `appscreen_set_background_image`\n- `appscreen_set_device_settings`\n- `appscreen_apply_position_preset`\n- `appscreen_set_text`\n- `appscreen_add_text_element`\n- `appscreen_add_emoji_element`\n- `appscreen_add_icon_element`\n- `appscreen_add_graphic_element`\n- `appscreen_update_element`\n- `appscreen_delete_element`\n- `appscreen_add_popout`\n- `appscreen_update_popout`\n- `appscreen_delete_popout`\n- `appscreen_apply_style_to_all`\n- `appscreen_export_project_template`\n- `appscreen_apply_project_template`\n- `appscreen_create_screenshot_set`\n- `appscreen_patch_state`\n- `appscreen_capture_editor_preview`\n- `appscreen_export_current_png`\n- `appscreen_export_all_zip`\n- `appscreen_demo_run_cable_launch_recipe`\n- `appscreen_upload_to_app_store`\n- `appscreen_raw_bridge_call`\n\n## Project style templates\n\n`appscreen_export_project_template` serializes a project's look as JSON, and\n`appscreen_apply_project_template` stamps that look onto the current project. Use it to\nkeep one brand style across projects without re-issuing every styling call.\n\n```jsonc\n{\n  \"formatVersion\": 1,\n  \"defaults\":   { \"background\": {}, \"screenshot\": {}, \"text\": {} },\n  \"screenshots\": [{ \"background\": {}, \"screenshot\": {}, \"text\": {}, \"elements\": [], \"popouts\": [] }]\n}\n```\n\nTemplates are style, not content:\n\n- **No images.** Screenshot images, background images (`imageSrc`), and element/popout\n  image sources are stripped, so a template stays small and portable instead of carrying\n  embedded base64. Applying a template never touches the target's images. Image-backed\n  overlay elements therefore come back inert — re-add their artwork with\n  `appscreen_add_graphic_element` after applying.\n- **No copy.** `headlines`/`subheadlines` are excluded; existing captions survive an\n  apply. Text layout and styling — including per-language layout in `languageSettings` —\n  is part of the template.\n- **Overlays are replaced.** `elements` and `popouts` from the template overwrite the\n  target screenshot's own.\n\n`mode` (default `all`) selects the scope: `defaults-only` styles just the project\ndefaults used by new screenshots; `all` additionally styles every existing screenshot by\nindex, cycling the template's screenshot entries when the counts differ.\n\n## Uploading to App Store Connect\n\n`appscreen_upload_to_app_store` uploads finished screenshots to Apple using the official\nApp Store Connect API. It appends to the screenshot set for one app, one locale and one\ndevice display type — it never deletes or replaces existing screenshots.\n\n### 1. Create an API key\n\nIn [App Store Connect](https://appstoreconnect.apple.com) go to **Users and Access →\nIntegrations → App Store Connect API**, create a key with the **App Manager** role, then note:\n\n- the **Key ID** (e.g. `2X9R4HXF34`),\n- the **Issuer ID** shown above the key list (a UUID),\n- and download the `.p8` private key file. Apple lets you download it **once** — store it\n  somewhere private (e.g. `~/.appstoreconnect/private_keys/AuthKey_2X9R4HXF34.p8`, mode `600`).\n\n### 2. Environment variables\n\n| Variable | Required | Description |\n|---|---:|---|\n| `ASC_KEY_ID` | Yes | App Store Connect API Key ID. Overridden by the tool's `keyId` argument. |\n| `ASC_ISSUER_ID` | Yes | Issuer ID (UUID). Overridden by the tool's `issuerId` argument. |\n| `ASC_PRIVATE_KEY_PATH` | Yes\\* | Path to the downloaded `.p8` file. Overridden by the tool's `privateKeyPath` argument. |\n| `ASC_PRIVATE_KEY` | Yes\\* | Alternative to the path: the `.p8` PEM contents inline. |\n\n\\* Provide either `ASC_PRIVATE_KEY_PATH` or `ASC_PRIVATE_KEY`.\n\n**Your private key stays on your machine.** It is read locally, used only to sign a\nshort-lived (10 minute) ES256 JWT, and is never logged, never returned in a tool result, and\nnever transmitted — only the resulting signed JWT is sent to Apple, exactly as the API requires.\nDon't commit the `.p8` file, and don't paste the key into a chat.\n\n### 3. Dry run first\n\n`dryRun` defaults to `true`. A dry run authenticates, resolves the editable App Store version,\nthe locale's localization and the screenshot set, and validates every file locally — format\n(PNG/JPEG), no alpha channel, and exact pixel dimensions for the display type — then reports what\n*would* be uploaded without reserving or transferring anything. Read that report, then re-run with\n`dryRun: false` to commit.\n\n```jsonc\n// 1. dry run (default)\n{\n  \"name\": \"appscreen_upload_to_app_store\",\n  \"arguments\": {\n    \"appId\": \"1234567890\",\n    \"locale\": \"en-US\",\n    \"displayType\": \"APP_IPHONE_67\",\n    \"files\": [\n      \"/Users/me/AppScreenMCP/outputs/en-US/01.png\",\n      \"/Users/me/AppScreenMCP/outputs/en-US/02.png\"\n    ]\n  }\n}\n\n// 2. same call plus \"dryRun\": false to actually upload\n```\n\nNotes:\n\n- `displayType` names lag Apple's marketing names: `APP_IPHONE_67` is today's **6.9\"** iPhone\n  slot (1320×2868), `APP_IPHONE_61` is the **6.3\"** slot, `APP_IPAD_PRO_3GEN_129` is the **13\"**\n  iPad slot (2064×2752).\n- The app needs a version in an editable state (e.g. *Prepare for Submission*) before uploading.\n- Apple allows at most 10 screenshots per set; the tool reports the existing count and refuses to\n  exceed the limit rather than deleting anything.\n\n### Validating screenshots without uploading\n\n`appscreen_validate_screenshots` runs the same local checks (format, alpha channel, exact pixel\ndimensions) for a `files` list and `displayType`, with no credentials required. Use it as a\npre-flight check before `appscreen_upload_to_app_store`; each result is `{ file, ok, issues[] }`.\n\n## Recommended workflow for agents\n\nFor production App Store or Google Play screenshot sets with different captions per screenshot, use:\n\n1. `appscreen_initialize`\n2. `appscreen_get_usage_guide`\n3. `appscreen_get_capabilities`\n4. `appscreen_create_screenshot_set`\n5. `appscreen_capture_editor_preview` if visual editor inspection is needed\n6. `appscreen_export_all_zip` if the ZIP was not already exported by `appscreen_create_screenshot_set`\n\nDo not use `appscreen_demo_run_cable_launch_recipe` for production multi-screen sets. It is a legacy/demo shortcut that applies one shared headline/subheadline map across all screenshots.\n\n## File inputs\n\nTools that accept images can receive either:\n\n- `filePath`: a local path readable by the MCP server process\n- `dataUrl`: a `data:image/*;base64,...` string\n- `base64` plus `mimeType`\n\nExports return base64 data and, when `saveToFile` is true, write files into `APPSCREEN_OUTPUT_DIR`.\n\n## Typical agent prompt\n\n```txt\nUse AppScreen MCP.\n\nFirst call appscreen_get_usage_guide.\n\nThen create a production screenshot set:\n- projectName: \"My App Launch\"\n- outputDevice: \"iphone-6.9\"\n- languages: [\"en\", \"de\"]\n- use a polished blue-purple gradient background\n- use centered phone mockups with a slight rotation\n- upload these local screenshot paths\n- give every screenshot different English and German headline/subheadline text\n- export all languages as ZIP\n- capture an editor preview\n- return the output file paths\n```\n\n## Self-hosting\n\nBy default the MCP server talks **stdio** and is launched by your MCP client as a child process. For a self-hosted deployment you can instead run it as a long-lived HTTP service that any MCP client can connect to over the network, with the AppScreen frontend running next to it in the same stack. Nothing then depends on the hosted GitHub Pages site.\n\n### One command with docker compose\n\nThe compose stack runs two containers: `appscreen-mcp` (the nginx frontend, port 8080) and `appscreen-mcp-server` (this MCP server on the Streamable HTTP transport, port 3000). The MCP server drives the frontend container over the internal compose network, so no traffic leaves your host.\n\nFrom the repository root:\n\n```bash\n# 1. Generate an auth token. Treat it like a password.\necho \"MCP_AUTH_TOKEN=$(openssl rand -hex 32)\" >> .env\n\n# 2. Start the stack (builds both images on first run).\ndocker compose up -d\n\n# 3. Check it is alive.\ncurl http://localhost:3000/health\n# {\"ok\":true,\"version\":\"1.0.2\",\"browserReady\":false}\n```\n\n`docker compose config` fails fast if `MCP_AUTH_TOKEN` is missing. This is deliberate: an unauthenticated MCP endpoint lets anyone who can reach it drive a browser, read local files through the `filePath` tool arguments, and use your App Store Connect credentials.\n\n`docker-compose.build.yml` is the same stack with the frontend built from source rather than pulled from ghcr.\n\n### Connecting an MCP client over HTTP\n\nThe endpoint is `POST/GET/DELETE http://<host>:3000/mcp` and every request must carry the bearer token:\n\n```\nAuthorization: Bearer <MCP_AUTH_TOKEN>\n```\n\nClaude Code:\n\n```bash\nclaude mcp add --transport http appscreen https://appscreen.example.com/mcp \\\n  --header \"Authorization: Bearer $MCP_AUTH_TOKEN\"\n```\n\nClients using a JSON config file:\n\n```json\n{\n  \"mcpServers\": {\n    \"appscreen\": {\n      \"type\": \"http\",\n      \"url\": \"https://appscreen.example.com/mcp\",\n      \"headers\": {\n        \"Authorization\": \"Bearer YOUR_TOKEN_HERE\"\n      }\n    }\n  }\n}\n```\n\nRaw check with curl:\n\n```bash\ncurl -sS -X POST http://localhost:3000/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json, text/event-stream\" \\\n  -H \"Authorization: Bearer $MCP_AUTH_TOKEN\" \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2025-06-18\",\"capabilities\":{},\"clientInfo\":{\"name\":\"curl\",\"version\":\"0\"}}}' -D -\n```\n\nThe response carries an `mcp-session-id` header; send it back on every subsequent request alongside the `Authorization` header.\n\n### Running the HTTP transport without Docker\n\n```bash\ncd mcp-server\nnpm run build\nMCP_TRANSPORT=http PORT=3000 MCP_AUTH_TOKEN=$(openssl rand -hex 32) npm start\n```\n\n### Health endpoint\n\n`GET /health` is intentionally unauthenticated so load balancers and orchestrators can probe it. It returns no secrets:\n\n```json\n{ \"ok\": true, \"version\": \"1.0.2\", \"browserReady\": true }\n```\n\n`browserReady` is `false` until the first tool call launches Chromium.\n\n### Security notes\n\n- **Always set `MCP_AUTH_TOKEN`.** Generate one with `openssl rand -hex 32`. Without it the server refuses to listen on anything except loopback, which means the published container port simply will not answer.\n- **TLS is the reverse proxy's job.** This server speaks plain HTTP. Put nginx, Caddy, Traefik, or your platform's ingress (Dokploy, for example) in front of it and terminate HTTPS there. A bearer token sent over plain HTTP across an untrusted network is a token you have given away.\n- **Rotate the token** by changing `.env` and running `docker compose up -d`; existing sessions die with the container.\n- **The stack is single-tenant.** All MCP sessions drive the one shared browser page, so two clients working at the same time will interfere with each other's project state. Run one stack per user.\n- **Concurrent sessions are capped at 32**, and idle ones are reclaimed after `MCP_SESSION_IDLE_MS`. Request bodies over `MCP_MAX_BODY_BYTES` are rejected with `413`. Both limits exist so a client that disappears or floods the endpoint cannot exhaust memory or wedge the server.\n- Requests carrying a browser `Origin` header are rejected unless listed in `MCP_ALLOWED_ORIGINS`, which blocks DNS-rebinding attacks against a loopback-bound server.\n\n## How it works\n\nThe MCP server starts a Playwright-controlled Chromium instance and opens the AppScreen frontend. The frontend exposes a stable automation bridge at:\n\n```js\nwindow.AppScreenMCP\n```\n\nThe MCP server calls that bridge directly from the browser context. This keeps automation reliable because the server does not depend on fragile button labels, CSS selectors, or UI layout details.\n\nThe browser profile is persistent by default, so AppScreen's IndexedDB data can survive MCP restarts. Exported PNG/ZIP files are saved separately to `APPSCREEN_OUTPUT_DIR`.\n\n## License\n\nMIT License.","readmeFilename":"README.md","_rev":"1-63fed99fb260eed92eb94c397642df93"}