MCP工具部署并发布流程
本文聚焦 MCP 工具服务开发,以高德天气工具为核心案例,深入解析开发过程中的关键技术与实践问题。通过构建城市名称与高德城市代码的映射表,解决了自然语言输入与 API 参数要求不匹配的难题,实现用户直接通过城市名称查询天气;同时,针对 API 调用中可能出现的无数据返回问题,详细阐述了从城市代码校验、API 密钥权限检查到网络状态排查的全流程解决方案。此外,结合异步请求库httpx实现高效 API
·
环境搭建
1:本地环境搭建使用编辑器打开对应的项目目录
2:使用uv来进行管理环境
# 如果没有创建uv,可以直接通过以下命令来进行安装
pip install uv
# 进入对应目录下创建项目
uv init mcp-server-weather
cd mcp-server-weather
# 创建uv环境
uv venv mcp-server-weather # 或者也可以直接使用 uv venv 也可以
# 安装对应安装包,缺什么依赖直接安装即可
uv pip install mcp
uv pip install numpy
3:编写MCP服务代码
# 直接在main方法中删除新环境中代码,使用自己的代码即可
# 下面是我给出的一个简单的示例,主要是用来获取天气情况,基于高德地图
import httpx
from mcp.server.fastmcp import FastMCP
import os
import argparse
# 初始化 MCP 服务器
mcp = FastMCP("mcp-gaode-weather-server-unique")
class GaodeWeatherTool:
def __init__(self, api_key):
"""初始化高德天气工具"""
self.api_key = api_key
self.base_url = "https://restapi.amap.com/v3/weather/weatherInfo"
self.headers = {"User-Agent": "weather-app/1.0"}
# 城市名称到高德代码的映射表(中国主要城市)
self.city_code_map = {
# 直辖市
"北京": "110000",
"上海": "310000",
"天津": "120000",
"重庆": "500000",
# 河北省
"石家庄": "130100",
"唐山": "130200",
"秦皇岛": "130300",
"邯郸": "130400",
"邢台": "130500",
"保定": "130600",
"张家口": "130700",
"承德": "130800",
"沧州": "130900",
"廊坊": "131000",
"衡水": "131100",
# 山西省
"太原": "140100",
"大同": "140200",
"阳泉": "140300",
"长治": "140400",
"晋城": "140500",
"朔州": "140600",
"晋中": "140700",
"运城": "140800",
"忻州": "140900",
"临汾": "141000",
"吕梁": "141100",
# 内蒙古自治区
"呼和浩特": "150100",
"包头": "150200",
"乌海": "150300",
"赤峰": "150400",
"通辽": "150500",
"鄂尔多斯": "150600",
"呼伦贝尔": "150700",
"巴彦淖尔": "150800",
"乌兰察布": "150900",
"兴安盟": "152200",
"锡林郭勒盟": "152500",
"阿拉善盟": "152900",
# 辽宁省
"沈阳": "210100",
"大连": "210200",
"鞍山": "210300",
"抚顺": "210400",
"本溪": "210500",
"丹东": "210600",
"锦州": "210700",
"营口": "210800",
"阜新": "210900",
"辽阳": "211000",
"盘锦": "211100",
"铁岭": "211200",
"朝阳": "211300",
"葫芦岛": "211400",
# 吉林省
"长春": "220100",
"吉林": "220200",
"四平": "220300",
"辽源": "220400",
"通化": "220500",
"白山": "220600",
"松原": "220700",
"白城": "220800",
"延边朝鲜族自治州": "222400",
# 黑龙江省
"哈尔滨": "230100",
"齐齐哈尔": "230200",
"鸡西": "230300",
"鹤岗": "230400",
"双鸭山": "230500",
"大庆": "230600",
"伊春": "230700",
"佳木斯": "230800",
"七台河": "230900",
"牡丹江": "231000",
"黑河": "231100",
"绥化": "231200",
"大兴安岭地区": "232700",
# 江苏省
"南京": "320100",
"无锡": "320200",
"徐州": "320300",
"常州": "320400",
"苏州": "320500",
"南通": "320600",
"连云港": "320700",
"淮安": "320800",
"盐城": "320900",
"扬州": "321000",
"镇江": "321100",
"泰州": "321200",
"宿迁": "321300",
# 浙江省
"杭州": "330100",
"宁波": "330200",
"温州": "330300",
"嘉兴": "330400",
"湖州": "330500",
"绍兴": "330600",
"金华": "330700",
"衢州": "330800",
"舟山": "330900",
"台州": "331000",
"丽水": "331100",
# 安徽省
"合肥": "340100",
"芜湖": "340200",
"蚌埠": "340300",
"淮南": "340400",
"马鞍山": "340500",
"淮北": "340600",
"铜陵": "340700",
"安庆": "340800",
"黄山": "341000",
"滁州": "341100",
"阜阳": "341200",
"宿州": "341300",
"巢湖": "341400",
"六安": "341500",
"亳州": "341600",
"池州": "341700",
"宣城": "341800",
# 福建省
"福州": "350100",
"厦门": "350200",
"莆田": "350300",
"三明": "350400",
"泉州": "350500",
"漳州": "350600",
"南平": "350700",
"龙岩": "350800",
"宁德": "350900",
# 江西省
"南昌": "360100",
"景德镇": "360200",
"萍乡": "360300",
"九江": "360400",
"新余": "360500",
"鹰潭": "360600",
"赣州": "360700",
"吉安": "360800",
"宜春": "360900",
"抚州": "361000",
"上饶": "361100",
# 山东省
"济南": "370100",
"青岛": "370200",
"淄博": "370300",
"枣庄": "370400",
"东营": "370500",
"烟台": "370600",
"潍坊": "370700",
"济宁": "370800",
"泰安": "370900",
"威海": "371000",
"日照": "371100",
"莱芜": "371200",
"临沂": "371300",
"德州": "371400",
"聊城": "371500",
"滨州": "371600",
"菏泽": "371700",
# 河南省
"郑州": "410100",
"开封": "410200",
"洛阳": "410300",
"平顶山": "410400",
"安阳": "410500",
"鹤壁": "410600",
"新乡": "410700",
"焦作": "410800",
"濮阳": "410900",
"许昌": "411000",
"漯河": "411100",
"三门峡": "411200",
"南阳": "411300",
"商丘": "411400",
"信阳": "411500",
"周口": "411600",
"驻马店": "411700",
"济源": "419001",
# 湖北省
"武汉": "420100",
"黄石": "420200",
"十堰": "420300",
"宜昌": "420500",
"襄阳": "420600",
"鄂州": "420700",
"荆门": "420800",
"孝感": "420900",
"荆州": "421000",
"黄冈": "421100",
"咸宁": "421200",
"随州": "421300",
"恩施土家族苗族自治州": "422800",
"仙桃": "429004",
"潜江": "429005",
"天门": "429006",
"神农架林区": "429021",
# 湖南省
"长沙": "430100",
"株洲": "430200",
"湘潭": "430300",
"衡阳": "430400",
"邵阳": "430500",
"岳阳": "430600",
"常德": "430700",
"张家界": "430800",
"益阳": "430900",
"郴州": "431000",
"永州": "431100",
"怀化": "431200",
"娄底": "431300",
"湘西土家族苗族自治州": "433100",
# 广东省
"广州": "440100",
"韶关": "440200",
"深圳": "440300",
"珠海": "440400",
"汕头": "440500",
"佛山": "440600",
"江门": "440700",
"湛江": "440800",
"茂名": "440900",
"肇庆": "441200",
"惠州": "441300",
"梅州": "441400",
"汕尾": "441500",
"河源": "441600",
"阳江": "441700",
"清远": "441800",
"东莞": "441900",
"中山": "442000",
"潮州": "445100",
"揭阳": "445200",
"云浮": "445300",
# 广西壮族自治区
"南宁": "450100",
"柳州": "450200",
"桂林": "450300",
"梧州": "450400",
"北海": "450500",
"防城港": "450600",
"钦州": "450700",
"贵港": "450800",
"玉林": "450900",
"百色": "451000",
"贺州": "451100",
"河池": "451200",
"来宾": "451300",
"崇左": "451400",
# 海南省
"海口": "460100",
"三亚": "460200",
"三沙": "460300",
"儋州": "460400",
# 四川省
"成都": "510100",
"自贡": "510300",
"攀枝花": "510400",
"泸州": "510500",
"德阳": "510600",
"绵阳": "510700",
"广元": "510800",
"遂宁": "510900",
"内江": "511000",
"乐山": "511100",
"南充": "511300",
"眉山": "511400",
"宜宾": "511500",
"广安": "511600",
"达州": "511700",
"雅安": "511800",
"巴中": "511900",
"资阳": "512000",
"阿坝藏族羌族自治州": "513200",
"甘孜藏族自治州": "513300",
"凉山彝族自治州": "513400",
# 贵州省
"贵阳": "520100",
"六盘水": "520200",
"遵义": "520300",
"安顺": "520400",
"毕节": "520500",
"铜仁": "520600",
"黔西南布依族苗族自治州": "522300",
"黔东南苗族侗族自治州": "522600",
"黔南布依族苗族自治州": "522700",
# 云南省
"昆明": "530100",
"曲靖": "530300",
"玉溪": "530400",
"保山": "530500",
"昭通": "530600",
"丽江": "530700",
"普洱": "530800",
"临沧": "530900",
"楚雄彝族自治州": "532300",
"红河哈尼族彝族自治州": "532500",
"文山壮族苗族自治州": "532600",
"西双版纳傣族自治州": "532800",
"大理白族自治州": "532900",
"德宏傣族景颇族自治州": "533100",
"怒江傈僳族自治州": "533300",
"迪庆藏族自治州": "533400",
# 西藏自治区
"拉萨": "540100",
"日喀则": "540200",
"昌都": "540300",
"林芝": "540400",
"山南": "540500",
"那曲": "540600",
"阿里地区": "542500",
# 陕西省
"西安": "610100",
"铜川": "610200",
"宝鸡": "610300",
"咸阳": "610400",
"渭南": "610500",
"延安": "610600",
"汉中": "610700",
"榆林": "610800",
"安康": "610900",
"商洛": "611000",
# 甘肃省
"兰州": "620100",
"嘉峪关": "620200",
"金昌": "620300",
"白银": "620400",
"天水": "620500",
"武威": "620600",
"张掖": "620700",
"平凉": "620800",
"酒泉": "620900",
"庆阳": "621000",
"定西": "621100",
"陇南": "621200",
"临夏回族自治州": "622900",
"甘南藏族自治州": "623000",
# 青海省
"西宁": "630100",
"海东": "630200",
"海北藏族自治州": "632200",
"黄南藏族自治州": "632300",
"海南藏族自治州": "632500",
"果洛藏族自治州": "632600",
"玉树藏族自治州": "632700",
"海西蒙古族藏族自治州": "632800",
# 宁夏回族自治区
"银川": "640100",
"石嘴山": "640200",
"吴忠": "640300",
"固原": "640400",
"中卫": "640500",
# 新疆维吾尔自治区
"乌鲁木齐": "650100",
"克拉玛依": "650200",
"吐鲁番": "650400",
"哈密": "650500",
"昌吉回族自治州": "652300",
"博尔塔拉蒙古自治州": "652700",
"巴音郭楞蒙古自治州": "652800",
"阿克苏地区": "652900",
"克孜勒苏柯尔克孜自治州": "653000",
"喀什地区": "653100",
"和田地区": "653200",
"伊犁哈萨克自治州": "654000",
"塔城地区": "654200",
"阿勒泰地区": "654300",
"石河子": "659001",
"阿拉尔": "659002",
"图木舒克": "659003",
"五家渠": "659004",
"北屯": "659005",
"铁门关": "659006",
"双河": "659007",
"可克达拉": "659008",
"昆玉": "659009",
}
async def query_weather(self, city: str, extensions: str = "base") -> dict:
"""
从高德地图 API 查询天气信息。
:param city: 城市名称或高德地图城市代码
:param extensions: 'base' 为实时天气,'all' 为预报天气
:return: 天气数据字典,若出错则包含 error 字段
"""
# 优先使用映射表中的代码,否则直接使用传入的值(可能是用户提供的代码)
city_code = self.city_code_map.get(city, city)
params = {
"key": self.api_key,
"city": city_code,
"extensions": extensions,
"output": "json"
}
print(f"DEBUG: Querying weather for city: {city} (code: {city_code}), params: {params}")
async with httpx.AsyncClient() as client:
try:
response = await client.get(self.base_url, params=params, headers=self.headers, timeout=10.0)
print(f"DEBUG: Response status: {response.status_code}, content: {response.text[:500]}...")
response.raise_for_status()
data = response.json()
if data.get("status") != "1":
# 检查是否是城市代码无效导致的错误
if data.get("info") == "INVALID_PARAMS" and city != city_code:
return {"error": f"未找到城市 '{city}' 的代码,请尝试其他城市"}
return {"error": f"API error: {data.get('info', 'Unknown error')}"}
lives = data.get("lives", [])
if not lives:
return {"message": "未找到该城市的天气数据"}
weather_info = lives[0]
result = {
"city": weather_info.get("city", city),
"weather": weather_info.get("weather", "Unknown"),
"temperature": weather_info.get("temperature", "Unknown"),
"winddirection": weather_info.get("winddirection", "Unknown"),
"windpower": weather_info.get("windpower", "Unknown"),
"humidity": weather_info.get("humidity", "Unknown"),
"reporttime": weather_info.get("reporttime", "Unknown")
}
return result
except httpx.RequestException as e:
return {"error": f"Weather query error: {str(e)}"}
def format_weather(self, weather_data: dict) -> str:
"""将天气数据格式化为易读文本"""
if "error" in weather_data:
return f"⚠️ {weather_data['error']}"
if "message" in weather_data:
return f"⚠️ {weather_data['message']}"
return (
f"🌍 {weather_data['city']}\n"
f"🌡 温度: {weather_data['temperature']}°C\n"
f"💧 湿度: {weather_data['humidity']}%\n"
f"🌬 风向: {weather_data['winddirection']} 风力: {weather_data['windpower']} 级\n"
f"🌤 天气: {weather_data['weather']}\n"
f"⏰ 更新时间: {weather_data['reporttime']}\n"
)
def main():
print("DEBUG: Starting MCP WeatherServer")
parser = argparse.ArgumentParser(description="Run MCP WeatherServer")
parser.add_argument("--api_key", type=str, required=True, help="高德地图 API 密钥")
args = parser.parse_args()
# 使用命令行提供的 API 密钥实例化天气工具
weather_tool = GaodeWeatherTool(api_key=args.api_key)
@mcp.tool()
async def query_weather(city_name: str) -> str:
"""
输入城市名称,返回今日天气查询结果。
:param city_name: 城市名称(如"杭州")
:return: 格式化后的天气信息
"""
data = await weather_tool.query_weather(city_name, extensions="base")
return weather_tool.format_weather(data)
mcp.run(transport='stdio')
if __name__ == "__main__":
main()
4:代码编写完成进行测试
# 下面是测试的命令
npx -y @modelcontextprotocol/inspector uv run main.py --api_key 高德地图的key
# 日志
Starting MCP inspector...
⚙️ Proxy server listening on port 6277
🔍 MCP Inspector is up and running at http://127.0.0.1:6274 🚀
# 上面的日志中清晰的显示对应的webui界面,通过端口6274
# 打开上面的界面,然后在左侧显示连接,连接完成会出现对应的界面,如下面展示的图片所示,点击Tools
# 工具,然后进行测试即可
5:打包部署
发布之前记得修改pyproject.toml文件
参考:
[project]
name = "mcp-gaode-weather-server-unique"
version = "0.1.0"
description = "输入Gaode api_key,获取天气信息"
readme = "README.md"
requires-python = ">=3.11"
dependencies = ["httpx>=0.28.1","mcp>=1.6.0", "numpy>=2.2.5"]
[project.scripts]
mcp-gaode-weather-server-unique = "main:main"
注意:name要唯一,不能重复,重复上传会报错
# 执行安装命令
uv pip install httpx
uv pip install build twine
# 打包编译环境
python -m build
# 编译完成后会显示两个目录,一个是dist,一个是mcp_gaode_weather_server_unique.egg-info
# 上传mcp工具到pypi官网,这个地方需要去这个官网进行注册,并且拿到对应的api token令牌
python -m twine upload dist/*
执行完后输入token,token为隐式输入
官网地址:https://pypi.org/
注册即可,注册完成后进行一个设备认证,双重认证完成之后才能创建这个API Token
6:调用MCP工具
使用CherryStudio工具进行测试
注意参数为三个,第一个是服务名称,第二个是apikey,第三个是具体的key值
结果显示:
结束语
MCP 协议为 AI 与外部世界的交互提供了一个优雅的解决方案,我的实现则展示了其在实际场景中的强大潜力。通过集成天气查询、谷歌检索、微表情分析和 ComfyUI 生图,我希望为开发者提供一个开箱即用的模板,激发更多创新灵感。
欢迎访问我的项目仓库,获取完整代码并尝试扩展功能!如果有任何问题或建议,随时在评论区或 GitHub 上与我交流。
项目地址:https://github.com/Dreamboat-Rachel/MCP-Server-For-Local
更多推荐
所有评论(0)