Composio MCP 實戰 2026:用一把 ck_ 金鑰自動發文到 Facebook 粉專(含 6 組除錯對照表與完整可執行程式碼)

Composio 官方文件沒告訴你的實戰細節:兩種 MCP 端點用不同金鑰(ck_ 走 Connect、ak_ 走 Platform),混用會得到看起來像「金鑰失效」的 401。本文以「自動發文到 Facebook 粉專」為例,完整拆解 Connect MCP 的四步握手、11 個 meta-tool、粉專五步流程(列粉專 → 驗證 website → 發文 → 讀回確認),並附上 6 組實際踩到的錯誤與解法對照表。所有內容均為本機實測驗證,含可直接執行的 Python 程式碼。

  • Dennis
  • 9 分鐘閱讀
Composio MCP 實戰 2026:用一把 ck_ 金鑰自動發文到 Facebook 粉專(含 6 組除錯對照表與完整可執行程式碼)

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/mcphttps://backend.composio.dev/v3/mcp/{SERVER_ID}
認證 headerx-consumer-api-keyx-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

三個結論

  1. ck_ 金鑰兩種 header 形式都通x-consumer-api-keyAuthorization: Bearer 皆可)
  2. ak_ 在 Connect 端點完全無效——連錯誤訊息都說它「not a valid AuthKit JWT」
  3. 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: <由第①步的回應標頭取得>

⚠️ 兩個實作細節常被忽略

  1. MCP 端點只接受 POST——送 GET 會回 405 method_not_allowed
  2. 回應是 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_SKILLSEARCH_SKILLSUSE_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"}]})

回應會給出 statusactive / initiated / failed

⚠️ initiated 不能用——代表 OAuth 授權沒走完,要請使用者重新授權。這是排查「明明連了卻不能用」的第一站。

Step 2:列出所有粉專(取得 page_id)

exec_tool("FACEBOOK_GET_USER_PAGES", {})

回應包含每個粉專的 idnametasks(權限清單)。

💡 tasks 要包含 CREATE_CONTENT 才能發文。缺這個權限會失敗。

Step 3:確認粉專正確性(強烈建議)

這一步是多品牌帳號的救命步驟。 一個 Facebook 帳號常同時管理多個品牌粉專,發錯會非常尷尬。

FACEBOOK_GET_PAGE_DETAILSwebsite 欄位來比對:

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:imageog: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 組實際踩到的錯

這是本文最有價值的部分——以下全部是實際發生的錯誤與真正原因

#錯誤訊息真正原因解法
1401 Authorization required金鑰放錯 header(ak_ 放進 x-consumer-api-key,或反之)確認 ck_x-consumer-api-key 配對
2404 on /tool_router/{key}/mcp該路徑形狀不支援;組織也還沒建 MCP Server改用 https://connect.composio.dev/mcp
3405 method_not_allowed對 MCP 端點發了 GETMCP 只接受 POST
4426 NONEXISTENT_VERSIONak_ 走 REST 打到已停用的平台 API 版本改走 Connect MCP(ck_
5(#10) requires pages_read_engagement讀取「單篇貼文」權限不足不影響發文;改用 FACEBOOK_GET_PAGE_POSTS 驗證
6Validation 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

⚠️ 兩點提醒:

  1. 較舊版本的 hermes mcp add 沒有 --header 參數,需直接編輯 config.yaml
  2. 加完後必須重啟 gateway,工具才會在對話中生效

(MCP 本身的完整觀念可看 MCP 完整教學 2026。)


FAQ

Composio 的 ck_ 和 ak_ 金鑰差在哪?

ck_consumer key,用在 Composio Connect 端點(connect.composio.dev/mcp),可存取全部已連接的 App;ak_platform key,用在 Platform MCPbackend.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_POSTlink 參數,Facebook 會自動抓該網址的 OG metaog:imageog: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 變動。但實際動手時,真正會卡住你的通常不是工具本身,而是這幾件事:

  1. 用對端點與金鑰——ck_ 走 Connect、ak_ 走 Platform,不能混
  2. 認清 HTTP 200 ≠ 成功——看 successful 欄位
  3. 多品牌帳號先驗證目標——用 website 欄位比對,別發錯粉專
  4. 接受平台限制——Facebook 不能發個人主頁,這是 2018 年就定下的規則

把這四點處理好,剩下的就是照著上面的五步流程走。整條路徑已在本機完整驗證,包含 Facebook 粉專、LinkedIn 個人檔案與 Instagram 三平台的自動發文。


本文所有數據與錯誤訊息均為 2026-09-19 於本機實測取得,非文件推測。文中 page_id 與金鑰均以佔位符呈現。

📬 訂閱 most.tw 電子報

每週精選 AI 工具教學與技術乾貨,直接送到你的信箱。免費、隨時可退訂。

💬 有問題想討論?加 LINE 聯絡我

歡迎透過 LINE 官方帳號直接留言,我會盡快回覆你的問題。

加入 LINE 好友