MCP Protocol Integration#
Dify supports the Model Context Protocol (MCP) in two directions:
- MCP client β Dify connects to remote MCP servers and invokes their tools from workflows or agents.
- MCP server (workflow-as-MCP) β any Dify app (Workflow, Chat, Advanced Chat, Agent Chat, Completion) can be exposed as an MCP server callable by external clients like Claude Desktop.
This article covers the server-side MCP implementation and the 2025-06-18 structured output upgrade introduced in PR #37892.
Key Source Files#
| File | Purpose |
|---|---|
api/core/mcp/types.py | Protocol version constants, Pydantic MCP types (Tool, CallToolResult, etc.) |
api/core/mcp/server/streamable_http.py | Core request handlers: version negotiation, tools/list, tools/call, structured output extraction |
api/controllers/mcp/mcp.py | HTTP entry point β parses MCP-Protocol-Version header, threads version through the call stack |
api/core/tools/mcp_tool/tool.py | MCP client tool β invokes remote MCP tools and processes their results inside Dify |
api/core/tools/mcp_tool/provider.py | MCP tool provider controller β maps remote MCP tool schemas to Dify ToolEntity objects |
api/core/tools/entities/tool_entities.py | ToolEntity definition, including the output_schema field populated from MCP outputSchema |
Protocol Version Negotiation#
Dify's MCP server supports three protocol versions :
| Constant | Value |
|---|---|
SERVER_LATEST_PROTOCOL_VERSION | "2025-06-18" |
SERVER_SUPPORTED_PROTOCOL_VERSIONS | frozenset({"2024-11-05", "2025-03-26", "2025-06-18"}) |
DEFAULT_NEGOTIATED_VERSION | "2025-03-26" |
initialize handshake β The client sends its preferred protocolVersion in the request body. The server echoes that version back if it is in the supported set; otherwise it falls back to SERVER_LATEST_PROTOCOL_VERSION (2025-06-18) .
Post-initialize requests β The client includes the MCP-Protocol-Version HTTP header. Resolution rules :
- Absent or empty β
DEFAULT_NEGOTIATED_VERSION(2025-03-26) - Known version β that version
- Unknown version β JSON-RPC
INVALID_REQUEST(-32600), except for fire-and-forget notifications which always return HTTP 202 initializemethod β header is ignored; body version governs
The resolved version is threaded through the full call stack from the HTTP controller to each handler, with no global state .
Structured Output (2025-06-18)#
Structured output is gated by _supports_structured_output(), which uses lexical string comparison β valid because MCP versions are YYYY-MM-DD strings :
STRUCTURED_OUTPUT_MIN_VERSION = "2025-06-18"
_supports_structured_output(v) β v >= "2025-06-18"
tools/list β outputSchema and title#
When _supports_structured_output returns True, handle_list_tools() sets two extra fields on the Tool object :
titleβ the app name (human-readable display name distinct fromname)outputSchemaβ{"type": "object"}(permissive; declares the tool returns a JSON object)
For older clients these fields are None and are stripped via exclude_none serialization, preserving byte-for-byte backward compatibility .
tools/call β structuredContent#
handle_call_tool() calls extract_structured_output() to populate structuredContent when the protocol version qualifies. The content depends on app mode :
WORKFLOWβ returns the rawoutputsmapping from the workflow execution (named output variables as keys)CHAT,ADVANCED_CHAT,AGENT_CHAT,COMPLETIONβ wraps the answer string as{"answer": <string>}
Both structuredContent and the legacy TextContent are always included for 2025-06-18 clients, per the MCP spec's backward-compatibility guidance .
MCP Client Side: Tool Entity Schema Population#
When Dify acts as an MCP client, MCPToolProviderController.from_entity() converts each remote MCP tool into a ToolEntity. The remote tool's outputSchema is stored directly in ToolEntity.output_schema :
output_schema=remote_mcp_tool.outputSchema or {}
ToolEntity.output_schema is defined in tool_entities.py as Mapping[str, object] defaulting to {}, with a validator that normalizes None to {} .
During invocation, MCPTool._invoke() processes result.structuredContent as follows :
- If both
self.entity.output_schemaandresult.structuredContentare present, each key-value pair instructuredContentis yielded as aVariableMessage, making structured output fields available as named variables in the Dify workflow. - If
result.structuredContentis present butself.entity.output_schemais not defined, the entirestructuredContentdictionary is yielded as a singleJsonMessage.
Text content is always processed regardless, with JSON detection to yield JsonMessage or TextMessage appropriately .
MCP Client Transport#
The MCPClient supports two transports β SSE and Streamable-HTTP β with URL-path-based selection: paths ending in /mcp use Streamable-HTTP; paths ending in /sse use SSE. If the path is ambiguous, SSE is tried first and falls back to Streamable-HTTP on failure .
Backward Compatibility#
All 2025-06-18 fields (title, outputSchema, structuredContent) are optional in the Pydantic models and default to None. No database migrations or schema changes were needed β the upgrade was purely logic-layer . Clients that send protocolVersion: "2024-11-05" in the initialize handshake receive the identical pre-upgrade response shape.