Skip to main content
Glama
Sailfish Devel MCP
by abranson

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

  • device prerequisite checks and opt-in installation of missing optional tools

  • 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

  • short and detached user-session command execution with the configured D-Bus environment

  • desktop-entry application launch as the configured session user

  • 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

  • OBS results, build logs, and guarded source-pin edits 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 configuration template 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.

For a device attached over USB, pass usb as the device argument to use the configured default device, or usb:<name> when the user supplies a device name. The MCP keeps that device's user and session metadata but connects to 192.168.2.15 without checking or storing SSH host keys. It trusts the supplied name and does not resolve it through DNS, so no separate validation connection is made.

Generic Python configuration code is tracked and works in a clean checkout. Keep all machine-specific values in ~/.config/sailfish-devel-mcp/config.json or its environment overrides. No Python bootstrap copy is needed.

Related MCP server: micro-mcp

Host prerequisites

Use a Linux host with Python 3.10 or newer. The server has no third-party Python runtime dependencies. Detached job supervision uses POSIX process groups and Linux /proc. The following programs are needed only for the workflows listed; installing every optional tool is unnecessary.

Workflow

Host requirements

Run from the checkout

python3, POSIX sh, dirname, mkdir, touch, and date for the logging wrapper

Install the Python package

pip and setuptools >=61 for packaging; python3 -m pip install . installs both CLI entry points

Device commands and deployment

OpenSSH ssh; scp for RPM copies and screenshot downloads; SSH credentials and a reachable device

Repository inspection

git; rg (ripgrep) for searches, with grep as a repository-search fallback

QML translation search

rg; the ternary-comment checker itself needs only Python

RPM builds and SDK refresh

A running Docker engine accessible to your user, git, and bash; ACL tools (setfacl) for Docker builds that need container write access

Installed SDK builds

A configured Sailfish Platform SDK sdk-chroot, registered architecture targets, and an appropriate Docker wrapper image; sdk-manage, mb2, sb2, zypper, RPM and build dependencies live inside the SDK

OBS results, logs and source-pin edits

Required: osc, with the selected server and credentials configured in your .oscrc

Android/AppSupport builds

ssh; a separate configured Linux build host with its Android tree and build dependencies

Run the MCP tool sailfish_doctor to report local command availability, helper provenance and installed SDK targets. It does not connect to devices, test Docker daemon access, or install host packages. Install host tools through your Linux distribution; the package names differ between distributions. Check RPM build requirements in each project's spec file and Android requirements in its tree's documentation.

The remote Android build host also needs sh, the selected bash or sh, nohup, setsid, base64, date, mkdir, chmod, cat, sed, cut, sort, tail, and sleep, plus Linux /proc. These support job bookkeeping and cancellation; the build command may require additional tools.

Device prerequisites and optional setup

Enable Sailfish developer mode and SSH access before using device tools. SSH authentication and root access must be configured by the device owner. Set the device's username and session-bus address in the local configuration: older devices can use nemo rather than defaultuser, and the user ID can differ. Start the graphical user session before checking Lipstick or session-bus tools. See the Sailfish developer-mode instructions and D-Bus reference.

Feature passed to sailfish_device_setup

Device requirements

commands

sh and env; arbitrary commands need their own executables

diagnostics

journalctl, cat, dbus-send, env; the configured session bus and Lipstick for topmost PID queries; readable /proc/<pid>/maps for process maps

screenshot

dbus-send, env, sg, stat, install, getent, dirname; Lipstick's screenshot API and access to the privileged group

touch

Python >=3.6 with standard-library os, re, struct, sys and time; sh, cat; readable /proc/bus/input/devices and permission to write the selected /dev/input/event<N>

input_trace

Optional evdev_trace from mce-tools, plus sh, cat, sleep; timeout is used when present, with a bounded fallback otherwise

app_launch

gio, getent, cut, nohup, mkdir, mktemp, mv, cat, tail, sleep, sh, env, and either runuser or su; installed desktop entries and a graphical user session

browser

systemctl, pidof, ps, awk, grep, invoker, sailfish-browser, dbus-send, sleep, sh, env, and either runuser or su; Sailfish Browser and its booster/session environment

install

pkcon for the default installer or rpm when explicitly selected; an SSH file-transfer subsystem compatible with the host's scp; package-install privileges

