1. Prompt
我们可以在服务端提前定义好一套完整的提示词,当客户端需要完成某个任务时,直接从服务器上获取提示词,再把提示词交给大模型,可以使得我们的任务更加流畅的完成。
1.1 代码审查
比如我们可以预先定义好以下提示词。
#代码审查
请审查以下{{language}}语言的代码,集中在{{focusArea}}部分:
{{language}}{{code}}
在使用的时候,可以传入 language=python
、focusArea=for
循环code=...
参数:
请审查以下python语言的代码,集中在for循环部分:`
for x in range(100):
print(x)
1.2 数据分析
请将{{month}}月的销售数据进行分析,分析部分重点为:
- 1.哪个商品销售量最多
- 2.哪个商品销售最少
- 3.哪个商品销售额最多
代码实现:新建一个文件夹Prompt_mcp,然后再其下创建两个py文件,分别为server.py和client.py。然后再创建一个data文件夹,用于存放要进行处理的政策数据
2. 服务端
服务端中通过@app.prompt()
装饰器即可添加提示词模板。
比如对于文件内容要点的提取,对政策文件归纳的示例代码如下
from mcp.server.fastmcp import FastMCP
app = FastMCP()
@app.prompt()
def policy_prompt(policy:str):
'''
能够对用户提供的政策内容,对其进行总结、提取关键信息的提示词模板
:param policy: 需要总结的政策内容
:return: 总结政策的提示词模板
'''
# 如果直接返回一个字符串,那么在客户端那边,接收到的是一个PromptMessage对象
# #这个对象默认的role=user。当然也可以返回其他的role,那么就必须定义成字典的形式
# #[{"role":"system""content": xxx}]
return [{
"role":"user",
"content":f'''
这个是政策内容:“{policy}”,请对该政策内容进行总结,总结的规则为:
1.提取政策要点。
2.针对每个政策要点按照以下格式进行总结
*要点标题:政策的标题,要包含具体的政策信息
*针对人群:政策针对的人群
*有效时间:政策执行的开始时间和结束时间
*相关部门:政策是由哪些部门执行总结的内容不要太官方,用通俗易懂的语言。
'''
}]
if __name__ == '__main__':
app.run(transport='sse')
对于提示词设置返回值时,需要注意上面代码注释的部分:
- 如果直接返回一个字符串,那么在客户端那边,接收到的是一个
PromptMessage
对象 - 这个对象默认的
role=user
。当然也可以返回其他的role
,那么就必须定义成字典的形式 - 比如:
[{"role":"system""content": xxx}]
3. 客户端
3.1 客户端调试过程
和之前Tool
和Resource
类似,创建session
会话之后,可以通过list_prompts()
函数获取服务端所有的提示词,然后通过session.get_prompt
即可获取指定的提示词。客户端示例代码如下:
from mcp.client.sse import sse_client
from mcp import ClientSession
from openai import OpenAI
from contextlib import AsyncExitStack
import asyncio
class MCPClient:
def __init__(self):
self.deepseek = OpenAI(
api_key="sk-5d307e0a4xxxxxxe4575ff9",
base_url="https://api.deepseek.com"
)
self.exit_stack = AsyncExitStack()
self.prompts = {} #注意这里修改
async def run(self, query):
# 1. 创建read_stream、write_stream
read_stream, write_stream = await self.exit_stack.enter_async_context(sse_client(url="http://127.0.0.1:8000/sse"))
# 2. 创建session对象
session: ClientSession = await self.exit_stack.enter_async_context(ClientSession(read_stream, write_stream))
# 3. 初始化
await session.initialize()
# 4. 获取服务端的所有提示词
prompts = (await session.list_prompts()).prompts
print(prompts)
async def aclose(self):
await self.exit_stack.aclose()
async def main():
client = MCPClient()
try:
with open("data/policy.txt",mode="r",encoding="utf-8") as f:
policy = f.read()
await client.run(f"帮我总结这个政策:{policy}")
finally:
await client.aclose()
if __name__ == '__main__':
asyncio.run(main())
代码执行结果如下:(run
函数中,前三步都是一样的,不需要修改,从第四步开始需要根据输出进行调试。最后面的主函数,就是对于一个文件的读写,可以直接使用同步函数,此时异步和同步没有区别了,注意指定的路径和编码形式)
获得的prompts
是一个列表,包含了服务端的所有的提示词,因此进一步进行遍历循环,获取里面的详细信息。代码如下:
functions = []
# 4. 获取所有的提示词
prompts = (await session.list_prompts()).prompts
# print(prompts)
for prompt in prompts:
name =prompt.name
description = prompt.description
prompt_arguments = prompt.arguments
# 仔细看,可发现这一步后续没有用到
self.prompts[name] = {
"name" : name,
"description" : description,
"arguments" : prompt_arguments,
}
functions.append({
"type":"function",
"function":{
"name":name,
"description":description,
"input_schema": None
}
})
# 创建消息发送给大模型
messages = [{
"role": "user",
"content": query
}]
deepseek_response = self.deepseek.chat.completions.create(
messages=messages,
model="deepseek-chat",
tools=functions
)
print(deepseek_response)
执行代码输出如下:(注意输出结果中的格式,可以按照逐层获取相应的消息。此外,在之前构建resource
服务项目中,创建self.resources
收集数据,是用来获取uri
,而在prompt
服务项目中不需要此参数,所有在仿造前面的框架进行设置时,可以发现这里的self.prompts
在添加数据后,在之后的代码中并没有使用)
通过对mcp中的Tool、Resource、Resource Template和Prompt服务项目的总结,可以发现: 在进行遍历循环时候,获取信息是针对具体的sesiion
会话的读取服务端的需求
- Tool中是
session.call_tool(name=function_name, arguments=function_arguments)
.即需要获取名称和参数 - Resource中是
session.read_resource(uri)
,需要获取uri,而uri是在所有资源列表中,可以通过名称访问,所以在类对象初始化时候创建了一个self.resources容器存放资源信息。 - Resource Templatez中也是
session.read_resource(uri)
,和前面一样 - Prompt中是
session.get_prompt(name=function_name,arguments=function_arguments)
.即需要获取名称和参数
综上分析,可知在Promp服务项目创建中,类对象初始化设置self.prompts
容器是没有必要的。
比如获取里面的function
信息。
逐层获取代码为:deepseek_response.choices[0].message.tool_calls[0].function
,执行输出如下
进一步,就是将大模型选择tool的message(过程)添加到messages中和选择具体的tool(结果)添加到messages中。相当于两个阶段的信息都添加到messages中,让大模型返回的结果更加精准。
chioce = deepseek_response.choices[0]
if chioce.finish_reason == "tool_calls":
# 将大模型选择tool的message添加到messages中
# messages中现在已经包含了一个工具选择的message
messages.append(chioce.message.model_dump()) #第一处添加,应该是在if判断下面,而不是在if上面
tool_call = chioce.message.tool_calls[0]
function = tool_call.function
function_name = function.name
function_arguments = json.loads(function.arguments) #输出的格式可以已看出是字符串,需要转字典
result = await session.get_prompt(name=function_name,arguments=function_arguments)
# print(result)
content = result.messages[0].content.text
# 先添加tool的message 第二处添加
messages.append({
"role":"tool",
"content": content,
"tool_call_id":tool_call.id
})
# 再将message发送给大模型返回最终结果
respose = self.deepseek.chat.completions.create(
model="deepseek-chat",
messages=messages,
)
print(respose.choices[0].message.content)
执行代码后,输出的结果如下:(受限于截图窗口大小,只截取部分内容。可以看出经过大模型处理的政策文件输出结果基本满足预期构想)
在执行上述的操作过程中,可以通过打印相应的结果变量进行查看,对于arguments
参数的处理,由于刚刚接触MCP服务项目开发,前两次估计很容易遗忘或者感觉到别扭,但是结合着输出结果和敲的次数多了,就知道在把变量传入到session
会话之前需要处理一下格式,不能直接传入一个字符串数据类型,比如打印function
变量输出结果,如下(注意看下面输出的红框中是字符串数据类型,即引号包含住了字典结构)
关于第二次使用大模型时,发送messages
中的内容问题,经过几个项目实战下来,可以发现,这里的roler
就是指tool
,然后为啥是tool
的思考,就是在于第一次时候我们把大模型选择工具的这个信息(过程)添加到messages
中了,因此,再调用时候还需要添加一个对应信息的编号,这个编号就对应着tool_call_id
。这个过程的理解可以通过自己改变role
的角色和删除 "tool_call_id":tool_call.id
这行代码执行,根据数据结果进一步理解和体会。(只有多实践,测试,才会更加理解这个过程)
messages.append({
"role":"tool",
"content": content,
"tool_call_id":tool_call.id
})
3.2 客户端全部代码
"""
-------------------------------------------------------------------------------
@Project : MCP projects
File : client.py
Time : 2025-06-05 8:23
author : musen
Email : xianl828@163.com
-------------------------------------------------------------------------------
"""
import json
from mcp.client.sse import sse_client
from mcp import ClientSession
from openai import OpenAI
from contextlib import AsyncExitStack
import asyncio
class MCPClient:
def __init__(self):
self.deepseek = OpenAI(
api_key="sk-5d307e0xxxxxxxx575ff9",
base_url="https://api.deepseek.com"
)
self.exit_stack = AsyncExitStack()
async def run(self, query):
# 1. 创建read_stream、write_stream
read_stream, write_stream = await self.exit_stack.enter_async_context(sse_client(url="http://127.0.0.1:8000/sse"))
# 2. 创建session对象
session: ClientSession = await self.exit_stack.enter_async_context(ClientSession(read_stream, write_stream))
# 3. 初始化
await session.initialize()
# 4. 获取所有的提示词
functions = []
prompts = (await session.list_prompts()).prompts
# print(prompts)
for prompt in prompts:
functions.append({
"type":"function",
"function":{
"name":prompt.name,
"description":prompt.description,
"input_schema": None
}
})
# 创建消息发送给大模型
messages = [{
"role": "user",
"content": query
}]
deepseek_response = self.deepseek.chat.completions.create(
messages=messages,
model="deepseek-chat",
tools=functions
)
chioce = deepseek_response.choices[0]
# print(chioce)
if chioce.finish_reason == "tool_calls":
# 将大模型选择tool的message添加到messages中
# messages中现在已经包含了一个工具选择的message
messages.append(chioce.message.model_dump())
tool_call = chioce.message.tool_calls[0]
function = tool_call.function
function_name = function.name
print(function)
function_arguments = json.loads(function.arguments) #输出的格式可以已看出是字符串,需要转字典
result = await session.get_prompt(name=function_name,arguments=function_arguments)
# print(result)
content = result.messages[0].content.text
# 先添加tool的message
messages.append({
"role":"tool",
"content": content,
"tool_call_id":tool_call.id
})
# 再将message发送给大模型返回最终结果
respose = self.deepseek.chat.completions.create(
model="deepseek-chat",
messages=messages,
)
print(respose.choices[0].message.content)
async def aclose(self):
await self.exit_stack.aclose()
async def main():
client = MCPClient()
try:
with open("data/policy.txt",mode="r",encoding="utf-8") as f:
policy = f.read()
await client.run(f"帮我总结这个政策:{policy}")
finally:
await client.aclose()
if __name__ == '__main__':
asyncio.run(main())
至此,关于MCP中的Prompt服务项目搭建步骤详解就梳理完毕,完结撒花✿✿ヽ(°▽°)ノ✿。