MCP入门级简单尝试

前言

既然MCP都已经出现了,甚至已经纳入面试题目了,就简单尝试一下这个新玩意儿。

建立一个最简单的stdio工具

我们可以从其他的一些博客中了解到,MCP本质上就是一个MCP Server配上一个MMCP Client>,然后MCP Client就可以调用MCP Server提供的服务。

既然如此,我们的首要任务也就是建立一个相当简易的MCP Server

无参

既然要尽可能简单,那就输出Hello, World!吧。

所以,我们的方法就可以定义出来:

python 复制代码
def hello() -> str: return "Hello, World!"

为了让MCP发现他是一个工具类方法,我们再加一个装饰器:

python 复制代码
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("HelloServer")

@mcp.tool(description="Say hello to the world")
def hello() -> str: return "Hello, World!"

if __name__ == "__main__":
    mcp.run()

看上去没啥问题,我们把这段写进server/basic_server.py文件中。

通过查看源码,我们可以知道,我们没有指定host(主机域名或IP)、port(主机开放端口)、transport(服务提供方式),于是MCP会给我们分配一个默认的配置:

  • host127.0.0.1
  • port8000
  • transportstdio

也就是说,上述代码,我们创立了一个运行在命令行中的MCP服务器。

然后,我们就可以在client中调用MCP了:

python 复制代码
import asyncio
from mcp import ClientSession, StdioServerParameters, types
from mcp.client.stdio import stdio_client

async def hello() -> None:
    # 通过 stdio 启动本地的 server.py
    server = StdioServerParameters(
        command="python",
        args=["servers/basic_server.py"],
    )

    # 连接并初始化会话
    async with stdio_client(server) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()

            # 调用 "hello" 工具
            result = await session.call_tool("hello")

            # 打印文本类型的返回内容
            for c in result.content:
                if isinstance(c, types.TextContent):
                    print(c.text)
                    break

if __name__ == "__main__":
    asyncio.run(hello())

可以看到,官方大量使用了async进行异步传输,所以我们也将大量使用awaitasyncio进行异步传输。

我们最后在执行这个客户端的时候,他会首先利用StdioServerParameters从终端启动MCP Server,然后再启动MCP Client,最后进行异步交互。

在交互过程中,我们拿到所有的输出,然后选择我们需要的输出。按道理来说,这个案例里面只会输出TextContent类型的结果,我们也只取其中的text属性。

也就是说,在MCP Client执行的终端里面,我们会看到Hello World!MCP Server那边因为没有单独启动,所以什么都没有。

有参

既然无参弄完了,那就试试有参?

就比如说,我输入什么东西,他都会返回:Hello, <your-input>

说来也简单,就是每个都带上参数就好了:

python 复制代码
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("HelloServer")

@mcp.tool(description="Say hello to anything")
def hello(text: str) -> str: return f"Hello, {text}!"

if __name__ == "__main__":
    mcp.run()

整挺好。写进servers/param_server.py中。

客户端也改一下:

python 复制代码
import asyncio
from mcp import ClientSession, StdioServerParameters, types
from mcp.client.stdio import stdio_client

async def hello(text: str) -> None:
    # 通过 stdio 启动本地的 server.py
    server = StdioServerParameters(
        command="python",
        args=["servers/param_server.py"],
    )

    # 连接并初始化会话
    async with stdio_client(server) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()

            # 调用 "hello" 工具
            result = await session.call_tool("hello", {"text": text})

            # 打印文本类型的返回内容
            for c in result.content:
                if isinstance(c, types.TextContent):
                    print(c.text)
                    break

if __name__ == "__main__":
    asyncio.run(hello("LangChain"))

于是,客户端就会输出:Hello, LangChain!

改用HTTP传输方式

我们可以从其他的博客中,看到传输方式包含stdiossehttp三种。因为stdio过于受限,sse又逐步暴露出更多的缺点,所以接下来的案例就直接上http了。

首先,因为默认给出来的就是stdio,所以我们首先要改一下MCP Server的传输方式配置,就像这样:

python 复制代码
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("HelloServer")

@mcp.tool(description="Say hello to anything")
def hello(text: str) -> str: return f"Hello, {text}!"

if __name__ == "__main__":
    mcp.run(transport="streamable-http")

虽然说,上面把transport参数和hostport参数放在了一起,但实际上他们在不同的位置起作用。transport参数在run中指定,而hostport参数在FastMCP构造函数中指定。

当然,我们还可以看源码,发现run(transport="streamable-http")实际等同于run_streamable_http_async(),因此我们还可以这么写:

python 复制代码
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("HelloServer")

@mcp.tool(description="Say hello to anything")
def hello(text: str) -> str: return f"Hello, {text}!"

if __name__ == "__main__":
    mcp.run_streamable_http_async()

值得注意的是,我们在其中并没有指定访问路由。这是因为MCP Server默认访问路由就是http://127.0.0.1:8000/mcp/。如果需要改动,同样在构造函数中指明:

python 复制代码
mcp = FastMCP(
    name = "HelloServer",           # 名称,可以为None
    host = "localhost",             # 默认值
    port = 8000,                    # 默认值
    streamable_http_path = "/mcp"   # 默认值
)

