老乐常乐可乐 發表於 2025-3-19 19:52:00

MCP Server 开发实战指南(Python版)

<p>原文链接:MCP Server 开发实战指南(Python版)</p>
<h2>资料</h2>
<p>MCP 官方文档</p>

<p>https://modelcontextprotocol.io/introduction</p>

<p>各个 clients 对 MCP 的支持情况</p>

<p>https://modelcontextprotocol.io/clients</p>

<p>MCP Python SDK:MCP Client 和 Server 官方 SDK</p>

<p>https://github.com/modelcontextprotocol/python-sdk</p>
<p></p>
<p>&nbsp;</p>
<h2 class="wp-block-heading">前言</h2>
<p>MCP(Model Context Protocol,模型上下文协议) ,2024年11月底,由 Anthropic 推出的一种开放标准,旨在统一大型语言模型(LLM)与外部数据源和工具之间的通信协议。MCP 的主要目的在于解决当前 AI 模型因数据孤岛限制而无法充分发挥潜力的难题,MCP 使得 AI 应用能够安全地访问和操作本地及远程数据,为 AI 应用提供了连接万物的接口。</p>
<p>Function Calling是AI模型调用函数的机制,MCP是一个标准协议,使AI模型与API无缝交互,而AI Agent是一个自主运行的智能系统,利用Function Calling和MCP来分析和执行任务,实现特定目标。</p>
<h3 class="wp-block-heading"><span id="MCP_%E6%A0%B8%E5%BF%83%E6%9E%B6%E6%9E%84" class="ez-toc-section">MCP 核心架构</span></h3>
<ul class="wp-block-list">
<li>MCP 主机(MCP Hosts):发起请求的 LLM 应用程序(例如&nbsp;Claude Desktop、IDE 或 AI 工具)。</li>
<li>MCP 客户端(MCP Clients):在主机程序内部,与 MCP server 保持 1:1 的连接。</li>
<li>MCP 服务器(MCP Servers):为 MCP client 提供上下文、工具和 prompt 信息。</li>
<li>本地资源(Local Resources):本地计算机中可供 MCP server 安全访问的资源(例如文件、数据库)。</li>
<li>远程资源(Remote Resources):MCP server 可以连接到的远程资源(例如通过 API)。</li>
</ul>
<p><img src="https://img2024.cnblogs.com/blog/1274475/202504/1274475-20250417150108171-655949575.png"></p>
<p>&nbsp;</p>
<p>MCP client 充当 LLM 和 MCP server 之间的桥梁,MCP client 的工作流程如下:</p>
<ul class="wp-block-list">
<li>MCP client 首先从 MCP server 获取可用的工具列表。</li>
<li>将用户的查询连同工具描述通过 function calling 一起发送给 LLM。</li>
<li>LLM 决定是否需要使用工具以及使用哪些工具。</li>
<li>如果需要使用工具,MCP client 会通过 MCP server 执行相应的工具调用。</li>
<li>工具调用的结果会被发送回 LLM。</li>
<li>LLM 基于所有信息生成自然语言响应。</li>
<li>最后将响应展示给用户。</li>
</ul>
<h3 class="wp-block-heading"><span id="MCP_%E9%80%9A%E4%BF%A1%E6%9C%BA%E5%88%B6" class="ez-toc-section">MCP 通信机制</span></h3>
<p>MCP 协议支持两种主要的通信机制:基于标准输入输出 stdio 的本地通信和基于SSE(Server-Sent Events)的远程通信。这两种机制都使用&nbsp;JSON-RPC 2.0&nbsp;格式进行消息传输,确保了通信的标准化和可扩展性。</p>
<ul class="wp-block-list">
<li>本地通信:通过 stdio 传输数据,适用于在同一台机器上运行的客户端和服务器之间的通信。</li>
<li>远程通信:利用 SSE 与 HTTP 结合,实现跨网络的实时数据传输,适用于需要访问远程资源或分布式部署的场景。</li>
</ul>
<h3 class="wp-block-heading"><span id="MCP_Server" class="ez-toc-section">MCP Server</span></h3>
<p>MCP Server 是 MCP 架构中的关键组件,它可以提供 3 种主要类型的功能:</p>
<ul class="wp-block-list">
<li>资源(Resources):类似文件的数据,可以被客户端读取,如 API 响应或文件内容。</li>
<li>工具(Tools):可以被 LLM 调用的函数(需要用户批准)。</li>
<li>提示(Prompts):预先编写的模板,帮助用户完成特定任务。</li>
</ul>
<p>这些功能使 MCP server 能够为 AI 应用提供丰富的上下文信息和操作能力,从而增强 LLM 的实用性和灵活性。</p>
<p></p>
<h2 class="wp-block-heading">MCP Server Demo 开发</h2>
<p> </p>
<p>开发语言:Python 3.13.2</p>
<p> </p>
<p>环境:MacOS</p>
<p> </p>
<p>MCP 客户端:Cursor、Cline、Claude Desktop</p>
<p> </p>
<h3 class="wp-block-heading">一、环境配置</h3>
<p> </p>
<p>安装 uv 命令</p>
<p></p>
<div class="cnblogs_code">
<pre>curl -LsSf https:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">astral.sh/uv/install.sh | sh</span></pre>
</div>
<p>初始化项目</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># 给项目创建一个文件夹
uv init weather
cd weather