services

systemctl, env; permissions for the selected system or user service

The prerequisite probe itself requires sh and id. dbus-send is supplied by dbus, and gio is supplied by glib2 in current Sailfish packaging. Base OS tools, missing applications, permissions, and session configuration require manual repair. Command availability and a session socket do not prove that every device API or touch input works.

Call sailfish_device_setup with install_missing omitted or false to check a device without installing anything:

{"device": "phone", "features": ["touch", "app_launch"]}

The result lists discovered executables, missing requirements, session issues, and optional packages that could be installed. The default features are commands, diagnostics, screenshot, touch, app_launch, install, and services. Select input_trace or browser explicitly when needed.

To install supported missing optional tools, explicitly opt in:

{"device": "phone", "features": ["touch", "input_trace"], "install_missing": true}

Setup requires a root SSH login and pkcon or zypper. It installs only missing python3-base (for Python) and mce-tools (for evdev_trace) from the device's existing repositories, then checks again. Package dependencies are resolved by the device package manager. It does not add repositories, enable SSH, change accounts or permissions, or repair base OS packages. Package availability can vary with the configured Sailfish release. The mappings follow the Python spec and Sailfish touch documentation.

Installation returns a detached job by default. Check sailfish_build_status or use sailfish-devel-jobs wait --kind build --job-id JOB_ID; the completed status includes the final report under device_setup and its JSON result_path. sailfish_build_cancel requests cancellation, but interrupting SSH does not roll back a package transaction. wait=true requests synchronous operation for controlled diagnostics.

Optional device companion

Development/test devices only. Do not install this companion on a personal or primary phone containing real accounts or sensitive data. It runs as root and can execute privileged development commands. Journal/D-Bus captures, screenshots and diagnostics can include private data returned to the host MCP client and model context. Present this warning before requesting first-install permission.

The host MCP remains the coordinator for builds, OBS, repository work and file transfers. The optional Python 3.6+ standard-library companion runs as a system service, with a root-only Unix socket reached over existing SSH access. It has no TCP listener. Session D-Bus calls, app launches and user-service operations run as the configured graphical user with that user's home and bus environment.

Use sailfish_device_setup for installation and updates:

{"device": "phone", "features": ["companion"], "companion": "check"}

Always request permission before first installation on each device. Only after approval, deploy with:

{"device": "phone", "features": ["companion"], "companion": "deploy", "confirm_companion_install": true}

Updates to recognized installations need no renewed permission; omit the confirmation flag. A recognized earlier user-service installation is migrated: setup verifies root-service readiness before stopping/removing the old user service. User-owned companion source is never executed as root during migration. Unknown files/symlinks are refused. The confirmation flag records the caller's assertion of approval, not an independent authentication mechanism.

Deployment requires root SSH and writes:

  • /opt/sailfish-devel-companion/{agent.py,owner,session.json}

  • /etc/systemd/system/sailfish-devel-companion.service

  • Runtime socket/lock in /run/sailfish-devel-companion/, with a separate /run/sailfish-devel-companion.control lifecycle lock.

  • Detached job state/logs under /var/lib/sailfish-devel-companion/jobs/.

Code, configuration, socket and job directories are private to root. Setup starts or restarts the service and returns the deployed source SHA-256. It does not enable boot autostart or start capture. An installed companion starts on demand for routed device work or a new capture. It exits successfully after one hour of inactivity, so systemd does not restart it. Active requests and detached jobs prevent idle shutdown and reset the idle period. Capture output does not count as activity; each capture expires after one hour, even if clients keep polling. Restart capture explicitly for a longer session. Idle shutdown discards the in-memory event history; detached job records remain on disk.

Management stop pauses on-demand startup persistently; start or a deployment update resumes it. Status inspection does not start a stopped service. A failed on-demand start selects SSH fallback before any requested operation executes. The system service is independent of the graphical session; session-dependent tools still require that session to be available. Optional Python installation still requires install_missing=true. Ordinary setup defaults perform no installation. Setup, recovery and RPM transfer/install retain their host implementations and do not depend on a working companion.

Existing device tools and fallback

