1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
|
# 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 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.
The Python configuration module is machine-local and intentionally ignored by
Git. Bootstrap a fresh checkout before running, testing, installing, or
packaging it:
```sh
cp src/sailfish_devel_mcp/config.py.template src/sailfish_devel_mcp/config.py
```
Keep machine-specific Python defaults only in the ignored `config.py`. Prefer
the JSON configuration described below for ordinary device, SDK, and build-host
settings.
## 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-<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`.
`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.<host>.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
```
|