Dify as MCP Server
by Yevanchen
Verified
# Dify 插件开发记录
## 项目概述
本文档记录 Dify 插件开发过程中的关键步骤、决策和问题解决方案。
## 开发环境
- 操作系统: macOS (Darwin arm64)
- 开发工具: Visual Studio Code, Git
- Dify 插件 CLI 版本: 0.0.1b67
## 开发时间线
### [2024-03-18]
- 初始化插件项目
- 设置基本结构
- 实现基础端点功能
- 配置远程 Git 仓库
- 完成端点基础测试
- 添加 GET 端点支持
- 重构 POST 端点命名,保持一致性
- 更新端点组配置
### [最新更新]
- 实现了完整的 ToolRegistry 系统用于工具注册和执行
- 添加了 JSON-RPC 处理方法,支持 initialize、list_tools 和 call_tool 方法
- 为 GET 端点添加了基础框架,准备实现 SSE 和 HTML 页面支持
- 发现并记录了 GET 端点中的方法缺失问题
- 修复了 JSON-RPC call_tool 方法中的异步事件循环问题
- 确认 JSON-RPC 请求能够到达服务器并开始处理,但响应可能因网关超时而中断
### [2024-03-19]
- 实现了GET端点的SSE支持,但客户端仍无法正确识别
- 尝试根据MCP规范修改SSE消息格式
- 发现了MCP规范正在进行重大更新(PR #206),从HTTP+SSE切换到新的"Streamable HTTP"传输
- 尝试根据新规范调整SSE消息格式和会话管理
- 添加了服务器端会话ID生成
- 更新了SSE消息格式以符合JSON-RPC 2.0标准
- 调研了无状态MCP服务器的可能性,这对Dify插件环境更为适合
- 添加了符合新标准的会话建立、通知和心跳消息
- Cursor客户端仍然显示"Failed to send an error response: Error: Not connected"错误
## 功能清单
- [x] 基础端点功能(GET/POST基本方法)
- [x] Dify Workflow 基本集成
- [x] MCP 标准 JSON-RPC 实现(基本功能已完成,initialize和list_tools正常工作)
- [x] SSE 流式响应支持(基本框架已添加,但客户端连接仍有问题)
- [ ] 服务器端会话管理(初步实现,但需进一步完善)
- [ ] 符合新规范的无状态模式
- [ ] 认证和安全特性
## 端点实现详情
以下是当前实现的端点详情:
### POST 端点
- **YAML 配置文件**: `difyapp_as_mcp_server_post.yaml`
- **实现类文件**: `difyapp_as_mcp_server_post.py`
- **类名**: `DifyappAsMcpServerEndpoint`
- **路径**: `/difyapp_as_mcp_server`
- **方法**: POST
- **当前功能**:
- 支持基础的 Dify Workflow 调用
- 实现了 JSON-RPC 处理框架
- 支持 MCP 标准的 initialize、list_tools 和 call_tool 方法
- 实现了工具注册和执行系统
- 解决了异步事件循环冲突问题
### GET 端点
- **YAML 配置文件**: `difyapp_as_mcp_server_get.yaml`
- **实现类文件**: `difyapp_as_mcp_server_get.py`
- **类名**: `DifyappAsMcpServerGetEndpoint`
- **路径**: `/difyapp_as_mcp_server`
- **方法**: GET
- **当前功能**:
- 返回基本状态信息
- 实现了SSE连接处理
- 添加了服务器生成的会话ID
- 按照新规范发送通知和心跳
- 仍面临客户端连接问题
## 当前实现的架构
### 工具注册和执行系统
插件实现了一个完整的工具注册和执行系统,包括:
1. **ToolDef 类**:
- 封装了工具函数及其元数据
- 能够自动生成工具 JSON Schema
- 支持异步执行工具函数
- 智能处理异步函数,避免事件循环冲突
2. **ToolRegistry 类**:
- 管理所有已注册的工具
- 提供装饰器接口用于注册工具
- 支持列出所有工具和执行指定工具
3. **工具注册示例**:
```python
@tool_registry.tool
async def dify_workflow(title: str, language: str = "English") -> str:
"""执行 Dify workflow 并返回结果
Args:
title: 要处理的标题或主题
language: 输出使用的语言 (默认英文)
"""
# 实现...
```
### SSE连接实现
根据新的MCP Streamable HTTP规范,实现了SSE连接处理:
```python
def _handle_sse_connection(self, r: Request, settings: Mapping) -> Response:
"""处理SSE连接请求 - 符合新的Streamable HTTP规范"""
# 服务器负责生成会话ID
session_id = str(uuid.uuid4())
def generate():
# 发送会话建立消息
session_msg = {
"jsonrpc": "2.0",
"method": "session.established",
"params": {"session_id": session_id},
"id": None
}
yield f"data: {json.dumps(session_msg)}\n\n"
# 后续消息...
return Response(
generate(),
status=200,
content_type="text/event-stream",
headers={
"Cache-Control": "no-cache, no-transform",
"Connection": "keep-alive",
"X-Accel-Buffering": "no",
"MCP-Session-ID": session_id # 在头部返回会话ID
}
)
```
## 遇到的问题及解决方案
1. **SSE消息格式问题**:
- 描述: Cursor客户端无法识别我们的SSE消息格式
- 解决方案: 根据新规范调整JSON-RPC消息格式,添加会话ID管理
2. **会话管理问题**:
- 描述: 原规范需要客户端创建会话ID,但新规范将责任转移到服务器端
- 解决方案: 在服务器端生成会话ID并通过HTTP头部返回
3. **连接断开问题**:
- 描述: 客户端仍然报告"Failed to send an error response: Error: Not connected"
- 解决方案: 尝试实现无状态模式,或进一步优化连接处理
4. **MCP规范更新**:
- 描述: MCP规范正在进行重大更新,从HTTP+SSE转向Streamable HTTP
- 解决方案: 跟踪规范变化,调整实现以符合最新标准
## 未来计划
- 实现完全符合新规范的无状态MCP服务器
- 优化会话管理和消息格式
- 探索使用Redis等技术实现跨请求的会话状态维护
- 考虑实现显式会话终止(通过HTTP DELETE)
- 添加身份验证和安全功能
- 实现工具调用流式进度通知
## 关键决策
1. **放弃使用完整MCP SDK**:
- 原因: Dify插件环境限制,无法直接使用SDK的服务器功能
- 替代方案: 自行实现核心功能,参考SDK规范和格式
2. **采用有限心跳模式**:
- 原因: Dify插件环境对请求处理时间有限制
- 实现: 心跳消息有限次数,客户端需自动重连
3. **考虑无状态模式**:
- 原因: 更适合Dify插件环境的限制
- 好处: 无需长期维护连接,每个请求独立处理
## API 文档
插件提供以下两类 API:
### 1. 非 MCP 标准 API
这些 API 是当前已实现的简单API,与标准 MCP 协议并行工作。
#### POST Workflow API
- **端点**: `/difyapp_as_mcp_server`
- **方法**: POST
- **请求格式**:
```json
{
"responseValues": {
"title": {
"value": "查询内容"
},
"language": {
"value": "语言选择"
}
}
}
```
- **响应格式**:
```json
{
"status": "success",
"workflow_response": {
"data": {
"outputs": {
"output": "生成的内容"
},
"status": "succeeded"
}
}
}
```
#### GET Status API
- **端点**: `/difyapp_as_mcp_server`
- **方法**: GET
- **请求参数**: 支持通过URL查询参数传递
- **响应格式**:
```json
{
"status": "success",
"message": "Dify MCP Server is running",
"app_id": "应用ID",
"query_params": {
"param1": "value1",
"param2": "value2"
}
}
```
### 2. MCP 标准协议 API
MCP 标准协议 API 遵循 Anthropic 的 MCP 规范,包含以下核心方法:
- **initialize**: 初始化连接并返回服务器信息
- 已成功实现,测试通过
- **list_tools**: 返回可用工具列表
- 已成功实现,测试通过
- **call_tool**: 调用特定工具并执行操作
- 基本框架已实现,请求能到达服务器并处理
- 存在网关超时问题,需进一步优化
## 测试记录
### 2024-03-18 测试
- **测试用例**: 使用 curl 请求发送标题 "kanye west",语言 "English"
- **请求命令**:
```bash
curl -X POST "https://jar8zeuq1rs88qdz.ai-plugin.io/difyapp_as_mcp_server" \
-H "Content-Type: application/json" \
-d '{"responseValues": {"title": {"value": "kanye west"}, "language": {"value": "English"}}}'
```
- **测试结果**: 成功,返回了关于 Kanye West 的详细 HTML 内容
- **问题**: 初始测试因模型凭证问题失败,第二次测试成功
### 2024-03-18 GET端点测试
- **测试用例**: 使用 curl 请求获取服务器状态
- **请求命令**:
```bash
curl -X GET "https://jar8zeuq1rs88qdz.ai-plugin.io/difyapp_as_mcp_server?param=test"
```
- **测试结果**: 失败,返回 "Internal Server Error: 'DifyappAsMcpServerGetEndpoint' object has no attribute '_serve_html_page'"
- **问题**: 需要实现GET端点的缺失方法
### [最新] JSON-RPC 方法测试
- **测试用例**: 使用 curl 测试 initialize 和 list_tools 方法
- **请求命令**:
```bash
# 测试 initialize 方法
curl -X POST "https://jar8zeuq1rs88qdz.ai-plugin.io/difyapp_as_mcp_server" \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "id": "test-123", "method": "initialize", "params": {}}'
# 测试 list_tools 方法
curl -X POST "https://jar8zeuq1rs88qdz.ai-plugin.io/difyapp_as_mcp_server" \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "id": "test-124", "method": "list_tools", "params": {}}'
```
- **测试结果**:
- initialize: 成功,返回正确的服务器信息
- list_tools: 成功,返回已注册工具列表
- call_tool: 请求能到达服务器且开始处理,但返回网关超时错误
## 部署说明
如何部署和使用该插件:
1. 在 Dify 平台中安装插件
2. 配置插件参数,包括:
- 应用 ID
- API 密钥
3. 端点可通过 HTTP 客户端或 MCP 兼容客户端访问
## 未来计划
- 解决网关超时问题,考虑通过以下方式:
- 优化工作流处理速度
- 实现SSE机制提供流式响应
- 调整网关超时设置
- 实现 GET 端点缺失的方法
- 实现 SSE 支持(高优先级)
- 添加认证机制(中优先级)
- 优化错误处理(中优先级)
- 实现说明页面(低优先级)
## 路线图
### Step 1: 实现dify workflow 的基础反向调用
- 描述: 实现dify workflow的基础反向调用功能,确保插件能够正确地与dify workflow进行交互。
- 目标: 完成基础反向调用的实现,确保插件能够在dify workflow中正确运行。
- 状态: ✅ 已完成(基础实现,非MCP标准)
### Step 2: 将反向调用的接口根据mcp开发标准变成符合mcp的协议
- 描述: 根据mcp开发标准,将插件的反向调用接口修改为符合mcp协议的接口,确保插件能够与mcp客户端进行正确的交互。
- 目标: 完成mcp协议接口的修改,确保插件能够与mcp客户端进行正确的交互。
- 状态: ✅ 基本完成(initialize和list_tools已完成,call_tool已解决异步问题但有网关超时)
### Step 3: 添加GET端点支持
- 描述: 添加GET端点支持,允许客户端查询服务器状态和应用信息,为SSE连接做准备。
- 目标: 完成GET端点的实现,为SSE连接提供基础。
- 状态: ✓ 基础框架已完成,存在未实现方法问题
### Step 4: 实现 SSE 流式响应
- 描述: 实现服务器发送事件(SSE)支持,使客户端可以接收流式响应和实时更新。
- 目标: 完成SSE响应的实现,支持MCP客户端的事件订阅功能。
- 状态: 📅 计划中(高优先级,可能是解决网关超时问题的关键)
### Step 5: 实现完整的 JSON-RPC 方法
- 描述: 实现完整的MCP标准JSON-RPC方法,包括initialize, list_tools和call_tool。
- 目标: 确保插件完全符合MCP协议规范,可与Claude等客户端完美交互。
- 状态: ✅ 基本完成(需解决call_tool网关超时问题)
### Step 6: 实现一个webapp(说明书)
- 描述: 实现一个webapp,用于展示插件的使用说明和示例,方便用户快速上手使用插件。
- 目标: 完成webapp的实现,确保用户能够通过webapp快速了解插件的使用方法。
- 状态: 📅 计划中(低优先级)
## 参考资料
- [MCP 协议文档](https://www.anthropic.com/claude/model-context-protocol)
- [Dify 插件开发文档](https://docs.dify.ai/v/zh-hans/advanced/plugin-system)
- [Dify Workflow API 文档](https://docs.dify.ai/v/zh-hans/api-reference/workflow-api)