Public host tool names and arguments are preserved. Journal reads, process maps, topmost PID, D-Bus calls, short commands, screenshots/touch, app/browser launch, service control and detached device commands prefer a compatible companion. Absent, incompatible or explicitly paused companions select the retained SSH implementation before execution. A protocol/transport error or failed operation after selecting the companion is reported, never retried over SSH. No automatic installation occurs. Root-only socket access does not elevate a non-root SSH account; it uses fallback.

Journal/process-map filtering runs on the device. Screenshot/touch workflows run under one device-local operation lock; file downloads remain host-side and happen after the device sequence. A download failure does not replay already-performed touch input. App launch adds observed foreground PID/verification where the desktop entry's executable can be identified; submission alone still does not prove visible rendering. Service calls include local state and bounded failure logs. Operations that start/stop the companion itself use SSH to avoid severing their own RPC.

Detached commands run under device-owned supervisors, survive host/SSH disconnects, and terminate their process groups on cancellation/timeout, including children whose leader has exited. Durable host receipts keep the existing job ID and status/cancel/CLI interface. Individual status reads contact the recorded device; listings show the last known snapshot. Jobs never switch backend after submission. Logs stay on the device and are retrieved in bounded pages/excerpts. Restart/reboot interruption is reported as a lost supervisor, not success. Managed stop, remove and update refuse while jobs are active; job submission and management share a lock. Completed job evidence is retained on companion removal.

Capture tools

All tools below have the prefix sailfish_device_companion_:

Suffix

Purpose

manage

Inspect installation or start/stop/remove the service

status

Running version, API capabilities, UID, session user and capture state

capture_start

One journal unit (source=journal, unit=example.service, scope=user/system, default user), or D-Bus interface (source=dbus, interface=org.example.Interface, bus=session/system)

capture_stop

Stop collection, retaining captured history

events

Read instance/after cursor pages, limit 1–100, optional wait_seconds 0–10

wait_service

Wait for unit to reach active/inactive/failed, scope=user/system, up to ten seconds

Root journal capture reads system storage directly; user scope filters by the configured user's UID and user unit, so it works even without separate per-user journal files. Session D-Bus capture drops to the graphical user. Capture is off initially; a second capture is refused until the first is stopped. D-Bus output is raw text lines. Inspect status/events for command failures and policy denials.

History is capped at 512 records of at most 4096 characters each. It survives SSH disconnects, but not companion restart/update or reboot. Save instance and next_after; reset reports a new instance and dropped reports overflow. Pages are also bounded to 64 KiB of record text. Synchronous command output is bounded to 2 MiB per stream, with truncation reported; detached logs remain complete.

For a direct MCP client, execute /opt/sailfish-devel-companion/agent.py --stdio as root over SSH. It bridges the five public companion MCP tools to the existing service. Internal execution/job RPCs are private to the root socket and support the existing host tools. Removal deletes only recognized installation files, stops capture, and preserves unexpected files and completed job evidence.

Trust and privacy

This is local development tooling intended for a trusted MCP client running on your own workstation. It communicates over stdio and has no network listener. The client invokes tools using your host account, SSH credentials and OBS credentials. Tool annotations describe effects; they do not enforce permissions or provide an approval layer.

Device command tools can execute arbitrary programs. Their default identity is the SSH login account, often root; setting the session environment does not change that identity. Select run_as_user=true when execution should use the configured Sailfish user. Android builds intentionally accept shell commands. Configured aliases are convenient names, not a host allowlist: explicit user@host targets are also supported. Target strings cannot contain SSH options or whitespace.

RPM builds and SDK maintenance use privileged Docker containers. Installed SDK builds mount the host user's home directory and the SDK read-write. Build only trusted source and images, including the optional third-party SDK mirror. Repository path checks constrain direct file operations; they do not sandbox build scripts or arbitrary device commands. The explicit chmod permission fallback broadens file permissions; prefer ACL support and the default error behaviour when ACL tools are unavailable.

Non-USB SSH connections follow the configured SSH host-key policy. USB mode connects to 192.168.2.15 and deliberately disables host-key checking and storage to support changing devices at that address. Use it only for a device connection you trust.

By default, SSH and SCP use ~/.ssh/config when it exists, or -F /dev/null when it is absent. Both choices skip the system SSH configuration; OpenSSH's built-in defaults, including normal non-USB host-key checks, still apply. An explicit paths.ssh_config must name an existing file; a missing explicit file remains an error. This policy applies to device and Android connections.

