MCP Usage (optics mcp)¶
optics mcp runs a Model Context Protocol server that exposes the optics-framework keyword engine to an LLM client (Claude Desktop, Claude Code, Cursor, …). The model can start a device/browser session, run automation keywords as tools, and observe device state through resources — driving a live target the way optics live does, but under agent control.
It reuses the in-process keyword machinery from the REST server (optics serve), so whatever driver and element sources optics already supports work here too. The driver is chosen at runtime via the start_session tool — nothing is hard-coded.
Verified end-to-end against a remote Appium hub on a physical Samsung A53:
start_session→screenshot(rendered PNG) →swipe→terminate_session.
1. Prerequisites¶
- Python 3.12+ and optics-framework installed.
- A driver target that optics can reach — e.g. a local Appium server with a connected device/emulator, or a remote Appium hub. You provide its URL and capabilities to
start_session. - An MCP-capable client (Claude Desktop/Code, Cursor, or the
fastmcpPython client for scripting). - (Optional) extras for richer element location: text detection (
googlevision) and image detection (templatematch) require their own credentials/config, exactly as in a normal opticsconfig.yaml.
2. Install¶
The MCP server depends on fastmcp, shipped as an optional extra:
If the extra is missing, optics mcp exits with a clear message telling you to install it — the rest of the CLI is unaffected.
3. Run the server¶
# stdio transport (default) — for local clients that spawn the process
optics mcp
# HTTP transport — for networked / multi-client use
optics mcp --transport http --host 127.0.0.1 --port 8090
| Flag | Default | Meaning |
|---|---|---|
--transport | stdio | stdio (local clients) or http |
--host | 127.0.0.1 | bind host (http only) |
--port | 8090 | bind port (http only) |
Docker¶
Containerized MCP runs HTTP transport bound to 0.0.0.0:8090 (stdio is for local clients that spawn the process). Images live under Docker/mcp/ and install the [mcp] extra (fastmcp).
Docker Compose (from the repo root):
# Production image (PyPI) — host port 8090
docker compose -f Docker/docker-compose.yml up --build mcp
# Development image (local .whl) — host port 8091
docker compose -f Docker/docker-compose.yml up --build mcp-dev
Standalone build/run:
docker build -f Docker/mcp/prod/Dockerfile -t optics-mcp-prod .
docker run -d -p 8090:8090 --name optics-mcp-prod optics-mcp-prod
Connect your MCP client to the container:
Use port 8091 for the mcp-dev compose service. When start_session targets Appium on the host, set "url": "http://host.docker.internal:4723". See Docker/deployment.md for vision-backend build args, Google Vision credential mounts, and dev-wheel builds.
4. Connect an MCP client¶
stdio — the client launches the server itself. Add to your client's MCP config (e.g. Claude Desktop claude_desktop_config.json, or Claude Code .mcp.json):
If optics isn't on the client's PATH, use the absolute path (e.g. /path/to/.venv/bin/optics) or "command": "python", "args": ["-m", "optics_framework.helper.cli", "mcp"].
HTTP — start the server yourself (optics mcp --transport http) and point the client at the URL:
5. The expected journey¶
start_session— open a session against your driver. Returns{ "session_id", "driver_id" }. The target app is launched automatically. Capture thesession_id; every other tool and resource needs it.- Observe — read a resource or call
screenshotto see the screen, readoptics://session/{session_id}/sourcefor the UI hierarchy, or callget_interactive_elementsfor tappable elements. - Act — call keyword tools (
press_element,enter_text,swipe,assert_presence, …) with thesession_id. terminate_session— release the driver when done.
start_session arguments¶
| Arg | Type | Notes |
|---|---|---|
driver | str | driver name, e.g. "appium" (default) |
url | str | driver/hub URL (e.g. local http://127.0.0.1:4723 or a remote hub) |
capabilities | object | driver capabilities (platform, device, app, auth…) |
elements_sources | list[str] | element sources to enable (see §7) |
text_detection | list[str] | optional OCR sources (e.g. ["googlevision"]) |
image_detection | list[str] | optional template sources (e.g. ["templatematch"]) |
project_path | str | optional project folder (loads bundled templates) |
Example — local Appium + Android emulator:
{
"driver": "appium",
"url": "http://127.0.0.1:4723",
"capabilities": {
"platformName": "Android",
"appium:automationName": "UiAutomator2",
"appium:deviceName": "emulator-5554",
"appium:appPackage": "com.android.settings",
"appium:appActivity": ".Settings"
},
"elements_sources": ["appium_find_element", "appium_page_source", "appium_screenshot"]
}
Omit appPackage/appActivity to attach to whatever is already on screen. For a remote/managed hub, set url to the hub and include any hub-specific capabilities (auth token, device id) just as you would in config.yaml.
Keyword parameters are strings¶
Every keyword tool takes session_id plus that keyword's parameters, and all parameters are typed as strings — pass "2", not 2. Element arguments accept the same locators optics uses elsewhere: xpath=…, text=…, css=…, an id, or an image template name.
6. Tools reference¶
start_session, terminate_session, and screenshot are purpose-built; every other tool is an optics keyword auto-exposed from ActionKeyword / AppManagement / Verifier. Representative set:
- Session/app:
start_session,terminate_session,launch_app,launch_other_app,close_and_terminate_app,get_app_version,get_driver_session_id. - Interact:
press_element,press_by_coordinates,press_by_percentage,press_keycode,enter_text,enter_number,clear_element_text,select_dropdown_option,detect_and_press. - Gestures/scroll:
swipe,swipe_by_percentage,swipe_from_element,swipe_until_element_appears,scroll,scroll_from_element,scroll_until_element_appears. - Observe/verify:
screenshot(rendered image),get_text,get_interactive_elements(acceptsfilter_config, e.g."buttons"),is_element,assert_presence,assert_equality,validate_element,validate_screen. - Misc:
sleep,execute_script.
The full machine-readable catalog (every keyword, its params and docs) is the optics://keywords resource.
screenshot returns a rendered image/png your client can display inline — prefer it over the screenshot resource when you want to see the screen.
7. Resources reference¶
| URI | Content |
|---|---|
optics://keywords | full keyword catalog (name, slug, description, params) |
optics://session/{session_id}/screenshot | screen as raw PNG bytes |
optics://session/{session_id}/source | page source / UI hierarchy |
optics://session/{session_id}/elements | interactive elements (unfiltered) |
optics://session/{session_id}/screen_elements | captured screen elements |
get_interactive_elements is available both as a resource (unfiltered) and as a tool (so the model can pass filter_config).
The screenshot resource delivers raw PNG bytes with a generic
application/octet-streammime (a limitation of templated MCP resources). For an image your client renders as a picture, use thescreenshottool.
8. Element sources decide what works¶
The keywords you can use depend on which elements_sources (and detection sources) you enable in start_session — same rules as a normal optics project:
| Capability | Needs |
|---|---|
Locate by xpath / text / id, tap, type | appium_find_element |
| Screenshots & image-based location | appium_screenshot |
Page source, get_interactive_elements, source-based extraction | appium_page_source |
| OCR / locate visible text on screen | a text_detection source (e.g. googlevision) |
| Image template matching | an image_detection source (e.g. templatematch) |
If you enable only appium_find_element + appium_screenshot and then call get_interactive_elements, optics raises E0202: No interactive elements retrieved using available strategies — that's expected; enable appium_page_source (or a vision source) for that path.
9. Troubleshooting¶
- First
start_sessionis slow against a remote hub (~30–60 s to allocate and launch). Give your client a generous timeout (thefastmcpPython client takesClient(url, timeout=180)). No module named 'fastmcp'/ "mcp extra required" — installoptics-framework[mcp].- Sessions aren't shared with
optics serve/optics live. Each is a separate process with its own in-memory session store. Alwaysstart_sessionin this server before using a keyword tool; you cannot attach to a session created elsewhere. - Device busy / already allocated — if your hub reports the device as busy, free it through your device-orchestration API, then retry
start_session. get_interactive_elements/sourceerrors — usually a missing element source; see §8.- Errors surface as MCP tool errors. An optics failure (element not found, bad config, driver error) comes back as a
ToolErrorcarrying the optics error code/message, so the model can read and react to it.
10. How it works (pointer)¶
optics mcp is a thin in-process wrapper over common/expose_api.py. It reflects the API keyword classes into typed tools and routes execution through the same execute_keyword path the REST server uses; read-only observers become resources. See optics_framework/helper/mcp_server.py and the "MCP server journey" section of CLAUDE.md for the internals.