然后客户端的改动说大也不大:

python 复制代码
import asyncio
from mcp import ClientSession, types
from mcp.client.streamable_http import streamablehttp_client

async def hello(text: str):
    # Connect to HTTP streaming server
    async with streamablehttp_client("http://localhost:8000/mcp/") as (read, write, get_session_id_callback):
        async with ClientSession(read, write) as session:
            await session.initialize()

            # Call "hello" tool
            result = await session.call_tool("hello", {"text": text})

            # Print text-type return content
            for c in result.content:
                if isinstance(c, types.TextContent):
                    print(c.text)
                    break

if __name__ == "__main__":
    asyncio.run(hello("LangChain"))

这里值得注意的是,streamablehttp_client包含三个内容:MemoryObjectReceiveStreamMemoryObjectSendStreamGetSessionIdCallback,从字面意义上区分也就是readwriteget_session_id_callback。而ClientSession的构造函数中,大量参数与上述三个的交集只有两个,分别是MemoryObjectReceiveStreamMemoryObjectSendStream,也就是只需要readwrite

剩下的就没变化了。

日志解析

虽然说在客户端这边只有一个Hello Langchain!,但是由于HTTP请求会单独拉起一个MCP Server,所以在MCP Server日志中,会产生一些日志:

text 复制代码
INFO:     127.0.0.1:37280 - "POST /mcp/ HTTP/1.1" 307 Temporary Redirect
[08/13/25 15:55:33] INFO     Created new transport with session ID: 6cf4fa2b3f8941cba2aff10439e268f9                          streamable_http_manager.py:233
INFO:     127.0.0.1:37280 - "POST /mcp HTTP/1.1" 200 OK
INFO:     127.0.0.1:37294 - "POST /mcp/ HTTP/1.1" 307 Temporary Redirect
INFO:     127.0.0.1:37308 - "GET /mcp/ HTTP/1.1" 307 Temporary Redirect
INFO:     127.0.0.1:37294 - "POST /mcp HTTP/1.1" 202 Accepted
INFO:     127.0.0.1:37308 - "GET /mcp HTTP/1.1" 200 OK
INFO:     127.0.0.1:37322 - "POST /mcp/ HTTP/1.1" 307 Temporary Redirect
INFO:     127.0.0.1:37322 - "POST /mcp HTTP/1.1" 200 OK
                    INFO     Processing request of type CallToolRequest server.py:625
INFO:     127.0.0.1:37328 - "POST /mcp/ HTTP/1.1" 307 Temporary Redirect
INFO:     127.0.0.1:37328 - "POST /mcp HTTP/1.1" 200 OK
                    INFO     Processing request of type ListToolsRequest    server.py:625
INFO:     127.0.0.1:37344 - "DELETE /mcp/ HTTP/1.1" 307 Temporary Redirect
                    INFO     Terminating session: 6cf4fa2b3f8941cba2aff10439e268f9  streamable_http.py:630
INFO:     127.0.0.1:37344 - "DELETE /mcp HTTP/1.1" 200 OK

这么大一串,其实看下来就是这么个流程:

  • 首先,默认服务是/mcp,你请求到了/mcp/,没事,给你掰回去(Redirecting);
  • 掰回去了,对上号了,用GET请求给你发一个号牌(SessionId);
  • 然后,收到了一个POST请求:CallToolRequest
  • 根据请求,先查一下字典里有没有这项服务:ListToolsRequest;
  • 执行请求并返回;
  • 结束会话(DELETE

这一套流程走完之后,一个依赖HTTPMCP请求就这样结束了。

相关推荐
数据科学作家1 小时前
学数据分析必囤!数据分析必看!清华社9本书覆盖Stata/SPSS/Python全阶段学习路径
人工智能·python·机器学习·数据分析·统计·stata·spss
HXQ_晴天2 小时前
CASToR 生成的文件进行转换
python
java1234_小锋3 小时前
Scikit-learn Python机器学习 - 特征预处理 - 标准化 (Standardization):StandardScaler
python·机器学习·scikit-learn
Python×CATIA工业智造3 小时前
Python带状态生成器完全指南:从基础到高并发系统设计
python·pycharm
向qian看_-_3 小时前
Linux 使用pip报错(error: externally-managed-environment )解决方案
linux·python·pip
Nicole-----4 小时前
Python - Union联合类型注解
开发语言·python
Eric.5657 小时前
python advance -----object-oriented
python
云天徽上7 小时前
【数据可视化-107】2025年1-7月全国出口总额Top 10省市数据分析:用Python和Pyecharts打造炫酷可视化大屏
开发语言·python·信息可视化·数据挖掘·数据分析·pyecharts
THMAIL7 小时前
机器学习从入门到精通 - 数据预处理实战秘籍:清洗、转换与特征工程入门
人工智能·python·算法·机器学习·数据挖掘·逻辑回归
@HNUSTer7 小时前
Python数据可视化科技图表绘制系列教程(六)
python·数据可视化·科技论文·专业制图·科研图表