前言:Agent 的"最后一公里"问题

在上一篇文章中,我们深入拆解了 Dify 工作流的核心节点,并实战构建了一个数据分析 Agent。这个 Agent 可以理解意图、调用工具、生成报告——但它的能力边界,仍然局限于 Dify 平台内部预置的工具集。

现实的企业环境远比这复杂:你的数据在 MySQL,你的文件在 Confluence,你的任务在 Jira,你的消息在钉钉……Agent 想要真正"跨系统操作",就必须能够与这些异构系统无缝对话。

这正是 MCP(Model Context Protocol,模型上下文协议) 诞生的原因。

本文将系统介绍 MCP 协议的设计理念与工作机制,并详细讲解如何在 Dify 中集成 MCP,让你的 Agent 真正具备跨系统操作能力。


一、MCP 协议:AI 世界的"USB 标准"

1.1 MCP 是什么?

MCP(Model Context Protocol)是由 Anthropic 于 2024 年底发布的开放协议,旨在为 AI 模型与外部系统之间建立标准化的通信接口。

一个形象的类比:在 USB 标准出现之前,每个设备厂商都有自己的接口标准,鼠标、键盘、U 盘各用各的插口,极其混乱。USB 的出现统一了接口规范,任何符合标准的设备都能即插即用。

MCP 之于 AI Agent,正如 USB 之于硬件外设:

  • 统一协议:无论是数据库、文件系统还是 SaaS 服务,都通过同一套 MCP 接口暴露给 Agent
  • 即插即用:新增一个 MCP Server,Agent 立即获得操作该系统的能力
  • 安全隔离:Agent 通过 MCP 协议间接操作系统,不直接持有系统凭证

1.2 MCP 的核心架构

MCP 采用 Client-Server 架构,由三个核心角色组成:

角色 职责 对应实体
MCP Host 发起请求的 AI 应用主体 Dify、Claude Desktop、Cursor
MCP Client 内嵌于 Host,管理与 Server 的连接 Dify 内置的 MCP 客户端
MCP Server 暴露工具/资源的外部系统适配器 MySQL Server、GitHub Server、Slack Server

完整的调用链路如下:

用户 → Dify (MCP Host)
           ↓
       MCP Client
           ↓ [MCP 协议通信]
       MCP Server (如 GitHub MCP Server)
           ↓
       GitHub API
           ↓
       返回结果 → MCP Client → Dify → 用户

1.3 MCP 提供的三类能力

MCP Server 可以向 Agent 暴露三类原语(Primitives):

① Tools(工具)

Agent 可以调用的函数,类似 Function Calling。例如:

  • create_issue:在 GitHub 创建 Issue
  • send_message:向钉钉群发送消息
  • query_database:执行 SQL 查询
② Resources(资源)

Agent 可以读取的数据源,类似文件或 URL。例如:

  • Git 仓库中的文件内容
  • 数据库中的表结构信息
  • Confluence 文档页面
③ Prompts(提示模板)

预定义的提示词模板,可被 Agent 复用。例如代码审查模板、SQL 生成模板等。

1.4 MCP 通信机制

MCP 支持两种传输方式:

传输方式 适用场景 特点
stdio 本地进程通信 低延迟,适合本地工具(如文件系统、本地数据库)
SSE(HTTP) 远程网络通信 支持跨网络,适合 SaaS 服务、云端系统

通信格式采用 JSON-RPC 2.0,结构清晰,易于调试和扩展。


二、Dify 集成 MCP 的方式

2.1 Dify 对 MCP 的支持现状

Dify 从 v0.10.x 版本开始逐步引入 MCP 支持。目前主要有两种集成路径:

  1. 内置 MCP Tool 节点:在工作流编辑器中直接添加 MCP 工具节点
  2. 自定义工具接入:通过 Dify 的"工具"功能,将 MCP Server 封装为自定义工具

本文重点介绍实战中最常用的自定义工具接入方式,兼容性更广,适合生产环境使用。

2.2 集成架构设计

Dify Workflow
    ↓
[Agent 节点] 或 [HTTP 请求节点]
    ↓
MCP Proxy 服务(中间层,将 HTTP 转为 MCP 协议)
    ↓
MCP Server(各类系统的 MCP 适配器)
    ↓
目标系统(GitHub / Jira / 数据库 / 钉钉等)

核心思路:在 Dify 与 MCP Server 之间部署一个轻量的 MCP Proxy,将 Dify 的 HTTP 请求转换为 MCP 协议调用,再将结果返回给 Dify。


三、实战:搭建 MCP Server 并接入 Dify

3.1 环境准备

我们以一个常见场景为例:让 Agent 能够操作 GitHub(创建 Issue、读取仓库信息、提交代码评论)。

所需环境:

  • Python 3.10+
  • Dify v0.10+ 实例(本地或云端)
  • GitHub Personal Access Token
  • MCP Python SDK:pip install mcp

3.2 编写 GitHub MCP Server

# github_mcp_server.py
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
import httpx
import json
import os

app = Server("github-mcp-server")

GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN")
GITHUB_BASE_URL = "https://api.github.com"

headers = {
    "Authorization": f"Bearer {GITHUB_TOKEN}",
    "Accept": "application/vnd.github+json",
    "X-GitHub-Api-Version": "2022-11-28"
}

@app.list_tools()
async def list_tools() -> list[Tool]:
    """声明此 MCP Server 提供的工具列表"""
    return [
        Tool(
            name="create_issue",
            description="在指定 GitHub 仓库创建一个新的 Issue",
            inputSchema={
                "type": "object",
                "properties": {
                    "owner": {"type": "string", "description": "仓库所有者"},
                    "repo": {"type": "string", "description": "仓库名称"},
                    "title": {"type": "string", "description": "Issue 标题"},
                    "body": {"type": "string", "description": "Issue 描述内容"},
                    "labels": {
                        "type": "array",
                        "items": {"type": "string"},
                        "description": "标签列表(可选)"
                    }
                },
                "required": ["owner", "repo", "title", "body"]
            }
        ),
        Tool(
            name="list_issues",
            description="获取指定仓库的 Issue 列表",
            inputSchema={
                "type": "object",
                "properties": {
                    "owner": {"type": "string", "description": "仓库所有者"},
                    "repo": {"type": "string", "description": "仓库名称"},
                    "state": {
                        "type": "string",
                        "enum": ["open", "closed", "all"],
                        "description": "Issue 状态过滤"
                    },
                    "per_page": {"type": "integer", "description": "每页数量,默认30"}
                },
                "required": ["owner", "repo"]
            }
        ),
        Tool(
            name="add_comment",
            description="在指定 Issue 或 PR 上添加评论",
            inputSchema={
                "type": "object",
                "properties": {
                    "owner": {"type": "string"},
                    "repo": {"type": "string"},
                    "issue_number": {"type": "integer", "description": "Issue 编号"},
                    "body": {"type": "string", "description": "评论内容"}
                },
                "required": ["owner", "repo", "issue_number", "body"]
            }
        )
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    """执行工具调用"""
    async with httpx.AsyncClient() as client:
        if name == "create_issue":
            url = f"{GITHUB_BASE_URL}/repos/{arguments['owner']}/{arguments['repo']}/issues"
            payload = {
                "title": arguments["title"],
                "body": arguments["body"],
                "labels": arguments.get("labels", [])
            }
            response = await client.post(url, headers=headers, json=payload)
            result = response.json()
            return [TextContent(
                type="text",
                text=f"Issue 创建成功!编号: #{result['number']}, 链接: {result['html_url']}"
            )]

        elif name == "list_issues":
            url = f"{GITHUB_BASE_URL}/repos/{arguments['owner']}/{arguments['repo']}/issues"
            params = {
                "state": arguments.get("state", "open"),
                "per_page": arguments.get("per_page", 30)
            }
            response = await client.get(url, headers=headers, params=params)
            issues = response.json()
            result_text = f"共找到 {len(issues)} 个 Issue:
"
            for issue in issues[:10]:
                result_text += f"- #{issue['number']} {issue['title']} [{issue['state']}]
"
            return [TextContent(type="text", text=result_text)]

        elif name == "add_comment":
            url = f"{GITHUB_BASE_URL}/repos/{arguments['owner']}/{arguments['repo']}/issues/{arguments['issue_number']}/comments"
            response = await client.post(url, headers=headers, json={"body": arguments["body"]})
            result = response.json()
            return [TextContent(
                type="text",
                text=f"评论已添加,链接: {result['html_url']}"
            )]

        else:
            return [TextContent(type="text", text=f"未知工具: {name}")]

async def main():
    async with stdio_server() as (read_stream, write_stream):
        await app.run(read_stream, write_stream, app.create_initialization_options())

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

3.3 部署 MCP Proxy(HTTP 桥接层)

为了让 Dify 能通过 HTTP 调用 MCP Server,我们需要一个桥接服务。使用 mcp-proxy 工具可以快速实现:

# 安装 mcp-proxy
pip install mcp-proxy

# 启动代理,将 HTTP 请求转发给 stdio 模式的 MCP Server
mcp-proxy --port 8080 --server "python github_mcp_server.py"   --env GITHUB_TOKEN=your_token_here

启动后,MCP Server 通过 HTTP 在 http://localhost:8080 提供服务,接口格式为:

POST http://localhost:8080/tools/call
Content-Type: application/json

{
  "name": "create_issue",
  "arguments": {
    "owner": "your-org",
    "repo": "your-repo",
    "title": "Bug: 登录失败",
    "body": "用户反馈在 iOS 16 上无法正常登录"
  }
}

3.4 在 Dify 中配置自定义工具

进入 Dify → 工具 → 自定义工具 → 新建,填写 OpenAPI 规范:

openapi: 3.0.0
info:
  title: GitHub MCP Tools
  version: 1.0.0
  description: 通过 MCP 协议操作 GitHub

servers:
  - url: http://localhost:8080

paths:
  /tools/call:
    post:
      operationId: call_mcp_tool
      summary: 调用 MCP 工具
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  description: 工具名称(create_issue / list_issues / add_comment)
                arguments:
                  type: object
                  description: 工具参数
              required:
                - name
                - arguments
      responses:
        '200':
          description: 工具执行结果
          content:
            application/json:
              schema:
                type: object

四、在 Dify 工作流中使用 MCP 工具

4.1 工作流设计:智能 Bug 报告助手

我们构建一个实际的 Agent 工作流:用户描述一个 Bug,Agent 自动在 GitHub 创建格式规范的 Issue,并根据严重程度添加对应标签。

[开始] → 用户输入 Bug 描述
  ↓
[LLM 节点] → 分析 Bug,提取结构化信息
  (标题、复现步骤、严重程度、影响范围)
  ↓
[IF/ELSE] → 判断严重程度
  ├── Critical → 标签: ["bug", "critical", "P0"]
  ├── High     → 标签: ["bug", "P1"]
  └── Normal   → 标签: ["bug", "P2"]
  ↓
[HTTP 请求节点] → 调用 MCP Proxy 创建 GitHub Issue
  ↓
[LLM 节点] → 生成用户友好的确认回复
  ↓
[结束] → 输出创建结果

4.2 关键节点配置

LLM 节点 Prompt(结构化提取)
你是一个专业的 Bug 分析助手。

用户描述的问题:
{{user_input}}

请分析并提取以下结构化信息,以 JSON 格式输出:
{
  "title": "简洁的 Issue 标题(50字以内)",
  "body": "完整的 Issue 描述,包含:
## 问题描述

## 复现步骤

## 期望行为

## 实际行为

## 环境信息",
  "severity": "critical | high | normal",
  "labels": ["相关标签"]
}

只输出 JSON,不要其他内容。
HTTP 请求节点配置
  • 方法:POST
  • URLhttp://localhost:8080/tools/call
  • 请求体
    {
      "name": "create_issue",
      "arguments": {
        "owner": "{{repo_owner}}",
        "repo": "{{repo_name}}",
        "title": "{{parsed_result.title}}",
        "body": "{{parsed_result.body}}",
        "labels": {{parsed_result.labels}}
      }
    }

4.3 多系统联动示例

通过组合多个 MCP Server,可以实现强大的跨系统工作流。例如:

用户报告 Bug
  ↓
[GitHub MCP] → 创建 Issue
  ↓
[Jira MCP]   → 同步创建对应工单
  ↓
[Slack MCP]  → 通知对应研发团队频道
  ↓
[钉钉 MCP]   → 向 oncall 工程师发送告警

这种"一次输入,多系统联动"的能力,正是 MCP 带给 AI Agent 的核心价值。


五、构建自己的 MCP Server:最佳实践

5.1 工具设计原则

一个高质量的 MCP Server 应遵循以下原则:

① 工具粒度要适中

不要设计过于复合的工具。错误示例:

# ❌ 不好:一个工具做太多事
"create_and_assign_and_notify_issue"

# ✅ 好:职责单一,可组合
"create_issue"
"assign_issue"
"send_notification"
② 描述信息是关键

MCP 工具的 description 字段直接影响 LLM 的工具选择准确率。描述应该:

  • 说明工具的功能(做什么)
  • 说明适用场景(什么情况下用)
  • 说明输出格式(返回什么)
Tool(
    name="search_issues",
    description="""搜索 GitHub 仓库中的 Issues。
适用场景:当用户询问某个仓库是否存在相关问题、查找历史 Bug 时使用。
参数说明:q 为搜索关键词,支持 GitHub 搜索语法。
返回:匹配的 Issue 列表,包含编号、标题、状态和链接。""",
    ...
)
③ 错误处理要详细
@app.call_tool()
async def call_tool(name: str, arguments: dict):
    try:
        # 执行工具逻辑
        result = await execute_tool(name, arguments)
        return [TextContent(type="text", text=result)]
    except httpx.HTTPStatusError as e:
        # 返回对 LLM 友好的错误信息
        return [TextContent(
            type="text",
            text=f"API 调用失败(状态码 {e.response.status_code}):{e.response.text}。请检查参数是否正确或权限是否足够。"
        )]
    except Exception as e:
        return [TextContent(
            type="text",
            text=f"工具执行出错:{str(e)}。建议尝试其他方式或联系管理员。"
        )]

5.2 安全性设计

MCP Server 处于 Agent 与真实系统之间,安全设计至关重要:

  • 最小权限原则:Token 只授予必要的权限范围(如只读 vs 读写)
  • 操作审计日志:记录所有工具调用,包括时间、参数、执行结果
  • 危险操作确认:删除、修改等破坏性操作需要额外的确认参数
  • 速率限制:防止 Agent 在异常情况下产生大量 API 调用
import asyncio
from functools import wraps

# 简单的速率限制装饰器
def rate_limit(max_calls: int, period: float):
    calls = []
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            now = asyncio.get_event_loop().time()
            calls[:] = [t for t in calls if now - t < period]
            if len(calls) >= max_calls:
                raise Exception(f"速率限制:每 {period} 秒最多调用 {max_calls} 次")
            calls.append(now)
            return await func(*args, **kwargs)
        return wrapper
    return decorator

@rate_limit(max_calls=10, period=60.0)
async def call_github_api(url, **kwargs):
    # 实际 API 调用逻辑
    pass

5.3 常用 MCP Server 生态

目前已有大量开源 MCP Server 可直接使用:

系统 MCP Server 主要功能
GitHub modelcontextprotocol/servers/github Issue、PR、代码搜索
文件系统 modelcontextprotocol/servers/filesystem 本地文件读写
PostgreSQL modelcontextprotocol/servers/postgres SQL 查询执行
Slack modelcontextprotocol/servers/slack 消息发送、频道管理
Google Drive modelcontextprotocol/servers/gdrive 文件搜索、读取
Puppeteer modelcontextprotocol/servers/puppeteer 浏览器自动化
Memory modelcontextprotocol/servers/memory 知识图谱持久化

六、Dify + MCP 完整架构图

                    ┌─────────────────────────────┐
                    │       用户 / 业务系统         │
                    └──────────────┬──────────────┘
                                   │
                    ┌──────────────▼──────────────┐
                    │         Dify 平台            │
                    │  ┌─────────────────────────┐ │
                    │  │      工作流编排引擎       │ │
                    │  │  Start → LLM → Agent    │ │
                    │  │    → HTTP → End         │ │
                    │  └─────────────────────────┘ │
                    └──────────────┬──────────────┘
                                   │ HTTP
                    ┌──────────────▼──────────────┐
                    │        MCP Proxy 层          │
                    │  (HTTP ↔ MCP 协议转换)     │
                    └──┬───────┬────────┬─────────┘
                       │       │        │
              ┌────────▼─┐ ┌───▼────┐ ┌─▼────────┐
              │ GitHub   │ │  Jira  │ │  数据库   │
              │ MCP Svr  │ │MCP Svr │ │ MCP Svr  │
              └────┬─────┘ └───┬────┘ └─────┬────┘
                   │           │             │
              ┌────▼─────┐ ┌───▼────┐ ┌─────▼────┐
              │  GitHub  │ │  Jira  │ │ MySQL /  │
              │   API    │ │  API   │ │ Postgres │
              └──────────┘ └────────┘ └──────────┘

七、MCP vs 传统 Function Calling 对比

维度 传统 Function Calling MCP 协议
标准化程度 各平台自定义,不统一 开放标准,统一规范
工具复用 需要为每个平台单独适配 一次开发,多处复用
动态发现 工具列表静态配置 运行时动态发现可用工具
资源访问 只支持函数调用 支持 Tools/Resources/Prompts
安全模型 依赖调用方实现 协议层内置权限控制
生态丰富度 平台各自为政 快速增长的开源生态

总结

MCP 协议正在成为 AI Agent 与外部系统交互的事实标准。它解决的核心问题是:

如何让 Agent 以标准化、安全的方式操作任意外部系统,而不是为每个系统单独开发集成代码?

结合 Dify 强大的工作流编排能力,MCP 让我们可以:

  1. 快速接入:利用现有 MCP Server 生态,分钟级集成主流系统
  2. 灵活编排:在 Dify 工作流中自由组合多个 MCP 工具
  3. 安全可控:MCP 协议层提供标准的权限隔离和审计能力
  4. 持续扩展:新增系统只需开发对应的 MCP Server,无需修改 Agent 逻辑

随着 MCP 生态的持续壮大,"任何系统都可以成为 Agent 的操作对象"这一愿景正在快速落地。对于正在构建企业级 AI Agent 的开发者来说,现在正是拥抱 MCP 的最佳时机。

💡 系列下一篇预告:《Dify 多 Agent 协作:如何设计 Supervisor + Worker 架构实现复杂任务分解》,敬请期待!

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