1 # MCP Server
2
3 The smbCloud CLI can run as a **Model Context Protocol (MCP) server**, so an AI
4 assistant or agent — Claude Desktop, Claude Code, Cursor, or any other
5 MCP-capable client — can send transactional email and manage your smbCloud
6 projects, tenants, Mail apps, and Auth apps directly, without you leaving the
7 chat to run `smb` commands by hand.
8 Start it with `smb --mcp`; it speaks standard MCP over stdio and exposes 31
9 tools covering the account, project, tenant, Mail, and Auth surfaces.
10
11 Cross-platform mobile and TV app testing is exposed as a separate, focused MCP
12 profile through `xcrs --mcp` or `smb --mcp --scope automation`. Keeping cloud
13 and device tools in separate profiles makes tool selection more predictable for
14 agents.
15
16 This page is the setup guide and the full tool reference. For how `--mcp`
17 compares to the CLI's other two interfaces (headless and `--tui`), see
18 [Interfaces](./interfaces.md). The server is also listed in the official MCP
19 Registry as `io.github.smbcloudXYZ/smbcloud-cli`, so clients that browse the
20 registry can install it for you — see [MCP Registry](./mcp-registry.md).
21 For building and starting the native runners, see
22 [ControlKit runners](./controlkit.md).
23
24 ## Prerequisites
25
26 1. [Install the CLI](./cli-install.md) (`smb --version` should print something).
27 2. Log in once, headlessly — the MCP server itself is non-interactive and can't
28 run the login flow:
29
30 ```sh
31 smb login
32 ```
33
34 This writes a token to `~/.smb/token` (`~/.smb-dev/token` in the `dev`
35 environment). Every MCP tool call reuses that token; there's nothing
36 client-specific to configure for auth.
37
38 ## Connect your client
39
40 Every MCP client ultimately does the same thing: run `smb --mcp` as a
41 subprocess and speak JSON-RPC over its stdin/stdout. The differences are in
42 where each client wants that command declared.
43
44 ### Claude Desktop
45
46 Edit your Claude Desktop config —
47 `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS,
48 `%APPDATA%\Claude\claude_desktop_config.json` on Windows — and add an entry
49 under `mcpServers`:
50
51 ```json
52 {
53 "mcpServers": {
54 "smbcloud": {
55 "command": "smb",
56 "args": ["--mcp"]
57 }
58 }
59 }
60 ```
61
62 Restart Claude Desktop. The tools listed below become available in any chat.
63
64 ### Claude Code
65
66 ```sh
67 claude mcp add smbcloud -- smb --mcp
68 ```
69
70 Or add it directly to your project's `.mcp.json`:
71
72 ```json
73 {
74 "mcpServers": {
75 "smbcloud": {
76 "command": "smb",
77 "args": ["--mcp"]
78 }
79 }
80 }
81 ```
82
83 ### Cursor
84
85 Add the same shape to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json`
86 (project-scoped):
87
88 ```json
89 {
90 "mcpServers": {
91 "smbcloud": {
92 "command": "smb",
93 "args": ["--mcp"]
94 }
95 }
96 }
97 ```
98
99 ### Any other MCP client
100
101 If your client's config isn't listed above, it almost certainly still takes a
102 `command` + `args` pair for a stdio server — point it at `smb` with `--mcp`
103 exactly as shown above. That's the entire integration surface; there's no
104 separate server to run or port to expose.
105
106 ### Targeting a local API during development
107
108 Add the global `-e`/`--environment` flag before `--mcp` to point the server at
109 a local dev API instead of production:
110
111 ```json
112 {
113 "mcpServers": {
114 "smbcloud-dev": {
115 "command": "smb",
116 "args": ["-e", "dev", "--mcp"]
117 }
118 }
119 }
120 ```
121
122 ## Session state: tenant and project selection
123
124 Several tools take an optional `project_id`, and project creation targets
125 whichever tenant is currently selected. Two tools manage that selection:
126
127 - **`tenant_use`** — selects a tenant (workspace) for the session. While
128 selected, `project_create` targets it instead of your personal tenant.
129 - **`project_use`** — selects a project for the session. Tools with an
130 optional `project_id` (Mail apps, Auth apps) default to it when omitted.
131
132 Both persist to `~/.smb/config.toml` (`~/.smb-dev/config.toml` in `dev`) — the
133 same file `smb tenant use` / `smb project use` write from the terminal. Switch
134 context in a chat with an agent, then run `smb project list` in a terminal a
135 minute later, and it reflects the same selection. Selecting a tenant that the
136 current project doesn't belong to clears the project selection, so a stale
137 cross-tenant `project_id` can never linger silently.
138
139 ## Tools reference
140
141 Every tool returns structured JSON — a single object for `_show`/`_new`/
142 `_update`/`_use`, an array for `_list`. Fields are the CLI's own JSON models,
143 not a separate MCP-specific shape.
144
145 ### Account
146
147 | Tool | Arguments | Returns |
148 | --- | --- | --- |
149 | `me` | _(none)_ | The authenticated user. |
150
151 ### Projects
152
153 | Tool | Arguments | Returns |
154 | --- | --- | --- |
155 | `project_list` | _(none)_ | The user's projects. |
156 | `project_show` | `id` | A single project by ID. |
157 | `project_use` | `id` | The selected project. Persists the selection (see above). |
158 | `project_create` | `name`, `description` (optional) | The created project. Targets the selected tenant, if any. |
159 | `project_update` | `id`, `description` | The updated project (runner preserved). |
160 | `project_delete` | `id` | Confirmation. **Destructive and irreversible.** |
161 | `deployments` | `project_id` | A project's deployments. |
162
163 ### Tenants
164
165 A tenant is a workspace — your personal one, or an organization you belong to.
166 `tenant_new`/`tenant_delete` only apply to organization tenants; the personal
167 tenant is bootstrapped on signup and isn't user-managed.
168
169 | Tool | Arguments | Returns |
170 | --- | --- | --- |
171 | `tenant_list` | _(none)_ | The user's tenants, with role and project count. |
172 | `tenant_show` | `id` | A single tenant by ID. |
173 | `tenant_use` | `id` | The selected tenant. Persists the selection (see above). |
174 | `tenant_new` | `name` | The created organization tenant. |
175 | `tenant_update` | `id`, `name` | The renamed tenant. |
176 | `tenant_delete` | `id` | Confirmation. **Destructive and irreversible** — cascades to every project, Mail app, and Auth app the tenant owns. |
177
178 ### Mail apps
179
180 A Mail app owns one domain's mail routing; it belongs to a project.
181
182 | Tool | Arguments | Returns |
183 | --- | --- | --- |
184 | `mail_list` | `project_id` (optional, defaults to the selected project) | The user's Mail apps. |
185 | `mail_show` | `id` | A single Mail app by ID. |
186 | `mail_new` | `name`, `domain`, `project_id` (optional), `aws_region` (optional) | The created Mail app. |
187 | `mail_update` | `id`, `name`/`domain`/`aws_region` (at least one) | The updated Mail app. |
188 | `mail_delete` | `id` | Confirmation. **Destructive and irreversible.** |
189
190 ### Sending email
191
192 | Tool | Arguments | Returns |
193 | --- | --- | --- |
194 | `mail_send` | `from`, `to`, `subject`/`html`/`text`/`cc`/`bcc`/`reply_to`/`idempotency_key` (optional; one of `html` or `text` required) | The sent message with its delivery status. |
195
196 `mail_send` is the one tool that does not use the `smb login` session. The send API
197 authenticates with a Mail app API key scoped to that app's verified sending domain,
198 so the key has to reach the server through its environment:
199
200 ```json
201 {
202 "mcpServers": {
203 "smbcloud": {
204 "command": "smb",
205 "args": ["--mcp"],
206 "env": { "SMB_MAIL_API_KEY": "smb_mail_your_key" }
207 }
208 }
209 }
210 ```
211
212 Mint the key for your Mail app in the smbCloud console. Without it, `mail_send`
213 fails with a message saying so and every other tool keeps working.
214
215 Reuse an `idempotency_key` and the original message comes back instead of a second
216 send, which is what you want when an agent retries after a timeout.
217
218 ### Mail inboxes
219
220 An inbox route forwards mail arriving at one address under a Mail app's domain.
221
222 | Tool | Arguments | Returns |
223 | --- | --- | --- |
224 | `mail_inbox_new` | `app_id`, `local_part`, `forward_to_email`, `sender_email` (optional) | The created inbox route. |
225 | `mail_inbox_update` | `app_id`, `id`, `local_part`/`forward_to_email`/`sender_email` (at least one) | The updated inbox route. |
226 | `mail_inbox_delete` | `app_id`, `id` | Confirmation. **Destructive and irreversible.** |
227 | `mail_inbox_test` | `app_id`, `id`, `recipient_email`/`subject`/`body` (all optional) | The test delivery result. |
228
229 ### Mail messages
230
231 Read-only access to an inbox's stored inbound message history.
232
233 | Tool | Arguments | Returns |
234 | --- | --- | --- |
235 | `mail_message_list` | `app_id`, `inbox_id`, `limit` (optional, default 10) | Recent inbound messages, newest first. |
236 | `mail_message_show` | `app_id`, `inbox_id`, `id` | A single inbound message. |
237
238 ### Auth apps
239
240 An Auth app is a hosted identity/sign-in service for your own product's end
241 users; it belongs to a project.
242
243 | Tool | Arguments | Returns |
244 | --- | --- | --- |
245 | `auth_app_list` | `project_id` (optional, defaults to the selected project) | The user's Auth apps. |
246 | `auth_app_show` | `id` | A single Auth app by ID. |
247 | `auth_app_new` | `name`, `project_id` (optional), `support_email` (optional) | The created Auth app. |
248 | `auth_app_update` | `id`, `name`/`support_email` (at least one) | The updated Auth app. |
249 | `auth_app_delete` | `id` | Confirmation. **Destructive and irreversible.** |
250
251 ### Mobile and TV app automation
252
253 Start a dedicated automation server instead of mixing device tools into the
254 cloud profile:
255
256 ```sh
257 smb --mcp --scope automation
258 # or install the smaller standalone crate:
259 xcrs --mcp
260 ```
261
262 > **Installing `xcrs`:** the standalone binary is published to
263 > [crates.io](https://crates.io/crates/xcrs) — install it with
264 > `cargo install xcrs`. It is **not** distributed through Homebrew; `brew
265 > install smbcloud-cli` only provides the `smb` binary (use `smb --mcp --scope
266 > automation` for the same tools without a separate install).
267
268 Both commands expose the same target-aware tools with the same names. Tool
269 names do not repeat the server brand because MCP clients already namespace
270 them by server.
271
272 **Recommended flow:**
273
274 1. Call `device_list` to discover Apple simulators and adb-connected Android
275 phones, tablets, emulators, and TV devices.
276 2. Call `device_select` with the Apple simulator/runner details or Android adb
277 serial. Later actions use that target by default.
278 3. Call `device_capabilities` before choosing an input or inspection action.
279 4. Use `screen_capture` or `ui_describe`/`ui_element_list` to
280 inspect the app, then use semantic `ui_tap` or the raw `input_*` tools.
281
282 | Tool | Purpose |
283 | --- | --- |
284 | `device_list` | Discover available Apple simulators and Android adb devices. |
285 | `device_select` | Remember the Apple or Android target used by later actions. |
286 | `device_capabilities` | Report the selected target's supported automation operations. |
287 | `app_install_launch` | Boot, install, and launch an iOS simulator app bundle. |
288 | `app_launch` / `app_terminate` | Start or stop an installed app on the selected target. |
289 | `screen_capture` | Capture the selected target as a PNG image. |
290 | `url_open` | Open an HTTP(S) URL or custom scheme where the target supports it. |
291 | `ui_describe` / `ui_element_list` / `ui_tap` | Inspect or activate accessible elements through ControlKit (Apple) or AndroidKit (Android). |
292 | `input_tap` / `input_text` / `input_swipe` / `input_button` | Drive shared touch, text, gesture, and button actions. |
293 | `input_click` | Click a macOS target with pointer coordinates. |
294 | `input_spatial_tap` | Perform a visionOS spatial tap. |
295 | `orientation_get` / `orientation_set` | Read or update orientation where supported. |
296
297 See [ControlKit runners](./controlkit.md) for Apple runner setup and
298 [XCRS AndroidKit](./androidkit.md) for Android semantic UI automation. Android
299 tools require adb and an authorized device; Android TV app launches resolve the
300 device's Leanback launcher activity.
301
302 #### Automation migration in 0.5.0
303
304 The automation profile is a clean breaking replacement. Old Apple-default and
305 Android-prefixed names are not exposed as aliases because duplicate tools make
306 agent selection less reliable.
307
308 | Previous tools | Replacement |
309 | --- | --- |
310 | `smb_list_simulators`, `smb_android_device_list` | `device_list` |
311 | `smb_use_target` | `device_select` |
312 | `smb_capabilities` | `device_capabilities` |
313 | `smb_boot_and_install` | `app_install_launch` |
314 | `smb_launch_app`, `smb_android_app_launch` | `app_launch` |
315 | `smb_terminate_app`, `smb_android_app_terminate` | `app_terminate` |
316 | `smb_screenshot`, `smb_android_screenshot` | `screen_capture` |
317 | `smb_open_url`, `smb_android_open_url` | `url_open` |
318 | `smb_describe_ui` | `ui_describe` |
319 | `smb_list_elements` | `ui_element_list` |
320 | `smb_tap`, `smb_android_tap` | `input_tap` |
321 | `smb_type_text`, `smb_android_type_text` | `input_text` |
322 | `smb_swipe`, `smb_android_swipe` | `input_swipe` |
323 | `smb_button`, `smb_android_press_button` | `input_button` |
324 | `smb_click` | `input_click` |
325 | `smb_gesture` | `input_spatial_tap` |
326 | `smb_orientation_get` | `orientation_get` |
327 | `smb_orientation_set` | `orientation_set` |
328
329 The standalone server makes the same replacement without the former `xcrs_`
330 prefix.
331
332 ## Safety: tools run without confirmation
333
334 The CLI's own `project delete`, `tenant delete`, and similar commands ask for
335 confirmation on a terminal — some require typing the resource's name back.
336 **MCP tools skip that entirely.** MCP is a non-interactive protocol, so a
337 `*_delete` tool call executes immediately the moment the client sends it. The
338 calling agent — and the person directing it — is responsible for confirming
339 intent before invoking a destructive tool, exactly as with any other
340 irreversible action an assistant can take on your behalf.
341
342 If you want a harder guardrail, keep destructive operations out of an agent's
343 reach at the client level (most MCP clients let you allow/deny individual
344 tools per server) rather than relying on the server to ask twice.
345
346 ## Not exposed as tools (yet)
347
348 `deploy` and `migrate` are **deliberately not** MCP tools. Both are tightly
349 coupled to the local working directory in ways a stdio tool call can't satisfy
350 cleanly:
351
352 - **`deploy`** reads `.smb/config.toml` (running interactive `setup_project`
353 when it is missing), builds the app locally, then uploads over rsync and
354 restarts over SSH. It depends on the caller's current directory, local build
355 toolchains, and on-disk SSH keys, and it streams long-running progress — none
356 of which map onto a single non-interactive tool call. For automated deploys,
357 use the headless CLI path instead (`smb --ci deploy`, see
358 [CI / non-interactive deploys](./ci.md)), which is what CI and the deploy
359 action already drive.
360 - **`migrate`** pushes local `.smb/config.toml` deploy fields up to the server,
361 so it is meaningless without a project directory to read from.
362
363 Exposing either as a tool would mean designing an explicit, directory-free
364 contract — e.g. accepting the full deploy configuration as arguments and
365 returning streamed progress as structured events — rather than wrapping the
366 existing directory-coupled handlers. That's intentionally left for a later
367 pass; the tools above cover the account, project, tenant, Mail, and Auth
368 surface that's well-defined over stdio today.
369
370 ## Talking to it directly
371
372 You don't need an MCP client to try this — the server reads newline-delimited
373 JSON-RPC on stdin and writes responses on stdout. Diagnostics go to stderr and
374 logging goes to the on-disk log file, so stdout stays a clean protocol channel
375 end to end. A minimal handshake plus a tool listing:
376
377 ```sh
378 printf '%s\n' \
379 '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}' \
380 '{"jsonrpc":"2.0","method":"notifications/initialized"}' \
381 '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
382 | smb --mcp
383 ```
384
385 A tool call names the tool and its arguments:
386
387 ```json
388 {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"project_show","arguments":{"id":"<id>"}}}
389 ```
390
391 ## FAQ
392
393 **Does the MCP server need a different install than the regular CLI?**
394 No — `smb --mcp` is the same binary you already use for `smb deploy`, `smb
395 project list`, and so on. There's nothing extra to install.
396
397 **Do I need to log in separately for MCP?**
398 No. Log in once with `smb login` on a terminal; every interface (headless,
399 `--tui`, `--mcp`) reads the same token from `~/.smb/token`. The one exception is
400 `mail_send`, which needs a Mail app API key in `SMB_MAIL_API_KEY` because the send
401 API authenticates per sending domain rather than per user.
402
403 **Can I run multiple smbCloud MCP servers for different environments?**
404 Yes — register two entries with different `args` (e.g. one with `-e dev`, one
405 without) under different names, as shown above.
406
407 **Does selecting a tenant or project in an agent chat affect my terminal?**
408 Yes, and vice versa — `tenant_use`/`project_use` write to the same
409 `~/.smb/config.toml` the CLI itself reads, so the two stay in sync.
410
411 **Is the MCP server safe to point an autonomous agent at?**
412 Every write tool — including every `*_delete` tool — executes immediately with
413 no confirmation prompt, since MCP is non-interactive by design. Treat tool
414 access the way you'd treat handing someone your terminal with `smb` already
415 logged in: fine for a trusted agent you're directing, not something to expose
416 unsupervised.