Configuration
Every setting the server and the agent read, generated from .env.example, which a test holds to the settings models.
Precedence, highest first: a real environment variable, then .env, then ios-mcp.toml, then the defaults in code. So a shell export or CI setting overrides this file without anyone editing it. Nested settings use a double underscore: IOS_MCP_
Copy .env.example to .env to change any of them. A value marked not set is commented out there: either the default shown for reference, or an example to fill in.
Model provider (agent only)
Section titled “Model provider (agent only)”The base install commits to no vendor; install the matching extra, e.g. uv sync --extra openai.
| Setting | Value | Notes |
|---|---|---|
IOS_AGENT_PROVIDER |
anthropic |
Anything init_chat_model accepts. Change the model with the provider. |
IOS_AGENT_MODEL |
claude-opus-5 |
|
IOS_AGENT_MAX_TOKENS |
16000 |
Thinking and the reply share this budget. |
IOS_AGENT_EFFORT |
medium |
Anthropic only, sent as output_config. Skipped for every other provider. |
IOS_AGENT_PROMPT_CACHE |
true |
Anthropic only. Marks the tools, the system prompt and the transcript for prompt caching. Other providers cache on their own terms and are sent nothing. Under measurement in docs/adr/0022. |
IOS_AGENT_TEMPERATURE |
0.7 not set |
Deliberately unset. Claude Opus 5, Opus 4.8, Opus 4.7 and Sonnet 5 reject it with a 400; set it only on a provider that accepts it. |
IOS_AGENT_MAX_STEPS |
24 |
Turns before the loop gives up on a goal. |
IOS_AGENT_EXTRA |
{"reasoning_effort": "high"} not set |
Anything this project has not heard of, passed to the provider untouched. Applied last, so it overrides everything above. JSON. |
IOS_AGENT_ROUTE_MODEL |
gpt-5.4-mini not set |
Start each run on a smaller model from the same provider, and move to IOS_AGENT_MODEL for the rest of the run at the first sign of trouble. Off by default; measured in docs/adr/0015 at about half the cost, which is a tie with its own bar rather than a reliable halving. |
IOS_AGENT_USD_PER_MTOK_IN |
5.0 not set |
What the eval suite prices tokens at, per million. Defaults are Claude Opus 5’s rates; nothing here can know what another vendor charges, so set both whenever the provider or model changes. Read from here like every other setting; they used to be read only from the process environment, which a .env never reaches, so setting them here silently did nothing. |
IOS_AGENT_USD_PER_MTOK_OUT |
25.0 not set |
|
IOS_AGENT_USD_PER_MTOK_CACHE_READ |
0.5 not set |
What a token read from, or written to, the provider’s prompt cache costs. Unset means Anthropic’s 5-minute rates: a tenth and one and a quarter of the input price. For OpenAI set the read price to its cached-input rate and the write price to the input price, since it charges nothing extra to write. |
IOS_AGENT_USD_PER_MTOK_CACHE_WRITE |
6.25 not set |
Provider API keys are NOT read by this project
Section titled “Provider API keys are NOT read by this project”Each vendor SDK resolves its own, and it does so better than we could: the Anthropic SDK also accepts an auth token or an ant auth login profile. Listed here only so the full set of knobs is in one place.
| Setting | Value | Notes |
|---|---|---|
ANTHROPIC_API_KEY |
not set | |
OPENAI_API_KEY |
not set | |
GOOGLE_API_KEY |
not set | |
GROQ_API_KEY |
not set |
(ollama runs locally and needs no key)
Tracing (agent only)
Section titled “Tracing (agent only)”Off by default; uv sync --extra tracing to use it. Every run becomes OpenTelemetry spans, scrubbed by the session’s redactor before export. See docs/adr/0023.
| Setting | Value | Notes |
|---|---|---|
IOS_AGENT_TRACING |
off |
off, otel or langsmith. otel exports over OTLP/HTTP to the endpoint below; langsmith exports the same spans to LangSmith with LANGSMITH_API_KEY. |
OTEL_EXPORTER_OTLP_ENDPOINT |
http://localhost:6006 not set |
Read by the OTLP exporter itself. A local Phoenix listens on port 6006. |
OTEL_EXPORTER_OTLP_HEADERS |
not set | |
OTEL_SERVICE_NAME |
ios-agent not set |
|
LANGSMITH_API_KEY |
not set | |
LANGSMITH_PROJECT |
ios-agent not set |
|
LANGSMITH_ENDPOINT |
https://api.smith.langchain.com not set |
Safety
Section titled “Safety”Read SAFETY.md before turning any of this off. Doing so is a reasonable choice for a simulator running tests, and is not a reasonable one for a device carrying someone’s real accounts.
| Setting | Value | Notes |
|---|---|---|
IOS_MCP_POLICY__ENABLED |
true |
The gate as a whole: approval, app blocklist, redaction, audit. |
IOS_MCP_POLICY__CONFIRM_DESTRUCTIVE |
true |
Ask before anything that sends, pays, deletes, confirms or signs out. |
IOS_MCP_POLICY__CONFIRM_REACHING_A_PERSON |
true |
Ask before a like, a follow, a reply: anything that reaches another person. |
IOS_MCP_POLICY__MAX_CONSECUTIVE_FAILURES |
5 |
Halt the session after this many failed actions in a row, or when the screen cycles between the same few states within this many steps. |
IOS_MCP_POLICY__LOOP_DETECTION_WINDOW |
6 |
|
IOS_MCP_POLICY__APP_ALLOWLIST |
["com.apple.Preferences"] not set |
Empty means “anything not blocked”. Set it to restrict the agent to one app. |
IOS_MCP_SECRET_ICLOUD_PASSWORD |
not set | Secrets are referenced, never inlined. ios_type_secret resolves a reference from the macOS keychain first, falling back to IOS_MCP_SECRET_<REF_UPPER>. The value never reaches a prompt, a tool result, or the audit trail. security add-generic-password -s ios-mcp -a icloud-password -w |
Device and WebDriverAgent
Section titled “Device and WebDriverAgent”| Setting | Value | Notes |
|---|---|---|
IOS_MCP_DEFAULT_DEVICE |
not set | UDID or a name substring. Unset picks the best ready device, preferring a simulator: acting on a real phone should be deliberate. |
IOS_MCP_WDA__BASE_URL |
not set | Point at a WebDriverAgent someone else is running (a device farm, a phone on Wi-Fi, a runner started by hand). When set, nothing is launched or torn down. |
IOS_MCP_WDA__HOME |
not set | Where WebDriverAgent is built and looked for. Unset: a clone’s vendor/wda when the server runs from one, else ~/Library/Application Support/ios-mcp/wda. ios-mcp prepare-wda simulator builds into the same place. |
IOS_MCP_WDA__BUNDLE_ID |
com.facebook.WebDriverAgentRunner.xctrunner not set |
Free Apple accounts cannot sign com.facebook.*, so the runner bundle id is configurable for exactly that reason. Left at this default, it is read from the runner ios-mcp prepare-wda device built. |
IOS_MCP_WDA__STARTUP_TIMEOUT_S |
90 not set |
A physical device needs a longer startup budget than a simulator. |
IOS_MCP_GOIOS__AUTO_START_TUNNEL |
true not set |
A cabled iOS 17+ device needs a go-ios tunnel. The userspace one needs no sudo, so it is started on demand; kernel is sudo ios tunnel start. |
IOS_MCP_GOIOS__TUNNEL_MODE |
userspace not set |
|
IOS_MCP_SIMULATOR__SHOW_WINDOW |
true |
simctl boot starts the runtime, not the window: without this a simulator runs headlessly and nothing appears on the Mac. Turn it off for CI, where a window is at best pointless. |
IOS_MCP_STABILIZE__MAX_WAIT_S |
20 not set |
A device snapshot was measured at about 3.7s in August 2026 against well under a second on a simulator, so max_wait_s must exceed stable_samples snapshots or a real device times out on every action. Settings root read in 0.4 to 0.6s on the same phone in October; the ceiling is kept for the slow case. |
IOS_MCP_STABILIZE__SIGNAL |
tree not set |
Settle on screenshots instead of a second tree read: wait until no new frame has appeared for QUIET_S, then read the tree once. A screenshot costs 0.13 to 0.17s on a phone against 1.3 to 1.8s for the tree, and this took 17 to 22% off a phone’s settle time. Anything the frames cannot answer falls back to the tree loop. QUIET_S has to outlast a pause inside a transition (Settings search holds still for 0.42s). See ADR 0019. |
IOS_MCP_STABILIZE__QUIET_S |
0.6 not set |
|
IOS_MCP_STABILIZE__FRAME_TIMEOUT_S |
3 not set |
|
IOS_MCP_SCREENSHOT__MAX_EDGE_PX |
1568 not set |
Screenshots leave the session with their long edge capped at this many pixels, because a native capture (2868 on an iPhone 17 Pro Max) is over the 2000 a vision API accepts once a request carries many images. 0 sends the native size. |
Perception
Section titled “Perception”These decide what an agent loop costs.
| Setting | Value | Notes |
|---|---|---|
IOS_MCP_DIGEST__TOKEN_BUDGET |
1500 |
Most tokens one screen may cost the model. Over it, the least salient elements are dropped and the digest says it was truncated. |
IOS_MCP_SNAPSHOT__MAX_DEPTH |
50 |
Depth 20 silently loses a third of a Settings screen; past 30 is free, because the round trip dominates rather than the traversal. |
Server transport
Section titled “Server transport”| Setting | Value | Notes |
|---|---|---|
IOS_MCP_SERVER__TRANSPORT |
stdio |
stdio for a local MCP client; http serves on this machine only. See docs/threat-model.md before changing the host. |
IOS_MCP_SERVER__PORT |
8765 not set |
|
IOS_MCP_LOG_LEVEL |
INFO |
Logs go to stderr, because stdout belongs to the protocol. |