多路由隔离:构建模块化Agent工具服务器
"""搜索GitHub仓库"""q=@github_mcp . tool() async def search_github_repos(query : str , limit : int = 10) - > Dict [ str , Any ] : """搜索GitHub仓库""" url = f"https://api.github.com/search/repositories?q= {
·
在构建多Agent系统时,工具隔离是一个关键需求。通过Starlette和FastMCP,我们可以创建一个支持多路由隔离的工具服务器,让不同Agent调用各自专属的工具集。
核心思路:路由隔离工具集
from starlette.applications import Starlette
from starlette.routing import Mount
from mcp.server.fastmcp import FastMCP
# 创建多个独立的MCP实例
github_mcp = FastMCP("GitHub API")
browser_mcp = FastMCP("Browser")
curl_mcp = FastMCP("Curl")
search_mcp = FastMCP("Search")
# 应用配置
app = Starlette(
routes=[
Mount("/github", app=github_mcp.sse_app()),
Mount("/browser", app=browser_mcp.sse_app()),
Mount("/curl", app=curl_mcp.sse_app()),
Mount("/search", app=search_mcp.sse_app()),
]
)
这种架构的关键优势在于:
- 工具隔离:每个路由下的工具相互独立
- 模块化设计:按功能划分工具组
- 独立扩展:不同工具集可以单独扩展和部署
- 权限控制:为不同Agent分配不同路由权限
工具定义示例
GitHub工具集(/github路由)
@github_mcp.tool()
async def search_github_repos(query: str, limit: int = 10) -> Dict[str, Any]:
"""搜索GitHub仓库"""
url = f"https://api.github.com/search/repositories?q={query}&per_page={limit}"
response = requests.get(url)
return response.json()
浏览器工具集(/browser路由)
@browser_mcp.tool()
async def get_webpage_title(url: str) -> Dict[str, Any]:
"""获取网页标题"""
try:
response = requests.get(url, timeout=5)
# 简单提取标题
import re
title_match = re.search(r'<title>(.*?)</title>', response.text, re.IGNORECASE | re.DOTALL)
title = title_match.group(1) if title_match else "未找到标题"
return {"url": url, "title": title}
except Exception as e:
return {"url": url, "error": str(e)}
客户端调用示例
不同Agent可以访问各自的路由:
# GitHub Agent
github_client = Client("http://localhost:8000/github/sse")
await github_client.call_tool("search_github_repos", {"query": "AI"})
# 浏览器Agent
browser_client = Client("http://localhost:8000/browser/sse")
await browser_client.call_tool("get_webpage_title", {"url": "https://example.com"})
架构优势分析
- 职责分离:每个工具组专注于特定领域
- 安全隔离:敏感工具(如curl)可单独保护
- 性能优化:高负载工具可独立扩展
- 版本管理:不同工具组可独立升级
实际应用场景
- 多Agent协作系统:不同Agent使用不同工具集
- 微服务架构:每个路由作为独立微服务
- 权限分级系统:根据用户角色分配路由访问权限
- A/B测试环境:不同路由部署不同版本工具
总结
通过Starlette的路由挂载功能和FastMCP的工具管理能力,我们可以构建高度模块化的Agent工具服务器。这种架构不仅实现了工具隔离,还为系统的可扩展性和安全性提供了坚实基础。
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
启动服务器后,不同Agent就可以通过各自的路由访问专属工具集,实现安全、高效的协作。
mcp 服务
from starlette.applications import Starlette
from starlette.routing import Mount
from mcp.server.fastmcp import FastMCP
import requests
from typing import Dict, Any
# Create multiple MCP servers
github_mcp = FastMCP("GitHub API")
browser_mcp = FastMCP("Browser")
curl_mcp = FastMCP("Curl")
search_mcp = FastMCP("Search")
@github_mcp.tool()
async def search_github_repos(query: str, limit: int = 10) -> Dict[str, Any]:
"""
搜索GitHub仓库
Args:
query: 搜索关键词
limit: 返回结果数量限制,默认10个
Returns:
包含仓库信息的字典
"""
url = f"https://api.github.com/search/repositories?q={query}&per_page={limit}"
response = requests.get(url)
return response.json()
@browser_mcp.tool()
async def get_webpage_title(url: str) -> Dict[str, Any]:
"""
获取网页标题
Args:
url: 网页地址
Returns:
包含网页标题的字典
"""
try:
response = requests.get(url, timeout=5)
# 简单提取标题
import re
title_match = re.search(r'<title>(.*?)</title>', response.text, re.IGNORECASE | re.DOTALL)
title = title_match.group(1) if title_match else "未找到标题"
return {"url": url, "title": title}
except Exception as e:
return {"url": url, "error": str(e)}
@curl_mcp.tool()
async def make_http_request(url: str, method: str = "GET", headers: Dict[str, str] = None) -> Dict[str, Any]:
"""
发起HTTP请求
Args:
url: 请求地址
method: HTTP方法,默认GET
headers: 请求头
Returns:
HTTP响应结果
"""
try:
if headers is None:
headers = {}
response = requests.request(method, url, headers=headers)
return {
"status_code": response.status_code,
"headers": dict(response.headers),
"content": response.text[:1000] # 限制返回内容长度
}
except Exception as e:
return {"error": str(e)}
@search_mcp.tool()
async def search_text(query: str, texts: list) -> Dict[str, Any]:
"""
在文本列表中搜索关键词
Args:
query: 搜索关键词
texts: 要搜索的文本列表
Returns:
包含匹配文本的列表
"""
results = []
for i, text in enumerate(texts):
if query.lower() in text.lower():
results.append({"index": i, "text": text})
return {"query": query, "results": results}
app = Starlette(
routes=[
# Using settings-based configuration
Mount("/github", app=github_mcp.sse_app()),
Mount("/browser", app=browser_mcp.sse_app()),
Mount("/curl", app=curl_mcp.sse_app()),
Mount("/search", app=search_mcp.sse_app()),
]
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
客户请求
import asyncio
from fastmcp import Client
client = Client("http://localhost:8000/search/sse")
async def call_tool(name: str):
async with client:
result = await client.list_tools()
print(result)
result = await client.call_tool("search_text", {"query": name,"texts":["dfdsf","sdfsdfsf"]})
print(result)
asyncio.run(call_tool("Ford"))
client = Client("http://localhost:8000/curl/sse")
async def call_tool(name: str):
async with client:
result = await client.list_tools()
print(result)
asyncio.run(call_tool("Ford"))
client = Client("http://localhost:8000/browser/sse")
async def call_tool(name: str):
async with client:
result = await client.list_tools()
print(result)
asyncio.run(call_tool("Ford"))
client = Client("http://localhost:8000/github/sse")
async def call_tool(name: str):
async with client:
result = await client.list_tools()
print(result)
asyncio.run(call_tool("Ford"))
更多推荐
所有评论(0)