Tool output is returned to the MCP client and may enter a model's context. Effective configuration includes private hostnames, usernames, source paths and OBS mappings. Screenshots, journals, source matches and job logs can contain private data; commands can contain credentials supplied by callers. Keep real configuration, credentials, task notes, screenshots and runtime logs out of source releases and public bug reports. The server's request logs record argument names rather than tool argument values, but job logs and verbose results retain command/output details. Configure the client's tool approvals to suit your workflow.

Running

From a checkout:

/path/to/sailfish-devel-mcp/bin/sailfish-devel-mcp

To inspect the effective configuration:

/path/to/sailfish-devel-mcp/bin/sailfish-devel-mcp --dump-config

Example MCP client configuration:

{
  "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:

{
  "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": "primary",
    "obs_servers": {
      "primary": "primary",
      "community": "community"
    }
  },
  "default_android_build_host": "android-builder",
  "android_build_hosts": {
    "android-builder": {
      "ssh_target": "user@android-build-host"
    }
  }
}

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_setup (conservatively annotated because installation is optional)

  • sailfish_device_lipstick_screenshot

  • sailfish_device_touch

  • sailfish_device_touch_workflow

  • sailfish_device_user_bus_call

  • sailfish_device_user_session_command

  • sailfish_device_command_start

  • sailfish_device_command_cancel

  • sailfish_device_install_rpm

  • sailfish_device_restart_service

  • sailfish_device_app_launch

  • sailfish_device_browser_launch

  • sailfish_build_rpm

  • sailfish_build_cancel

  • sailfish_android_build

  • sailfish_android_build_cancel

  • sailfish_sdk_refresh_metadata

  • sailfish_obs_source_pin (apply)

sailfish_device_lipstick_screenshot defaults to ~/Pictures/Screenshots/lipstick-<timestamp>.png under /home/<username>, 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. Command tools return compact metadata and bounded output by default; set verbose only when the full command, stdout, and stderr are needed. This synchronous tool is limited to 15 seconds.

Use sailfish_device_command_start for GDB, tracing, or any command that might run longer than 15 seconds. It starts a detached host-side SSH process and returns a job id immediately, so the command survives the MCP request and stdio server exiting. Use scheduled follow-ups for long jobs, or the CLI waiter below when explicitly waiting. For occasional checks, use sailfish_device_command_status with lines=0 and at most 15 wait_seconds. Terminal failures include a bounded log excerpt automatically. Full logs remain under the local MCP state directory in device-commands/<job-id>/command.log. Use sailfish_device_command_cancel to ask the owning supervisor to terminate the SSH process group.

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_app_launch launches an installed desktop entry using gio, as the configured device user from their passwd home directory. For example:

{"device": "my-device", "desktop_file": "sailfish-browser.desktop"}

A basename resolves under /usr/share/applications; an absolute device path ending in .desktop is also accepted. No URL is required. It does not kill running apps, restart boosters, or change preferences. The process detaches with stdin/stdout/stderr redirected, avoiding an SSH timeout when app children retain the connection. wait_seconds (0–5, default 1) checks for immediate launcher failure; timeout (up to 15 seconds) must exceed it.

The receipt includes submitted, the launch supervisor PID (not the app PID), launch_exit_code if already available, and private remote_log/remote_status paths under the user's ~/.cache/sailfish-devel-mcp/launch.* directory. Read those paths with device commands for later failures and remove that specific directory when its evidence is no longer needed. Submission or gio exit zero does not prove foreground activation/rendering: check Lipstick's topmost PID and a screenshot separately. This desktop-entry path was validated manually for Browser on a Sailfish device; it does not claim to execute all of Lipstick's icon/switcher logic.

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. Successful builds run the helper in quiet mode while retaining the complete job log. For occasional status checks, use lines=0 and after_revision to suppress unchanged output. Scheduled progress checks need bounded log evidence to choose the next interval; follow the long-job policy below instead of repeatedly calling short status waits. A terminal failure includes a bounded diagnostic excerpt; request more only when needed. Status waits are capped at 15 seconds so a monitoring request stays comfortably below the stdio transport's observed lifetime limit.

