Development Background of MCP
The birth of MCP marks that prompt engineering has entered a new stage of development. By providing more structured context information, it significantly enhances the capabilities of the model. When designing prompts, our goal is to integrate more specific information (such as local files, database contents, or real-time network data) so that the model can better understand and solve practical problems.
Limitations of Traditional Methods
Before the emergence of MCP, to solve complex problems, we had to:
- Manually filter information from the database
- Use tools to retrieve relevant information
- Add the filtered information to the prompt one by one
This method works well for simple problems (such as requiring the large model to make summaries), but it becomes increasingly difficult to handle as the complexity of the problem increases.
Emergence of Function Call
To overcome these challenges, many large language model (LLM) platforms (such as OpenAI and Google) have introduced the function call feature. This mechanism allows the model to call predefined functions as needed to obtain data or perform specific operations, greatly improving the degree of automation.
Limitations of Function Call
However, function call also has obvious limitations:
- High dependence on the platform
- Differences in API implementation among different LLM platforms
- Developers must rewrite the code when switching models, increasing development costs
- Challenges in aspects such as security and interactivity
Design Concept of MCP
In fact, data and tools have always been there. The key lies in how to connect them to the model more intelligently and uniformly. Anthropic designed MCP based on such a need. As a "universal adapter" for AI models, it enables LLMs to easily access data or call tools.
Advantages of MCP
How the Model Intelligently Selects Agents/Tools
The core of MCP is to enable us to conveniently call multiple tools. So, when does the LLM (model) determine which tools to use?
Tool Selection Process
Anthropic provides us with a detailed explanation. When a user asks a question:
- The client (Claude Desktop/Cursor) sends the question to the LLM
- The LLM analyzes the available tools and decides which one(s) to use
- The client executes the selected tool(s) through the MCP Server
- The execution result of the tool is sent back to the LLM
- The LLM summarizes the execution result and generates natural language to present to the user
How does the model determine which tools to use?
By analyzing the client example code provided by the MCP official, it can be seen that the model relies on the prompt to identify the currently available tools. The specific approach is as follows:
- Pass the usage descriptions of each tool to the model in text form
- Enable the model to know which tools are available for selection
- Make the best choice based on the real-time situation
Analysis of Key Code
async def start(self):
# Initialize all mcp servers
for server in self.servers:
await server.initialize()
# Get all tools named all_tools
all_tools = []
for server in self.servers:
tools = await server.list_tools()
all_tools.extend(tools)
# Format the functional descriptions of all tools into a string for the LLM to use
tools_description = "\\n".join(
[tool.format_for_llm() for tool in all_tools]
)
# Ask the LLM (Claude) which tools should be used
system_message = (
"You are a helpful assistant with access to these tools:\\n\\n"
f"{tools_description}\\n"
"Choose the appropriate tool based on the user's question. "
"If no tool is needed, reply directly.\\n\\n"
"IMPORTANT: When you need to use a tool, you must ONLY respond with "
"the exact JSON object format below, nothing else:\\n"
"{\\n"
' "tool": "tool-name",\\n'
' "arguments": {\\n'
' "argument-name": "value"\\n'
" }\\n"
"}\\n\\n"
"After receiving a tool's response:\\n"
"1. Transform the raw data into a natural, conversational response\\n"
"2. Keep responses concise but informative\\n"
"3. Focus on the most relevant information\\n"
"4. Use appropriate context from the user's question\\n"
"5. Avoid simply repeating the raw data\\n\\n"
"Please use only the tools that are explicitly defined above."
)
messages = [{"role": "system", "content": system_message}]
while True:
# Assume that the user message input has been processed here
messages.append({"role": "user", "content": user_input})
# Send the system_message and the user message input to the LLM together
llm_response = self.llm_client.get_response(messages)
Tool Formatting
How is the tool description information formatted?
class Tool:
"""Represents a tool with its properties and formatting."""
def __init__(
self, name: str, description: str, input_schema: dict[str, Any]
) -> None:
self.name: str = name
self.description: str = description
self.input_schema: dict[str, Any] = input_schema
# Convert the tool's name/tool's purpose (description) and the tool's required parameters (args_desc) into text
def format_for_llm(self) -> str:
"""Format tool information for LLM.
Returns:
A formatted string describing the tool.
"""
args_desc = []
if "properties" in self.input_schema:
for param_name, param_info in self.input_schema["properties"].items():
arg_desc = (
f"- {param_name}: {param_info.get('description', 'No description')}"
)
if param_name in self.input_schema.get("required", []):
arg_desc += " (required)"
args_desc.append(arg_desc)
return f""" Tool: {self.name} Description: {self.description} Arguments: {chr(10).join(args_desc)} """
Source of Tool Information
Where do the tool's description and input_schema come from? By analyzing the Python SDK source code of MCP:
In most cases, when using the decorator @mcp.tool()
to decorate a function, the corresponding name and description directly come from the function name of the user-defined function and the function's docstring, etc.
@classmethod
def from_function(
cls,
fn: Callable,
name: str | None = None,
description: str | None = None,
context_kwarg: str | None = None,
) -> "Tool":
"""Create a Tool from a function."""
func_name = name or fn.__name__ # Get the function name
if func_name == "<lambda>":
raise ValueError("You must provide a name for lambda functions")
func_doc = description or fn.__doc__ or "" # Get the function docstring
is_async = inspect.iscoroutinefunction(fn)
Summary: The model determines which tools to use through prompt engineering, that is, by providing structured descriptions of all tools and few-shot examples. On the other hand, Anthropic must have specifically trained Claude. After all, it is its own protocol, and Claude can better understand the tool prompts and output structured tool call JSON code.
Tool Execution and Result Feedback Mechanism
The execution of tools is relatively simple and straightforward. Continuing from the previous step, we send the system prompt (instructions and tool call descriptions) and the user message to the model together, and then receive the model's reply.
Execution Process
After the model analyzes the user's request, it decides whether to call a tool:
- When no tool is needed: The model directly generates a natural language reply
- When a tool is needed: The model outputs a structured JSON-formatted tool call request
If the reply contains a structured JSON-formatted tool call request, the client will execute the corresponding tool based on this JSON code.
Code Implementation
async def start(self):
# As introduced above, how the model selects tools
while True:
# Assume that the user message input has been processed here
messages.append({"role": "user", "content": user_input})
# Get the output of the LLM
llm_response = self.llm_client.get_response(messages)
# Process the output of the LLM (if there is a tool call, execute the corresponding tool)
result = await self.process_llm_response(llm_response)
# If the result is different from the llm_response, it means a tool call has been executed (there is additional information)
# Then resend the result of the tool call to the LLM for processing
if result != llm_response:
messages.append({"role": "assistant", "content": llm_response})
messages.append({"role": "system", "content": result})
final_response = self.llm_client.get_response(messages)
logging.info("\\nFinal response: %s", final_response)
messages.append(
{"role": "assistant", "content": final_response}
)
# Otherwise, it means no tool call has been executed, so directly return the output of the LLM to the user
else:
messages.append({"role": "assistant", "content": llm_response})
Error Handling
If there is a problem with the JSON code of the tool call or the model has hallucinations, the system will skip the invalid call request.
Conclusions and Practical Suggestions
Based on the above principle analysis, it can be seen that tool documentation is crucial. The model relies on the tool description text to understand and select applicable tools, which means that carefully written tool names, docstrings, and parameter descriptions are particularly important.
Given that the selection mechanism of MCP is based on prompts, theoretically, any model that can provide corresponding tool descriptions can be used compatibly with MCP.