MCP Server Tool Execution Error Handling: A Deep Dive into the Dify Plugin Architecture

The junjiem/dify-plugin-tools-mcp_sse repository implements a multi-layered error handling strategy that catches JSON-RPC protocol errors, HTTP transport failures, SSE connection crashes, and invalid tool parameters, wrapping them in descriptive exceptions while preserving the original error context.

The junjiem/dify-plugin-tools-mcp_sse plugin enables Dify to invoke tools hosted on remote MCP (Multi-Chat-Plugin) servers via SSE or HTTP transports. When tool execution fails on the remote MCP server, the framework provides robust error handling mechanisms across the McpSseClient, McpStreamableHttpClient, and McpClients classes to ensure failures are detected early and reported clearly.

Low-Level Client Error Detection

The foundation of error handling resides in utils/mcp_client.py, where two transport-specific clients handle protocol-level failures before they propagate to the orchestration layer.

JSON-RPC Error Inspection

Both McpSseClient and McpStreamableHttpClient inspect every JSON-RPC response for error fields. In methods like list_tools (lines 58-65), call_tool (lines 80-83), and list_resources (lines 95-101), the code explicitly checks if "error" in response: and raises an Exception containing the server-returned error object.

For specific unsupported method errors, the framework implements graceful degradation. When encountering JSON-RPC error codes -32001 or -32601, the client returns an empty list instead of raising an exception, allowing the system to continue operating when optional features are unavailable.

HTTP Transport Failures

Non-2xx HTTP responses and network timeouts trigger immediate failures in the send_message methods (lines 62-66 for SSE, lines 63-66 for streamable HTTP). These raise a ValueError containing the status code, reason phrase, and raw response content:

ValueError(f"{self.name} - MCP Server response: {response.status_code} {response.reason_phrase} ({response.content})")

SSE Connection and Listener Errors

The SSE transport includes defensive programming against connection degradation. In _listen_messages (lines 42-46), the framework captures exceptions from malformed events or connection errors, storing them in _thread_exception and signaling _error_event. When send_message or connect subsequently checks _thread_exception, it raises a ConnectionError to alert calling code of the transport failure.

Additionally, lines 30-35 validate that SSE-provided endpoints share the same scheme and netloc as the original request, raising ValueError for origin mismatches that could indicate security issues or configuration errors.

Resource Payload Validation

When reading resources, the execution logic validates that responses contain either 'text' or 'blob' keys. Missing these required fields triggers an Exception with the message "Unsupported resource: {content}", preventing the system from processing malformed payloads.

High-Level Orchestration Safeguards

The McpClients class serves as the public API entry point in utils/mcp_client.py, adding validation layers before invoking low-level clients.

Pre-Validation Checks

Before executing any tool, execute_tool validates two critical conditions:

  • Tool existence validation (lines 95-100): Verifies the requested tool name exists in self._tool_actions (populated via fetch_tools() if empty), raising an exception if the tool is unknown.
  • Server availability check (lines 102-104): Confirms the owning server exists in self._clients, preventing calls to disconnected or unconfigured backends.

Unified Exception Wrapping

A comprehensive try/except block (lines 105-107) wraps the entire execution flow. Any exception raised by underlying client methods (call_tool, read_resource, get_prompt) is caught and re-raised with a unified "Error executing tool: …" prefix. This ensures callers receive consistent error types while maintaining visibility into the root cause through the exception message.

Practical Error Handling Example

The following demonstration shows how these mechanisms surface when invoking non-existent tools or encountering server errors:

from utils.mcp_client import McpClients

servers_cfg = {
    "exampleMcp": {
        "url": "https://my-mcp.example.com",
        "transport": "sse",
        "headers": {"Authorization": "Bearer <token>"}
    }
}

clients = McpClients(servers_cfg, resources_as_tools=False, prompts_as_tools=False)

try:
    # Attempt to call a non-existent tool

    result = clients.execute_tool("nonexistent_tool", {"param": "value"})
except Exception as exc:
    # Output: "Error executing tool: There is not a tool named 'nonexistent_tool'"

    print(f"Execution failed: {exc}")

If the server returns a JSON-RPC method not found error, the exception message propagates as:

Error executing tool: ExampleMCP - MCP Server tools/call error: {'code': -32601, 'message': 'Method not found'}

Summary

  • JSON-RPC protocol errors are inspected in list_tools, call_tool, and related methods, with specific handling for unsupported method codes (-32001, -32601).
  • HTTP transport failures trigger ValueError exceptions containing detailed status and response information from send_message.
  • SSE connection errors are captured in _listen_messages and raised as ConnectionError when detected during message sending.
  • Input validation in McpClients.execute_tool verifies tool names and server availability before attempting remote calls.
  • Unified wrapping ensures all failures surface as descriptive exceptions with consistent prefixes while preserving original error context.

Frequently Asked Questions

What happens when the remote MCP server returns a JSON-RPC error code?

When the server returns an error field in the JSON-RPC response, the client raises a standard Exception containing the server name and error details. For specific codes like -32001 (method not supported) or -32601 (method not found), the client returns an empty list instead of crashing, enabling graceful degradation for optional MCP features.

How does the plugin handle network timeouts or HTTP 500 errors?

The send_message methods in both McpSseClient and McpStreamableHttpClient validate HTTP status codes. Any non-2xx response immediately raises a ValueError that includes the status code, reason phrase, and raw response content, allowing developers to diagnose transport layer issues without parsing logs.

Where does the tool existence validation occur in the codebase?

Tool existence checks happen in McpClients.execute_tool at lines 95-100 in utils/mcp_client.py. The method first populates the tool cache via fetch_tools() if empty, then verifies the requested tool name exists in self._tool_actions before attempting the remote call.

Can SSE connection failures be detected before attempting to send a message?

Yes. The _listen_messages method captures background listener exceptions in _thread_exception. When send_message or connect is subsequently called, it checks this storage and raises a ConnectionError if the listener has crashed, preventing attempts to communicate over broken transports.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →