MCP 简介和使用

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={})]

参考链接:https://github.langchain.ac.cn/langgraph/reference/mcp/#langchain_mcp_adapters.client.MultiServerMCPClient.get_prompt

获取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"}

参考链接:https://github.langchain.ac.cn/langgraph/reference/mcp/#langchain_mcp_adapters.client.MultiServerMCPClient.get_resources

传递环境变量

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"
        },
    }
})

# ...
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容