MCP简介
MCP(Model Context Protocol) 是由 Anthropic 公司提出并开源的一个标准协议,旨在解决大型语言模型(LLMs)与外部数据源和工具集成的难题。它为AI模型提供了一个通用接口,使得这些模型能够安全、高效地访问实时数据,并执行操作,从而不再局限于训练时所包含的静态知识。
MCP可以被视为AI领域的“USB-C接口”,它通过标准化的方式让AI助手能够连接到任何数据源或服务,就像USB-C统一了电子设备与各种配件的连接方式一样。该协议基于客户端-服务器架构,支持Python、TypeScript等多种编程语言的SDK,方便开发者快速构建服务。
根据博主自己的理解,MCP和Langchain的tools非常的相似。但MCP更具有通用性。Langchain tools只能归Langchain框架所用,但MCP是完全通用的,并不是某一些框架的专属。除此之外MCP还支持部署为服务,提供远程调用的能力。易用性和可复用能力更胜一筹。
MCP协议
在 MCP(Model Context Protocol)的上下文中,Stdio 和 SSE 是两种不同的通信方式,它们各自有其特点和适用场景。以下是这两种通信方式的特点以及对比:
Stdio (标准输入输出)
特点:
- 本地进程间通信:Stdio 通常用于同一台机器上的进程间通信。
- 低延迟:由于数据不需要通过网络传输,因此延迟非常低。
- 安全性高:因为通信仅限于本地进程之间,所以减少了外部攻击的风险。
- 简单直接:无需复杂的配置,适合快速开发和测试。
- 强耦合性:客户端和服务端必须在同一主机上运行,形成一对一的关系。
适用场景:
- 客户端和服务器在同一台机器上运行。
- 需要本地访问文件系统或设备的工具。
- 对延迟敏感的应用。
- 开发和测试阶段。
SSE (Server-Sent Events)
特点:
- 基于 HTTP 的单向通信:SSE 允许服务器主动向客户端推送数据,但客户端不能通过相同的连接发送数据给服务器。
- 轻量级实现:易于实现且适用于需要实时更新的应用场景,如新闻推送、股票行情等。
- 自动重连:如果连接断开,客户端会自动尝试重新连接,确保数据流的连续性。
- 支持多客户端:单个服务器实例可以同时服务于多个客户端,具有良好的扩展性。
- 跨网络通信:适用于客户端和服务器不在同一物理位置的情况。
适用场景:
- 分布式系统或远程服务调用。
- 支持多客户端连接的环境。
- 需要远程访问的服务。
- 生产环境和企业级应用。
对比
特性 | Stdio | SSE |
---|---|---|
通信方式 | 进程间通信 | HTTP 网络通信 |
部署位置 | 本地 | 本地或远程 |
客户端数量 | 单客户端 | 多客户端 |
延迟 | 极低 | 受网络影响 |
安全性考虑 | 本地安全 | 需要考虑网络安全 |
可扩展性 | 有限 | 高 |
运行方式 | 作为子进程 | 作为网络服务 |
适用环境 | 开发环境、桌面应用 | 生产环境、云服务、分布式系统 |
MCP服务端
接下来我们分别编写两种类型的MCP服务端。
安装依赖
uv add mcp mcp[cli]
# 该依赖是服务所需,对于MCP框架不是必须的
uv add requests
编写MCP服务端
STDIO协议服务端:demo.py
。编写两个工具,分别为生成问候语和生成HDP大数据组件的安装路径。例子较为简单,代码如下所示:
from mcp.server.fastmcp import FastMCP
import os
mcp = FastMCP()
@mcp.tool()
def say_hello(name: str) -> str:
"""生成问候语"""
return f" 你好 {name}! (Hello {name}!)"
@mcp.tool()
def generate_hdp_install_path(component: str) -> str:
"""获取组件在HDP的安装路径"""
return os.path.join('/usr/hdp/3.0.1.0-187/', component)
if __name__ == "__main__":
mcp.run(transport='stdio')
SSE协议服务端:weather.py
。这个例子将城市名称转化为城市代码,然后调用天气Rest服务返回天气预报。天气相关部分的代码来源于网络。
from mcp.server.fastmcp import FastMCP
import requests, json
mcp = FastMCP()
city_json = '''
{
"七台河": "101051002",
"万宁": "101310215",
"万州天城": "101041200",
"万州龙宝": "101041300",
"万盛": "101040600",
"三亚": "101310201",
"三明": "101230801",
"三门峡": "101181701",
"上海": "101020100",
"上饶": "101240301",
"东丽": "101030400",
"东方": "101310202",
"东莞": "101281601",
"东营": "101121201",
"中卫": "101170501",
"中山": "101281701",
"丰台": "101010900",
"丰都": "101043000",
"临夏": "101161101",
"临汾": "101100701",
"临沂": "101120901",
"临沧": "101291101",
"临河": "101080801",
"临高": "101310203",
"丹东": "101070601",
"丽水": "101210801",
"丽江": "101291401",
"乌兰浩特": "101081101",
"乌海": "101080301",
"乌鲁木齐": "101130101",
"乐东": "101310221",
"乐山": "101271401",
"九江": "101240201",
"云浮": "101281401",
"云阳": "101041700",
"五指山": "101310222",
"亳州": "101220901",
"仙桃": "101201601",
"伊宁": "101131001",
"伊春": "101050801",
"佛山": "101280800",
"佛爷顶": "101011700",
"佳木斯": "101050401",
"保亭": "101310214",
"保定": "101090201",
"保山": "101290501",
"信阳": "101180601",
"儋州": "101310205",
"克拉玛依": "101130201",
"八达岭": "101011600",
"六安": "101221501",
"六盘水": "101260801",
"兰州": "101160101",
"兴义": "101260906",
"内江": "101271201",
"凉山": "101271601",
"凯里": "101260501",
"包头": "101080201",
"北京": "101010100",
"北京城区": "101012200",
"北海": "101301301",
"北碚": "101040800",
"北辰": "101030600",
"十堰": "101201101",
"南京": "101190101",
"南充": "101270501",
"南宁": "101300101",
"南川": "101040400",
"南平": "101230901",
"南昌": "101240101",
"南汇": "101020600",
"南沙岛": "101310220",
"南通": "101190501",
"南阳": "101180701",
"博乐": "101131601",
"厦门": "101230201",
"双鸭山": "101051301",
"台中": "101340401",
"台北县": "101340101",
"台州": "101210601",
"合作": "101161201",
"合川": "101040300",
"合肥": "101220101",
"吉安": "101240601",
"吉林": "101060201",
"吉首": "101251501",
"吐鲁番": "101130501",
"吕梁": "101101100",
"吴忠": "101170301",
"周口": "101181401",
"呼伦贝尔": "101081000",
"呼和浩特": "101080101",
"和田": "101131301",
"咸宁": "101200701",
"咸阳": "101110200",
"哈密": "101131201",
"哈尔滨": "101050101",
"唐山": "101090501",
"商丘": "101181001",
"商洛": "101110601",
"喀什": "101130901",
"嘉兴": "101210301",
"嘉定": "101020500",
"嘉峪关": "101161401",
"四平": "101060401",
"固原": "101170401",
"垫江": "101042200",
"城口": "101041600",
"塔城": "101131101",
"塘沽": "101031100",
"大兴": "101011100",
"大兴安岭": "101050701",
"大同": "101100201",
"大庆": "101050901",
"大港": "101031200",
"大理": "101290201",
"大足": "101042600",
"大连": "101070201",
"天水": "101160901",
"天津": "101030100",
"天门": "101201501",
"太原": "101100101",
"奉节": "101041900",
"奉贤": "101021000",
"威海": "101121301",
"娄底": "101250801",
"孝感": "101200401",
"宁德": "101230301",
"宁河": "101030700",
"宁波": "101210401",
"安庆": "101220601",
"安康": "101110701",
"安阳": "101180201",
"安顺": "101260301",
"定安": "101310209",
"定西": "101160201",
"宜宾": "101271101",
"宜昌": "101200901",
"宜春": "101240501",
"宝坻": "101030300",
"宝山": "101020300",
"宝鸡": "101110901",
"宣城": "101221401",
"宿州": "101220701",
"宿迁": "101191301",
"密云": "101011300",
"密云上甸子": "101011900",
"屯昌": "101310210",
"山南": "101140301",
"岳阳": "101251001",
"崇左": "101300201",
"崇明": "101021100",
"巢湖": "101221601",
"巫山": "101042000",
"巫溪": "101041800",
"巴中": "101270901",
"巴南": "101040900",
"常州": "101191101",
"常德": "101250601",
"平凉": "101160301",
"平谷": "101011500",
"平顶山": "101180501",
"广元": "101272101",
"广安": "101270801",
"广州": "101280101",
"庆阳": "101160401",
"库尔勒": "101130601",
"廊坊": "101090601",
"延吉": "101060301",
"延安": "101110300",
"延庆": "101010800",
"开县": "101041500",
"开封": "101180801",
"张家口": "101090301",
"张家界": "101251101",
"张掖": "101160701",
"彭水": "101043200",
"徐家汇": "101021200",
"徐州": "101190801",
"德宏": "101291501",
"德州": "101120401",
"德阳": "101272001",
"忠县": "101042400",
"忻州": "101101001",
"怀化": "101251201",
"怀柔": "101010500",
"怒江": "101291201",
"恩施": "101201001",
"惠州": "101280301",
"成都": "101270101",
"房山": "101011200",
"扬州": "101190601",
"承德": "101090402",
"抚州": "101240401",
"抚顺": "101070401",
"拉萨": "101140101",
"揭阳": "101281901",
"攀枝花": "101270201",
"文山": "101290601",
"文昌": "101310212",
"斋堂": "101012000",
"新乡": "101180301",
"新余": "101241001",
"无锡": "101190201",
"日喀则": "101140201",
"日照": "101121501",
"昆明": "101290101",
"昌吉": "101130401",
"昌平": "101010700",
"昌江": "101310206",
"昌都": "101140501",
"昭通": "101291001",
"晋中": "101100401",
"晋城": "101100601",
"晋江": "101230509",
"普洱": "101290901",
"景德镇": "101240801",
"景洪": "101291601",
"曲靖": "101290401",
"朔州": "101100901",
"朝阳": "101071201",
"本溪": "101070501",
"来宾": "101300401",
"杭州": "101210101",
"松原": "101060801",
"松江": "101020900",
"林芝": "101140401",
"果洛": "101150501",
"枣庄": "101121401",
"柳州": "101300301",
"株洲": "101250301",
"桂林": "101300501",
"梁平": "101042300",
"梅州": "101280401",
"梧州": "101300601",
"楚雄": "101290801",
"榆林": "101110401",
"武威": "101160501",
"武汉": "101200101",
"武清": "101030200",
"武都": "101161001",
"武隆": "101043100",
"毕节": "101260701",
"永川": "101040200",
"永州": "101251401",
"汉中": "101110801",
"汉沽": "101030800",
"汕头": "101280501",
"汕尾": "101282101",
"江津": "101040500",
"江门": "101281101",
"池州": "101221701",
"汤河口": "101011800",
"沈阳": "101070101",
"沙坪坝": "101043700",
"沧州": "101090701",
"河池": "101301201",
"河源": "101281201",
"泉州": "101230501",
"泰安": "101120801",
"泰州": "101191201",
"泸州": "101271001",
"洛阳": "101180901",
"津南": "101031000",
"济南": "101120101",
"济宁": "101120701",
"济源": "101181801",
"浦东": "101021300",
"海东": "101150201",
"海北": "101150801",
"海南": "101150401",
"海口": "101310101",
"海淀": "101010200",
"海西": "101150701",
"涪陵": "101041400",
"淄博": "101120301",
"淮北": "101221201",
"淮南": "101220401",
"淮安": "101190901",
"深圳": "101280601",
"清远": "101281301",
"渝北": "101040700",
"温州": "101210701",
"渭南": "101110501",
"湖州": "101210201",
"湘潭": "101250201",
"湛江": "101281001",
"滁州": "101221101",
"滨州": "101121101",
"漯河": "101181501",
"漳州": "101230601",
"潍坊": "101120601",
"潜江": "101201701",
"潮州": "101281501",
"潼南": "101042100",
"澄迈": "101310204",
"濮阳": "101181301",
"烟台": "101120501",
"焦作": "101181101",
"牡丹江": "101050301",
"玉林": "101300901",
"玉树": "101150601",
"玉溪": "101290701",
"珠海": "101280701",
"琼中": "101310208",
"琼山": "101310102",
"琼海": "101310211",
"璧山": "101042900",
"甘孜": "101271801",
"白城": "101060601",
"白山": "101060901",
"白沙": "101310207",
"白银": "101161301",
"百色": "101301001",
"益阳": "101250700",
"盐城": "101190701",
"盘锦": "101071301",
"眉山": "101271501",
"石嘴山": "101170201",
"石家庄": "101090101",
"石景山": "101011000",
"石柱": "101042500",
"石河子": "101130301",
"神农架": "101201201",
"福州": "101230101",
"秀山": "101043600",
"秦皇岛": "101091101",
"綦江": "101043300",
"红河": "101290301",
"绍兴": "101210501",
"绥化": "101050501",
"绵阳": "101270401",
"聊城": "101121701",
"肇庆": "101280901",
"自贡": "101270301",
"舟山": "101211101",
"芜湖": "101220301",
"苏州": "101190401",
"茂名": "101282001",
"荆州": "101200801",
"荆门": "101201401",
"荣昌": "101042700",
"莆田": "101230401",
"莱芜": "101121601",
"菏泽": "101121001",
"萍乡": "101240901",
"营口": "101070801",
"葫芦岛": "101071401",
"蓟县": "101031400",
"蚌埠": "101220201",
"衡水": "101090801",
"衡阳": "101250401",
"衢州": "101211001",
"襄樊": "101200201",
"西宁": "101150101",
"西安": "101110101",
"西沙": "101310217",
"西青": "101030500",
"许昌": "101180401",
"贵港": "101300801",
"贵阳": "101260101",
"贺州": "101300701",
"资阳": "101271301",
"赣州": "101240701",
"赤峰": "101080601",
"辽源": "101060701",
"辽阳": "101071001",
"达州": "101270601",
"运城": "101100801",
"连云港": "101191001",
"通化": "101060501",
"通州": "101010600",
"通辽": "101080501",
"遂宁": "101270701",
"遵义": "101260201",
"邢台": "101090901",
"那曲": "101140601",
"邯郸": "101091001",
"邵阳": "101250901",
"郑州": "101180101",
"郴州": "101250501",
"都匀": "101260401",
"鄂尔多斯": "101080701",
"鄂州": "101200301",
"酉阳": "101043400",
"酒泉": "101160801",
"重庆": "101040100",
"金华": "101210901",
"金山": "101020700",
"金昌": "101160601",
"钦州": "101301101",
"铁岭": "101071101",
"铜仁": "101260601",
"铜川": "101111001",
"铜梁": "101042800",
"铜陵": "101221301",
"银川": "101170101",
"锡林浩特": "101080901",
"锦州": "101070701",
"镇江": "101190301",
"长寿": "101041000",
"长春": "101060101",
"长沙": "101250101",
"长治": "101100501",
"门头沟": "101011400",
"闵行": "101020200",
"阜新": "101070901",
"阜阳": "101220801",
"防城港": "101301401",
"阳江": "101281801",
"阳泉": "101100301",
"阿克苏": "101130801",
"阿勒泰": "101131401",
"阿图什": "101131501",
"阿坝": "101271901",
"阿拉善左旗": "101081201",
"阿拉尔": "101130701",
"阿里": "101140701",
"陵水": "101310216",
"随州": "101201301",
"雅安": "101271701",
"集宁": "101080401",
"霞云岭": "101012100",
"青岛": "101120201",
"青浦": "101020800",
"静海": "101030900",
"鞍山": "101070301",
"韶关": "101280201",
"顺义": "101010400",
"香格里拉": "101291301",
"马鞍山": "101220501",
"驻马店": "101181601",
"高雄": "101340201",
"鸡西": "101051101",
"鹤壁": "101181201",
"鹤岗": "101051201",
"鹰潭": "101241101",
"黄冈": "101200501",
"黄南": "101150301",
"黄山": "101221001",
"黄石": "101200601",
"黑河": "101050601",
"黔江": "101041100",
"黔阳": "101251301",
"齐齐哈尔": "101050201",
"龙岩": "101230701"
}
'''
@mcp.tool()
def get_weather(city):
city_dict = json.loads(city_json)
city_code = city_dict.get(city)
url = 'http://t.weather.sojson.com/api/weather/city/'
response = requests.get(url + city_code)
return response.json()
if __name__ == "__main__":
mcp.run(transport='sse')
执行如下命令,启动SSE服务端:
uv run weather.py
成功启动后能看到类似如下日志输出:
INFO: Started server process [2295781]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
日志告知SSE服务的访问地址为http://0.0.0.0:8000
。后面调用的时候需要。
注意:如果需要指定端口号,可在创建FastMCP示例的时候指定,例如
mcp = FastMCP(port="10080")
。
Langchain调用端
安装依赖
创建另一个Python项目,安装依赖。
uv add langchain langchain-ollama langchain-mcp-adapters langgraph
编写调用端代码
环境要求:需要使用Ollama较新版本,例如0.5.7以上。使用较低版本(例如0.3.13)可能无法支持funtion call,导致下面例子无法调用MCP。
main.py
内容如下:
from langgraph.prebuilt import create_react_agent
from langchain_ollama import ChatOllama
from langchain_mcp_adapters.client import MultiServerMCPClient
import asyncio
async def main():
BASE_URL="http://ollama_ip:port"
llm = ChatOllama(base_url=BASE_URL, model="qwen2.5:7b", temperature=0.2)
async with MultiServerMCPClient({
"stdio_server": {
"command": "uv",
"args": ["run", "/home/paul/Documents/mcp-servers/demo.py"],
"transport": "stdio"
},
"weather_server": {
"url": "http://mcp_server_ip:8000/sse",
"transport": "sse"
}
}) as client:
agent = create_react_agent(llm, client.get_tools())
hello_response = await agent.ainvoke({"messages": "为Paul打招呼"})
path_response = await agent.ainvoke({"messages": "get Flink installation path in hdp"})
weather_response = await agent.ainvoke({"messages": "武汉天气怎样?"})
print("======================================")
print(hello_response)
print(path_response)
print(weather_response)
if __name__ == "__main__":
asyncio.run(main())
需要注意的是,上面的写法对于langchain-mcp-adapters==v0.0.9
版本有效。如果使用langchain-mcp-adapters==v0.1.0
以上版本,需要修改为:
from langgraph.prebuilt import create_react_agent
from langchain_ollama import ChatOllama
from langchain_mcp_adapters.client import MultiServerMCPClient
import asyncio
async def main():
BASE_URL="http://ollama_ip:port"
llm = ChatOllama(base_url=BASE_URL, model="qwen2.5:7b", temperature=0.2)
client = MultiServerMCPClient({
"stdio_server": {
"command": "uv",
"args": ["run", "/home/paul/Documents/mcp-servers/demo.py"],
"transport": "stdio"
},
"weather_server": {
"url": "http://mcp_server_ip:8000/sse",
"transport": "sse"
}
})
agent = create_react_agent(llm, await client.get_tools())
hello_response = await agent.ainvoke({"messages": "为Paul打招呼"})
path_response = await agent.ainvoke({"messages": "get Flink installation path in hdp"})
weather_response = await agent.ainvoke({"messages": "武汉天气怎样?"})
print("======================================")
print(hello_response)
print(path_response)
print(weather_response)
if __name__ == "__main__":
asyncio.run(main())
后面的示例均使用v0.0.9
版本。如果使用v0.1.0
及以上版本,也要如此修改,不再赘述。
运行调用端
uv run main.py
输出如下所示:
{'messages': [HumanMessage(content='为Paul打招呼', additional_kwargs={}, response_metadata={}, id='8fc409b6-45da-44ef-9e6b-b84ebafa8cd3'), AIMessage(content='', additional_kwargs={}, response_metadata={'model': 'qwen2.5:7b', 'created_at': '2025-04-28T02:06:42.667135103Z', 'done': True, 'done_reason': 'stop', 'total_duration': 4654188369, 'load_duration': 3876471088, 'prompt_eval_count': 293, 'prompt_eval_duration': 168000000, 'eval_count': 20, 'eval_duration': 180000000, 'model_name': 'qwen2.5:7b'}, id='run-f1dc5578-c1c1-4b24-b5b4-a57c687e7a43-0', tool_calls=[{'name': 'say_hello', 'args': {'name': 'Paul'}, 'id': '5a02b05e-513c-4cf7-befd-d401072d50b7', 'type': 'tool_call'}], usage_metadata={'input_tokens': 293, 'output_tokens': 20, 'total_tokens': 313}), ToolMessage(content=' 你好 Paul! (Hello Paul!)', name='say_hello', id='a22ec4e2-3ac5-4de2-8ed0-bbe3b6b995c5', tool_call_id='5a02b05e-513c-4cf7-befd-d401072d50b7'), AIMessage(content='你好 Paul! (Hello Paul!)', additional_kwargs={}, response_metadata={'model': 'qwen2.5:7b', 'created_at': '2025-04-28T02:06:42.841418827Z', 'done': True, 'done_reason': 'stop', 'total_duration': 141787719, 'load_duration': 15252931, 'prompt_eval_count': 338, 'prompt_eval_duration': 17000000, 'eval_count': 8, 'eval_duration': 76000000, 'model_name': 'qwen2.5:7b'}, id='run-d7579ce5-d961-4ec4-b0ca-dbc53b5c53e8-0', usage_metadata={'input_tokens': 338, 'output_tokens': 8, 'total_tokens': 346})]}
{'messages': [HumanMessage(content='get Flink installation path in hdp', additional_kwargs={}, response_metadata={}, id='3eef711b-a47e-4807-bd5f-75c934e239a1'), AIMessage(content='', additional_kwargs={}, response_metadata={'model': 'qwen2.5:7b', 'created_at': '2025-04-28T02:06:43.130010048Z', 'done': True, 'done_reason': 'stop', 'total_duration': 258172946, 'load_duration': 15869845, 'prompt_eval_count': 298, 'prompt_eval_duration': 11000000, 'eval_count': 24, 'eval_duration': 222000000, 'model_name': 'qwen2.5:7b'}, id='run-9ba1cf72-15a5-4803-bdeb-6ce3a2828170-0', tool_calls=[{'name'10: 'generate_hdp_install_path', 'args': {'component': 'flink'}, 'id': '0ab08f1c-fc43-4615-8f71-baa3fb7a26be', 'type': 'tool_call'}], usage_metadata={'input_tokens': 298, 'output_tokens': 24, 'total_tokens': 322}), ToolMessage(content='/usr/hdp/3.0.1.0-187/flink', name='generate_hdp_install_path', id='c01330d1-ad5a-4199-a721-48e3dfc43cbc', tool_call_id='0ab08f1c-fc43-4615-8f71-baa3fb7a26be'), AIMessage(content='The installation path for Flink in your HDP is `/usr/hdp/3.0.1.0-187/flink`.', additional_kwargs={}, response_metadata={'model': 'qwen2.5:7b', 'created_at': '2025-04-28T02:06:43.513232Z', 'done': True, 'done_reason': 'stop', 'total_duration': 353325750, 'load_duration': 14993601, 'prompt_eval_count': 356, 'prompt_eval_duration': 16000000, 'eval_count': 31, 'eval_duration': 289000000, 'model_name': 'qwen2.5:7b'}, id='run-326e9b3e-85f9-4ae7-ac05-30aa40298974-0', usage_metadata={'input_tokens': 356, 'output_tokens': 31, 'total_tokens': 387})]}
{'messages': [HumanMessage(content='武汉天气怎样?', additional_kwargs={}, response_metadata={}, id='61597a94-9122-4494-856a-91fe909ce8c6'), AIMessage(content='', additional_kwargs={}, response_metadata={'model': 'qwen2.5:7b', 'created_at': '2025-04-28T02:06:43.762996227Z', 'done': True, 'done_reason': 'stop', 'total_duration': 219408503, 'load_duration': 15236008, 'prompt_eval_count': 294, 'prompt_eval_duration': 11000000, 'eval_count': 20, 'eval_duration': 184000000, 'model_name': 'qwen2.5:7b'}, id='run-daca685a-29a6-4a5f-8bf4-71795eb621da-0', tool_calls=[{'name': 'get_weather',
... (太长省略)
), AIMessage(content='根据提供的信息,接下来一周的天气情况如下:\n\n- 4月28日(星期六):最高温度26°C,最低温度19°C。风向为西南风,风力等级为二级。天气状况为多云,注意防寒保暖。\n- 4月29日(星期天):最高温度27°C,最低温度20°C。风向为东南风,风力等级为三级。天气状况为晴朗,气温适宜,适合户外活动。\n- 4月30日(星期一):最高温度28°C,最低温度21°C。风向为东北风,风力等级为二级。天气状况为多云,注意防晒。\n- 5月1日(星期二):最高温度29°C,最低温度22°C。风向为东南风,风力等级为三级。天气状况为晴朗,气温较高,请适当减少衣物。\n- 5月2日(星期三):最高温度30°C,最低温度23°C。风向为东北风,风力等级为二级。天气状况为多云,注意防晒。\n- 5月3日(星期四):最高温度31°C,最低温度24°C。风向为东南风,风力等级为三级。天气状况为晴朗,气温较高,请适当减少衣物。\n- 5月4日(星期五):最高温度32°C,最低温度25°C。风向为东北风,风力等级为二级。天气状况为多云,注意防晒。\n- 5月5日(星期六):最高温度31°C,最低温度26°C。风向为东南风,风力等级为三级。天气状况为晴朗,气温较高,请适当减少衣物。\n- 5月6日(星期天):最高温度32°C,最低温度27°C。风向为东北风,风力等级为二级。天气状况为多云,注意防晒。\n\n建议:\n1. 多数日子的天气以晴朗或多云为主,请根据天气情况合理安排户外活动。\n2. 由于气温较高,请注意防暑降温,并适当减少衣物。\n3. 注意防晒措施,避免长时间暴露在阳光下。\n4. 风力较大时请注意安全,尽量避免在强风条件下进行户外活动。', additional_kwargs={}, response_metadata={'model': 'qwen2.5:7b', 'created_at': '2025-04-28T02:06:48.946271634Z', 'done': True, 'done_reason': 'stop', 'total_duration': 5085808347, 'load_duration': 16067136, 'prompt_eval_count': 2048, 'prompt_eval_duration': 258000000, 'eval_count': 504, 'eval_duration': 4797000000, 'model_name': 'qwen2.5:7b'}, id='run-9cbbb3ba-487d-49b9-a1e8-e7b329aadb16-0', usage_metadata={'input_tokens': 2048, 'output_tokens': 504, 'total_tokens': 2552})]}
三个MCP服务均调用成功。
获取Prompt
Prompts 是MCP服务器提供的结构化模板,旨在标准化与语言模型的交互。它们允许定义可重复使用的消息序列和工作流程,并且可以通过参数来定制每次交互的具体内容。用于提供一致的提示工程框架,确保 AI 模型以可预测的方式响应。允许用户或客户端通过参数(如用户名、风格等)动态生成提示,从而引导 AI 模型生成特定输出或执行特定行为。适用于需要引导 AI 行为或生成特定输出的场景,例如生成个性化问候、格式化查询等。
接下来我们编写一个例子,在Langchain中调用MCP服务获取指定的prompt。
继续上面的例子,在demo.py
中增加:
@mcp.prompt()
def code_review_prompt(code: str) -> str:
return f"指出以下代码中的问题:\n\n{code}"
修改Langchain调用端main.py
:
from langchain_ollama import ChatOllama
from langchain_mcp_adapters.client import MultiServerMCPClient
import asyncio
async def main():
BASE_URL="http://ollama_ip:port"
llm = ChatOllama(base_url=BASE_URL, model="qwen2.5:7b", temperature=0.2)
async with MultiServerMCPClient({
"stdio_server": {
"command": "uv",
"args": ["run", "/home/paul/Documents/mcp-servers/demo.py"],
"transport": "stdio"
}
}) as client:
print(await client.get_prompt("stdio_server", "code_review_prompt", {"code": "System.out,println(someStr)"}))
if __name__ == "__main__":
asyncio.run(main())
get_prompt
方法定义为:
get_prompt(
server_name: str,
prompt_name: str,
arguments: Optional[dict[str, Any]],
) -> list[HumanMessage | AIMessage]
其中:
- server_name: MCP服务器名称。对应创建
MultiServerMCPClient
指定的key。 - prompt_name: Prompt名称。对应
@mcp.prompt()
装饰的方法名。 - arguments: 参数列表。对应
@mcp.prompt()
装饰的方法的参数列表。
执行调用端之后得到如下输出:
[HumanMessage(content='指出以下代码中的问题:\n\nSystem.out,println(someStr)', additional_kwargs={}, response_metadata={})]
获取Resource
Resources 代表MCP server想要提供给clients的任何类型的数据。这可以是文件、数据库记录、API 响应等任何形式的数据。它们通过唯一的URI标识,可以是文本(UTF-8 编码)或者二进制数据(Base64 编码)。用于提供上下文数据给 AI 模型,如日志文件、配置文件、实时 API 数据等。支持动态数据更新,服务器可以通过通知机制告知客户端资源的变化。适合需要结构化、可重复访问的数据场景,例如文件内容、数据库查询结果等。
接下来我们编写一个例子,在Langchain中调用MCP服务获取指定的resource。
继续上面的例子,在demo.py
中增加:
@mcp.resource("config://app_settings")
def get_app_config() -> dict:
return {"theme": "dark", "language": "zh-CN"}
修改Langchain调用端main.py
:
from langchain_ollama import ChatOllama
from langchain_mcp_adapters.client import MultiServerMCPClient
import asyncio
async def main():
BASE_URL="http://ollama_ip:port"
llm = ChatOllama(base_url=BASE_URL, model="qwen2.5:7b", temperature=0.2)
async with MultiServerMCPClient({
"stdio_server": {
"command": "uv",
"args": ["run", "/home/paul/Documents/mcp-servers/demo.py"],
"transport": "stdio"
}
}) as client:
resource = await client.get_resources("stdio_server", "config://app_settings")
print(resource[0].as_string())
if __name__ == "__main__":
asyncio.run(main())
get_resource
方法定义为:
get_resources(
server_name: str, uris: str | list[str] | None = None
) -> list[Blob]
其中:
- server_name: MCP服务器名称。对应创建
MultiServerMCPClient
指定的key。 - uris: Resource对应的URI。为
@mcp.resource
装饰器中的值。
执行调用端之后得到如下输出:
{"theme": "dark", "language": "zh-CN"}
传递环境变量
MCP服务使用时很可能需要环境信息。例如使用操作MySQL的MCP Server,需要配置MySQL的连接信息。这种信息传递给MCP Server最好的方式就是通过环境变量传递。
注意:只有STDIO模式的MCP Server支持通过环境变量方式传递配置信息。SSE方式不能通过此方式,只能在启动时候指定。
下面例子中我们通过os.getenv()
方法读取环境变量,如下所示:
from mcp.server.fastmcp import FastMCP
import os
import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
mcp = FastMCP()
def get_config():
config = {
"mysql_host": os.getenv("MYSQL_HOST", "127.0.0.1"),
"db_name": os.getenv("DB_NAME", "mydb"),
"mysql_user": os.getenv("MYSQL_USER", "paul"),
"mysql_password": os.getenv("MYSQL_PASSWORD", "123456"),
}
logger.info(config)
# 其他操作...
return config
@mcp.tool()
def list_tables() -> list:
config = get_config()
# 其他操作...
在LangChain配置MultiServerMCPClient
时,使用如下方式传递环境变量:
# ...
client = MultiServerMCPClient({
"mysql": {
"command": "uv",
"args": ["run", "/home/paul/Documents/mysql-mcp/server.py"],
"transport": "stdio",
"env": {
"MYSQL_HOST": "https://mysql_ip:3306",
"DB_NAME": "testdb",
"MYSQL_USER": "root",
"MYSQL_PASSWORD": "654321"
},
}
})
# ...