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)