去年参加Gradio Agents & MCP黑客松时,我原本只是想随便玩玩Anthropic提出的Model Context Protocol(MCP)标准,没想到意外打开了LLM工具开发的新世界。作为一个从没接触过MCP的开发者,我用三天时间构建了一套地理计算工具集,让LLM能够处理"从马德里到巴塞罗那开车要多久"这类现实问题。这段经历彻底改变了我对AI应用开发的认知。
MCP本质上是一套让LLM调用外部工具的标准协议。想象LLM是个博学但"四肢不全"的学者——它能写诗作文、解数学题,但无法获取实时天气、计算两地距离或生成图表。通过MCP,我们可以给LLM"安装"各种功能模块:我的地理工具包能让LLM完成地址解析、路径规划和耗时估算的完整工作流,而无需预先训练任何地理知识。
传统开发中,我们为函数写docstring更多是给同事看的注释。但在MCP世界里,docstring直接决定了LLM能否正确使用你的工具。这是我的坐标转换函数示例:
python复制def get_coords_from_address(address: str) -> str:
"""将街道地址转换为经纬度坐标
参数:
address (str): 需查询的地址(如"巴黎埃菲尔铁塔")
返回:
str: 格式化坐标 "纬度: XX.XXXX, 经度: YY.YYYY"
示例:
>>> get_coords_from_address("纽约自由女神像")
'纬度: 40.6892, 经度: -74.0445'
"""
# 实际调用地理编码API的实现...
几个关键发现:
实践建议:用自然语言描述函数时,想象你在教一个完全不懂编程的人如何使用这个工具。避免使用"输入字符串"这类术语,改用"请输入城市名+地标"等直观说明。
初期版本我犯了个典型错误——路径规划工具返回了包含所有途经点坐标的完整JSON(约5KB)。这直接导致两个问题:
优化方案出乎意料的简单:
python复制# 优化前 - 返回完整坐标序列
{"route": [[40.7128,-74.0060], [34.0522,-118.2437], ...]}
# 优化后 - 返回服务器生成的地图图片路径
{"map_image": "/generated/route_abc123.webp"}
技术实现要点:
实测效果:相同问题"从旧金山到洛杉矶途径哪些主要城市"的响应速度从3.2秒提升到0.8秒,且LLM能更专注于文本分析而非解析坐标数据。
最让我震惊的是LLM展现的工具组合能力。当我提供以下三个独立工具后:
get_coords(address)get_route(start,end)estimate_time(route)LLM能自动处理这种复杂查询:"工作日早高峰从我家到公司要多久?途中经过哪些咖啡店?" 它会:
关键发现:工具设计要遵循Unix哲学——每个工具只做好一件事。功能越单一,组合灵活性越高。我的路径计算工具最初包含耗时估算,拆分成独立工具后反而支持了更多使用场景。
使用Gradio创建MCP工具简单得不可思议:
python复制import gradio as gr
def your_function(params):
# 工具实现...
return result
# 一行代码暴露MCP接口
gr.Interface(your_function).launch(mcp_server=True)
完整开发流程:
通过20+次迭代,我总结出这些工具设计原则:
| 维度 | 反模式 | 推荐做法 |
|---|---|---|
| 功能粒度 | 多功能复合工具 | 单一功能微工具 |
| 参数设计 | 复杂嵌套对象 | 扁平化基本类型 |
| 错误处理 | 返回Python异常 | 结构化错误消息 |
| 响应格式 | 自由文本 | JSON Schema |
典型错误响应优化示例:
python复制# 不推荐
raise ValueError("Invalid address format")
# 推荐
return {
"error": {
"code": 400,
"type": "INVALID_INPUT",
"message": "地址需包含城市名,如'上海东方明珠'"
}
}
缓存策略:对地理编码等第三方API调用添加LRU缓存
python复制from functools import lru_cache
@lru_cache(maxsize=1000)
def get_coords(address: str):
# 调用地理编码API
...
异步处理:对耗时操作使用async/await
python复制async def generate_route_map(route_id):
# 异步生成地图
...
预计算:对高频查询预先计算热点路线
python复制# 启动时预加载热门城市间路线
HOT_ROUTES = {
('北京','上海'): precompute_route(...),
('广州','深圳'): precompute_route(...)
}
现象:LLM没有调用你开发的工具
现象:工具收到不符合预期的参数
python复制def validate_address(address):
if ',' not in address:
raise ValueError("地址格式应为'地点,城市'")
现象:复杂查询中途失败
python复制def search_pois(area, page=1):
"""返回分页结果"""
return {
"data": [...],
"next_page": page + 1 if has_more else None
}
python复制def get_route(start, end, detail_level="brief"):
"""detail_level: brief/full"""
这套方法不仅适用于地理计算,还可用于:
最近我将同样的模式应用到了餐饮领域,开发了:
每个工具不过50-100行Python代码,但组合起来能让LLM变身美食顾问。比如当用户问"公司5公里内适合团队聚餐的意大利餐厅,人均200-300元,要有素食选项",LLM能自动链式调用:
这种开发模式最令人兴奋的是——你永远无法预测用户会怎样组合你的工具。就像乐高积木,简单的模块能搭建出无限可能。