The smbCloud CLI can run as a Model Context Protocol (MCP) server, so an AI
assistant or agent — Claude Desktop, Claude Code, Cursor, or any other
MCP-capable client — can send transactional email and manage your smbCloud
projects, tenants, Mail apps, and Auth apps directly, without you leaving the
chat to run smb commands by hand.
Start it with smb --mcp; it speaks standard MCP over stdio and exposes 41
tools covering the account, project, deploy, tenant, Mail, and Auth surfaces.
Cross-platform mobile and TV app testing is exposed as a separate, focused MCP
profile through xcrs --mcp or smb --mcp --scope automation. Keeping cloud
and device tools in separate profiles makes tool selection more predictable for
agents.
This page is the setup guide and the full tool reference. For how --mcp
compares to the CLI's other two interfaces (headless and --tui), see
Interfaces. The server is also listed in the official MCP
Registry as io.github.smbcloudXYZ/smbcloud-cli, so clients that browse the
registry can install it for you — see MCP Registry.
For building and starting the native runners, see
ControlKit runners.
-
Install the CLI (
smb --versionshould print something). -
Log in once, headlessly — the MCP server itself is non-interactive and can't run the login flow:
smb login
This writes a token to
~/.smb/token(~/.smb-dev/tokenin thedevenvironment). Every MCP tool call reuses that token; there's nothing client-specific to configure for auth.
Every MCP client ultimately does the same thing: run smb --mcp as a
subprocess and speak JSON-RPC over its stdin/stdout. The differences are in
where each client wants that command declared.
Edit your Claude Desktop config —
~/Library/Application Support/Claude/claude_desktop_config.json on macOS,
%APPDATA%\Claude\claude_desktop_config.json on Windows — and add an entry
under mcpServers:
{
"mcpServers": {
"smbcloud": {
"command": "smb",
"args": ["--mcp"]
}
}
}Restart Claude Desktop. The tools listed below become available in any chat.
claude mcp add smbcloud -- smb --mcpOr add it directly to your project's .mcp.json:
{
"mcpServers": {
"smbcloud": {
"command": "smb",
"args": ["--mcp"]
}
}
}Add the same shape to ~/.cursor/mcp.json (global) or .cursor/mcp.json
(project-scoped):
{
"mcpServers": {
"smbcloud": {
"command": "smb",
"args": ["--mcp"]
}
}
}If your client's config isn't listed above, it almost certainly still takes a
command + args pair for a stdio server — point it at smb with --mcp
exactly as shown above. That's the entire integration surface; there's no
separate server to run or port to expose.
Add the global -e/--environment flag before --mcp to point the server at
a local dev API instead of production:
{
"mcpServers": {
"smbcloud-dev": {
"command": "smb",
"args": ["-e", "dev", "--mcp"]
}
}
}Several tools take an optional project_id, and project creation targets
whichever tenant is currently selected. Two tools manage that selection:
tenant_use— selects a tenant (workspace) for the session. While selected,project_createtargets it instead of your personal tenant.project_use— selects a project for the session. Tools with an optionalproject_id(Mail apps, Auth apps) default to it when omitted. Passfrontend_app_idto select one of the project's frontend apps too.
Both persist to ~/.smb/config.toml (~/.smb-dev/config.toml in dev) — the
same file smb tenant use / smb project use write from the terminal. Switch
context in a chat with an agent, then run smb project list in a terminal a
minute later, and it reflects the same selection. Selecting a tenant that the
current project doesn't belong to clears the project selection, so a stale
cross-tenant project_id can never linger silently.
Every tool returns structured JSON — a single object for _show/_new/
_update/_use, an array for _list. Fields are the CLI's own JSON models,
not a separate MCP-specific shape.
| Tool | Arguments | Returns |
|---|---|---|
me |
(none) | The authenticated user. |
| Tool | Arguments | Returns |
|---|---|---|
project_list |
(none) | The user's projects. |
project_show |
id |
A single project by ID. |
project_use |
id, frontend_app_id (optional, must belong to the project) |
The selected project. Persists the selection (see above). |
project_create |
name, description (optional) |
The created project. Targets the selected tenant, if any. |
project_update |
id, description, runner (optional) |
The updated project. The runner is kept when omitted. |
project_delete |
id |
Confirmation. Destructive and irreversible. |
deployments |
project_id |
A project's deployments. On MCP Apps hosts it also renders a dashboard (see below). |
deployment_show |
project_id, id |
A single deployment. |
A project holds a deploy repo (the git repository or monorepo root) and the
frontend apps that ship from it. runner is one of node_js, static, ruby,
swift, rust.
| Tool | Arguments | Returns |
|---|---|---|
frontend_app_list |
project_id |
The project's frontend apps. |
frontend_app_show |
id |
A single frontend app. |
frontend_app_new |
project_id, name, source_path (optional), runner (optional, default node_js) |
The created frontend app. |
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. |
deploy_config_show |
frontend_app_id |
The app's server-side deploy config. |
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. |
These act on a directory on the machine running smb --mcp, so each takes an
explicit absolute cwd. They never prompt: anything the interactive CLI would
ask is an argument, or the call fails with a message saying what is missing.
| Tool | Arguments | Returns |
|---|---|---|
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. |
deploy |
cwd, project (optional monorepo sub-project) |
{ success, exit_code, output_tail }. |
migrate |
cwd |
{ success, exit_code, output_tail }. |
deploy and migrate need an existing .smb/config.toml in cwd (run
project_init first). deploy builds locally, pushes to the server, and
restarts the app, so it can take minutes; the call returns when it finishes.
Both run the smb binary itself as a child process in cwd with --ci,
rather than in the MCP process. The deploy paths use paths relative to the
working directory and print progress to stdout, which in an MCP session is the
JSON-RPC channel. output_tail is the last 200 lines of combined output with
colors and spinner frames removed. The call is flagged as an error when the
command exits non-zero.
Three tools declare an MCP Apps view. Hosts that support the extension, such as Claude and ChatGPT, render it in a sandboxed frame next to the result:
-
deployments→ui://smbcloud/deployments.html: a table of deployments with status, app, commit, and created/updated times, newest first, plus a Refresh button. -
mail_message_list→ui://smbcloud/inbox.html: the inbox as a table (from, subject, preview, received, forward status). Click a row to open the message, which callsmail_message_show; Back returns to the list and Refresh reloads it. Only the plain-text body is shown, neverhtml_body. -
auth_app_showandauth_app_new→ui://smbcloud/auth-app.html: a setup card with the client ID (copy), the client secret (masked, with Reveal and Copy), the support email, and a Sign in with Apple checklist. The secret is only present when the API returns it.
List views show the newest 50 rows. Other clients see the same JSON result as before. Each
view is a single self-contained HTML document that makes no network requests; Refresh and
drill-in go back through the host with a tools/call. Tool _meta carries the view URI
under both ui.resourceUri and the legacy ui/resourceUri key, since older hosts read only
the flat one.
A tenant is a workspace — your personal one, or an organization you belong to.
tenant_new/tenant_delete only apply to organization tenants; the personal
tenant is bootstrapped on signup and isn't user-managed.
| Tool | Arguments | Returns |
|---|---|---|
tenant_list |
(none) | The user's tenants, with role and project count. |
tenant_show |
id |
A single tenant by ID. |
tenant_use |
id |
The selected tenant. Persists the selection (see above). |
tenant_new |
name |
The created organization tenant. |
tenant_update |
id, name |
The renamed tenant. |
tenant_delete |
id |
Confirmation. Destructive and irreversible — cascades to every project, Mail app, and Auth app the tenant owns. |
A Mail app owns one domain's mail routing; it belongs to a project.
| Tool | Arguments | Returns |
|---|---|---|
mail_list |
project_id (optional, defaults to the selected project) |
The user's Mail apps. |
mail_show |
id |
A single Mail app by ID. |
mail_new |
name, domain, project_id (optional), aws_region (optional) |
The created Mail app. |
mail_update |
id, name/domain/aws_region (at least one) |
The updated Mail app. |
mail_delete |
id |
Confirmation. Destructive and irreversible. |
| Tool | Arguments | Returns |
|---|---|---|
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. |
mail_send is the one tool that does not use the smb login session. The send API
authenticates with a Mail app API key scoped to that app's verified sending domain,
so the key has to reach the server through its environment:
{
"mcpServers": {
"smbcloud": {
"command": "smb",
"args": ["--mcp"],
"env": { "SMB_MAIL_API_KEY": "smb_mail_your_key" }
}
}
}Mint the key for your Mail app in the smbCloud console. Without it, mail_send
fails with a message saying so and every other tool keeps working.
Reuse an idempotency_key and the original message comes back instead of a second
send, which is what you want when an agent retries after a timeout.
An inbox route forwards mail arriving at one address under a Mail app's domain.
| Tool | Arguments | Returns |
|---|---|---|
mail_inbox_new |
app_id, local_part, forward_to_email, sender_email (optional) |
The created inbox route. |
mail_inbox_update |
app_id, id, local_part/forward_to_email/sender_email (at least one) |
The updated inbox route. |
mail_inbox_delete |
app_id, id |
Confirmation. Destructive and irreversible. |
mail_inbox_test |
app_id, id, recipient_email/subject/body (all optional) |
The test delivery result. |
Read-only access to an inbox's stored inbound message history.
| Tool | Arguments | Returns |
|---|---|---|
mail_message_list |
app_id, inbox_id, limit (optional, default 10) |
Recent inbound messages, newest first. |
mail_message_show |
app_id, inbox_id, id |
A single inbound message. |
An Auth app is a hosted identity/sign-in service for your own product's end users; it belongs to a project.
| Tool | Arguments | Returns |
|---|---|---|
auth_app_list |
project_id (optional, defaults to the selected project) |
The user's Auth apps. |
auth_app_show |
id |
A single Auth app by ID. |
auth_app_new |
name, project_id (optional), support_email (optional) |
The created Auth app. |
auth_app_update |
id, name/support_email (at least one) |
The updated Auth app. |
auth_app_delete |
id |
Confirmation. Destructive and irreversible. |
Start a dedicated automation server instead of mixing device tools into the cloud profile:
smb --mcp --scope automation
# or install the smaller standalone crate:
xcrs --mcpInstalling
xcrs: the standalone binary is published to crates.io — install it withcargo install xcrs. It is not distributed through Homebrew;brew install smbcloud-clionly provides thesmbbinary (usesmb --mcp --scope automationfor the same tools without a separate install).
Both commands expose the same target-aware tools with the same names. Tool names do not repeat the server brand because MCP clients already namespace them by server.
Recommended flow:
- Call
device_listto discover Apple simulators and adb-connected Android phones, tablets, emulators, and TV devices. - Call
device_selectwith the Apple simulator/runner details or Android adb serial. Later actions use that target by default. - Call
device_capabilitiesbefore choosing an input or inspection action. - Use
screen_captureorui_describe/ui_element_listto inspect the app, then use semanticui_tapor the rawinput_*tools.
| Tool | Purpose |
|---|---|
device_list |
Discover available Apple simulators and Android adb devices. |
device_select |
Remember the Apple or Android target used by later actions. |
device_capabilities |
Report the selected target's supported automation operations. |
app_install_launch |
Boot, install, and launch an iOS simulator app bundle. |
app_launch / app_terminate |
Start or stop an installed app on the selected target. |
screen_capture |
Capture the selected target as a PNG image. |
url_open |
Open an HTTP(S) URL or custom scheme where the target supports it. |
ui_describe / ui_element_list / ui_tap |
Inspect or activate accessible elements through ControlKit (Apple) or AndroidKit (Android). |
input_tap / input_text / input_swipe / input_button |
Drive shared touch, text, gesture, and button actions. |
input_click |
Click a macOS target with pointer coordinates. |
input_spatial_tap |
Perform a visionOS spatial tap. |
orientation_get / orientation_set |
Read or update orientation where supported. |
See ControlKit runners for Apple runner setup and XCRS AndroidKit for Android semantic UI automation. Android tools require adb and an authorized device; Android TV app launches resolve the device's Leanback launcher activity.
The automation profile is a clean breaking replacement. Old Apple-default and Android-prefixed names are not exposed as aliases because duplicate tools make agent selection less reliable.
| Previous tools | Replacement |
|---|---|
smb_list_simulators, smb_android_device_list |
device_list |
smb_use_target |
device_select |
smb_capabilities |
device_capabilities |
smb_boot_and_install |
app_install_launch |
smb_launch_app, smb_android_app_launch |
app_launch |
smb_terminate_app, smb_android_app_terminate |
app_terminate |
smb_screenshot, smb_android_screenshot |
screen_capture |
smb_open_url, smb_android_open_url |
url_open |
smb_describe_ui |
ui_describe |
smb_list_elements |
ui_element_list |
smb_tap, smb_android_tap |
input_tap |
smb_type_text, smb_android_type_text |
input_text |
smb_swipe, smb_android_swipe |
input_swipe |
smb_button, smb_android_press_button |
input_button |
smb_click |
input_click |
smb_gesture |
input_spatial_tap |
smb_orientation_get |
orientation_get |
smb_orientation_set |
orientation_set |
The standalone server makes the same replacement without the former xcrs_
prefix.
The CLI's own project delete, tenant delete, and similar commands ask for
confirmation on a terminal — some require typing the resource's name back.
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
*_delete tool call executes immediately the moment the client sends it. The
calling agent — and the person directing it — is responsible for confirming
intent before invoking a destructive tool, exactly as with any other
irreversible action an assistant can take on your behalf.
If you want a harder guardrail, keep destructive operations out of an agent's reach at the client level (most MCP clients let you allow/deny individual tools per server) rather than relying on the server to ask twice.
These stay in the terminal on purpose:
login,signup,logout,account forgot. They take credentials or end the session, and a password should not pass through a chat. Tools that need a session tell you to runsmb login.control-kit. Device automation lives in its own profile (--scope automation, above).
You don't need an MCP client to try this — the server reads newline-delimited JSON-RPC on stdin and writes responses on stdout. Diagnostics go to stderr and logging goes to the on-disk log file, so stdout stays a clean protocol channel end to end. A minimal handshake plus a tool listing:
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
| smb --mcpA tool call names the tool and its arguments:
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"project_show","arguments":{"id":"<id>"}}}Does the MCP server need a different install than the regular CLI?
No — smb --mcp is the same binary you already use for smb deploy, smb project list, and so on. There's nothing extra to install.
Do I need to log in separately for MCP?
No. Log in once with smb login on a terminal; every interface (headless,
--tui, --mcp) reads the same token from ~/.smb/token. The one exception is
mail_send, which needs a Mail app API key in SMB_MAIL_API_KEY because the send
API authenticates per sending domain rather than per user.
Can I run multiple smbCloud MCP servers for different environments?
Yes — register two entries with different args (e.g. one with -e dev, one
without) under different names, as shown above.
Does selecting a tenant or project in an agent chat affect my terminal?
Yes, and vice versa — tenant_use/project_use write to the same
~/.smb/config.toml the CLI itself reads, so the two stay in sync.
Is the MCP server safe to point an autonomous agent at?
Every write tool, including every *_delete tool and deploy, executes immediately with
no confirmation prompt, since MCP is non-interactive by design. Treat tool
access the way you'd treat handing someone your terminal with smb already
logged in: fine for a trusted agent you're directing, not something to expose
unsupervised.