# Function Calling vs ReAct Strategy in MCP Tool Execution: Architectural Differences Explained

> Discover the architectural differences between Function Calling and ReAct strategies for MCP tool execution. Understand how LLM capabilities and Chain-of-Thought parsing impact tool use.

- Repository: [Junjie.M/dify-plugin-agent-mcp_sse](https://github.com/junjiem/dify-plugin-agent-mcp_sse)
- Tags: architecture
- Published: 2026-03-05

---

**The Function Calling strategy leverages native LLM tool-call capabilities with structured JSON payloads, while the ReAct strategy uses Chain-of-Thought text parsing with "Action:" markers to trigger tool execution through an intermediate parser.**

The `junjiem/dify-plugin-agent-mcp_sse` repository implements two distinct approaches for MCP (Model Context Protocol) tool execution within AI agent workflows. Understanding how the **Function Calling strategy** differs from the **ReAct strategy** is essential for selecting the right architecture based on your LLM provider's capabilities and your application's reliability requirements.

## Core Architectural Differences

### LLM-Tool Interface Patterns

The fundamental distinction lies in how each strategy interfaces with the LLM. **Function Calling** utilizes the model's native `tool_calls` capability, where the LLM returns structured objects that map directly to executable functions. In contrast, **ReAct** (Reasoning + Acting) relies on a Chain-of-Thought scratchpad where the model generates plain text containing "Action:" and "Observation" markers.

In [`strategies/function_calling.py`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/strategies/function_calling.py), the strategy checks for tool calls via structured attributes:

```python

# Streaming detection (lines 71-75)

if self.check_tool_calls(chunk):
    tool_calls = chunk.delta.message.tool_calls
    

# Blocking detection (lines 77-81)  

if self.check_blocking_tool_calls(result):
    tool_calls = result.message.tool_calls

```

Meanwhile, in [`strategies/ReAct.py`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/strategies/ReAct.py), detection occurs through text parsing:

```python

# After parsing streamed output via CotAgentOutputParser (lines 33-38)

if scratchpad.action:
    # Extract action from text marker

    action = scratchpad.action

```

### Tool Call Detection Mechanisms

**Function Calling** inspects the `tool_calls` attribute on message objects, checking `chunk.delta.message.tool_calls` for streaming responses or `result.message.tool_calls` for blocking calls (lines 71‑81 of [`strategies/function_calling.py`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/strategies/function_calling.py)).

**ReAct** uses `CotAgentOutputParser.handle_react_stream_output` to process the text stream, then examines `scratchpad.action` to determine if a tool invocation is required (lines 33‑38 of [`strategies/ReAct.py`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/strategies/ReAct.py)).

## MCP Tool Integration and Execution Flow

### Function Calling Strategy Implementation

In the Function Calling approach, MCP tools are fetched once and converted to `PromptMessageTool` objects. These are passed directly to the LLM via the `tools=` parameter in the `invoke` method.

From [`strategies/function_calling.py`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/strategies/function_calling.py) (lines 84‑99):

```python

# Fetch MCP tools and convert to PromptMessageTool

mcp_tools = await self.mcp_clients.get_tools()
prompt_messages_tools = [
    PromptMessageTool(
        name=tool.name,
        description=tool.description,
        parameters=tool.parameters
    )
    for tool in mcp_tools
]

```

And lines 175‑176 show the invocation:

```python

# Pass to LLM with native tool support

response = self.session.model.llm.invoke(
    model_config,
    prompt_messages,
    tools=prompt_messages_tools
)

```

The model returns structured `tool_calls` objects that are parsed via `extract_tool_calls` (lines 93‑100), with arguments accessed through `prompt_message.function.arguments` as pre-encoded JSON.

### ReAct Strategy Implementation

The ReAct strategy takes a different approach. While it also fetches MCP tools via `McpClients` (lines 42‑58 of [`strategies/ReAct.py`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/strategies/ReAct.py)), it does not pass them as a structured `tools=` parameter. Instead, tools are interpolated into the system prompt as text descriptions.

From [`strategies/ReAct.py`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/strategies/ReAct.py) (lines 79‑94):

```python

# Tools embedded in system prompt, not passed as structured parameter

system_prompt = self._generate_system_prompt(
    tools_description=mcp_tools_description,
    # ... other prompt components

)

# Invoke without tools parameter

response = self.session.model.llm.invoke(
    model_config,
    prompt_messages
    # No tools= argument here

)

```

The model generates text containing "Action:" markers. The `CotAgentOutputParser` extracts these into `Action` objects, which trigger `_handle_invoke_action` (lines 70‑124). This method decides whether to call an MCP tool via `mcp_clients.execute_tool` or a native Dify tool.

## Argument Handling and Payload Processing

The strategies diverge significantly in how they process tool arguments.

**Function Calling** receives arguments as pre-validated JSON via the model's native function calling schema. In [`strategies/function_calling.py`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/strategies/function_calling.py), the `extract_tool_calls` method parses `prompt_message.function.arguments` directly (lines 93‑100):

```python
tool_calls = []
for prompt_message in prompt_messages:
    if prompt_message.role == "assistant" and prompt_message.tool_calls:
        for tool_call in prompt_message.tool_calls:
            arguments = json.loads(tool_call.function.arguments)
            tool_calls.append({
                "name": tool_call.function.name,
                "arguments": arguments
            })

```

**ReAct** must handle raw text that may not be valid JSON. In [`strategies/ReAct.py`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/strategies/ReAct.py), the `_handle_invoke_action` method implements fallback parsing (lines 97‑115):

```python
try:
    tool_call_args = orjson.loads(action.action_input)
except orjson.JSONDecodeError:
    # Fallback: treat as single string argument

    tool_call_args = {"input": action.action_input}

# Or infer positional arguments if schema expects array

if isinstance(tool_call_args, dict) and not tool_call_args:
    # Handle empty dict case with positional inference

    pass

```

This makes Function Calling more robust for structured data, while ReAct offers flexibility for models that don't enforce JSON schemas.

## Iteration Control and Termination Logic

Both strategies respect `maximum_iterations`, but implement termination differently.

**Function Calling** manages state through `function_call_state` and explicitly removes tools on the final iteration to force a text response. From [`strategies/function_calling.py`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/strategies/function_calling.py) (lines 140‑144):

```python
if iteration_step == max_iteration_steps:
    # Remove all tools to force final answer

    prompt_messages_tools = []
    function_call_state = False
else:
    function_call_state = True

```

**ReAct** uses `run_agent_state` and stops when the parser detects a "Final Answer" action or when no action is parsed after the maximum rounds. From [`strategies/ReAct.py`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/strategies/ReAct.py) (lines 86‑106):

```python
while run_agent_state and iteration_step < max_iteration_steps:
    # ... generate response ...

    
    if scratchpad.is_final_answer:
        run_agent_state = False
        break
    elif not scratchpad.action:
        # No action detected, stop iteration

        run_agent_state = False
        break

```

Additionally, ReAct explicitly manages stop tokens, adding `"Observation"` to the stop list (unless the provider is in `ignore_observation_providers`), whereas Function Calling relies on the model's native `STREAM_TOOL_CALL` feature.

## Logging and Observability Differences

Both strategies use `create_log_message` and `finish_log_message`, but capture different metadata.

**Function Calling** logs structured tool interactions with `tool_name`, `tool_input`, and raw `output`. From [`strategies/function_calling.py`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/strategies/function_calling.py) (lines 251‑262):

```python
self.create_log_message(
    session_id=self.session.id,
    node_id=self.node_id,
    tool_name=tool_call["name"],
    tool_input=tool_call["arguments"],
    output=result
)

```

**ReAct** logs the complete scratchpad including thought, action, and observation. From [`strategies/ReAct.py`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/strategies/ReAct.py) (lines 560‑586):

```python
self.create_log_message(
    session_id=self.session.id,
    node_id=self.node_id,
    thought=scratchpad.thought,
    action=scratchpad.action,
    observation=scratchpad.observation
)

# Later finish log includes tool details

self.finish_log_message(
    tool_name=action.action_name,
    tool_call_args=action.action_input,
    output=observation
)

```

This makes ReAct logs more verbose for debugging reasoning steps, while Function Calling logs focus on structured input/output contracts.

## Summary

- **Function Calling** leverages native LLM `tool_calls` capabilities with structured JSON payloads, passing tools via the `tools=` parameter in [`strategies/function_calling.py`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/strategies/function_calling.py).
- **ReAct** uses Chain-of-Thought text generation with "Action:" markers, embedding tool descriptions in system prompts via [`strategies/ReAct.py`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/strategies/ReAct.py).
- **Detection**: Function Calling checks `message.tool_calls` attributes; ReAct parses text via `CotAgentOutputParser` inspecting `scratchpad.action`.
- **Arguments**: Function Calling receives pre-validated JSON; ReAct implements fallback parsing for raw text in `_handle_invoke_action`.
- **Termination**: Function Calling removes tools on final iteration; ReAct stops on "Final Answer" detection or missing actions.
- **Logging**: Function Calling logs structured tool I/O; ReAct logs complete reasoning scratchpads.

## Frequently Asked Questions

### Which strategy should I use with OpenAI models?

Use the **Function Calling strategy** when working with OpenAI models or any provider that supports native `tool_calls` capabilities. According to the source code in [`strategies/function_calling.py`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/strategies/function_calling.py), this approach passes tools via the `tools=` parameter and receives structured JSON arguments via `prompt_message.function.arguments`, eliminating the parsing overhead and potential errors associated with text-based CoT parsing.

### How does ReAct handle malformed JSON arguments?

The ReAct strategy in [`strategies/ReAct.py`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/strategies/ReAct.py) implements a robust fallback mechanism in the `_handle_invoke_action` method (lines 97‑115). When `orjson.loads` fails to parse the action input as JSON, the strategy falls back to treating the input as a single string argument or infers positional arguments from the schema. This makes it resilient to models that output imperfectly formatted tool calls in plain text.

### Can I switch strategies without modifying MCP server configuration?

Yes, both strategies interact with MCP servers through the same `McpClients` abstraction in [`utils/mcp_client.py`](https://github.com/junjiem/dify-plugin-agent-mcp_sse/blob/main/utils/mcp_client.py). The MCP server configuration remains agnostic to the execution strategy. Switching between Function Calling and ReAct only requires changing the strategy class in your agent configuration, as both strategies fetch tools via `mcp_clients.get_tools()` and execute them via `mcp_clients.execute_tool()`, ensuring seamless interoperability.

### What are the performance implications of each approach?

**Function Calling** generally offers lower latency and higher reliability because it relies on the model's native ability to generate structured JSON tool calls, eliminating the need for text parsing and intermediate `CotAgentOutputParser` processing. **ReAct** incurs additional overhead from generating Chain-of-Thought reasoning tokens and parsing "Action:" markers from text, but provides greater compatibility with models that lack native tool-call support and offers superior observability through detailed scratchpad logging of thoughts and observations.