Local SDK builds use the canonical helper. Ordinary builds share the base's mb2-managed .default working snapshot; custom repositories, packages and local RPMs use isolated originals. snapshot_key explicitly isolates a project even without custom inputs. Supply it when your project or machine policy requires per-project isolation. snapshot_repository and snapshot_package use the same helper options; repeat these inputs on later builds so outdated snapshots can be restored. Only original targets go to mb2; working .default children are managed by mb2. Bases remain free of project dependencies.

sailfish_android_build starts a remote Android/AppSupport build on the configured build host. Configure android_build_hosts.<host>.project_dir or pass project_dir to point at the remote Android source tree. 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, or pass a job id and wait_seconds to wait mechanically for a state change. Running jobs omit their log by default, while failed jobs include a bounded diagnostic excerpt. Request bounded lines during scheduled progress checks; use verbose only when full command/job inspection is needed. Individual status waits are capped at 15 seconds. Use sailfish_android_build_cancel to terminate the identity-checked process group.

sailfish_sdk_refresh_metadata starts a detached job to refresh zypper metadata in the installed SDK exact requested target using the same privileged Docker wrapper as local builds. aarch64 refreshes the parent; aarch64.default or aarch64-project refreshes that specific snapshot. No .default suffix is inferred. Use it when local SDK builds fail because a package listed in repository metadata cannot be downloaded.

The OBS tools, including sailfish_obs_source_pin, accept a friendly server name configured in paths.obs_servers; each value is an .oscrc alias or API URL passed to osc -A. 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. Keep machine- or organization-specific server mappings in the local config rather than in the repository.

sailfish_obs_results can wait mechanically in intervals of at most 15 seconds until OBS no longer reports an active state. sailfish_obs_buildlog returns a bounded tail by default; set verbose only when the complete response is required.

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.

OBS source pins

sailfish_obs_source_pin edits an existing _service revision parameter and, optionally, its existing branch parameter. It uses osc api and your configured credentials. No local OBS checkout is needed. Install osc through your Linux distribution; it is an external executable, not a Python package dependency.

  • action: "read" (default): return source pins and the file's sha256.

  • action: "preview": supply revision and optionally branch; return before/ after values, the original sha256 and new_sha256, without a remote write.

  • action: "apply": supply the same changes, expected_sha256 copied from the read/preview, and an OBS commit comment. If there are multiple source pins, supply the zero-based service_index returned by read.

For example, after reading home:example:feature/sample, preview with:

{"project":"home:example:feature","package":"sample","action":"preview","revision":"v1.2.3"}

Apply with the returned original hash and a meaningful comment. The tool checks the whole _service hash and rechecks immediately before uploading only that file. Existing XML formatting, comments and unrelated parameters are preserved; the tool does not change repository URLs or create missing parameters. It refuses packages with staged OBS uploads to avoid committing another operation's unfinished work. It does not resolve Git refs or check whether a commit has been pushed. Follow the project's authorized OBS server, package and branch mapping.

The OBS file-write API does not provide an atomic conditional update: another writer can still change the package between the final check and upload. Coordinate concurrent writers. Applying creates an OBS source revision and can trigger the configured remote source services and builds; the tool does not wait for them. An unchanged pin does not create a commit. Successful writes are verified at the returned OBS revision. unknown or committed_unverified means inspect/read back before any retry; never blindly repeat a write. The total operation timeout is at most 15 seconds. Because apply writes remotely, the tool is annotated as mutating even though read and preview do not write.

Smoke Test

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:

PYTHONPATH=src python3 -m unittest discover -s tests

The canonical helper lives in the build-sailfishos skill. Update the exact vendored copy with:

python3 scripts/update_build_helper.py /path/to/build-sailfishos/scripts/build_sailfishos.py

Compact jobs and diagnostics

RPM installation and SDK metadata refresh now return detached build-job receipts by default. Wait for sailfish_build_status to finish successfully before dependent actions; sailfish_build_cancel cancels these jobs. Explicit wait=true retains legacy synchronous behavior. For a multi-RPM transaction, timeout bounds the whole async job; cancellation of local SSH does not guarantee rollback of a remote RPM transaction. Inspect package state before retrying an interrupted install.

sailfish_obs_watch runs osc results --watch --fail-on-error in a detached job, using the selected OBS alias and package filter. It does not modify OBS sources.

For long jobs, confirm detached startup and arrange a quiet scheduled follow-up unless the user opts out, then return to the user. Each follow-up should inspect bounded progress evidence and notify once on completion or failure. If scheduling is unavailable, report that limitation. When the user explicitly requests waiting, use one CLI process without repeated model-driven status calls:

bin/sailfish-devel-jobs wait --kind build --job-id JOB_ID
bin/sailfish-devel-jobs wait --kind device --job-id JOB_ID
bin/sailfish-devel-jobs wait --kind android --host HOST --job-id JOB_ID
bin/sailfish-devel-jobs status --kind build --job-id JOB_ID
bin/sailfish-devel-jobs log --kind build --job-id JOB_ID --offset 0 --max-bytes 6000

The waiter emits one JSON result at completion or failure. Exit codes are 0 for success, 1 for failure/cancellation and 124 for wait expiration (which leaves the job running). Installed packages expose sailfish-devel-jobs on PATH. Android jobs accept an optional --state-dir matching their creation call.

Status results include a revision. Send after_revision on an occasional later check to receive a minimal unchanged receipt when nothing changed. Explicit lines, verbose, or log_offset requests still return their requested detail. Build/device log pages are capped at 12,000 bytes and return next_offset and eof. Tails are byte bounded too, including logs containing very long lines. Verbose status retains full job metadata; compact completion exposes helper version, backend, targets, duration, RPM paths, rpmlint and dependency diagnostics.

sailfish_doctor checks configured helper provenance, local executable availability, SDK targets and configuration presence without contacting devices or OBS. It does not prove Docker daemon access or network connectivity. The canonical helper also provides --doctor and --refresh-metadata --target TARGET [--force-refresh].

The QML checker lexes strings/comments and multiline ternaries. It reports missing branch-local //: and //% comments; it is a focused checker, not a full QML parser.

Maintaining the helper and skills

The vendored helper is pinned by vendor/build-helper.json with its version, SHA-256 and available source revision/dirty state. Verify parity without editing:

python3 scripts/update_build_helper.py /path/to/build-sailfishos/scripts/build_sailfishos.py --check

The version check, CLI-control tests and snapshot tests guard the interface. Update the canonical skill first, vendor it, and run both test suites before release. skills/sailfish-devel is a separate optional skill for deployment, debugging, Android and OBS workflows. Install/symlink that directory in your skill directory; the canonical RPM skill stays focused on building and packaging.

Detecting a stale host process

sailfish_doctor includes host_runtime: the PID, process start time (when available), source-load time, and SHA-256 identities of package Python sources at startup and currently on disk. restart_needed: true means the source tree changed after startup; restart the MCP connection before relying on the updated implementation. null means identity could not be checked. This is separate from the existing canonical/vendored helper integrity check and does not restart the server or interrupt jobs automatically.

Build input provenance

Use spec_path in build/preflight requests to choose a repository's RPM spec, for example rpm/sensorfw-qt5.spec. Ambiguous selection reports candidates. Managed snapshots track dependency inputs, including local RPM hashes. Use reset_snapshot: true when deliberately resetting a project's snapshot to reapply changed inputs; SDK bases remain untouched. Build results include provenance and artifact_checksums for tracing RPMs back to source and inputs. After interrupted preparation, resume_snapshot: true explicitly retries the identical recorded inputs without resetting an existing snapshot. Missing or different recipes remain rejected. A build failure after preparation retains the completed recipe, so it does not by itself require a reset.

Available Tools

35 tools
sailfish_android_buildStart Android BuildA

Start a remote Android/AppSupport build under nohup on the configured build host; project_dir must be configured for the host or supplied.

Inputs: shell_command (required), host, shell, job_id, +4 more.

sailfish_android_build_cancelCancel Android BuildC

Cancel a remote Android/AppSupport build after verifying its process identity.

Inputs: job_id (required), host, timeout, state_dir.

sailfish_android_build_hostsAndroid Build HostsB
Read-onlyIdempotent

List configured remote Android/AppSupport build hosts.

Inputs: no named parameters.

sailfish_android_build_statusAndroid Build StatusA
Read-onlyIdempotent

