MCP Server Setup SOP
Context
MCP (Model Context Protocol) servers extend Claude Code’s capabilities — connecting to APIs, databases, and external services. Many require API keys or OAuth credentials. This SOP ensures we set them up securely and consistently.
Principles
- No secrets on disk. Never commit API keys to
.envfiles, config files, or code. Secrets live in 1Password only. - 1Password is the single source of truth. All API credentials go in the Ray Agent vault. Retrieved at runtime via
opCLI. - Wrapper script pattern. Each MCP server that needs secrets gets a
start.shthat pulls from 1Password and exec’s the server. - Allowlist tools. Only enable the tools you need. Use
X_API_TOOL_ALLOWLISTor equivalent to limit surface area. - 7-day release policy still applies. New MCP server packages go through the same supply-chain-security checks as any other dependency.
Setup Pattern
1. Store credentials in 1Password
op item create --category=login \
--title="Service Name - API Keys" \
--vault="Ray Agent" \
"api_key[password]=sk-xxx" \
"api_secret[password]=xxx"
2. Create a wrapper script
Save as ~/Projects/<server-name>/start.sh:
#!/bin/bash
# Start <service> MCP server with secrets from 1Password
# No secrets on disk — pulled at runtime from Ray Agent vault
export API_KEY=$(op item get <item-id> --fields api_key --reveal --vault "Ray Agent")
export API_SECRET=$(op item get <item-id> --fields api_secret --reveal --vault "Ray Agent")
exec /path/to/venv/bin/python /path/to/server.py
chmod +x start.sh
3. Register in ~/.mcp.json
{
"mcpServers": {
"service-name": {
"command": "bash",
"args": ["/Users/ray/Projects/<server-name>/start.sh"],
"cwd": "/Users/ray/Projects/<server-name>"
}
}
}
Do NOT use the "env" block in mcp.json for secrets — that would put them in a file on disk. The wrapper script pulls them from 1Password at process start.
4. Allowlist tools in settings.json
"mcp__<server-name>__*"
Or specific tools only:
"mcp__<server-name>__readThing",
"mcp__<server-name>__searchThing"
5. Reload
Run /reload-plugins in the Claude Code session, or wait for the daily 4am restart.
Active MCP Servers
| Server | Location | 1Password Item | Tools Enabled |
|---|---|---|---|
| QMD | /opt/homebrew/lib/node_modules/@tobilu/qmd | N/A (no secrets) | query, get, multi_get, status |
| X/Twitter (xmcp) | ~/Projects/xmcp | X Twitter API - Bearer Token | searchRecentTweets, findTweetById, findUserByUsername, + 4 more |
| Notion | Cloud (claude.ai) | N/A (OAuth via Anthropic) | All (*) |
| Gmail | Cloud (claude.ai) | N/A (OAuth via Anthropic) | All (*) |
| Google Calendar | Cloud (claude.ai) | N/A (OAuth via Anthropic) | All (*) |
| Slack | Cloud (claude.ai) | N/A (OAuth via Anthropic) | All (*) |
Anti-patterns
.envfiles with secrets — Even if gitignored, secrets sit on disk in plaintext. Use the wrapper script pattern instead.- Secrets in
~/.mcp.jsonenv block — Same problem, different file. The env block is for non-sensitive config only. - Hardcoded secrets in start scripts — The script should call
op item get, never contain the actual secret value. direnv— Unnecessary complexity for our use case. The wrapper script is simpler and more explicit.- Relying on
cwdfrom mcp.json — Claude Code’s/mcpreconnect may not honor thecwdfield. Alwayscdto the server directory explicitly instart.shAND addsys.path.insert(0, '/absolute/path')in any Python-cinline scripts. - Slow startup from network fetches — If the server fetches a remote spec (OpenAPI, schema, etc.) at startup, cache it locally. Claude Code has a short connection timeout — a 4-second spec download can cause “Failed to reconnect.”