让你的 coding agent 一次配好,直接操作你的 SaaS 工作栈 — agent-saas-gateway
You are running the agent-saas-gateway skill. Goal: give a developer a locally-hosted "actions gateway" so their coding agent can drive the SaaS apps they already work in — GitHub, Notion, Slack, Airtable, BigQuery, Gmail — from a single MCP endpoint, without ever handing a raw PAT to the agent process.
底层工具是 oomol-lab/open-connector(开源、可自托管、MCP + HTTP + OpenAPI 四合一)。这门课直接消费上游镜像,不加 wrapper、不发内部 fork。
心智模型
- Provider connections live inside the gateway. A GitHub PAT / Notion integration token / Slack bot token gets stored once (locally, in the gateway's SQLite).
- Runtime tokens are what your coding agent actually sees. They're short-lived
oct_...bearer tokens minted by the gateway; you rotate or revoke them without touching your PATs. - Actions are the verbs (
github.add_issue_labels,notion.create_page,slack.send_channel_message, …). 1,000+ providers × 10,000+ actions ship with the gateway. - Four surfaces front the same catalog: MCP at
POST /mcp, HTTP runtime API under/v1/*, OpenAPI at/openapi.json, and a Web Console athttp://localhost:3000.
Iron rules
- Do not wrap the gateway. Install and run the upstream repo + its official
docker-compose.yml+ghcr.io/oomol-lab/open-connector:latestdirectly. Any custom image, private fork, or hand-rolled CLI shim will be rejected. - No provider secret ever leaves the local network. The coding agent only receives an
oct_...runtime token; the actual GitHub PAT / Notion token / Slack bot token is at rest inside the gateway container's SQLite volume. - The gateway itself does not call any LLM. OpenConnector is pure plumbing — it authenticates you, routes Action calls to provider APIs, and logs the round-trips. All model reasoning happens inside the coding-agent host you already run (Claude Code, Codex, Cursor, Claude Desktop, Cline, Windsurf, Gemini CLI, …) on whatever subscription that host is already signed into. This shape matches other consumer-side plumbing courses on Clawvard (
agent-lazy-coder,mcp-snoop,build-mcp-server,agent-plan-then-execute): the course does not introduce a new commercial API key, does not add a first-party Clawvard service, and does not proxy inference through any OpenAI-shape relay. - User-visible URLs use
https://clawvard.school. For upstream references usehttps://github.com/oomol-lab/open-connectorandhttps://openconnector.dev. - Prefer fine-grained GitHub PATs with only the scopes the popular task needs. For the pre-baked triage flow that is
repo:read+issues:write(labels + comments) +pull_requests:write.
1. Prerequisites
- Docker Desktop or Docker Engine ≥ 24 with
docker compose(the gateway ships as one container + one named volume). - Node ≥ 20 (only needed if you wire MCP into Claude Code / Codex via
npx -y mcp-remote; the HTTP path works with any language). - A coding agent host with either native MCP support (Claude Code, Codex CLI, Cursor, Claude Desktop, Cline, Windsurf, etc.) OR the ability to POST JSON over HTTP.
- A GitHub fine-grained PAT for the main popular task — scopes:
repometadata read,issues:write,pull_requests:write, on the specific repo you want to triage. Generate athttps://github.com/settings/personal-access-tokens/new. - Optional: Notion integration token + a database shared with that integration, if you want to do popularTask #2 (cross-SaaS orchestration). Notion tokens come from
https://www.notion.so/my-integrations.
No Clawvard credentials required — the coding agent host handles its own model auth.
2. Boot the gateway (~2 minutes)
git clone https://github.com/oomol-lab/open-connector.git
cd open-connector
docker compose up -d
That pulls ghcr.io/oomol-lab/open-connector:latest, mounts the connector-data volume, and binds the runtime to http://localhost:3000.
Sanity check:
curl -s http://localhost:3000/v1/health
# → {"success":true,"message":"OK","data":{"ok":true,"runtime":"oomol-connect"},"meta":{}}
Open the Web Console at http://localhost:3000 in a browser. Left rail lists 1,000+ providers; top nav has Connections, Access, Runs.
Port already in use? Edit
docker-compose.ymlports:to"3010:3000"and everything below stays the same exceptlocalhost:3000→localhost:3010.docker pulldenied? GHCR sometimes 401s anonymous pulls behind corporate proxies — rundocker login ghcr.io -u <your-github-username>with a GitHub PAT that hasread:packagesand try again.
3. Wire your first provider connection — GitHub
You have two supported paths for GitHub; pick either.
3a. Fine-grained PAT (fastest, recommended for a first run)
⚠ Do not substitute
gh auth token. The gateway's credential save callsgithub.get_current_userunder the hood (GET https://api.github.com/user) to bind the connection to an identity. User PATs and OAuth user tokens hit that endpoint fine; but a GitHub App installation token (which is what many CI runners, GitHub Codespaces, andghsessions signed in via a GitHub App return) will fail withcredential_verification_failed → Resource not accessible by integration. If you see that error, you are almost certainly using an App installation token — the fix is to generate a real fine-grained PAT via the web UI below.
Generate a fine-grained PAT at https://github.com/settings/personal-access-tokens/new:
- Repository access: pick the one repo you want to triage.
- Repository permissions:
Contents: Read,Issues: Read and write,Pull requests: Read and write,Metadata: Read(auto-selected). - Expiration: 30 days (or shorter for a demo).
Store the PAT into the gateway as the default connection:
export GH_PAT='github_pat_...'
curl -s -X PUT http://localhost:3000/api/connections/github \
-H 'content-type: application/json' \
-d "{\"authType\":\"api_key\",\"values\":{\"apiKey\":\"$GH_PAT\"}}"
Verify the connection identifies your account:
curl -s -X POST http://localhost:3000/v1/actions/github.get_current_user \
-H 'content-type: application/json' \
-d '{"input":{}}'
You should see your GitHub login in data.
3b. OAuth2 (recommended when the learner is packaging this for teammates)
# 1. Store an OAuth App client (create at https://github.com/settings/developers).
curl -s -X PUT http://localhost:3000/api/oauth/configs/github \
-H 'content-type: application/json' \
-d '{"clientId":"...","clientSecret":"..."}'
# 2. Start the flow and open the returned authorizationUrl in a browser.
curl -s -X POST http://localhost:3000/api/oauth/authorizations \
-H 'content-type: application/json' \
-d '{"service":"github"}'
Complete the browser callback; the gateway persists the resulting access token as the default GitHub connection.
4. Mint a runtime token for the coding agent
Provider credentials never travel to the agent. Instead, give the agent a short-lived runtime token:
curl -s -X POST http://localhost:3000/api/runtime-tokens \
-H 'content-type: application/json' \
-d '{"label":"claude-code-local","allowedServices":["github"]}'
# → { "data": { "id": "...", "token": "oct_..." , ... } }
export CONNECTOR_TOKEN='oct_...'
Scope the token to only the providers this agent session needs. Rotate or delete tokens at any time (GET /api/runtime-tokens, DELETE /api/runtime-tokens/:id), or via the Web Console Access tab, without touching the underlying PAT.
5. Wire the gateway into your coding agent as an MCP server
The gateway exposes MCP at http://localhost:3000/mcp. Bridge it into a stdio-only coding agent with mcp-remote:
Claude Code
claude mcp add saas-gateway --scope user -- \
npx -y mcp-remote http://localhost:3000/mcp \
--header "Authorization: Bearer $CONNECTOR_TOKEN"
Codex CLI / Cursor / Cline / Windsurf / Claude Desktop
Add this JSON to the host's MCP config (~/.claude/mcp_servers.json, ~/.cursor/mcp.json, Windsurf mcp_config.json, or Claude Desktop claude_desktop_config.json):
{
"mcpServers": {
"saas-gateway": {
"command": "npx",
"args": [
"-y", "mcp-remote",
"http://localhost:3000/mcp",
"--header", "Authorization: Bearer oct_YOUR_TOKEN"
]
}
}
}
Restart the host. In the tools panel you should now see four discovery tools from the gateway:
list_apps— every provider you've connected.search_actions— free-text search across 10,000+ Actions.get_action_guide— returns a rendered markdown guide for one Action, including its exact input schema.execute_action— call one Action with a JSON input.
6. Popular task — turn your coding agent into a GitHub duty bot
Tell the coding agent, in plain language:
"Look at every open issue on
<owner>/<repo>that hasn't been touched in 30 days. Add thestalelabel and post a comment saying 'Marked stale by SaaS gateway on<date>. Reply within 7 days to keep this issue open.' Summarize what you did in a markdown table."
Under the hood, the agent will:
search_actions("github list repository issues")→ findgithub.list_repository_issues.get_action_guide("github.list_repository_issues")→ see it takes{owner, repo, state, since}.execute_action("github.list_repository_issues", { owner, repo, state: "open", since: "<30 days ago>" }).- For each issue:
execute_action("github.add_issue_labels", { owner, repo, issueNumber, labels: ["stale"] }). - For each issue:
execute_action("github.create_issue_comment", { owner, repo, issueNumber, body: "…" }). - Compile a markdown report (issue URL → labels added → comment URL).
Open the Web Console Runs tab to see every Action call the agent made, its request/response, and any errors — the audit trail lives with the gateway, not with the model host.
Golden path fallback (no MCP host). You can drive the same three Actions directly over HTTP if your learner only has a shell:
curl -s -X POST http://localhost:3000/v1/actions/github.list_repository_issues \
-H "Authorization: Bearer $CONNECTOR_TOKEN" -H 'content-type: application/json' \
-d '{"input":{"owner":"OWNER","repo":"REPO","state":"open"}}'
curl -s -X POST http://localhost:3000/v1/actions/github.add_issue_labels \
-H "Authorization: Bearer $CONNECTOR_TOKEN" -H 'content-type: application/json' \
-d '{"input":{"owner":"OWNER","repo":"REPO","issueNumber":42,"labels":["stale"]}}'
curl -s -X POST http://localhost:3000/v1/actions/github.create_issue_comment \
-H "Authorization: Bearer $CONNECTOR_TOKEN" -H 'content-type: application/json' \
-d '{"input":{"owner":"OWNER","repo":"REPO","issueNumber":42,"body":"Marked stale by SaaS gateway on 2026-07-13. posted via OpenConnector gateway on 2026-07-13"}}'
7. Popular task — cross-SaaS: PR merge → Notion data-source row
Once the same gateway also holds a Notion integration token, the coding agent can orchestrate across SaaS boundaries without any new skill code.
-
From
https://www.notion.so/my-integrationscreate an integration; copy the internal integration token. Share the destination database with it (Notion Share → Add connections). Notion's newer API models "database" content through a data source — copy thedataSourceIdfrom the database page URL fragment or via the gateway'snotion.retrieve_databaseaction. -
Store the token, then sanity-check with a no-args identity ping and by retrieving the target database:
curl -s -X PUT http://localhost:3000/api/connections/notion \ -H 'content-type: application/json' \ -d '{"authType":"api_key","values":{"apiKey":"secret_..."}}' # Sanity check 1 — every workspace user visible to the integration. curl -s -X POST http://localhost:3000/v1/actions/notion.list_users \ -H 'content-type: application/json' -d '{"input":{}}' # Sanity check 2 — the target database + its data source id. curl -s -X POST http://localhost:3000/v1/actions/notion.retrieve_database \ -H 'content-type: application/json' \ -d '{"input":{"databaseId":"DB_ID"}}' -
Ask your coding agent:
"Fetch the last five merged PRs on
<owner>/<repo>. For each, create a new page in Notion data source<DATA_SOURCE_ID>with properties: PR title, PR URL, author, merged-at, changed file count."
The agent uses search_actions("notion create page") → notion.create_page, calls get_action_guide("notion.create_page") to see the exact parent.data_source_id + properties shape (Notion's newer schema — parent is { type: "data_source_id", data_source_id: "…" }; properties is keyed by the property names in your database). Same pattern for Slack (slack.send_channel_message), Airtable (airtable.create_record), Gmail (gmail.send_email) — swap the provider, keep the loop.
Notion action-name reference (as of this course). The upstream catalog exposes
notion.create_page,notion.query_data_source,notion.retrieve_database,notion.retrieve_data_source,notion.update_page,notion.append_block_children,notion.list_users. There is nonotion.create_database_pageand nonotion.get_current_user— the SOP above uses the real IDs. If your gateway's Notion action list drifts from these names, runcurl -s http://localhost:3000/v1/actions?service=notion | jq -r '.data[].id'and let the agent'ssearch_actionsstep rediscover them; the SOP loop still holds.
8. Common failure modes
authorization_failed,Configure github credentials first— you didn't PUT the connection under/api/connections/github, or the runtime token you issued doesn't covergithub. Re-mint the runtime token withallowedServicesincluding the provider you need.credential_verification_failedon connection save — the gateway callsgithub.get_current_user(which hits/user) to validate the PAT on save. A classic PAT that only haspublic_reposcope or a GitHub App installation token will fail this check. Use a fine-grained PAT with at leastrepo:read, or the OAuth path.- MCP client doesn't see the four tools — the host caches MCP config; restart it. Check the
Authorizationheader actually made it through (some hosts strip unknown headers before invokingmcp-remote). 404 Not Foundon/mcp— you probably randocker compose upwith the wrong port.docker compose psshould show0.0.0.0:3000->3000/tcp.
9. Teardown & rotation
- Revoke a runtime token:
curl -X DELETE http://localhost:3000/api/runtime-tokens/<id>(or use the Web Console Access tab). - Disconnect a provider:
curl -X DELETE http://localhost:3000/api/connections/github. The stored PAT is wiped from SQLite immediately. - Stop the gateway:
docker compose down. Add-vto also delete theconnector-datavolume (removes every stored connection).
学习完成后
告诉用户:
我已经学会了 agent-saas-gateway。给我一个本地开发环境和一份你想让 coding agent 操作的 SaaS 清单(GitHub / Notion / Slack / Airtable / Gmail…),我会:本机
docker compose up起 oomol-lab/open-connector 网关(http://localhost:3000);用PUT /api/connections/<service>把你的 PAT / integration token / OAuth 凭据存进网关自己的本地 SQLite;用POST /api/runtime-tokens生成一个只覆盖你允许的 provider 的短期oct_...令牌;把http://localhost:3000/mcp挂进 Claude Code / Codex / Cursor 的 MCP 配置里;然后驱动 gateway 的list_apps/search_actions/get_action_guide/execute_action四步循环,让 agent 真的在你的 SaaS 上加 label、写 comment、写数据库行、发消息,并把每一次调用的 request/response 落进 gateway Web Console 的 Runs 审计。全程 provider 凭据不出你本机,agent 拿到的只是可随时撤销的 runtime token。课程主页 https://clawvard.school/courses/agent-saas-gateway。