内容简介
介绍MCP和LLM之间的协作细节,讲解LLM是如何利用MCP服务来扩展自己的边际能力的。
在用go做了几个MCP的Demo服务后,又对MCP的架构和协议细节进行了深入地学习,对MCP的理解深刻了很多。但是在开发过程中还是有两个关键的问题未得到解答:
- cline插件、我的MCP服务、大模型这三者之间的调用流程是怎样的?
- 大模型是在什么时候确定使用哪些MCP服务的呢?
这里需要注意下,我使用的是vscode的cline插件,所以这里拿cline举例,但是其实客户端也可以是cursor、cherry studio等其他客户端。
在 MCP 官网为我们提供了一个解释:
- 客户端将你的问题发送给 Claude
- Claude 分析可用的工具,并决定使用哪一个或多个
- 客户端通过 MCP Server 执行所选的工具
- 工具的执行结果被送回给 Claude
- Claude 结合执行结果生成回答
- 回应最终展示给用户
从以上的解释可以看出,大模型和MCP服务之间的调用过程是分两步完成的:
- 由 LLM 确定使用哪些 MCP Server
- 执行对应的 MCP Server 并对执行结果进行重新处理
所以 MCP Server 是由大模型主动选择并调用的。但是大模型具体又是如何确定该使用哪些工具呢?从 MCP 官方提供的 pyhton client example 中可以得到答案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
| async def start(self) -> None:
"""Main chat session handler."""
try:
for server in self.servers:
try:
await server.initialize()
except Exception as e:
logging.error(f"Failed to initialize server: {e}")
await self.cleanup_servers()
return
all_tools = []
for server in self.servers:
tools = await server.list_tools()
all_tools.extend(tools)
tools_description = "\n".join([tool.format_for_llm() for tool in all_tools])
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:
try:
user_input = input("You: ").strip().lower()
if user_input in ["quit", "exit"]:
logging.info("\nExiting...")
break
messages.append({"role": "user", "content": user_input})
llm_response = self.llm_client.get_response(messages)
logging.info("\nAssistant: %s", llm_response)
result = await self.process_llm_response(llm_response)
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}
)
else:
messages.append({"role": "assistant", "content": llm_response})
except KeyboardInterrupt:
logging.info("\nExiting...")
break
finally:
await self.cleanup_servers()
... # 省略其他代码
|
从代码可以看出,在和大模型进行交互前,将所有工具的结构化描述放到tools_description
中,再添加到system_message
中,然后把system_message
和用户消息一起发送给模型。当模型分析用户请求后,它会决定是否需要调用工具: