Skip to content

Commit 65743a3

Browse files
authored
feat: non-resource interface support for ecs V2 (#360)
1 parent 6da1b7a commit 65743a3

File tree

10 files changed

+1058
-78
lines changed

10 files changed

+1058
-78
lines changed

server/mcp_server_ecs/README.md

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,40 @@ ECS
1414
## Features
1515

1616
- Query instance information
17-
- Query event information
17+
- Query event information
1818
- Query region information
1919
- Simple instance operations
20+
- Event subscription management
21+
- Instance console diagnostics
2022

2123
## Available Tools
2224
Since some interfaces have a lot of input parameters and return content, some uncommon content will cause too much context burden on llm. In order to avoid unnecessary token waste, ECS MCP Server only provides queries for common content.
2325

26+
### Instance Management
2427
- `describe_instances`: [query instance list](https://www.volcengine.com/docs/6396/70466)
2528
- `describe_images`: [query image list](https://www.volcengine.com/docs/6396/70808)
2629
- `describe_instance_types`: [query instance type list](https://www.volcengine.com/docs/6396/92769)
30+
- `describe_instance_type_families`: [query instance type family list](https://www.volcengine.com/docs/6396/120172)
2731
- `describe_available_resource`: [query available resources](https://www.volcengine.com/docs/6396/76279)
28-
- `describe_system_events`: [query system events](https://www.volcengine.com/docs/6396/129399)
29-
- `describe_regions`: [query region list](https://www.volcengine.com/docs/6396/1053194)
30-
- `describe_zones`: [query availability zone list](https://www.volcengine.com/docs/6396/120518)
3132
- `start_instances`: [start instances](https://www.volcengine.com/docs/6396/101068)
3233
- `renew_instance`: [renew instance](https://www.volcengine.com/docs/6396/76276)
3334

35+
### Region & Availability Zone
36+
- `describe_regions`: [query region list](https://www.volcengine.com/docs/6396/1053194)
37+
- `describe_zones`: [query availability zone list](https://www.volcengine.com/docs/6396/120518)
38+
39+
### System Events
40+
- `describe_system_events`: [query system events](https://www.volcengine.com/docs/6396/129399)
41+
- `update_system_events`: [update system event status](https://www.volcengine.com/docs/6396/129400)
42+
- `describe_event_types`: [query event type list](https://www.volcengine.com/docs/6396/196424)
43+
44+
### Event Subscription
45+
- `describe_subscriptions`: [query event subscription list](https://www.volcengine.com/docs/6396/166018)
46+
47+
### Instance Diagnostics
48+
- `get_console_output`: [get instance console output](https://www.volcengine.com/docs/6396/176876)
49+
- `get_console_screenshot`: [get instance console screenshot](https://www.volcengine.com/docs/6396/176875)
50+
3451
## Usage Guide
3552

3653
### Prerequisites

server/mcp_server_ecs/README_zh.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,37 @@ ECS MCP Server 是一个模型上下文协议(Model Context Protocol)服务器
1616
- 查询事件信息
1717
- 查询地域信息
1818
- 简单操作实例
19+
- 事件订阅管理
20+
- 实例控制台诊断
1921

2022
## 可用工具
2123
由于部分接口的入参和返回内容较多,一些不常用的内容会对大模型造成过长的上下文负担,为了避免不必要的token浪费,ECS MCP Server仅提供常见内容的查询。
2224

25+
### 实例管理
2326
- `describe_instances`: [查询实例列表](https://www.volcengine.com/docs/6396/70466)
2427
- `describe_images`: [查询镜像列表](https://www.volcengine.com/docs/6396/70808)
2528
- `describe_instance_types`: [查询实例规格列表](https://www.volcengine.com/docs/6396/92769)
29+
- `describe_instance_type_families`: [查询实例规格族列表](https://www.volcengine.com/docs/6396/120172)
2630
- `describe_available_resource`: [查询可用资源](https://www.volcengine.com/docs/6396/76279)
27-
- `describe_system_events`: [查询系统事件](https://www.volcengine.com/docs/6396/129399)
28-
- `describe_regions`: [查询地域列表](https://www.volcengine.com/docs/6396/1053194)
29-
- `describe_zones`: [查询可用区列表](https://www.volcengine.com/docs/6396/120518)
3031
- `start_instances`: [启动实例](https://www.volcengine.com/docs/6396/101068)
3132
- `renew_instance`: [续费实例](https://www.volcengine.com/docs/6396/76276)
3233

34+
### 地域与可用区
35+
- `describe_regions`: [查询地域列表](https://www.volcengine.com/docs/6396/1053194)
36+
- `describe_zones`: [查询可用区列表](https://www.volcengine.com/docs/6396/120518)
37+
38+
### 系统事件
39+
- `describe_system_events`: [查询系统事件](https://www.volcengine.com/docs/6396/129399)
40+
- `update_system_events`: [更新系统事件状态](https://www.volcengine.com/docs/6396/129400)
41+
- `describe_event_types`: [查询事件类型列表](https://www.volcengine.com/docs/6396/196424)
42+
43+
### 事件订阅
44+
- `describe_subscriptions`: [查询事件订阅列表](https://www.volcengine.com/docs/6396/166018)
45+
46+
### 实例诊断
47+
- `get_console_output`: [获取实例控制台输出](https://www.volcengine.com/docs/6396/176876)
48+
- `get_console_screenshot`: [获取实例控制台截图](https://www.volcengine.com/docs/6396/176875)
49+
3350
## 使用指南
3451

3552
### 前置准备

server/mcp_server_ecs/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ dependencies = [
1111
"concurrent-log-handler==0.9.25",
1212
"dynaconf==3.2.10",
1313
"jsonref>=1.1.0",
14+
"aiohttp>=3.13.2"
1415
]
1516

1617
[tool.hatch.metadata]
Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,29 @@
1-
from mcp import types
2-
31
from mcp_server_ecs.common.logs import LOG
42

53

6-
# handle empty response error
7-
def _handle_empty_response(action_name):
8-
LOG.error(f"{action_name} returned empty response")
9-
return [types.TextContent(type="text", text="Error: empty response")]
10-
11-
12-
# handle exception error
13-
def _handle_exception(action_name, error):
14-
LOG.error(f"Exception when calling {action_name}: {str(error)}")
15-
return [types.TextContent(type="text", text=f"Error: {str(error)}")]
4+
class ToolExecutionError(Exception):
5+
"""工具执行错误,会被框架捕获并设置 isError=True"""
6+
pass
167

178

18-
def handle_error(action_name, error=None):
19-
"""Handle API error response
9+
def handle_error(action_name: str, error: Exception | None = None) -> None:
10+
"""
11+
处理 API 错误,抛出异常让框架设置 isError=True
12+
13+
符合 MCP 规范:通过抛出异常让框架自动设置 CallToolResult.isError = True,
14+
而不是返回错误文本(那样 isError 会保持 False)。
2015
2116
Args:
2217
action_name: API action name
2318
error: Exception object (optional)
24-
25-
Returns:
26-
A list of TextContent objects representing the error in a unified format
19+
20+
Raises:
21+
ToolExecutionError: 包含错误信息的异常
2722
"""
28-
# create a mapping table for handling functions
29-
handlers = {
30-
True: lambda: _handle_exception(action_name, error),
31-
False: lambda: _handle_empty_response(action_name),
32-
}
33-
34-
return handlers[error is not None]()
23+
if error:
24+
error_msg = str(error)
25+
LOG.error(f"Exception when calling {action_name}: {error_msg}")
26+
raise ToolExecutionError(error_msg) from error
27+
else:
28+
LOG.error(f"{action_name} returned empty response")
29+
raise ToolExecutionError("empty response")
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
"""
2+
MCP Server Metrics 中间件
3+
4+
用于统计工具调用次数、成功/失败率、耗时等指标。
5+
输出结构化日志,便于 TLS 等日志采集系统解析统计。
6+
7+
实现原理:
8+
通过替换 MCP Server 底层的 CallToolRequest handler,
9+
在请求处理层面拦截所有工具调用,实现真正的中间件效果。
10+
11+
使用方式:
12+
from mcp_server_ecs.common.middleware import install_metrics_handler
13+
14+
mcp = FastMCP("ECS MCP Server", ...)
15+
16+
# 在所有 @mcp.tool() 注册完成后,启动前调用
17+
install_metrics_handler(mcp)
18+
19+
日志输出格式:
20+
[MCP_METRICS] {"timestamp": 1701590400.123, "type": "tool_call", "tool": "describe_instances", "status": "success", "duration_ms": 45.23}
21+
"""
22+
23+
import json
24+
import time
25+
from typing import TYPE_CHECKING
26+
27+
from mcp import types
28+
29+
from mcp_server_ecs.common.logs import LOG
30+
31+
if TYPE_CHECKING:
32+
from mcp.server.fastmcp import FastMCP
33+
34+
# 日志前缀,便于 TLS 过滤
35+
LOG_PREFIX = "[MCP_METRICS]"
36+
37+
38+
def _log_metric(**kwargs) -> None:
39+
"""输出结构化日志"""
40+
log_data = {
41+
"timestamp": time.time(),
42+
"type": "tool_call",
43+
**kwargs
44+
}
45+
metric_log = f"{LOG_PREFIX} {json.dumps(log_data, ensure_ascii=False)}"
46+
LOG.info(metric_log)
47+
48+
49+
def install_metrics_handler(mcp: "FastMCP") -> None:
50+
"""
51+
安装 metrics handler,拦截所有 CallToolRequest
52+
53+
这是真正的中间件方案,通过替换底层 request handler 实现,
54+
不需要修改任何工具代码,对所有工具调用生效。
55+
56+
注意: 必须在所有 @mcp.tool() 注册完成后调用
57+
58+
Args:
59+
mcp: FastMCP 实例
60+
61+
TLS 采集配置建议:
62+
- 过滤规则: 包含 "[MCP_METRICS]"
63+
- JSON 解析: 提取日志中的 JSON 部分
64+
- 聚合统计: count by tool, status
65+
"""
66+
server = mcp._mcp_server
67+
original_handler = server.request_handlers[types.CallToolRequest]
68+
69+
async def metrics_handler(req: types.CallToolRequest) -> types.ServerResult:
70+
"""带 metrics 统计的 CallToolRequest handler"""
71+
tool_name = req.params.name if req.params else "unknown"
72+
start_time = time.time()
73+
74+
try:
75+
result = await original_handler(req)
76+
duration_ms = (time.time() - start_time) * 1000
77+
78+
# result 是 ServerResult,内部包含 CallToolResult
79+
# 需要检查 result.root.isError
80+
is_error = False
81+
error_msg = None
82+
if hasattr(result, 'root') and hasattr(result.root, 'isError'):
83+
is_error = result.root.isError
84+
# 如果是错误,提取错误信息
85+
if is_error and hasattr(result.root, 'content'):
86+
for content in result.root.content:
87+
if hasattr(content, 'text'):
88+
error_msg = content.text
89+
break
90+
91+
metric_data = {
92+
"tool": tool_name,
93+
"status": "error" if is_error else "success",
94+
"duration_ms": round(duration_ms, 2),
95+
}
96+
if error_msg:
97+
metric_data["error_msg"] = error_msg
98+
99+
_log_metric(**metric_data)
100+
return result
101+
102+
except Exception as e:
103+
duration_ms = (time.time() - start_time) * 1000
104+
105+
_log_metric(
106+
tool=tool_name,
107+
status="error",
108+
duration_ms=round(duration_ms, 2),
109+
error_type=type(e).__name__,
110+
error_msg=str(e),
111+
)
112+
raise
113+
114+
# 替换 handler
115+
server.request_handlers[types.CallToolRequest] = metrics_handler
116+
LOG.info("MCP metrics handler installed - all tool calls will be monitored")

server/mcp_server_ecs/src/mcp_server_ecs/main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import argparse
88

99
from mcp_server_ecs.common.logs import LOG
10+
from mcp_server_ecs.common.middleware import install_metrics_handler
1011
from mcp_server_ecs.tools import event, instance, mcp, region
1112

1213

@@ -24,6 +25,9 @@ def main():
2425
LOG.info(
2526
f"Including tool types: {event.__name__}, {instance.__name__}, {region.__name__}"
2627
)
28+
29+
install_metrics_handler(mcp)
30+
2731
LOG.info(f"Starting ECS MCP Server with {args.transport} transport")
2832

2933
mcp.run(transport=args.transport)

0 commit comments

Comments
 (0)