List remote Android/AppSupport build jobs or read one job's status and recent log output.

Inputs: host, lines, job_id, timeout, +5 more.

sailfish_build_cancelCancel Sailfish BuildB

Request cancellation of a local asynchronous Sailfish RPM build job.

Inputs: job_id (required).

sailfish_build_preflightPreflight Sailfish BuildB
Read-onlyIdempotent

Validate and return a structured Sailfish RPM build plan without mutating the project.

Inputs: project_path (required), arch, clean, debug, +17 more.

sailfish_build_rpmBuild Sailfish RPMB

Start an asynchronous RPM build. Ordinary local builds share mb2 snapshots; custom inputs or an explicit key use an isolated original target. paths.local_sdk defaults to the live installed SDK, with third-party coderus Docker-image fallback only for explicit named releases.

Inputs: project_path (required), arch, wait, clean, +18 more.

sailfish_build_statusBuild Job StatusA
Read-onlyIdempotent

Read status and recent log output for asynchronous Sailfish RPM build jobs.

Inputs: lines, job_id, verbose, max_bytes, +3 more.

sailfish_device_app_launchLaunch a Sailfish applicationA

Launch an installed .desktop entry using gio as the configured session user from their home. Detaches all streams and returns a launch receipt and remote log/status paths. Does not stop apps or boosters. Submission is not proof of foreground activation or rendering; verify separately.

Inputs: desktop_file (required), device, timeout, wait_seconds.

sailfish_device_browser_launchLaunch Sailfish BrowserB

Stop stale browser state, launch Sailfish Browser with display/session env, and report topmost PID details.

Inputs: url (required), device, timeout, stop_stale, +1 more.

sailfish_device_command_cancelCancel Device Command JobB

Request cancellation of a detached device command and its process group.

Inputs: job_id (required).

sailfish_device_command_startStart Device Command JobA

Start a detached SSH command with the configured Sailfish user-session environment and immediately return a job id. Use this for GDB, tracing, and any device command that may exceed 15 seconds.

Inputs: command (required), device, timeout, run_as_user.

sailfish_device_command_statusDevice Command Job StatusA
Read-onlyIdempotent

List detached device command jobs or read one job's status. A bounded failure excerpt is included automatically; running logs are omitted by default.

Inputs: lines, job_id, verbose, max_bytes, +3 more.

sailfish_device_install_rpmInstall Device RPMB

Start a detached RPM copy/install transaction; use sailfish_build_status/cancel.

Inputs: wait, device, timeout, rpm_path, +4 more.

sailfish_device_journalRead Device JournalC
Read-onlyIdempotent

Read recent journalctl output from a Sailfish OS device.

Inputs: grep, unit, lines, since, +2 more.

sailfish_device_lipstick_screenshotLipstick ScreenshotB

Ask Lipstick to save a screenshot on the device, optionally pulling it locally.

Inputs: device, timeout, local_path, privileged, +1 more.

sailfish_device_proc_mapsRead Process MapsC
Read-onlyIdempotent

Read or filter /proc//maps on a Sailfish OS device.

Inputs: pid (required), device, timeout, contains, +1 more.

sailfish_device_restart_serviceManage Device ServiceC

Run systemctl start/stop/restart/status for a system or user service.

Inputs: unit (required), mode, action, device, +1 more.

sailfish_devicesList Sailfish DevicesA
Read-onlyIdempotent

List configured Sailfish OS device aliases and SSH targets.

Inputs: no named parameters.

sailfish_device_setupCheck or Set Up Device PrerequisitesA

Check command availability and session configuration over SSH. By default this only reads device state. Explicit install_missing=true installs missing python3-base or mce-tools from existing repositories in a detached job; use sailfish_build_status/cancel. Missing base OS tools need manual repair.

Inputs: wait, device, timeout, features, +1 more.

sailfish_device_topmost_pidTopmost Window PIDB
Read-onlyIdempotent

Query Lipstick for the current topmost window process id.

Inputs: device, timeout.

sailfish_device_touchDevice Touch InputC

Discover the touchscreen input device or inject tap/swipe events over SSH.

Inputs: action (required), x, y, end_x, +10 more.

sailfish_device_touch_workflowScreenshot And TouchC

