Dify 把能力切得很開——知識庫、工具、代理、應用各自獨立。概念很強,但沒有一份「我想做 X 就照著點」的手冊,連老手都會卡。這頁用情境式(case study)寫:每個情境給你「想達成什麼 → 官方步驟手順 → 截圖 → 會卡在哪」。照真實自架環境(Dify 1.14)整理。
搞懂這四塊各自是什麼、怎麼組合,後面所有情境都變簡單。
flowchart LR
subgraph APP["🖥️ 應用(App)= 對外的成品"]
direction TB
CHAT["聊天助手"]:::a
AGENT["Agent"]:::a
CF["Chatflow"]:::a
WF["Workflow"]:::a
end
MODEL["🧠 模型
雲端 / 本機"]:::m
KB["📚 知識庫 (RAG)
你的靜態文件"]:::k
TOOL["🔧 工具
內建 / 自訂API / MCP"]:::t
MODEL --> APP
KB -->|"當上下文"| APP
TOOL -->|"agent 可呼叫"| APP
APP -.->|"發佈成 MCP server"| OUT["🌐 別的 agent 來用"]:::o
classDef a fill:#dbeafe,stroke:#2563eb,color:#17335e
classDef m fill:#ede9fe,stroke:#7c3aed,color:#4c1d95
classDef k fill:#dcfce7,stroke:#16a34a,color:#14532d
classDef t fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
classDef o fill:#fef3c7,stroke:#d97706,color:#78350f
| 積木 | 是什麼 | 在 Dify 哪裡 |
|---|---|---|
| 🧠 模型 | agent 的腦。雲端(Groq/OpenAI…)或本機(Ollama) | 設定 → 模型供應商 |
| 📚 知識庫 (RAG) | 你上傳的靜態文件,給 app 當「上下文」查 | 頂上「知識庫」 |
| 🔧 工具 | agent 能呼叫的能力:內建、自訂(你的 API)、MCP(別人的) | 頂上「工具」(內建 / 自定義 / 工作流 / MCP 四個分頁) |
| 🖥️ 應用 (App) | 把上面組起來的成品:聊天 / Agent / Chatflow / Workflow | 頂上「工作室」 |
DuckDuckGo / WebScraper 之類搜尋工具。➡️ 深入:Dify RAG 雙庫架構


➡️ 深入:自訂工具 / MCP 架構

mcp-server(說明「make dify's workflow as a MCP server」)→ 安裝。/e/<id>/mcp(還有 /sse)。Agent Chat App does not support blocking mode——Agent 是多步 ReAct、只能串流,而外掛用 blocking 呼叫。想對外開 MCP,就把邏輯做成 Chatflow(能掛工具、能忠實呈現、又支援 blocking)。App Input Schema 要填一整個「MCP 工具定義」JSON(name / description / inputSchema),不是只填參數:
{
"name": "query_order",
"description": "查詢訂單狀態,回傳原文",
"inputSchema": {
"type": "object",
"properties": { "query": { "type": "string", "description": "使用者問題" } },
"required": ["query"]
}
}
https://你的網域/e/<id>/mcp)就對外通。實測跨機器成功:另一台機器上的 AI 用 MCP client 連進來 → initialize → tools/list → tools/call → 跑你的 Chatflow → 拿到結果。「別的 AI 把你的 Dify 當工具」整條跑通。
2024-11-05——最初 launch 那一版、最舊的。目前正式最新是 2025-11-25,另有 2026-07-28 RC(launch 以來最大改版,核心改成 stateless)。好消息:tools/list / tools/call 向後相容,基本串接照跑;但新版功能(結構化輸出、OAuth 流程、stateless)這台還沒有 → 要新特性得等 Dify/外掛升級,或改用跟得較前的 MCP server(n8n / FastMCP)。這是最常卡的:同樣一份「資料」,到底該做成知識庫、自訂工具、還是MCP?先看資料的性質,不是看它「是什麼檔」。
flowchart TD
Q{"這份資料的性質?"}:::q
Q -->|"靜態文件 / 知識 / 問答
(PDF、手冊、規章)"| RAG["📚 做成知識庫 (RAG)
掛到 app 上下文"]:::k
Q -->|"會變動 / 要即時查 / 能做動作
(訂單、庫存、DB)"| API{"有現成 API 嗎?"}:::d
API -->|"有"| CT["🔧 自訂工具
貼 OpenAPI schema"]:::t
API -->|"沒有,但想開放/跨平台"| MCP["🌐 自己寫 MCP server
包成工具"]:::o
Q -->|"就幾筆 / 一張小表"| SMALL["兩種都行:
塞知識庫最快 / 做小 API 較活"]:::s
classDef q fill:#ede9fe,stroke:#7c3aed,color:#4c1d95
classDef d fill:#dbeafe,stroke:#2563eb,color:#17335e
classDef k fill:#dcfce7,stroke:#16a34a,color:#14532d
classDef t fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
classDef o fill:#fef3c7,stroke:#d97706,color:#78350f
classDef s fill:#f1f5f9,stroke:#64748b,color:#334155
| 你手上的東西 | 建議做法 | 為什麼 |
|---|---|---|
| 一份文件 / 一本手冊 | 知識庫 (RAG) | 靜態、要語意查、附出處 |
| 一個資料庫 / ERP(有 API) | 自訂工具(OpenAPI) | 即時、會變、走現成介面 |
| 一個資料庫(沒 API) | 先包一層 API,再做自訂工具 / MCP | 不要讓 agent 直連 DB,走乾淨介面 |
| 一份 RAG,想開放給別的 agent | 用 chat/agent app 接知識庫 → 發佈成 MCP;或自己寫 MCP server | 對外變成「問就有答」的工具 |
| 就 10 筆資料 / 一張小表 | 塞知識庫最快;要常變就做小 API | 看會不會變 |
這是把 情境 2(RAG) 接上 情境 5(開 MCP),而且剛好全程用 Chat 型別——所以開得成 MCP(Agent 開不了)。完整六步:
mcp-server → 新增端點:App 選這個 Chat app、App Type = Chat、App Input Schema 填工具定義 JSON、設 Bearer Token → 拿到 MCP 網址。| 知識庫 (RAG) | 自訂工具 | MCP(client/server) | |
|---|---|---|---|
| 解決 | 查靜態文件 | 接你的活系統/API | 跨平台互通工具 |
| 資料 | 不常變 | 即時、會變 | 看裡面包什麼 |
| 方向 | app 內查 | app → 你的 API | client↔server 兩端 |
| Agent(模型自由總結) | Chatflow(忠實呈現) | |
|---|---|---|
| 特性 | 彈性、能對話 | 一字不差、流程固定 |
| 風險 | 總結那步可能改寫/幻覺 | 零改寫 |
| 適合 | 探索式、可容錯 | 錯不得的資料(財務/醫療/法遵) |
| 坑 | 怎麼回事 / 怎麼繞 |
|---|---|
| MCP 發佈按鈕找不到 | 不在「發佈」下拉、不在「功能」;只在 chat/agent app 的「概覽」頁。chatflow/workflow 沒有(UI 未接)。 |
| 工具 → MCP 分頁 | 那是「接別人的」(client),不是發佈你的。 |
| 內部系統被擋 (SSRF) | Dify 封鎖私有 IP → 放行信任位址,或走公開網域 443。 |
| token 過期 | 用系統的「不過期整合金鑰」。 |
| 工具成功但答案是假的 | 弱模型拿到真資料仍改寫 → 錯不得的資料改用 Chatflow 忠實呈現,別讓 Agent 自由總結。 |
| 功能面板 | 「功能」裡是開場白/語音/檔案上傳等體驗開關,沒有 MCP。 |

Dify 的 MCP 端點只有「一個靜態 Bearer token」,做不了 Azure AD(Entra ID)/ OIDC——而且它跑的是舊協定 2024-11-05,連 MCP 原生 OAuth 都沒有。所以企業級授權(AAD / SSO)一定要在 Dify 前面加一層 gateway,Dify 藏在後面、只吃內部 token。
flowchart LR
C(("🤖 MCP client
呼叫方")):::c
AAD[("🔑 Azure AD
Entra ID")]:::aad
C -.->|"先拿 token
client credentials"| AAD
C ==>|"① 帶 AAD token"| GW["🛡️ Gateway
驗 Entra JWT"]:::gw
GW -.->|"驗簽 / issuer / aud"| AAD
GW ==>|"② 換內部 token 轉發"| EP["🔌 Dify / n8n MCP 端點
私網 · 強 token"]:::ep
EP ==> SYS[("🗄️ 你的系統
RAG · API · DB")]:::sys
classDef c fill:#dbeafe,stroke:#2563eb,color:#17335e
classDef aad fill:#ede9fe,stroke:#7c3aed,color:#4c1d95
classDef gw fill:#fef3c7,stroke:#d97706,color:#78350f
classDef ep fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
classDef sys fill:#dcfce7,stroke:#16a34a,color:#14532d
呼叫方先跟 Entra 拿 token(服務對服務走 client credentials,不用真人登入)→ 帶進 gateway → gateway 驗簽 / issuer / audience → 驗過才換內部 token 轉給後面。AAD 這關永遠在 gateway,不在 Dify。
| 做法 | 適合 |
|---|---|
Azure API Management(validate-jwt 對你們 tenant 驗) | 企業標準;要 API 治理 / 配額 / log 一起 |
| oauth2-proxy + Entra OIDC | 輕量、自架、放 nginx 前面 |
| App Service / App Gateway 「Easy Auth」 | host 在 Azure 時,幾乎零程式 |
Authorization header 帶 AAD token;token 取得 / 刷新得你或 gateway 處理。各家 client 支援度不同,先驗。
flowchart LR
AG(("🤖 Agent
呼叫方")):::c
ENTRA[("🔑 Entra ID
Azure AD")]:::aad
AG ==>|"① AAD token"| GW["🛡️ Gateway
驗 Entra(對外門)"]:::gw
GW -.->|"驗 JWT"| ENTRA
GW ==>|"② 放行"| N8N["🔗 n8n
AAD-aware 編排層"]:::n8n
N8N -.->|"③ 用 Entra 憑證拿 token"| ENTRA
N8N ==>|"④ 帶 AAD token 打"| SYS[("🗄️ AAD 保護的內部系統")]:::sys
N8N ==>|"⑤ 內部 token · 免 AAD"| DIFY["🖥️ Dify
RAG / 能力提供"]:::dify
N8N ==>|"⑥ 匯總 → 回 Agent"| AG
classDef c fill:#dbeafe,stroke:#2563eb,color:#17335e
classDef aad fill:#ede9fe,stroke:#7c3aed,color:#4c1d95
classDef gw fill:#fef3c7,stroke:#d97706,color:#78350f
classDef n8n fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
classDef sys fill:#dcfce7,stroke:#16a34a,color:#14532d
classDef dify fill:#e0f2fe,stroke:#0284c7,color:#075985
① Agent 帶 AAD token → ② gateway 驗 Entra 放行 → n8n 進場 → ③ n8n 用 Entra 憑證(client credentials)拿 token → ④ 帶 token 打「AAD 保護的系統」→ ⑤ 另一手用內部 token 打 Dify(Dify 不吃 AAD) → ⑥ n8n 匯總回 Agent。
「某信箱能進哪隻 API / 哪個 RAG」這種細粒度授權——關鍵是把政策放一個地方(Entra roles/groups,或中央 PDP),由 PEP 執行;Dify 待在信任邊界後面、不做授權。別在每個 app 重做一套權限(那正是 AD 已經幫你做的事)。
flowchart LR
AG(("🤖 Agent
帶 AD token
身分 + roles")):::c
ENTRA[("🔑 Entra ID
身分 + App Roles/Groups")]:::aad
PDP["📜 PDP 中央授權政策
Entra roles / OPA"]:::pdp
PEP["🛡️ Gateway / n8n
PEP:驗身分 + 執行授權"]:::pep
AG ==>|"AD token"| PEP
PEP -.->|"authN 驗簽"| ENTRA
PEP -.->|"authZ:能碰 RAG-7 嗎?"| PDP
PDP -.-> ENTRA
PEP ==>|"✅ 放行 · 內部 token"| DIFY["🖥️ Dify / API
能力提供(信任邊界後)"]:::dify
DIFY ==> RES[("🗄️ 100 API · 30 RAG
各自資源")]:::res
classDef c fill:#dbeafe,stroke:#2563eb,color:#17335e
classDef aad fill:#ede9fe,stroke:#7c3aed,color:#4c1d95
classDef pdp fill:#fce7f3,stroke:#db2777,color:#831843
classDef pep fill:#fef3c7,stroke:#d97706,color:#78350f
classDef dify fill:#e0f2fe,stroke:#0284c7,color:#075985
classDef res fill:#dcfce7,stroke:#16a34a,color:#14532d
| 角色 | 誰 | 做什麼 |
|---|---|---|
| 身分 + 政策 | Entra ID | 證明你是誰 + App Roles/Groups(誰有哪些資源的權) |
| PDP(政策決策) | Entra roles / OPA | 回答「user X 能碰 RAG-7 嗎」——只養這一套 |
| PEP(政策執行) | Gateway / n8n | 驗身分 + 問 PDP + 放行 / 擋 |
| 能力提供 | Dify / API | 信任邊界後,只吃內部 token,不做授權 |
把上面全部收斂成「大企業 × 分級」場景的定案骨架:敏感內容分知識庫、每庫一個 Dify app(各自 API key)、n8n 驗身分後依 level 挑 key 打(內部走 REST API,Dify 不用做 MCP);對外要標準化工具才由 n8n 出 MCP。
flowchart LR
EXT(("🤖 外部
AI client / 系統")):::c
ENTRA[("🔑 Entra ID")]:::aad
N8N["🛡️🔗 Gateway / n8n
驗身分 + PEP 依 level 路由
對外:MCP 或 API"]:::n8n
EXT ==>|"AD token"| N8N
N8N -.->|"authN + authZ"| ENTRA
N8N ==>|"key① 一般 · REST API"| A1["🖥️ Dify app 一般"]:::d
N8N ==>|"key② 經理"| A2["🖥️ Dify app 經理"]:::d
N8N ==>|"key③ 處長+"| A3["🖥️ Dify app 機敏"]:::d
A1 ==> K1[("📚 KB-一般")]:::k
A2 ==> K2[("📚 KB-經理")]:::k
A3 ==> K3[("📚 KB-財務/薪資")]:::kr
classDef c fill:#dbeafe,stroke:#2563eb,color:#17335e
classDef aad fill:#ede9fe,stroke:#7c3aed,color:#4c1d95
classDef n8n fill:#fef3c7,stroke:#d97706,color:#78350f
classDef d fill:#e0f2fe,stroke:#0284c7,color:#075985
classDef k fill:#dcfce7,stroke:#16a34a,color:#14532d
classDef kr fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
| 原則 | 做法 |
|---|---|
| MCP 只在最外層 | 對 AI client 才由 n8n 出 MCP;對一般系統出 API 即可 |
| 內部一律 REST API | n8n 用各 app 的 API key 打 Dify /v1/chat-messages,Dify 免裝 mcp-server |
| per-level = 分庫分 app | 敏感(財務/薪資)物理分知識庫;n8n 依授權挑對應那把 key |
| Dify 不做授權 | 信任邊界後,只提供「該庫的 RAG」能力 |
agent 直連 n8n、n8n 自己驗 AAD(不用 gateway)。⬇️ 下載可匯入的 n8n workflow(JSON) → n8n 右上「⋯ → Import from File」。五個節點、一條線:
| # | 節點 | 做什麼 |
|---|---|---|
| 1 | Webhook(agent 直連入口) | agent 帶 Authorization: Bearer 〔AAD token〕 + {query} POST 進來 |
| 2 | 取 Entra JWKS | 抓 Entra 公鑰(驗簽用) |
| 3 | Code(PEP · 自驗) | n8n 自己驗 JWT 簽章 + exp/aud → 讀 roles → 決定 level → 挑對應 Dify key |
| 4 | HTTP Request | 用挑到的 key 打 Dify /v1/chat-messages(內部 REST,不走 MCP) |
| 5 | Respond | 回傳答案給 agent |
走向一條直線:Webhook → 取 JWKS → Code(自驗+路由)→ HTTP(打 Dify)→ Respond。唯一不可免的「外部」是 Entra 本身(拿公鑰驗 token),中間那層 gateway 拿掉了。核心在第 3 個 Code 節點(驗簽 → roles → level → key):
const roles = payload.roles || []; // Entra App Roles
let level='general', keyEnv='DIFY_KEY_GENERAL';
if (roles.includes('RAG.Sensitive')) { level='sensitive'; keyEnv='DIFY_KEY_SENSITIVE'; }
else if (roles.includes('RAG.Manager')) { level='manager'; keyEnv='DIFY_KEY_MANAGER'; }
// → 用 $env[keyEnv] 那把 key 打對應的 Dify app(分級的知識庫)
n8n 要設 env:NODE_FUNCTION_ALLOW_BUILTIN=crypto(讓 Code 能用 crypto 驗簽)、ENTRA_JWKS_URL(https://login.microsoftonline.com/〔tenant〕/discovery/v2.0/keys)、EXPECTED_AUD、DIFY_BASE、DIFY_KEY_GENERAL / MANAGER / SENSITIVE。正式環境記得快取 JWKS,別每次都打 Entra。
RAG.Sensitive)授權給該 agent。agent 拿到的 token 就帶 roles claim,n8n 讀它路由。要你跟 Entra 申請的是:tenant、App 註冊、client secret、App Roles——不是 redirect URI。同一個 n8n,兩種入口都能接——不是二選一。分水嶺是人登入(互動)還是機器 / service(M2M)。共通點:身分永遠從 Entra 發(n8n 生不出來);差別在「token 怎麼到手」+「拿到後怎麼授權」:人 → per-person 分級路由;機器 → 取被授權的固定那份(不分級)。
flowchart LR
U(("👤 人 / 員工")):::u
M(("🤖 機器 / service")):::m
ENTRA[("🔑 Entra")]:::aad
N8N["🔗 同一個 n8n
兩種入口都接"]:::n8n
U ==>|"/login → auth-code(拿員工身分)"| N8N
M ==>|"M2M token(app 身分)"| N8N
N8N -.->|"驗身分"| ENTRA
N8N ==>|"人:per-person 分級路由"| D1["🖥️ 依 level 的 Dify app"]:::d
N8N ==>|"機器:取被授權的固定那份"| D2["🖥️ 該 service 的 Dify app(固定)"]:::d
classDef u fill:#dbeafe,stroke:#2563eb,color:#17335e
classDef m fill:#e0e7ff,stroke:#4338ca,color:#312e81
classDef aad fill:#ede9fe,stroke:#7c3aed,color:#4c1d95
classDef n8n fill:#fef3c7,stroke:#d97706,color:#78350f
classDef d fill:#e0f2fe,stroke:#0284c7,color:#075985
情境一|人登入(1 萬員工多半是這個)—— n8n 當網頁節點,auth-code + redirect URI:員工被導去 Entra 登入,登入完 Entra 把 code callback 回 n8n 的 webhook(= redirect URI),n8n 用 code 換到「這個員工」的 token(帶 roles)→ 依 level 分流。拿到員工身分 → 做得到 per-level;這裡就要 redirect URI。 ⬇️ 這條的 n8n sample(auth-code 人登入版,8 節點:/login → 導去 Entra → /callback 換 token → 取 JWKS → 驗簽(RS256 + exp/nbf/iss/aud)→ 分流) — 這版一樣真驗簽,自己捏的 token 過不了。
flowchart LR
U(("👤 員工")):::u
ENTRA[("🔑 Entra 登入")]:::aad
WH["🔗 n8n webhook
= redirect URI"]:::n8n
PEP["🛡️ n8n:驗 token + 依 roles 分流"]:::pep
DIFY["🖥️ 對應的 Dify app(分級)"]:::d
U ==>|"① 開入口"| WH
WH ==>|"② 導去 Entra 登入"| ENTRA
ENTRA ==>|"③ callback code 回 n8n"| WH
WH ==>|"④ 用 code 換 token(HTTP)"| ENTRA
WH ==>|"⑤ 拿到『員工』token → 驗 → roles"| PEP
PEP ==> DIFY
classDef u fill:#dbeafe,stroke:#2563eb,color:#17335e
classDef aad fill:#ede9fe,stroke:#7c3aed,color:#4c1d95
classDef n8n fill:#fef3c7,stroke:#d97706,color:#78350f
classDef pep fill:#fde68a,stroke:#b45309,color:#78350f
classDef d fill:#e0f2fe,stroke:#0284c7,color:#075985
情境二|機器 agent(M2M)—— client credentials / Managed Identity,無 redirect:service / 系統自己跟 Entra 拿 token(或更好:Managed Identity / Entra Agent ID 平台自動發、免管 secret)。關鍵觀念:M2M 沒有「人」的身分,只有「app / service 身分」——它的授權是「這個 service 被授權讀哪一份資料」,通常是一份固定範圍,不做 per-person 分級路由。所以流程更單純:驗這個 service 的 token → 確認它有那份資料的權 → 打它被授權的那個 Dify app(固定那份)。不用 redirect URI。 ⬇️ 這條的 n8n sample(機器自驗 AAD;其中 role→key 代表「這個 service 被授權的那份 KB」,對單一 service 是固定的)
flowchart LR
A(("🤖 機器 / service
app 身分,非人")):::m
ENTRA[("🔑 Entra token 端點")]:::aad
PEP["🛡️ n8n:驗 service token
+ 確認資料權(非分級)"]:::pep
DIFY["🖥️ 它被授權的
那份 Dify app / KB(固定)"]:::d
A ==>|"① client credentials /
Managed Identity 拿 token"| ENTRA
ENTRA ==>|"token(app roles = 資料範圍)· 無 redirect"| A
A ==>|"② 帶 service token 打 n8n"| PEP
PEP ==> DIFY
classDef m fill:#dbeafe,stroke:#2563eb,color:#17335e
classDef aad fill:#ede9fe,stroke:#7c3aed,color:#4c1d95
classDef pep fill:#fde68a,stroke:#b45309,color:#78350f
classDef d fill:#e0f2fe,stroke:#0284c7,color:#075985
/login(auth-code)→ 員工身分 → per-person 分級路由;機器 / 自動流程走 M2M token → 取它被授權的固定那份(不分級)。同一台 n8n 同時有這兩條入口,依「是人還是 service」走不同授權。只是別把 M2M 當成一個人去分級——M2M 沒有人的身分,只有 app 身分 + 資料範圍。上面的 sample 光貼在頁面上,說服力停在「看起來對」。所以另外做了一個跑得起來的參考實作——docker compose up 之後跑測試,親眼看到它擋掉偽造 token、依職級路由到不同知識庫。
解開後:docker compose up -d --build → docker compose run --rm tests。裡面的兩支 workflow 就是上面可下載的同一份(只多了 active 供 CLI 匯入)。為什麼偽造 token 過不了:mock IdP 的私鑰只有它自己有,JWKS 只公布公鑰;n8n 用公鑰 crypto.verify(RS256) 驗簽,沒私鑰就簽不出有效簽章。
📚 回課綱總表 →