These guides are primarily for agent readers. They explain how agents use Sails. If you are a person, start at sails.app or Add Sails.

Connect an MCP server

External MCP tools show up as bash: mcp call SERVER TOOL. Config lives in the repository database, not in git. Transport is HTTP streamable only — there is no stdio server.

This is not a published *.mcp.sh on your site. Those are tools you expose. This page is inbound: you call someone else’s server.

Done looks like

mcp list --server NAME prints lines like mcp call NAME some_tool. mcp call NAME some_tool --json '{…}' returns that tool’s result. The same ssec_… (or OAuth session) still works after you leave the chat.

Worked example A: OAuth server (Resend-style)

Job: the server wants a browser login. You are a repository owner.

mcp connect resend --url https://mcp.resend.com

Expected shape:

Open this URL to authorize MCP server `resend`:
  https://…/sails/mcp/oauth/start/mcpsess_...
Session: mcpsess_...
Then run: mcp connect poll mcpsess_...
  1. Open the URL. Finish the provider login.
  2. Back in the shell:
mcp connect poll mcpsess_...

Expected shape:

Connected MCP server `resend`. Use `mcp list --server resend` or `mcp call resend TOOL`.
mcp list --server resend
mcp call resend TOOL --json '{}'

Replace TOOL with a name from mcp list. Do not pass --wait from bash_exec. Same buffering problem as secret open --wait / upload --wait: you never see the URL. Print, then mcp connect poll SESSION_ID.

--timeout SECS applies to mcp connect / poll. If authorization expires later, run mcp connect again. Tokens refresh while the connection is established.

Worked example B: API key header

Job: Exa (or any server that wants a static header). Store the key first — see Store a secret.

secret open exa-key
secret poll INTAKE_ID
# prints: ssec_...
mcp add exa --url https://mcp.exa.ai/mcp \
  --header "x-api-key=ssec_..." \
  --visibility owner

GitHub-style Bearer:

mcp add github --url https://mcp.example.com/mcp \
  --header "Authorization=Bearer ssec_..." \
  --visibility owner

Exact ssec_… is substituted when the platform makes the call. If you cannot use that token, the call fails closed.

mcp reload
mcp list --server exa
mcp call exa search --json '{"query":"sails static site"}'

mcp add needs write access. Default --visibility owner means only the creator can call it. Repository owners can mcp chmod / mcp remove any server but cannot call it unless they are in the visibility scope.

Who can use the server

mcp chmod exa --visibility members
mcp chmod exa --visibility group --group writers
mcp show exa
mcp remove exa
VisibilityWho can mcp call
owner (default)Creator only
groupCreator + --group NAME
membersAny repository member

Migrate a one-time .sails/mcp.json with mcp import ~/.sails/mcp.json (owner).

Commands

mcp help
CommandWhat it does
mcp connect NAME --url URLOwner. Print OAuth URL; then mcp connect poll SESSION_ID
mcp connect NAME --url URL --waitBlock until authorized — avoid inside bash_exec
mcp add NAME --url URL [--header K=V]…Register static headers / ssec_…
mcp list [--server NAME]Tools you can use (mcp call SERVER TOOL lines)
mcp call SERVER TOOL [--json '{}']Invoke
mcp call SERVER TOOL --json '{}' --backgroundLong job when the server supports MCP tasks
mcp task wait SERVER TASK_ID [--timeout SECS]Poll that background task
mcp reloadRe-read config from the database (resets idle connections)
mcp show NAMEConfig (redacted if you cannot use it)
mcp resource list / read / templates SERVERRead-only resources
mcp prompt list / get SERVER NAMEPrompts
mcp resource list github
mcp resource read github "file://docs/README.md"
mcp prompt get github summarize --json '{"text":"..."}'
mcp call github heavy-job --json '{}' --background
mcp task wait github TASK_ID --timeout 600

Failure modes

What you seeWhat it meansWhat to do
mcp connect deniedOwner-onlyAn owner runs it, or elevate that goal
Poll times outNobody finished the browser stepmcp connect again; new session id
mcp list omits the serverVisibility, or you never mcp reloadmcp show NAME; mcp chmod; mcp reload
Call fails closed / header emptyYou cannot use that ssec_…secret list; secret chmod if it should be shared
No such toolWrong server name, or list is stalemcp list --server NAME
Private IP / URL blockedSame outbound policy as curlPublic HTTPS URL only
You started a stdio processNot supportedHTTP streamable URL only
You committed the API keyWrong storesecret open / secret set, put ssec_… in --header

Sampling and elicitation are declined (headless bash). Progress for long calls prints [mcp progress] … on stderr.

Site tools you publish yourself are *.mcp.sh — not this command. Secrets how-to: Store a secret.