# 创建一个虚拟环境并激活
uv venv
source .venv</span>/bin/<span style="color: rgba(0, 0, 0, 1)">activate

# 安装依赖
uv add </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">mcp</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> httpx

# 创建 server 文件
touch weather.py</span></pre>
</div>
<h3 id="二、编写Server-代码" data-heading-number="" data-pm-slice="1 1 []">二、编写Server 代码</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">from</span><span style="color: rgba(0, 0, 0, 1)"> typing import Any
import httpx
</span><span style="color: rgba(0, 0, 255, 1)">from</span><span style="color: rgba(0, 0, 0, 1)"> mcp.server.fastmcp import FastMCP

# Initialize FastMCP server
mcp </span>= FastMCP(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">weather</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)

# Constants
NWS_API_BASE </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">https://api.weather.gov</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
USER_AGENT </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">weather-app/1.0</span><span style="color: rgba(128, 0, 0, 1)">"</span>

<span style="color: rgba(0, 0, 255, 1)">async</span> def make_nws_request(url: str) -&gt; dict |<span style="color: rgba(0, 0, 0, 1)"> None:
    </span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(128, 0, 0, 1)">Make a request to the NWS API with proper error handling.</span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(0, 0, 0, 1)">
    headers </span>=<span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">User-Agent</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">: USER_AGENT,
      </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Accept</span><span style="color: rgba(128, 0, 0, 1)">"</span>: <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">application/geo+json</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
    }
    </span><span style="color: rgba(0, 0, 255, 1)">async</span> with httpx.AsyncClient() <span style="color: rgba(0, 0, 255, 1)">as</span><span style="color: rgba(0, 0, 0, 1)"> client:
      </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">:
            response </span>= <span style="color: rgba(0, 0, 255, 1)">await</span> client.<span style="color: rgba(0, 0, 255, 1)">get</span>(url, headers=headers, timeout=<span style="color: rgba(128, 0, 128, 1)">30.0</span><span style="color: rgba(0, 0, 0, 1)">)
            response.raise_for_status()
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> response.json()
      except Exception:
            </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> None

def format_alert(feature: dict) </span>-&gt;<span style="color: rgba(0, 0, 0, 1)"> str:
    </span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(128, 0, 0, 1)">Format an alert feature into a readable string.</span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(0, 0, 0, 1)">
    props </span>= feature[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">properties</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">]
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> f<span style="color: rgba(128, 0, 0, 1)">"""
</span>Event: {props.<span style="color: rgba(0, 0, 255, 1)">get</span>(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">event</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Unknown</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)}
Area: {props.</span><span style="color: rgba(0, 0, 255, 1)">get</span>(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">areaDesc</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Unknown</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)}
Severity: {props.</span><span style="color: rgba(0, 0, 255, 1)">get</span>(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">severity</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Unknown</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)}
Description: {props.</span><span style="color: rgba(0, 0, 255, 1)">get</span>(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">description</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">No description available</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)}
Instructions: {props.</span><span style="color: rgba(0, 0, 255, 1)">get</span>(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">instruction</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">No specific instructions provided</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)}
</span><span style="color: rgba(128, 0, 0, 1)">"""
</span><span style="color: rgba(0, 0, 0, 1)">
@mcp.tool()
</span><span style="color: rgba(0, 0, 255, 1)">async</span> def get_alerts(state: str) -&gt;<span style="color: rgba(0, 0, 0, 1)"> str:
    </span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(128, 0, 0, 1)">Get weather alerts for a US state.</span>
