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 41
9 tools covering the account, project, deploy, 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 Pass `frontend_app_id` to select one of the project's frontend apps too.
132
133 Both persist to `~/.smb/config.toml` (`~/.smb-dev/config.toml` in `dev`) — the
134 same file `smb tenant use` / `smb project use` write from the terminal. Switch
135 context in a chat with an agent, then run `smb project list` in a terminal a
136 minute later, and it reflects the same selection. Selecting a tenant that the
137 current project doesn't belong to clears the project selection, so a stale
138 cross-tenant `project_id` can never linger silently.
139
140 ## Tools reference
141
142 Every tool returns structured JSON — a single object for `_show`/`_new`/
143 `_update`/`_use`, an array for `_list`. Fields are the CLI's own JSON models,
144 not a separate MCP-specific shape.
145
146 ### Account
147
148 | Tool | Arguments | Returns |
149 | --- | --- | --- |
150 | `me` | _(none)_ | The authenticated user. |
151
152 ### Projects
153
154 | Tool | Arguments | Returns |
155 | --- | --- | --- |
156 | `project_list` | _(none)_ | The user's projects. |
157 | `project_show` | `id` | A single project by ID. |
158 | `project_use` | `id`, `frontend_app_id` (optional, must belong to the project) | The selected project. Persists the selection (see above). |
159 | `project_create` | `name`, `description` (optional) | The created project. Targets the selected tenant, if any. |
160 | `project_update` | `id`, `description`, `runner` (optional) | The updated project. The runner is kept when omitted. |
161 | `project_delete` | `id` | Confirmation. **Destructive and irreversible.** |
162 | `deployments` | `project_id` | A project's deployments. On MCP Apps hosts it also renders a dashboard (see below). |
163 | `deployment_show` | `project_id`, `id` | A single deployment. |
164
165 ### Frontend apps, deploy repos, and deploy config
166
167 A project holds a deploy repo (the git repository or monorepo root) and the
168 frontend apps that ship from it. `runner` is one of `node_js`, `static`, `ruby`,
169 `swift`, `rust`.
170
171 | Tool | Arguments | Returns |
172 | --- | --- | --- |
173 | `frontend_app_list` | `project_id` | The project's frontend apps. |
174 | `frontend_app_show` | `id` | A single frontend app. |
175 | `frontend_app_new` | `project_id`, `name`, `source_path` (optional), `runner` (optional, default `node_js`) | The created frontend app. |
176 | `deploy_repo_new` | `project_id`, `repository`, `repo_kind` (`single_app` or `monorepo`), `runner` (optional) | The created repo. A single-app repo embeds the app it creates. |
177 | `deploy_config_show` | `frontend_app_id` | The app's server-side deploy config. |
178 | `deploy_config_update` | `frontend_app_id`, plus any of `runner`, `deployment_method` (`git` or `rsync`), `kind`, `source_path`, `remote_path`, `package_manager`, `pm2_app`, `port`, `output_path`, `build_command`, `install_command`, `binary_name`, `build_target`, `shared_lib_path` | The updated config. Only the fields passed change. |
179
180 ### Local tools (`project_init`, `deploy`, `migrate`)
181
182 These act on a directory on the machine running `smb --mcp`, so each takes an
183 explicit absolute `cwd`. They never prompt: anything the interactive CLI would
184 ask is an argument, or the call fails with a message saying what is missing.
185
186 | Tool | Arguments | Returns |
187 | --- | --- | --- |
188 | `project_init` | `cwd`, `name`, `description` (optional), `repository`, `repo_kind`, `runner`, `apps` (`name`, `source_path`, `runner`), `overwrite` (all optional) | The project, its first app, and the written config path. Writes `<cwd>/.smb/config.toml`. Refuses to replace an existing config unless `overwrite` is true. |
189 | `deploy` | `cwd`, `project` (optional monorepo sub-project) | `{ success, exit_code, output_tail }`. |
190 | `migrate` | `cwd` | `{ success, exit_code, output_tail }`. |
191
192 `deploy` and `migrate` need an existing `.smb/config.toml` in `cwd` (run
193 `project_init` first). `deploy` builds locally, pushes to the server, and
194 restarts the app, so it can take minutes; the call returns when it finishes.
195
196 Both run the `smb` binary itself as a child process in `cwd` with `--ci`,
197 rather than in the MCP process. The deploy paths use paths relative to the
198 working directory and print progress to stdout, which in an MCP session is the
199 JSON-RPC channel. `output_tail` is the last 200 lines of combined output with
200 colors and spinner frames removed. The call is flagged as an error when the
201 command exits non-zero.
202
203 #### Interactive views (MCP Apps)
204
205 Three tools declare an [MCP Apps](https://modelcontextprotocol.io/extensions/apps) view.
206 Hosts that support the extension, such as Claude and ChatGPT, render it in a sandboxed frame
207 next to the result:
208
209 - `deployments` → `ui://smbcloud/deployments.html`: a table of deployments with status, app,
210 commit, and created/updated times, newest first, plus a **Refresh** button.
211 - `mail_message_list` → `ui://smbcloud/inbox.html`: the inbox as a table (from, subject,
212 preview, received, forward status). Click a row to open the message, which calls
213 `mail_message_show`; **Back** returns to the list and **Refresh** reloads it. Only the
214 plain-text body is shown, never `html_body`.
215
216 - `auth_app_show` and `auth_app_new` → `ui://smbcloud/auth-app.html`: a setup card with the
217 client ID (copy), the client secret (masked, with Reveal and Copy), the support email, and a
218 Sign in with Apple checklist. The secret is only present when the API returns it.
219
220 List views show the newest 50 rows. Other clients see the same JSON result as before. Each
221 view is a single self-contained HTML document that makes no network requests; Refresh and
222 drill-in go back through the host with a `tools/call`. Tool `_meta` carries the view URI
223 under both `ui.resourceUri` and the legacy `ui/resourceUri` key, since older hosts read only
224 the flat one.
225
226 ### Tenants
227
228 A tenant is a workspace — your personal one, or an organization you belong to.
229 `tenant_new`/`tenant_delete` only apply to organization tenants; the personal
230 tenant is bootstrapped on signup and isn't user-managed.
231
232 | Tool | Arguments | Returns |
233 | --- | --- | --- |
234 | `tenant_list` | _(none)_ | The user's tenants, with role and project count. |
235 | `tenant_show` | `id` | A single tenant by ID. |
236 | `tenant_use` | `id` | The selected tenant. Persists the selection (see above). |
237 | `tenant_new` | `name` | The created organization tenant. |
238 | `tenant_update` | `id`, `name` | The renamed tenant. |
239 | `tenant_delete` | `id` | Confirmation. **Destructive and irreversible** — cascades to every project, Mail app, and Auth app the tenant owns. |
240
241 ### Mail apps
242
243 A Mail app owns one domain's mail routing; it belongs to a project.
244
245 | Tool | Arguments | Returns |
246 | --- | --- | --- |
247 | `mail_list` | `project_id` (optional, defaults to the selected project) | The user's Mail apps. |
248 | `mail_show` | `id` | A single Mail app by ID. |
249 | `mail_new` | `name`, `domain`, `project_id` (optional), `aws_region` (optional) | The created Mail app. |
250 | `mail_update` | `id`, `name`/`domain`/`aws_region` (at least one) | The updated Mail app. |
251 | `mail_delete` | `id` | Confirmation. **Destructive and irreversible.** |
252
253 ### Sending email
254
255 | Tool | Arguments | Returns |
256 | --- | --- | --- |
257 | `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. |
258
259 `mail_send` is the one tool that does not use the `smb login` session. The send API
260 authenticates with a Mail app API key scoped to that app's verified sending domain,
261 so the key has to reach the server through its environment:
262
263 ```json
264 {
265 "mcpServers": {
266 "smbcloud": {
267 "command": "smb",
268 "args": ["--mcp"],
269 "env": { "SMB_MAIL_API_KEY": "smb_mail_your_key" }
270 }
271 }
272 }
273 ```
274
275 Mint the key for your Mail app in the smbCloud console. Without it, `mail_send`
276 fails with a message saying so and every other tool keeps working.
277
278 Reuse an `idempotency_key` and the original message comes back instead of a second
279 send, which is what you want when an agent retries after a timeout.
280
281 ### Mail inboxes
282
283 An inbox route forwards mail arriving at one address under a Mail app's domain.
284
285 | Tool | Arguments | Returns |
286 | --- | --- | --- |
287 | `mail_inbox_new` | `app_id`, `local_part`, `forward_to_email`, `sender_email` (optional) | The created inbox route. |
288 | `mail_inbox_update` | `app_id`, `id`, `local_part`/`forward_to_email`/`sender_email` (at least one) | The updated inbox route. |
289 | `mail_inbox_delete` | `app_id`, `id` | Confirmation. **Destructive and irreversible.** |
290 | `mail_inbox_test` | `app_id`, `id`, `recipient_email`/`subject`/`body` (all optional) | The test delivery result. |
291
292 ### Mail messages
293
294 Read-only access to an inbox's stored inbound message history.
295
296 | Tool | Arguments | Returns |
297 | --- | --- | --- |
298 | `mail_message_list` | `app_id`, `inbox_id`, `limit` (optional, default 10) | Recent inbound messages, newest first. |
299 | `mail_message_show` | `app_id`, `inbox_id`, `id` | A single inbound message. |
300
301 ### Auth apps
302
303 An Auth app is a hosted identity/sign-in service for your own product's end
304 users; it belongs to a project.
305
306 | Tool | Arguments | Returns |
307 | --- | --- | --- |
308 | `auth_app_list` | `project_id` (optional, defaults to the selected project) | The user's Auth apps. |
309 | `auth_app_show` | `id` | A single Auth app by ID. |
310 | `auth_app_new` | `name`, `project_id` (optional), `support_email` (optional) | The created Auth app. |
311 | `auth_app_update` | `id`, `name`/`support_email` (at least one) | The updated Auth app. |
312 | `auth_app_delete` | `id` | Confirmation. **Destructive and irreversible.** |
313
314 ### Mobile and TV app automation
315
316 Start a dedicated automation server instead of mixing device tools into the
317 cloud profile:
318
319 ```sh
320 smb --mcp --scope automation
321 # or install the smaller standalone crate:
322 xcrs --mcp
323 ```
324
325 > **Installing `xcrs`:** the standalone binary is published to
326 > [crates.io](https://crates.io/crates/xcrs) — install it with
327 > `cargo install xcrs`. It is **not** distributed through Homebrew; `brew
328 > install smbcloud-cli` only provides the `smb` binary (use `smb --mcp --scope
329 > automation` for the same tools without a separate install).
330
331 Both commands expose the same target-aware tools with the same names. Tool
332 names do not repeat the server brand because MCP clients already namespace
333 them by server.
334
335 **Recommended flow:**
336
337 1. Call `device_list` to discover Apple simulators and adb-connected Android
338 phones, tablets, emulators, and TV devices.
339 2. Call `device_select` with the Apple simulator/runner details or Android adb
340 serial. Later actions use that target by default.
341 3. Call `device_capabilities` before choosing an input or inspection action.
342 4. Use `screen_capture` or `ui_describe`/`ui_element_list` to
343 inspect the app, then use semantic `ui_tap` or the raw `input_*` tools.
344
345 | Tool | Purpose |
346 | --- | --- |
347 | `device_list` | Discover available Apple simulators and Android adb devices. |
348 | `device_select` | Remember the Apple or Android target used by later actions. |
349 | `device_capabilities` | Report the selected target's supported automation operations. |
350 | `app_install_launch` | Boot, install, and launch an iOS simulator app bundle. |
351 | `app_launch` / `app_terminate` | Start or stop an installed app on the selected target. |
352 | `screen_capture` | Capture the selected target as a PNG image. |
353 | `url_open` | Open an HTTP(S) URL or custom scheme where the target supports it. |
354 | `ui_describe` / `ui_element_list` / `ui_tap` | Inspect or activate accessible elements through ControlKit (Apple) or AndroidKit (Android). |
355 | `input_tap` / `input_text` / `input_swipe` / `input_button` | Drive shared touch, text, gesture, and button actions. |
356 | `input_click` | Click a macOS target with pointer coordinates. |
357 | `input_spatial_tap` | Perform a visionOS spatial tap. |
358 | `orientation_get` / `orientation_set` | Read or update orientation where supported. |
359
360 See [ControlKit runners](./controlkit.md) for Apple runner setup and
361 [XCRS AndroidKit](./androidkit.md) for Android semantic UI automation. Android
362 tools require adb and an authorized device; Android TV app launches resolve the
363 device's Leanback launcher activity.
364
365 #### Automation migration in 0.5.0
366
367 The automation profile is a clean breaking replacement. Old Apple-default and
368 Android-prefixed names are not exposed as aliases because duplicate tools make
369 agent selection less reliable.
370
371 | Previous tools | Replacement |
372 | --- | --- |
373 | `smb_list_simulators`, `smb_android_device_list` | `device_list` |
374 | `smb_use_target` | `device_select` |
375 | `smb_capabilities` | `device_capabilities` |
376 | `smb_boot_and_install` | `app_install_launch` |
377 | `smb_launch_app`, `smb_android_app_launch` | `app_launch` |
378 | `smb_terminate_app`, `smb_android_app_terminate` | `app_terminate` |
379 | `smb_screenshot`, `smb_android_screenshot` | `screen_capture` |
380 | `smb_open_url`, `smb_android_open_url` | `url_open` |
381 | `smb_describe_ui` | `ui_describe` |
382 | `smb_list_elements` | `ui_element_list` |
383 | `smb_tap`, `smb_android_tap` | `input_tap` |
384 | `smb_type_text`, `smb_android_type_text` | `input_text` |
385 | `smb_swipe`, `smb_android_swipe` | `input_swipe` |
386 | `smb_button`, `smb_android_press_button` | `input_button` |
387 | `smb_click` | `input_click` |
388 | `smb_gesture` | `input_spatial_tap` |
389 | `smb_orientation_get` | `orientation_get` |
390 | `smb_orientation_set` | `orientation_set` |
391
392 The standalone server makes the same replacement without the former `xcrs_`
393 prefix.
394
395 ## Safety: tools run without confirmation
396
397 The CLI's own `project delete`, `tenant delete`, and similar commands ask for
398 confirmation on a terminal — some require typing the resource's name back.
399 **MCP tools skip that entirely.** Destructive tools carry the `destructiveHint` annotation so clients can prompt you, but the server itself does not ask. MCP is a non-interactive protocol, so a
400 `*_delete` tool call executes immediately the moment the client sends it. The
401 calling agent — and the person directing it — is responsible for confirming
402 intent before invoking a destructive tool, exactly as with any other
403 irreversible action an assistant can take on your behalf.
404
405 If you want a harder guardrail, keep destructive operations out of an agent's
406 reach at the client level (most MCP clients let you allow/deny individual
407 tools per server) rather than relying on the server to ask twice.
408
409 ## Not exposed as tools
410
411 These stay in the terminal on purpose:
412
413 - **`login`, `signup`, `logout`, `account forgot`.** They take credentials or
414 end the session, and a password should not pass through a chat. Tools that
415 need a session tell you to run `smb login`.
416 - **`control-kit`.** Device automation lives in its own profile
417 (`--scope automation`, above).
418
419 ## Talking to it directly
420
421 You don't need an MCP client to try this — the server reads newline-delimited
422 JSON-RPC on stdin and writes responses on stdout. Diagnostics go to stderr and
423 logging goes to the on-disk log file, so stdout stays a clean protocol channel
424 end to end. A minimal handshake plus a tool listing:
425
426 ```sh
427 printf '%s\n' \
428 '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}' \
429 '{"jsonrpc":"2.0","method":"notifications/initialized"}' \
430 '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
431 | smb --mcp
432 ```
433
434 A tool call names the tool and its arguments:
435
436 ```json
437 {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"project_show","arguments":{"id":"<id>"}}}
438 ```
439
440 ## FAQ
441
442 **Does the MCP server need a different install than the regular CLI?**
443 No — `smb --mcp` is the same binary you already use for `smb deploy`, `smb
444 project list`, and so on. There's nothing extra to install.
445
446 **Do I need to log in separately for MCP?**
447 No. Log in once with `smb login` on a terminal; every interface (headless,
448 `--tui`, `--mcp`) reads the same token from `~/.smb/token`. The one exception is
449 `mail_send`, which needs a Mail app API key in `SMB_MAIL_API_KEY` because the send
450 API authenticates per sending domain rather than per user.
451
452 **Can I run multiple smbCloud MCP servers for different environments?**
453 Yes — register two entries with different `args` (e.g. one with `-e dev`, one
454 without) under different names, as shown above.
455
456 **Does selecting a tenant or project in an agent chat affect my terminal?**
457 Yes, and vice versa — `tenant_use`/`project_use` write to the same
458 `~/.smb/config.toml` the CLI itself reads, so the two stay in sync.
459
460 **Is the MCP server safe to point an autonomous agent at?**
461 Every write tool, including every `*_delete` tool and `deploy`, executes immediately with
462 no confirmation prompt, since MCP is non-interactive by design. Treat tool
463 access the way you'd treat handing someone your terminal with `smb` already
464 logged in: fine for a trusted agent you're directing, not something to expose
465 unsupervised.