# Sailfish Devel MCP Host-side Model Context Protocol server for Sailfish OS development workflows. This first iteration is dependency-free: it implements MCP over stdio directly with Python's standard library and exposes typed tools around commands that are easy to get wrong during day-to-day Sailfish work. ## Scope The server currently exposes tools for: - Sailfish device access over SSH - defaultuser session-bus calls - Lipstick screenshots - touchscreen discovery and tap/swipe injection - combined screenshot, touchscreen discovery, and touch injection workflows - topmost window PID lookup - process map inspection - journal log reads - RPM copy/install on a device - system and user service management - user-session command execution with the configured D-Bus environment - Sailfish Browser launch/debug helpers - preflighted Docker/mb2 RPM builds through the vendored `build-sailfishos` helper - cancellable local and remote Android/AppSupport build jobs - installed SDK repository metadata refresh - Jolla OBS result and build-log lookup through `osc` - repository status and search under the configured git root - RPM spec metadata summaries - QML translation search and Sailfish ternary translation checks The committed defaults are deliberately generic. Device tools default to the placeholder SSH target `root@device`, the Sailfish user-session bus at `/run/user/100000/dbus/user_bus_socket`, `~/git` as the local source root, `~/OBS` as the OBS checkout root, and the vendored build helper at `src/sailfish_devel_mcp/vendor/build_sailfishos.py`. Put a config file at `~/.config/sailfish-devel-mcp/config.json` or pass `--config` to provide your real device and OBS settings. Device entries can also carry the preferred user, architecture, and configured release label. If an installed SDK is available, set `paths.local_sdk` to its `sdk-chroot` path; builds will use it when it has a target matching the requested release and architecture, otherwise they fall back to a matching tag in the third-party coderus Docker mirror. Neither a configured device label nor mirror tag availability independently establishes the current official SailfishOS release or SDK target. ## Running From a checkout: ```sh /path/to/sailfish-devel-mcp/bin/sailfish-devel-mcp ``` To inspect the effective configuration: ```sh /path/to/sailfish-devel-mcp/bin/sailfish-devel-mcp --dump-config ``` Example MCP client configuration: ```json { "mcpServers": { "sailfish-devel": { "command": "/path/to/sailfish-devel-mcp/bin/sailfish-devel-mcp" } } } ``` The wrapper writes startup, shutdown, MCP request, and tool-call logs to `~/.local/state/sailfish-devel-mcp/server.log` by default. Override the log directory with `SAILFISH_DEVEL_MCP_LOG_DIR`. If the MCP client uses a different server label, set `SAILFISH_DEVEL_MCP_SERVER_LABEL` in the client environment so the wrapper startup line includes the same label. ## Configuration Example: ```json { "default_device": "phone", "devices": { "phone": { "ssh_target": "root@phone", "username": "defaultuser", "architecture": "aarch64", "release": "live", "user_bus_runtime_dir": "/run/user/100000", "user_bus_address": "unix:path=/run/user/100000/dbus/user_bus_socket" } }, "paths": { "git_root": "~/git", "obs_root": "~/OBS", "ssh_config": "~/.ssh/config", "local_sdk": "/srv/mer/sdks/sfossdk/sdk-chroot", "osc_api_alias": "your-obs-alias" }, "default_android_build_host": "android-builder", "android_build_hosts": { "android-builder": { "ssh_target": "user@android-build-host", "project_dir": "/path/to/alien-aliendalvik-system", "state_dir": "/tmp/sailfish-devel-mcp/android-builds" } } } ``` ## Tool Notes The server keeps local paths scoped to the configured git root, OBS checkout root, and `/tmp` for tools that read or write files. Device access still uses SSH, so the usual SSH prompts, permissions, and command failures are surfaced as tool results. Mutating tools are annotated as non-read-only: - `sailfish_device_lipstick_screenshot` - `sailfish_device_touch` - `sailfish_device_touch_workflow` - `sailfish_device_user_bus_call` - `sailfish_device_user_session_command` - `sailfish_device_install_rpm` - `sailfish_device_restart_service` - `sailfish_device_browser_launch` - `sailfish_build_rpm` - `sailfish_build_cancel` - `sailfish_android_build` - `sailfish_android_build_cancel` - `sailfish_sdk_refresh_metadata` `sailfish_device_lipstick_screenshot` defaults to `~/Pictures/Screenshots/lipstick-.png` under `/home/`, where `username` comes from the device config. Lipstick rejects screenshot save paths outside the home directory. When that directory is missing, the tool derives ownership with `stat -L`, creates `Pictures` as that owner/group with mode `775`, and creates `Pictures/Screenshots` as that owner and the `privileged` group with mode `755` when the group exists. `sailfish_device_touch` supports `discover`, `tap`, and `swipe`. Discovery prints `/proc/bus/input/devices` and can also run `evdev_trace -i` when `include_evdev_trace` is true. Tap and swipe auto-select a likely touchscreen input event device unless `input_device` is supplied. Coordinates are raw input/display coordinates, so pair this tool with a current screenshot when choosing points. `sailfish_device_touch_workflow` wraps the common UI-debugging sequence: capture a Lipstick screenshot, optionally list touch devices, inject a tap or swipe, and optionally capture a second screenshot. It returns each step's structured result separately. `sailfish_device_user_session_command` runs an argv command with `XDG_RUNTIME_DIR` and `DBUS_SESSION_BUS_ADDRESS` set from the configured device. Set `run_as_user` when the command should execute as the configured Sailfish username through `runuser` or `su`. `sailfish_device_install_rpm` accepts either `rpm_path` for one local RPM or `rpm_paths` for a dependency set. With `rpm_paths`, the tool copies every RPM to the remote `remote_dir` and runs one `pkcon install-local` or `rpm -Uvh` command with all copied files, so dependencies can be resolved together. `sailfish_device_browser_launch` stops the browser booster service and stale browser/firejail PIDs when requested, launches Sailfish Browser through `invoker` with the display and user-session environment, then queries Lipstick for the topmost PID and checks whether that process has `libxul.so` mapped. `sailfish_build_rpm` can use a configured device's `architecture` and `release` as defaults when the call includes `device`. If `paths.local_sdk` is set, the build defaults to `live` and first checks the installed SDK targets. `live` uses the unversioned local target for the requested architecture, for example `aarch64`. A named production release is used only when passed explicitly through the tool arguments, environment, or device config; it uses a matching versioned local target when available, and otherwise falls back to a matching tag in the third-party `coderus/sailfishos-platform-sdk` Docker mirror. Tags in that mirror indicate image availability; they do not identify the current official SailfishOS release or SDK target. The wrapper image defaults to `sailfish-sdk-build-engine:$USER` and can be overridden with `SAILFISH_SDK_BUILD_ENGINE_IMAGE`. Use `sailfish_build_preflight` to validate backend, image/target selection, architectures, local RPM inputs, pull policy, VCS behavior, and artifact paths without pulling an image or changing the project. `sailfish_build_rpm` accepts the same `backend`, `local_sdk`, `target`, `pull_policy`, `no_vcs_apply`, and `allow_untrusted_rpms` controls. An explicitly selected `local` backend does not silently fall back to Docker. Asynchronous jobs use confined UUID job directories and expose helper metadata and RPM paths through `sailfish_build_status`; use `sailfish_build_cancel` to terminate the tracked process group. `sailfish_android_build` starts a remote Android/AppSupport build on the configured build host. Configure `android_build_hosts..project_dir` or pass `project_dir` to point at the remote Android tree, for example an `alien-aliendalvik-system` checkout. The tool writes job state under the remote `state_dir`, atomically creates a per-job directory, and starts its own process group with `nohup` and `setsid`, so the SSH session used to launch the job can disconnect without killing the build. Set `build_timeout` for a remote build lifetime limit. Poll with `sailfish_android_build_status`; omit `job_id` to list recent jobs, pass a job id to read state and tail `build.log`, or use `sailfish_android_build_cancel` to terminate the identity-checked process group. `sailfish_sdk_refresh_metadata` refreshes zypper metadata in the installed SDK main target, for example `aarch64.default`, using the same privileged Docker wrapper style as local SDK builds. Use it when local SDK builds fail because a package listed in repository metadata cannot be downloaded. `sailfish_obs_results` and `sailfish_obs_buildlog` accept `server` as `internal`, `partner`, or `community`. `internal` maps to the `.oscrc` alias `jolla`; the other names map to matching aliases. Omit `server` to use `paths.osc_api_alias`. The advanced `api_alias` argument accepts any raw `osc -A` alias or API URL and cannot be combined with `server`. `sailfish_obs_buildlog` defaults to `osc api` with `nostream=1` so a build log request does not become a long-running live stream. Set `nostream` to `false` to use `osc remotebuildlog`. Read-only tools include build preflight/status, the journal, topmost PID, process maps, OBS lookup, repo search, spec summary, and QML checks. ## Smoke Test ```sh printf '%s\n' \ '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"smoke","version":"0"}}}' \ '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \ | /path/to/sailfish-devel-mcp/bin/sailfish-devel-mcp ``` ## Development Run tests without installing the package: ```sh PYTHONPATH=src python3 -m unittest discover -s tests ``` The canonical helper lives in the `build-sailfishos` skill. Update the exact vendored copy with: ```sh python3 scripts/update_build_helper.py /path/to/build-sailfishos/scripts/build_sailfishos.py ```