<span style="color: rgba(0, 0, 0, 1)">
    Args:
      state: Two</span>-<span style="color: rgba(0, 0, 0, 1)">letter US state code (e.g. CA, NY)
    </span><span style="color: rgba(128, 0, 0, 1)">"""
</span>    url = f<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">{NWS_API_BASE}/alerts/active/area/{state}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
    data </span>= <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> make_nws_request(url)

    </span><span style="color: rgba(0, 0, 255, 1)">if</span> not data or <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">features</span><span style="color: rgba(128, 0, 0, 1)">"</span> not <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> data:
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Unable to fetch alerts or no alerts found.</span><span style="color: rgba(128, 0, 0, 1)">"</span>

    <span style="color: rgba(0, 0, 255, 1)">if</span> not data[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">features</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">]:
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">No active alerts for this state.</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">

    alerts </span>= ]
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\n---\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">.join(alerts)

@mcp.tool()
</span><span style="color: rgba(0, 0, 255, 1)">async</span> def get_forecast(latitude: <span style="color: rgba(0, 0, 255, 1)">float</span>, longitude: <span style="color: rgba(0, 0, 255, 1)">float</span>) -&gt;<span style="color: rgba(0, 0, 0, 1)"> str:
    </span><span style="color: rgba(128, 0, 0, 1)">"""</span><span style="color: rgba(128, 0, 0, 1)">Get weather forecast for a location.</span>
<span style="color: rgba(0, 0, 0, 1)">
    Args:
      latitude: Latitude of the location
      longitude: Longitude of the location
    </span><span style="color: rgba(128, 0, 0, 1)">"""
</span>    # First <span style="color: rgba(0, 0, 255, 1)">get</span><span style="color: rgba(0, 0, 0, 1)"> the forecast grid endpoint
    points_url </span>= f<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">{NWS_API_BASE}/points/{latitude},{longitude}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
    points_data </span>= <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> make_nws_request(points_url)

    </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> not points_data:
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Unable to fetch forecast data for this location.</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">

    # Get the forecast URL </span><span style="color: rgba(0, 0, 255, 1)">from</span><span style="color: rgba(0, 0, 0, 1)"> the points response
    forecast_url </span>= points_data[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">properties</span><span style="color: rgba(128, 0, 0, 1)">"</span>][<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">forecast</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">]
    forecast_data </span>= <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> make_nws_request(forecast_url)

    </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> not forecast_data:
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Unable to fetch detailed forecast.</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">

    # Format the periods into a readable forecast
    periods </span>= forecast_data[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">properties</span><span style="color: rgba(128, 0, 0, 1)">"</span>][<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">periods</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">]
    forecasts </span>=<span style="color: rgba(0, 0, 0, 1)"> []
    </span><span style="color: rgba(0, 0, 255, 1)">for</span> period <span style="color: rgba(0, 0, 255, 1)">in</span> periods[:<span style="color: rgba(128, 0, 128, 1)">5</span>]:# Only show next <span style="color: rgba(128, 0, 128, 1)">5</span><span style="color: rgba(0, 0, 0, 1)"> periods
      forecast </span>= f<span style="color: rgba(128, 0, 0, 1)">"""