Capture a Lipstick screenshot, optionally list touch inputs, then inject a tap or swipe.

Inputs: action (required), x, y, end_x, +19 more.

sailfish_device_user_bus_callUser Bus CallC

Run a typed dbus-send method call on the defaultuser session bus.

Inputs: path (required), member (required), interface (required), destination (required), +3 more.

sailfish_device_user_session_commandUser Session CommandA

Run a short command, limited to 15 seconds, with the configured Sailfish user-session D-Bus environment. Use sailfish_device_command_start for anything that may run longer.

Inputs: command (required), device, timeout, verbose, +1 more.

sailfish_doctorA
Read-onlyIdempotent

Check helper compatibility, local tools, SDK targets and configuration without contacting devices.

Inputs: no named parameters.

sailfish_obs_buildlogOBS Build LogC
Read-onlyIdempotent

Fetch a build log from a configured OBS server or explicit API alias; defaults to the API nostream form.

Inputs: arch (required), package (required), project (required), repository (required), +6 more.

sailfish_obs_resultsOBS ResultsA
Read-onlyIdempotent

Read OBS results. Use wait_seconds for bounded mechanical monitoring instead of repeated model-driven status calls.

Inputs: project (required), server, package, timeout, +4 more.

sailfish_obs_watchB

Watch OBS until completion in a detached local job; use sailfish_build_status/cancel or the jobs CLI waiter.

Inputs: project (required), server, package, timeout, +1 more.

sailfish_qml_check_translator_ternariesCheck QML Ternary TranslationsC
Read-onlyIdempotent

Flag QML ternary qsTrId expressions that need branch-local comments.

Inputs: path.

sailfish_qml_find_translationsFind QML TranslationsC
Read-onlyIdempotent

Find qsTrId and translator comments in QML files.

Inputs: path, timeout.

sailfish_repo_findRepo FindC
Read-onlyIdempotent

Search a repo or subtree under the configured git root.

Inputs: query (required), path, timeout, max_count, +1 more.

sailfish_repo_statusRepo StatusB
Read-onlyIdempotent

Run git status --short --branch under the configured git root.

Inputs: path, timeout.

sailfish_sdk_refresh_metadataRefresh SDK MetadataB

Start a detached SDK metadata refresh; use sailfish_build_status/cancel.

Inputs: arch, wait, force, device, +4 more.

sailfish_spec_summaryRPM Spec SummaryC
Read-onlyIdempotent

Parse high-level metadata from a Sailfish RPM spec file.

Inputs: repo_path, spec_path.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.3/5.0

    Scored across 35 tools

    Disambiguation4/5

    Tools are grouped by domain and most have clearly distinct purposes, such as local RPM build, Android build, OBS, device SSH commands, and QML translation checks. Some boundaries blur because sailfish_build_status/cancel are reused for multiple local async jobs despite the 'build' name, and device_user_session_command vs device_command_start require reading descriptions to distinguish short from long-running commands.

    Naming Consistency4/5

    All tool names use snake_case with a consistent sailfish_ prefix, which makes the set predictable. However, the suffix pattern is not uniformly verb_noun: sailfish_devices, sailfish_doctor, sailfish_spec_summary, and sailfish_obs_watch mix noun/state and action forms, though deviations are minor.

    Tool Count3/5

    35 tools is heavy for a single MCP server, even though the server spans several subdomains including device, build, Android, OBS, repo, SDK, spec, and QML. The breadth justifies many tools, but the surface is at the upper end and could likely be consolidated in places.

    Completeness4/5

    The tool set covers a wide lifecycle: build planning, local and Android builds, device inspection and command execution, OBS monitoring, SDK refresh, repo search, spec parsing, and QML translation checks. Minor gaps remain, such as RPM uninstall, explicit app stop/termination, git write operations, and signing/deployment workflows.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      Not graded
      quality
      C
      maintenance
      Enables agents to connect to remote MCP servers once, access their tools through a compact MCP endpoint, pair a CLI inside sandboxes, and create watches that turn command or tool output into pollable structured events.
      2
      -
    • F
      license
      Not graded
      quality
      C
      maintenance
      Enables secure discovery and invocation of sandboxed filesystem, repository inspection, and utility tools through a unified MCP client with schema validation, timeouts, and execution traces.
      -