MCP servers
Slack, Gmail, Salesforce and Shopify serve the same MCP servers as the real vendors, at the run's address and with the persona's token. An agent that works through MCP tools needs only a config file.
Apps with an MCP server
| App | Server | Token | Example tools |
|---|---|---|---|
| Slack | Slack's MCP server | user: | slack_send_message,
slack_add_reaction, slack_create_conversation |
| Gmail | Gmail's MCP server | user: | search_threads,
get_thread, create_draft |
| Salesforce | Hosted MCP server sobject-all | mcp: |
soqlQuery, find, getObjectSchema |
| Shopify | Shopify's MCP server | setup: | create-collection,
add-to-collection, bulk-update-product-status |
Google Calendar, Google Sheets, Google Drive and Zendesk have no MCP server in StateWeave: an agent reaches them
through their APIs (see Apps and credentials). In a run, their mcp field is
null and they are left out of the MCP config.
The MCP config
Every run comes with its MCP servers in the format Claude Code and most MCP clients read. The server names are the app names, and each server is Streamable HTTP with the token in its headers:
{
"mcpServers": {
"slack": {
"type": "http",
"url": "https://t-7d1kq4.app.stateweave.net/mcp",
"headers": {"Authorization": "Bearer xoxp-…"}
},
"gmail": {
"type": "http",
"url": "https://t-m3x9ce.app.stateweave.net/mcp/v1",
"headers": {"Authorization": "Bearer ya29.…"}
}
}
}Three ways to get it:
sw runwrites it to a file and passes the path inSTATEWEAVE_MCP_CONFIG.run.mcp_servers()in Python returns the inner{app: {...}}object.sw mcp-config RUNprints it for a run that is still going.
Claude Code
Run Claude Code headless with the run's servers as its only MCP servers:
sw run t-0e4243 --agent claude-code -- sh -c 'claude -p "$STATEWEAVE_PROMPT" \
--mcp-config "$STATEWEAVE_MCP_CONFIG" --strict-mcp-config \
--allowedTools "mcp__slack__*"'--strict-mcp-configkeeps your own MCP servers out of the run.- In print mode (
-p) Claude Code refuses a tool nobody approved, so--allowedToolsmust name the servers of every app the test uses, asmcp__<app>__*.
This script works out the apps from the config by itself and, for an answer test, asks for the answer in the
test's format and hands Claude Code's reply in. Save it as claude_code.sh:
#!/bin/sh
# An agent for `sw run`: Claude Code, headless, with the run's apps as its only MCP servers and every tool of
# theirs allowed (print mode denies a tool call nobody approved). For an answer task, Claude Code's last message is
# handed in as the answer.
#
# sw run t-3fa2c1 --agent claude-code -- sh claude_code.sh
#
# Flags as documented in Claude Code's CLI reference, headless and permissions pages (code.claude.com/docs).
set -ef # -f: the mcp__<app>__* patterns stay patterns
servers=$(python3 -c 'import json, os; print(" ".join(f"mcp__{a}__*" for a in json.load(open(os.environ["STATEWEAVE_MCP_CONFIG"]))["mcpServers"]))')
prompt="$STATEWEAVE_PROMPT"
if [ "$STATEWEAVE_KIND" = "answer" ]; then
prompt="$prompt
When you are done, reply with only this JSON and nothing else: $STATEWEAVE_ANSWER_FORMAT"
fi
answer=$(claude -p "$prompt" --mcp-config "$STATEWEAVE_MCP_CONFIG" --strict-mcp-config --allowedTools $servers)
if [ "$STATEWEAVE_KIND" = "answer" ]; then
printf '%s' "$answer" > "$STATEWEAVE_ANSWER_FILE"
fisw run t-bde537 --agent claude-code -- sh claude_code.shThe Claude Agent SDK's mcp_servers option takes the same objects as run.mcp_servers()
returns.
Any MCP client
With the official MCP Python SDK (version 2), connect over Streamable HTTP with the run's headers:
import asyncio
import httpx2
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client
async def list_tools(server):
async with httpx2.AsyncClient(headers=server["headers"]) as http:
async with streamable_http_client(server["url"], http_client=http) as streams:
async with ClientSession(streams[0], streams[1]) as session:
await session.initialize()
return [tool.name for tool in (await session.list_tools()).tools]
for name, server in run.mcp_servers().items():
print(name, asyncio.run(list_tools(server)))Calls through MCP count. A tool call reaches the same app as an API call does. It is recorded on the run and graded the same way.