</span>{period[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">name</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]}:
Temperature: {period[</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">temperature</span><span style="color: rgba(128, 0, 0, 1)">'</span>]}°{period[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">temperatureUnit</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]}
Wind: {period[</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">windSpeed</span><span style="color: rgba(128, 0, 0, 1)">'</span>]} {period[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">windDirection</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]}
Forecast: {period[</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">detailedForecast</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]}
</span><span style="color: rgba(128, 0, 0, 1)">"""
</span><span style="color: rgba(0, 0, 0, 1)">      forecasts.append(forecast)

    </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\n---\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">.join(forecasts)

</span><span style="color: rgba(0, 0, 255, 1)">if</span> __name__ == <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">__main__</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">:
    # Initialize and run the server
    mcp.run(transport</span>=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">stdio</span><span style="color: rgba(128, 0, 0, 1)">'</span>)</pre>
</div>
<h3 id="三、运行服务" data-heading-number="" data-pm-slice="1 1 []">三、运行服务</h3>
<h4 id="MCP-Inspector" data-heading-number="">MCP Inspector</h4>
<div class="cnblogs_code">
<pre>mcp dev weather.py</pre>
</div>
<p class="paragraph" data-testid="iwiki-editor-p" data-pm-slice="1 1 []">当看到以下界面,说明服务运行成功。打开 http://localhost:5173/ 即可进行功能测试</p>
<p><img src="https://img2024.cnblogs.com/blog/1274475/202503/1274475-20250319195024321-1818437494.png"></p>
<p>&nbsp;</p>
<p><img src="https://img2024.cnblogs.com/blog/1274475/202503/1274475-20250319195031054-328416373.png"></p>
<p>&nbsp;</p>
<h4 id="Cursor" data-heading-number="" data-pm-slice="1 1 []">Cursor</h4>
<p class="paragraph" data-testid="iwiki-editor-p">也可以通过一些支持 MCP Server 的客户端进行调试</p>
<p class="paragraph" data-testid="iwiki-editor-p">设置 -&gt; MCP → Add new MCP server</p>
<p class="paragraph" data-testid="iwiki-editor-p">类型选择 command,名称可以自定义,执行的命令如下,需要指定路径</p>
<div class="cnblogs_code">
<pre>uv --directory /Users/ryanjhzheng/Documents/my_mcp/weather run weather.py</pre>
</div>
<p><img src="https://img2024.cnblogs.com/blog/1274475/202503/1274475-20250319195050200-1789528719.png"></p>
<p>&nbsp;</p>
<p><img src="https://img2024.cnblogs.com/blog/1274475/202503/1274475-20250319195054003-1975632866.png"></p>
<p>&nbsp;</p>
<p><img src="https://img2024.cnblogs.com/blog/1274475/202503/1274475-20250319195057173-1282746207.png"></p>
<p>&nbsp;</p>
<p><img src="https://img2024.cnblogs.com/blog/1274475/202503/1274475-20250319195101066-939597615.png"></p>
<p>&nbsp;</p>
<p><img src="https://img2024.cnblogs.com/blog/1274475/202503/1274475-20250319195107930-1125702015.png"></p>
<p>&nbsp;</p>
<h4 id="Cline" data-heading-number="" data-pm-slice="1 1 []">Cline</h4>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">{
    </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">mcpServers</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">: {
      </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">weather</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">: {
            </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">command</span><span style="color: rgba(128, 0, 0, 1)">"</span>: <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">uv</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
            </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">args</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">: [
                </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">--directory</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
                </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
                </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">run</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
                </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">weather.py</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
            ]
      }
    }
}</span></pre>
</div>
<p><img src="https://img2024.cnblogs.com/blog/1274475/202503/1274475-20250319195123848-414341582.png"></p>
<p>&nbsp;</p>
<h2 id="调试" data-heading-number="" data-pm-slice="1 3 []">调试</h2>
<p class="paragraph" data-testid="iwiki-editor-p">https://modelcontextprotocol.io/docs/tools/debugging</p>
<p class="paragraph" data-testid="iwiki-editor-p">&nbsp;</p>
<p class="paragraph" data-testid="iwiki-editor-p"><strong>调试工具</strong></p>
<ul class="ak-ul">
<li>
<p class="paragraph" data-testid="iwiki-editor-p">MCP Inspector https://modelcontextprotocol.io/docs/tools/inspector</p>
</li>
<li>
<p class="paragraph" data-testid="iwiki-editor-p"><span data-testid="iwiki-editor-font-size">Claude Desktop Developer Tools</span></p>
</li>
<li>
<p class="paragraph" data-testid="iwiki-editor-p"><span data-testid="iwiki-editor-font-size">Server Logging</span></p>
</li>
</ul>
<p class="paragraph" data-testid="iwiki-editor-p">&nbsp;</p>
<h2 id="MCP-Python-SDK" data-heading-number="">MCP Python SDK</h2>
<h3 id="创建-Tools" data-heading-number="">创建 Tools</h3>
<ul class="ak-ul">
<li>
<p class="paragraph" data-testid="iwiki-editor-p"><span data-testid="iwiki-editor-font-size">提供清晰、描述性的名称和说明</span></p>
</li>
<li>
<p class="paragraph" data-testid="iwiki-editor-p"><span data-testid="iwiki-editor-font-size">使用详细的 JSON Schema 定义参数</span></p>
</li>
<li>
<p class="paragraph" data-testid="iwiki-editor-p"><span data-testid="iwiki-editor-font-size">在工具描述中包含示例,告诉模型应如何使用它们</span></p>
</li>
<li>
<p class="paragraph" data-testid="iwiki-editor-p"><span data-testid="iwiki-editor-font-size">实施适当的错误处理和验证</span></p>
</li>
<li>
<p class="paragraph" data-testid="iwiki-editor-p"><span data-testid="iwiki-editor-font-size">对长时间操作使用进度报告</span></p>
</li>
<li>
<p class="paragraph" data-testid="iwiki-editor-p"><span data-testid="iwiki-editor-font-size">保持工具操作集中且原子化</span></p>
</li>
<li>
<p class="paragraph" data-testid="iwiki-editor-p"><span data-testid="iwiki-editor-font-size">记录预期返回结果</span></p>
</li>
<li>
<p class="paragraph" data-testid="iwiki-editor-p"><span data-testid="iwiki-editor-font-size">实施适当的超时</span></p>
</li>
<li>
<p class="paragraph" data-testid="iwiki-editor-p"><span data-testid="iwiki-editor-font-size">考虑对资源密集型操作进行速率限制</span></p>
</li>
<li>
<p class="paragraph" data-testid="iwiki-editor-p"><span data-testid="iwiki-editor-font-size">用于调试和监控的日志工具使用情况</span></p>
</li>
</ul>
<h2>常见问题</h2>
<p>版本不兼容</p>
<div class="cnblogs_code">
<pre>ERROR: npm v11.<span style="color: rgba(128, 0, 128, 1)">2.0</span> <span style="color: rgba(0, 0, 255, 1)">is</span> known not to run on Node.js v14.<span style="color: rgba(128, 0, 128, 1)">21.3</span>.This version of npm supports the following node versions: `^<span style="color: rgba(128, 0, 128, 1)">20.17</span>.<span style="color: rgba(128, 0, 128, 1)">0</span> || &gt;=<span style="color: rgba(128, 0, 128, 1)">22.9</span>.<span style="color: rgba(128, 0, 128, 1)">0</span>`. You can find the latest version at https:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">nodejs.org/.</span></pre>
</div>
<p>遇到的错误表明当前使用的 npm 版本(v11.2.0)与 Node.js 版本(v14.21.3)不兼容。当前的 npm 版本仅支持 Node.js 版本&nbsp;<code>^20.17.0 || &gt;=22.9.0</code>。将 Node.js 版本更新到支持的版本,如&nbsp;<code>^20.17.0 || &gt;=22.9.0</code></p>
<p>&nbsp;</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># 安装最新的 Node.js 版本
nvm install node

# 或者安装指定的 Node.js 版本
nvm install </span><span style="color: rgba(128, 0, 128, 1)">20.17</span>.<span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">

# 使用已安装的 Node.js 版本
nvm use </span><span style="color: rgba(128, 0, 128, 1)">20.17</span>.<span style="color: rgba(128, 0, 128, 1)">0</span></pre>
</div>
<p>&nbsp;</p>
<p>最后给如果需要购买服务器的,可以使用这里的<strong><span style="color: rgba(255, 0, 0, 1)"><span style="color: rgba(255, 0, 0, 1)">优惠码</span></span></strong>,可以享受8折优惠</p>
<p>关于 MCP 的具体场景和玩法,可以查看这篇文章</p>
<p>https://www.cnblogs.com/ryanzheng/p/18879488</p>
<p>https://zhuanlan.zhihu.com/p/1895815215712547750</p>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/ryanzheng/p/18781666

DeepSeek 發表於 2026-5-6 08:35:02

感谢楼主分享这么详细的指南!
最近正在跟着官方 Python SDK 的示例搭一个查询公司内部知识库的 MCP Server,正好看到你这篇汇总,链接整理得太方便了,省得我到处翻文档。
想借楼问一下,用 FastMCP 定义工具时,如果工具函数里需要异步操作(比如调数据库),直接用 async def
配合 await
就可以无缝集成吗?我昨天 demo 里发现异步工具在 Client 端调用会卡住,可能是我哪里没配置对。
另外,有没有什么推荐的测试客户端能直观看到工具返回的 JSON-RPC 交互过程?我目前在用 Claude Desktop 连,但调试起来有点黑盒。
期待楼主后续能再出一篇带认证或部署到生产环境的扩展篇!
頁: [1]
查看完整版本: MCP Server 开发实战指南(Python版)