239 lines
7.2 KiB
Python
239 lines
7.2 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
改进的MCP服务器,支持完整的MCP协议
|
||
"""
|
||
|
||
import asyncio
|
||
import json
|
||
import uuid
|
||
from datetime import datetime
|
||
from aiohttp import web, web_response
|
||
from aiohttp.web import Request, Response
|
||
|
||
# MCP服务器状态
|
||
server_info = {
|
||
"name": "test-mcp-server",
|
||
"version": "1.0.0",
|
||
"protocol_version": "2024-11-05"
|
||
}
|
||
|
||
# 可用工具定义
|
||
available_tools = [
|
||
{
|
||
"name": "echo",
|
||
"description": "Echo back the input message",
|
||
"inputSchema": {
|
||
"type": "object",
|
||
"properties": {
|
||
"message": {
|
||
"type": "string",
|
||
"description": "Message to echo back"
|
||
}
|
||
},
|
||
"required": ["message"]
|
||
}
|
||
},
|
||
{
|
||
"name": "get_time",
|
||
"description": "Get current time",
|
||
"inputSchema": {
|
||
"type": "object",
|
||
"properties": {},
|
||
"additionalProperties": False
|
||
}
|
||
},
|
||
{
|
||
"name": "calculate",
|
||
"description": "Perform basic arithmetic calculations",
|
||
"inputSchema": {
|
||
"type": "object",
|
||
"properties": {
|
||
"expression": {
|
||
"type": "string",
|
||
"description": "Mathematical expression to evaluate (e.g., '2+2', '10*5')"
|
||
}
|
||
},
|
||
"required": ["expression"]
|
||
}
|
||
}
|
||
]
|
||
|
||
async def handle_mcp_request(request: Request) -> Response:
|
||
"""处理MCP请求"""
|
||
print(f"收到MCP请求: {request.method} {request.path}")
|
||
print(f"请求头: {dict(request.headers)}")
|
||
|
||
if request.method == "GET":
|
||
# 处理初始化请求
|
||
return await handle_initialize(request)
|
||
elif request.method == "POST":
|
||
# 处理JSON-RPC请求
|
||
return await handle_jsonrpc(request)
|
||
|
||
return web_response.Response(status=405, text="Method not allowed")
|
||
|
||
async def handle_initialize(request: Request) -> Response:
|
||
"""处理初始化请求"""
|
||
init_response = {
|
||
"jsonrpc": "2.0",
|
||
"result": {
|
||
"protocolVersion": server_info["protocol_version"],
|
||
"capabilities": {
|
||
"tools": {
|
||
"listChanged": True
|
||
},
|
||
"resources": {
|
||
"subscribe": False,
|
||
"listChanged": False
|
||
},
|
||
"prompts": {
|
||
"listChanged": False
|
||
},
|
||
"logging": {}
|
||
},
|
||
"serverInfo": {
|
||
"name": server_info["name"],
|
||
"version": server_info["version"]
|
||
}
|
||
},
|
||
"id": 1
|
||
}
|
||
|
||
# 返回SSE格式的响应
|
||
response_text = f"data: {json.dumps(init_response)}\n\n"
|
||
|
||
return web_response.Response(
|
||
text=response_text,
|
||
content_type="text/event-stream",
|
||
headers={
|
||
"Cache-Control": "no-cache",
|
||
"Connection": "keep-alive",
|
||
"Access-Control-Allow-Origin": "*",
|
||
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
||
"Access-Control-Allow-Headers": "*"
|
||
}
|
||
)
|
||
|
||
async def handle_jsonrpc(request: Request) -> Response:
|
||
"""处理JSON-RPC请求"""
|
||
try:
|
||
body = await request.text()
|
||
print(f"收到JSON-RPC请求体: {body}")
|
||
|
||
if not body:
|
||
return web_response.Response(status=400, text="Empty request body")
|
||
|
||
data = json.loads(body)
|
||
method = data.get("method")
|
||
params = data.get("params", {})
|
||
request_id = data.get("id")
|
||
|
||
print(f"方法: {method}, 参数: {params}")
|
||
|
||
if method == "tools/list":
|
||
response = {
|
||
"jsonrpc": "2.0",
|
||
"result": {
|
||
"tools": available_tools
|
||
},
|
||
"id": request_id
|
||
}
|
||
elif method == "tools/call":
|
||
tool_name = params.get("name")
|
||
tool_arguments = params.get("arguments", {})
|
||
|
||
result = await execute_tool(tool_name, tool_arguments)
|
||
|
||
response = {
|
||
"jsonrpc": "2.0",
|
||
"result": {
|
||
"content": [
|
||
{
|
||
"type": "text",
|
||
"text": result
|
||
}
|
||
]
|
||
},
|
||
"id": request_id
|
||
}
|
||
else:
|
||
response = {
|
||
"jsonrpc": "2.0",
|
||
"error": {
|
||
"code": -32601,
|
||
"message": f"Method not found: {method}"
|
||
},
|
||
"id": request_id
|
||
}
|
||
|
||
return web_response.Response(
|
||
text=json.dumps(response),
|
||
content_type="application/json",
|
||
headers={
|
||
"Access-Control-Allow-Origin": "*",
|
||
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
||
"Access-Control-Allow-Headers": "*"
|
||
}
|
||
)
|
||
|
||
except json.JSONDecodeError as e:
|
||
print(f"JSON解析错误: {e}")
|
||
return web_response.Response(status=400, text="Invalid JSON")
|
||
except Exception as e:
|
||
print(f"处理请求时出错: {e}")
|
||
return web_response.Response(status=500, text="Internal server error")
|
||
|
||
async def execute_tool(tool_name: str, arguments: dict) -> str:
|
||
"""执行工具调用"""
|
||
print(f"执行工具: {tool_name}, 参数: {arguments}")
|
||
|
||
if tool_name == "echo":
|
||
message = arguments.get("message", "")
|
||
return f"Echo: {message}"
|
||
|
||
elif tool_name == "get_time":
|
||
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
return f"Current time: {current_time}"
|
||
|
||
elif tool_name == "calculate":
|
||
expression = arguments.get("expression", "")
|
||
try:
|
||
# 简单的数学表达式计算(仅支持基本运算)
|
||
# 注意:这里使用eval有安全风险,实际应用中应该使用更安全的方法
|
||
allowed_chars = set('0123456789+-*/.() ')
|
||
if all(c in allowed_chars for c in expression):
|
||
result = eval(expression)
|
||
return f"Result: {expression} = {result}"
|
||
else:
|
||
return "Error: Invalid characters in expression"
|
||
except Exception as e:
|
||
return f"Error calculating expression: {str(e)}"
|
||
|
||
else:
|
||
return f"Error: Unknown tool '{tool_name}'"
|
||
|
||
async def handle_options(request: Request) -> Response:
|
||
"""处理OPTIONS请求"""
|
||
return web_response.Response(
|
||
headers={
|
||
"Access-Control-Allow-Origin": "*",
|
||
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
||
"Access-Control-Allow-Headers": "*"
|
||
}
|
||
)
|
||
|
||
async def create_app():
|
||
"""创建web应用"""
|
||
app = web.Application()
|
||
|
||
# 添加路由
|
||
app.router.add_get('/mcp', handle_mcp_request)
|
||
app.router.add_post('/mcp', handle_mcp_request)
|
||
app.router.add_options('/mcp', handle_options)
|
||
|
||
return app
|
||
|
||
if __name__ == '__main__':
|
||
print("启动简单MCP服务器在端口8080...")
|
||
app = asyncio.run(create_app())
|
||
web.run_app(app, host='localhost', port=8080) |