Start with your coding agent¶
This kit connects a coding agent (Claude Code, Codex or another MCP client) to Skeg, and teaches it when and how to verify your app on a device. No file in the kit holds a credential.
| File | Purpose |
|---|---|
.mcp.json.example |
MCP server entry for Claude Code and other JSON clients. |
codex-config.toml.example |
The same entry for Codex. |
skills/skeg-verify/SKILL.md |
The verification workflow the agent follows. |
FIRST-RUN.md |
The prompt for a first setup check. |
1. Install the runner¶
The agent starts Skeg's runner on your machine.
- In Skeg, open Setup and download Skeg Runner for your OS.
- Open it once. It creates the runner launcher:
- macOS and Linux:
~/.config/skeg/skeg-runner - Windows:
%USERPROFILE%\.config\skeg\skeg-runner.bat
- macOS and Linux:
- You do not need to pair this runner or keep it running for the agent. The MCP server starts its own runner with your project token.
2. Create a project token¶
- In Skeg, open your project, then Settings → Agent tokens.
- Create a token named after the machine or agent, for example
laptop-claude. - Copy the secret. Skeg shows it once.
The token names the project and is the permission. Revoke it there when you stop using it.
3. Choose the device¶
- Attach the device: an Android emulator or phone (
adb deviceslists it), an iOS Simulator (Mac with Xcode), or the Mac itself. - In Skeg Setup, pick the platform, enter the app id and device, and Save configuration. Make it the project's default target.
- To use a different saved target, set
SKEG_TARGETto its name.
4. Model credentials (goal runs only)¶
Replays and hand recordings need no model. Goal runs and plan ports do. The key must be in the MCP server's environment. A model set in Skeg's chat does not reach the agent.
| Provider | Set |
|---|---|
| Anthropic (default) | ANTHROPIC_API_KEY |
| OpenAI | SKEG_LLM_PROVIDER=openai and OPENAI_API_KEY |
| LM Studio (local) | SKEG_LLM_PROVIDER=lmstudio, optionally LMSTUDIO_ENDPOINT |
SKEG_LLM_MODEL picks a model other than the provider's default. Add any variable you set to the
env block (JSON) or the env_vars list (TOML).
5. Put the files in place¶
Export the variables in the shell that starts your agent (for example in ~/.zshrc):
export SKEG_RUNNER_BIN="$HOME/.config/skeg/skeg-runner"
export SKEG_TOKEN="<your project token>"
export ANTHROPIC_API_KEY="<your key>" # only for goal runs
| Agent | MCP config | Skill |
|---|---|---|
| Claude Code | Copy .mcp.json.example to .mcp.json at the repository root. |
.claude/skills/skeg-verify/SKILL.md |
| Codex | Merge codex-config.toml.example into your per-user ~/.codex/config.toml and set command to your full launcher path. Do not commit it: Codex cannot expand a variable there. |
.agents/skills/skeg-verify/SKILL.md |
| Other MCP clients | Merge the skeg entry from .mcp.json.example into the client's MCP config. |
Reference the file from your AGENTS.md: "Follow skills/skeg-verify/SKILL.md to verify on device." |
The skill and .mcp.json hold no secrets or personal paths, so you can commit them for your
team. Each developer exports their own SKEG_RUNNER_BIN and SKEG_TOKEN. The Codex config holds
a personal path, so it stays in each developer's ~/.codex/config.toml.
Apps started from the Dock or Start menu do not read your shell profile
A desktop app or IDE launched from the GUI never sees variables exported in ~/.zshrc.
The MCP server then fails with spawn '${SKEG_RUNNER_BIN}' ENOENT. Start the agent from a
terminal, set the variables for GUI apps (macOS: launchctl setenv SKEG_TOKEN …), or put the
absolute launcher path in a per-user MCP config.
Fill in the Project facts section at the top of SKILL.md: your build command, app id and
regression plans.
6. Restart and check¶
- Restart the agent, or reconnect its MCP servers.
- Paste the prompt from
FIRST-RUN.md. - Fix any failing line it reports before you ask for a verification.
First-run prompt¶
Paste this into your coding agent after you install the kit and restart it:
Check my Skeg setup, identify the project and target, and list available plans.
Use the skeg MCP server: call check_setup, then list_targets and list_plans.
Report:
- the server and project you are connected to
- any check_setup line that is not ok, with its fix
- the default target (platform, device, app id) and the other saved targets
- whether a goal model key is set
- the plans, with name, platform, tags and whether each is enabled
Do not start an inspector or run anything yet.
A good answer names your project, shows no failing checks, and lists your plans. A failing line means the setup is not done: fix it before you ask the agent to verify anything.
Kit files¶
MCP config¶
Claude Code expands ${VAR} and ${VAR:-default} in .mcp.json
(docs).
{
"mcpServers": {
"skeg": {
"type": "stdio",
"command": "${SKEG_RUNNER_BIN}",
"args": ["--mode", "mcp", "--server", "https://api.getskeg.dev"],
"env": {
"SKEG_TOKEN": "${SKEG_TOKEN:-}",
"SKEG_TARGET": "${SKEG_TARGET:-}",
"ANTHROPIC_API_KEY": "${ANTHROPIC_API_KEY:-}"
}
}
}
}
Codex forwards the variables listed in env_vars from its own environment
(docs).
# Skeg MCP server for Codex. Merge into your per-user ~/.codex/config.toml.
# Not for a committed .codex/config.toml: Codex does not expand variables in `command`, so the
# path below is yours alone. No secrets here: env_vars forwards them from Codex's environment.
[mcp_servers.skeg]
# Full path to the runner launcher. Codex does not expand variables in this field.
command = "/Users/you/.config/skeg/skeg-runner"
args = ["--mode", "mcp", "--server", "https://api.getskeg.dev"]
env_vars = ["SKEG_TOKEN", "SKEG_TARGET", "ANTHROPIC_API_KEY"]
The skill¶
Codex loads skills from .agents/skills/ (docs).
Claude Code loads them from .claude/skills/ (docs).
---
name: skeg-verify
description: Verify this app on a real device or simulator with Skeg's MCP tools. Replay stored plans to catch regressions (no model), or have Skeg's goal agent check new behavior against acceptance criteria, then report the evidence and, after review, promote the run to a plan. Use after a change with visible app behavior, or when asked to "verify on device", "check it with Skeg", "run the Skeg smoke" or "replay the plans".
---
# skeg-verify
Verify app behavior through the `skeg` MCP server. Do not substitute raw `adb`, `xcrun` or
screenshots of your own for a Skeg verdict: the user relies on Skeg's evidence and run history.
Edit the **Project facts** section once for your app. The rest stays as is.
## Project facts
- Build and install: `<your command, for example ./gradlew installDebug>`
- App id: `<package or bundle id>`
- Regression plans: `<plan names, or a tag such as smoke>`
## 1. Check the setup
1. Call `check_setup`. Each line has a status, a source and a fix.
- A `fail` line blocks every line after it. Stop and tell the user its fix.
- "No Skeg project token", or a refused token: ask the user for a token from Skeg
(Settings → Agent tokens), then ask them to restart the MCP server.
- The runner line reads "not running yet" until the first `start_inspector`. That is expected.
2. Confirm the project name is the one the user expects.
3. Call `list_targets`. Use the target whose platform, device and app id match what you installed.
Note its **platform**: the reset rules below depend on it.
## 2. Build, install and reset
1. Build and install with the project's own toolchain (Project facts). Skeg does not build apps.
A build or unit-test failure is already the answer: report it and stop.
2. Start from a known state:
| Platform | A plan saved with `reset_app: true` | Otherwise |
|---|---|---|
| Android | Its first step wipes app data and relaunches. Nothing to do. | Reset before a goal run or recording, for example `adb -s <serial> shell pm clear <app id>`. |
| iOS Simulator | Its first step wipes app data and the simulator keychain, then relaunches. Nothing to do. | Reinstall the app or reset it the way the project documents. |
| macOS | **Not supported.** A reset step fails the run. Never pass `reset_app: true` for a macOS target. | Reset the way the project documents. Ask the user before you delete app data on their Mac. |
## 3. Pick the path
| The change… | Path |
|---|---|
| should leave behavior unchanged | **A. Replay** stored plans |
| changes behavior on purpose | **B. Goal verification**, then review and promote |
| changes behavior, short flow, or no model key | **C. Hand recording** |
| nothing yet: you only need to see the screen | `start_inspector`, `screenshot`, `stop_inspector` |
### A. Replay (no model)
1. `list_plans`. Find each regression plan by exact name, one match each. Duplicate names: stop
and ask the user to rename one. For a tag, use `execute_tag` instead of one call per plan.
2. `execute_plan` with `plan_id` and `include_screenshots: false`. No inspector is needed.
3. Read the report:
- `run.status` `pass` means **every recorded step passed again**. It shows the change broke
nothing the plan covers. It does **not** show that new behavior works.
- For each failed step, quote `index`, `action`, `selector` and the failing event's `detail`.
Steps after the first failure are skipped on purpose.
4. Classify each failure:
- **App regression:** the app no longer shows or does what the step expects. Fix, reinstall,
replay.
- **Intended change:** you changed that label or flow on purpose. Do not edit the app back.
Use path B or C to record the new behavior.
- **Fragile step:** a `point=` selector, or an id many views share. Report it. Suggest a unique
id in the app.
- **Infrastructure:** `infrastructure_error`, zero steps, device or server unreachable. It says
nothing about the app. Report it. Retry once at most.
5. **Coverage:** check that the steps reach the screens you changed. If they do not, say the
replay proves nothing about those screens, and also run path B or C for them.
### B. Goal verification (needs a model key)
1. `start_inspector` with the target name. It returns when the screen is settled.
2. `list_environments` if the flow needs test data. Refer to values as `${NAME}`. Never paste a
value into a goal or criterion.
3. `start_goal`:
- `goal`: what a user does, in user terms. Do not reveal implementation details or the answer.
- `criteria`: observable facts that must hold **where the run ends**.
- `intermediate_criteria`: facts seen only **during** the run (a toast, a passing screen).
Putting these in `criteria` fails a correct run.
- One fact per criterion. Set your own `run_id` so a retry never runs twice.
4. `goal_result` with `wait_seconds` (at most 900). Call again while `finished` is false. While a
goal runs, `tap`, `type_text` and `assert_visible` refuse.
5. Read `verification.verdict`:
- `passed`: **each criterion** was established by an observation on the device.
- `failed`: quote each failed criterion and its reason. That is a defect candidate.
- `inconclusive` or `infrastructure-error`: nothing was established. **Not a pass.** Read
`verification.summary` for why. Do not rerun in a loop.
6. Do not promote yet. Go to **4. Report**, then **5. Review before promoting**.
7. `stop_inspector`.
### C. Hand recording (no model)
1. Reset the app, `start_inspector` (pass `environment` if you will type test data), `screenshot`.
2. Drive the flow with `tap` (prefer `text` over coordinates), `type_text`, `clear_text`, `swipe`.
Tap a field before you type into it. Type secrets alone, as `${PASSWORD}`.
3. Check each tap result: `recorded_as: point` does not replay on other screen sizes. Name that
step in your report.
4. End with `assert_visible` on the outcome. A plan without an assertion proves only that taps
landed.
5. Wrong turn: `discard_recording`, reset, start over.
6. Go to **4. Report** and **5. Review before promoting**. Then `save_recorded_plan` replaces
promotion. Replay the saved plan with path A to prove it passes without you.
## 4. Report
State the path, the verdict and the evidence:
- **Replay:** plan names and ids, run ids, status, and each failed step with its classification.
Say which changed screens the plans did and did not cover.
- **Goal:** run id, verdict, and one line per criterion with its result and proving step.
- **Recording:** the steps, any `point` steps, and the replay result after saving.
- Any Skeg problem you hit (a confusing result, a missing tool, a manual step).
Never call a replay pass "verified new behavior". Never call `inconclusive` or
`infrastructure-error` a pass.
## 5. Review before promoting
Promotion turns a run into a stored plan that every later replay trusts. Get it right first:
1. Show the user the verdict and per-criterion evidence (goal) or the recorded steps (recording).
2. Promote or save only after the user agrees, or if they asked for it up front.
3. Use a unique plan name. Pass `reset_app: true` only for Android or iOS targets.
4. `promote_goal_run` and `save_recorded_plan` create a **draft**. Tell the user the plan name.
They approve it in Skeg, and add it to a tag when it should join a suite.