Composio 是 AI Agent 的工具整合層——串接 1,500+ 個 App,讓 Agent 不必自己寫每個 SaaS 的 API。但它的官方文件有一個很少人講清楚的關鍵:它有兩種 MCP 端點,用不同的金鑰,混用會得到一個看起來像「金鑰被撤銷」的 401。
本文用「自動發文到 Facebook 粉專」當實例,把整條路走一遍。所有內容都是本機實測的結果,包含官方文件沒寫的錯誤訊息與解法。
ℹ️ 如果你還不熟 Composio 是什麼、它的架構與 CLI 怎麼用,建議先看站上的 Composio 2026 完整指南(概念篇)。本文是動手實戰篇,兩篇互補。
⚠️ 先講最重要的一件事:兩種端點、兩把金鑰
這是整篇文章最容易卡住的地方。Composio 有兩種 MCP 端點,長得很像但完全不能混用:
| Composio Connect(推薦 ✅) | Platform MCP | |
|---|---|---|
| 端點 | https://connect.composio.dev/mcp | https://backend.composio.dev/v3/mcp/{SERVER_ID} |
| 認證 header | x-consumer-api-key | x-api-key |
| 金鑰格式 | ck_...(consumer key) | ak_...(platform key) |
| 需要先建 MCP Server | 不用 ✅ | 要(要先取得 SERVER_ID) |
| 可存取的連接範圍 | 全部已連接的 App | 部分(依 Server 設定) |
實測:兩把金鑰看到的東西不一樣
同一組帳號,兩把金鑰查出來的連接數量不同:
ak_ → REST /api/v3/connected_accounts → 2 個連接
ck_ → MCP COMPOSIO_MANAGE_CONNECTIONS → 6 個連接
要存取 Facebook,非用 ck_ 不可。
四組 header × 金鑰組合實測結果
| 送出的 header | 金鑰 | 結果 |
|---|---|---|
x-consumer-api-key: ck_... | ck_ | ✅ 200 成功 |
Authorization: Bearer ck_... | ck_ | ✅ 200 成功 |
x-api-key: ck_... | ck_ | ❌ 401 — No Authorization: Bearer header on request |
x-consumer-api-key: ak_... | ak_ | ❌ 401 — Bearer token rejected: not a valid AuthKit JWT |
三個結論:
ck_金鑰兩種 header 形式都通(x-consumer-api-key與Authorization: Bearer皆可)ak_在 Connect 端點完全無效——連錯誤訊息都說它「not a valid AuthKit JWT」x-api-key這個 header 名稱 Connect 端點不認(那是 Platform MCP 在用的)
🔴 陷阱:把
mcp.composio.dev當端點。大量教學文還在寫https://mcp.composio.dev/composio/server/{ID},該主機已不提供 MCP 服務(會導向行銷頁),失敗訊息長得像認證問題而不是端點失效,非常容易誤導。
🔌 MCP 連線:四步握手
Composio Connect 走 Streamable HTTP transport,必須照順序完成四件事:
① POST initialize → Mcp-Session-Id 在「回應標頭」裡(不是 body!)
② POST notifications/initialized
③ POST tools/list → 取得 meta-tool 清單
④ POST tools/call → 實際執行
必要 HTTP 標頭(②③④ 都要帶第①步拿到的 session id):
Content-Type: application/json
Accept: application/json, text/event-stream
x-consumer-api-key: ck_xxxxxxxx
Mcp-Session-Id: <由第①步的回應標頭取得>
⚠️ 兩個實作細節常被忽略:
- MCP 端點只接受 POST——送 GET 會回
405 method_not_allowed- 回應是 SSE 格式(
data: {...}),不能直接json.loads(整個 body),要先抓data:開頭的行
🧰 11 個 Meta-Tool:Connect MCP 的全部工具
Connect MCP 不會把 1,500 個 App 的工具全部塞給你,而是給你 11 個「元工具」,讓你搜尋、授權、執行:
| 工具 | 用途 |
|---|---|
COMPOSIO_SEARCH_TOOLS | 用自然語言找工具(會回傳建議執行計畫+已知陷阱,非常好用) |
COMPOSIO_GET_TOOL_SCHEMAS | 取得特定工具的完整參數定義 |
COMPOSIO_MULTI_EXECUTE_TOOL | 實際執行工具(一次最多 50 個,可平行) |
COMPOSIO_MANAGE_CONNECTIONS | 列出/新增/改名/移除 App 連接 |
COMPOSIO_WAIT_FOR_CONNECTIONS | 等使用者完成 OAuth 授權 |
COMPOSIO_REMOTE_BASH_TOOL | 在遠端沙箱執行 bash |
COMPOSIO_REMOTE_WORKBENCH | 在遠端沙箱執行 Python(適合處理大量資料) |
COMPOSIO_SUBMIT_FEEDBACK | 回報工具執行結果有誤 |
COMPOSIO_MANAGE_SKILL/SEARCH_SKILLS/USE_SKILL | 組織內的可重用 Skill |
最關鍵的觀念:發 Facebook 貼文時,你不是直接呼叫「Facebook 工具」,而是:
呼叫 COMPOSIO_MULTI_EXECUTE_TOOL
└─ 參數裡指定 tool_slug = "FACEBOOK_CREATE_POST"
└─ arguments 放 page_id / message / link
理解這層代理關係,後面就不會迷路。
📘 實戰:自動發文到 Facebook 粉專(五步)
Step 1 確認 Facebook 連接狀態 COMPOSIO_MANAGE_CONNECTIONS (action=list)
↓
Step 2 列出該帳號管理的所有粉專 FACEBOOK_GET_USER_PAGES
↓
Step 3 確認「哪個粉專才是對的」 FACEBOOK_GET_PAGE_DETAILS(比對 website)
↓
Step 4 發文 FACEBOOK_CREATE_POST
↓
Step 5 讀回驗證 FACEBOOK_GET_PAGE_POSTS
Step 1:確認連接狀態
call_meta("COMPOSIO_MANAGE_CONNECTIONS",
{"toolkits": [{"name": "facebook", "action": "list"}]})
回應會給出 status:active / initiated / failed。
⚠️
initiated不能用——代表 OAuth 授權沒走完,要請使用者重新授權。這是排查「明明連了卻不能用」的第一站。
Step 2:列出所有粉專(取得 page_id)
exec_tool("FACEBOOK_GET_USER_PAGES", {})
回應包含每個粉專的 id、name、tasks(權限清單)。
💡
tasks要包含CREATE_CONTENT才能發文。缺這個權限會失敗。
Step 3:確認粉專正確性(強烈建議)
這一步是多品牌帳號的救命步驟。 一個 Facebook 帳號常同時管理多個品牌粉專,發錯會非常尷尬。
用 FACEBOOK_GET_PAGE_DETAILS 查 website 欄位來比對:
exec_tool("FACEBOOK_GET_PAGE_DETAILS", {"page_id": "<你的 page_id>"})
實測回應長這樣:
{
"category": "Personal blog",
"name": "你的粉專名稱",
"id": "123456789012345",
"website": "https://your-site.com/" ← 決定性證據
}
如果沒有 website 欄位可判斷,退而求其次用 fan_count(粉絲數)與 category 推斷。
Step 4:發文
exec_tool("FACEBOOK_CREATE_POST", {
"page_id": "<page_id>",
"message": "貼文文字內容",
"link": "https://your-site.com/article/",
"published": True,
})
成功回應:
{"data":{"results":[{"response":{"successful":true,
"data":{"id":"<page_id>_<post_id>"}},"index":0}],
"success_count":1,"error_count":0,"successful":true}}
💡
link是彩蛋:Facebook 會自動抓取該 URL 的og:image與og:description,生成圖文連結預覽。實測確認只要網站的 OG meta 正確,貼文就會自動帶封面圖,不必另外上傳。
Step 5:讀回驗證
發布成功不等於顯示正確。用 FACEBOOK_GET_PAGE_POSTS 讀回確認:
exec_tool("FACEBOOK_GET_PAGE_POSTS", {"page_id": "<page_id>", "limit": 3})
回應中的 attachments.data[].media.image.src 會是 Facebook 快取的圖片網址,可確認連結預覽與封面圖都正確渲染。
🔥 除錯對照表:6 組實際踩到的錯
這是本文最有價值的部分——以下全部是實際發生的錯誤與真正原因:
| # | 錯誤訊息 | 真正原因 | 解法 |
|---|---|---|---|
| 1 | 401 Authorization required | 金鑰放錯 header(ak_ 放進 x-consumer-api-key,或反之) | 確認 ck_ ↔ x-consumer-api-key 配對 |
| 2 | 404 on /tool_router/{key}/mcp | 該路徑形狀不支援;組織也還沒建 MCP Server | 改用 https://connect.composio.dev/mcp |
| 3 | 405 method_not_allowed | 對 MCP 端點發了 GET | MCP 只接受 POST |
| 4 | 426 NONEXISTENT_VERSION | 用 ak_ 走 REST 打到已停用的平台 API 版本 | 改走 Connect MCP(ck_) |
| 5 | (#10) requires pages_read_engagement | 讀取「單篇貼文」權限不足 | 不影響發文;改用 FACEBOOK_GET_PAGE_POSTS 驗證 |
| 6 | Validation error: toolkit: Expected object | 參數型別錯(傳字串) | 用 {"toolkit": {"slug": "facebook"}} |
🚨 最危險的誤判:HTTP 200 不代表成功
Composio 的 execute API 一律回 HTTP 200,成功與否藏在 JSON 的 successful 欄位:
{"successful": false, "error": "{\"status\":426,...}", "status_code": 426}
只檢查 HTTP 狀態碼,會把失敗當成功。 判斷邏輯應該長這樣:
ok = ('"successful":true' in txt.replace(" ", "")
and '"error_count":0' in txt.replace(" ", ""))
💻 完整可執行程式碼
以下為可直接執行的 Python(僅用標準庫,不需額外套件):
import json, urllib.request, urllib.error
CK = "ck_xxxxxxxxxxxx" # consumer key(不是 ak_!)
MCP_URL = "https://connect.composio.dev/mcp"
PAGE_ID = "123456789012345" # 你的粉專 ID
_session = {"id": None}
def _rpc(payload):
h = {"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
"x-consumer-api-key": CK}
if _session["id"]:
h["Mcp-Session-Id"] = _session["id"]
req = urllib.request.Request(MCP_URL, data=json.dumps(payload).encode(),
headers=h, method="POST")
try:
with urllib.request.urlopen(req, timeout=180) as r:
return r.status, dict(r.headers), r.read().decode("utf-8", "ignore")
except urllib.error.HTTPError as e:
return e.code, dict(e.headers), e.read().decode("utf-8", "ignore")
def _parse(body):
"""回應是 SSE 格式,必須抓 data: 行"""
out = []
for line in body.splitlines():
if line.startswith("data:"):
try: out.append(json.loads(line[5:].strip()))
except Exception: pass
if not out:
try: out.append(json.loads(body))
except Exception: pass
return out
def connect():
"""四步握手(每次程序啟動做一次)"""
st, hdrs, _ = _rpc({"jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": {"protocolVersion": "2024-11-05", "capabilities": {},
"clientInfo": {"name": "my-agent", "version": "1.0"}}})
_session["id"] = hdrs.get("Mcp-Session-Id") or hdrs.get("mcp-session-id")
_rpc({"jsonrpc": "2.0", "method": "notifications/initialized"})
def _collect(body):
txt = ""
for m in _parse(body):
if m.get("error"):
txt += "ERROR " + json.dumps(m["error"], ensure_ascii=False)
r = m.get("result")
if r:
for c in r.get("content", []):
txt += c.get("text", "")
return txt
def call_meta(tool_name, arguments):
"""呼叫 meta-tool(如 COMPOSIO_MANAGE_CONNECTIONS)"""
_, _, body = _rpc({"jsonrpc": "2.0", "id": 2, "method": "tools/call",
"params": {"name": tool_name, "arguments": arguments}})
return _collect(body)
def exec_tool(tool_slug, arguments, rid=10):
"""執行任一 App 工具(經 MULTI_EXECUTE_TOOL 代理)"""
_, _, body = _rpc({"jsonrpc": "2.0", "id": rid, "method": "tools/call",
"params": {"name": "COMPOSIO_MULTI_EXECUTE_TOOL",
"arguments": {"tools": [{"tool_slug": tool_slug,
"arguments": arguments}]}}})
txt = _collect(body)
ok = ('"successful":true' in txt.replace(" ", "")
and '"error_count":0' in txt.replace(" ", ""))
return ok, txt
# ---------------- 實際使用 ----------------
if __name__ == "__main__":
connect()
print("連接狀態:", call_meta("COMPOSIO_MANAGE_CONNECTIONS",
{"toolkits": [{"name": "facebook", "action": "list"}]})[:300])
ok, pages = exec_tool("FACEBOOK_GET_USER_PAGES", {})
print("粉專清單 ok =", ok)
ok, res = exec_tool("FACEBOOK_CREATE_POST", {
"page_id": PAGE_ID,
"message": "貼文內容",
"link": "https://example.com/article/",
"published": True,
}, rid=20)
print("發文 ok =", ok)
print(res[:300])
❓ 為什麼 Facebook 不能發到「個人主頁」?
這是很常見的疑問,答案是不行,而且是 Facebook 官方關掉的。
Facebook 於 2018-04-24 廢除了 publish_actions 權限——該權限原本允許 App 以使用者身分發文到個人動態。之後打舊端點會直接回:
{"error":{"message":"(#200) This endpoint is deprecated since the
required permission publish_actions is deprecated","type":"OAuthException"}}
目前只剩兩條路:
| 方式 | 可自動化? |
|---|---|
發到粉絲專頁(publish_pages 權限) | ✅ 可以 |
| Share Dialog(分享對話框) | ❌ 需要使用者手動點擊 |
在 Composio 的 41 個 Facebook 工具裡,沒有任何一個支援個人主頁——這不是 Composio 的限制,是 Facebook 不給。
📌 有趣的對比:LinkedIn 發個人檔案沒問題,Facebook 卻不行。這是兩家平台政策差異,不是技術問題。
🔐 安全注意事項
| 項目 | 建議做法 |
|---|---|
| 金鑰存放 | 放環境變數檔並 chmod 600,不要寫進程式碼或 git |
| 權限範圍 | ck_ 可見全部連接的 App(可能含 Gmail、其他 SaaS)→ 洩漏影響面大,務必保管 |
| header 命名 | 明確用 x-consumer-api-key,不要混用 x-api-key |
| 發文前確認 | 多品牌帳號務必先跑 FACEBOOK_GET_PAGE_DETAILS 核對 website |
| 成功判斷 | 檢查 JSON 的 successful,不要只看 HTTP 200 |
🧩 掛進 Hermes Agent(可選)
若用 Hermes Agent,可直接把 Composio 註冊成 MCP server:
mcp_servers:
composio:
url: https://connect.composio.dev/mcp
headers:
x-consumer-api-key: "ck_xxxxxxxx"
timeout: 180
connect_timeout: 60
或用互動指令:
hermes mcp add composio --url https://connect.composio.dev/mcp --auth header
⚠️ 兩點提醒:
- 較舊版本的
hermes mcp add沒有--header參數,需直接編輯config.yaml- 加完後必須重啟 gateway,工具才會在對話中生效
(MCP 本身的完整觀念可看 MCP 完整教學 2026。)
FAQ
Composio 的 ck_ 和 ak_ 金鑰差在哪?
ck_ 是 consumer key,用在 Composio Connect 端點(connect.composio.dev/mcp),可存取全部已連接的 App;ak_ 是 platform key,用在 Platform MCP 或 backend.composio.dev/api/v3/* REST。兩者不能互換,放錯 header 會得到 401。
為什麼用 Composio 發 LinkedIn 會回 426?
實測發現:用 ak_ 走 REST 打到 LinkedIn 的 /rest/posts 會回 426 NONEXISTENT_VERSION(API 版本 20241101 已停用)。改用 ck_ 走 Connect MCP 就正常。
Facebook 貼文的封面圖要自己上傳嗎?
不用。只要在 FACEBOOK_CREATE_POST 帶 link 參數,Facebook 會自動抓該網址的 OG meta(og:image、og:description)生成圖文預覽。前提是你的網頁有正確設定 OG 標籤。
為什麼 Composio API 回 HTTP 200 但其實失敗了?
這是它的設計:HTTP 200 代表「請求已受理」,成功與否在回應 JSON 的 successful 欄位。務必檢查該欄位,否則會把失敗當成功。
一個帳號管理多個粉專,怎麼確保發對?
跑 FACEBOOK_GET_PAGE_DETAILS 查每個粉專的 website 欄位比對。實測中這個方法成功從多個粉專中辨識出正確的那一個。
MCP 端點可以用 GET 嗎?
不行。實測回 405 method_not_allowed,並提示「This MCP endpoint accepts POST requests」。所有 MCP 請求都必須是 POST。
結論
Composio 的價值在於把 1,500+ 個 App 的整合與 OAuth 都收斂成一層,讓 Agent 不必自己面對每個 SaaS 的 API 變動。但實際動手時,真正會卡住你的通常不是工具本身,而是這幾件事:
- 用對端點與金鑰——
ck_走 Connect、ak_走 Platform,不能混 - 認清 HTTP 200 ≠ 成功——看
successful欄位 - 多品牌帳號先驗證目標——用
website欄位比對,別發錯粉專 - 接受平台限制——Facebook 不能發個人主頁,這是 2018 年就定下的規則
把這四點處理好,剩下的就是照著上面的五步流程走。整條路徑已在本機完整驗證,包含 Facebook 粉專、LinkedIn 個人檔案與 Instagram 三平台的自動發文。
本文所有數據與錯誤訊息均為 2026-09-19 於本機實測取得,非文件推測。文中 page_id 與金鑰均以佔位符呈現。
