MCP 服务:ds 余额查询 + ros 邮件/QQ/服务器状态

This commit is contained in:
RRRosmontis
2026-08-27 21:43:28 +08:00
commit 74402b5e55
4 changed files with 1209 additions and 0 deletions
+270
View File
@@ -0,0 +1,270 @@
import json
import os
import sys
import requests
BASE_URL = 'https://status.rrrosmontis.icu'
# ============================================================
# 内部辅助
# ============================================================
_session = None
def get_session():
"""获取或创建带 cookie 的 requests Session"""
global _session
if _session is None:
_session = requests.Session()
return _session
def ensure_login():
"""确保已登录,如果未登录则自动登录"""
s = get_session()
resp = s.get(f'{BASE_URL}/api/me', timeout=10)
data = resp.json()
if data.get('logged_in'):
return
account = os.environ.get('ROS_SERVERS_STATUS_ACCOUNT', '')
password = os.environ.get('ROS_SERVERS_STATUS_PASSWORD', '')
if not account or not password:
raise Exception('未登录,且环境变量 ROS_SERVERS_STATUS_ACCOUNT / ROS_SERVERS_STATUS_PASSWORD 未设置')
payload = {'username': account, 'password': password}
resp = s.post(f'{BASE_URL}/api/login', json=payload, timeout=10)
result = resp.json()
if result.get('status') != 'success':
raise Exception(f"登录失败: {result.get('message', '未知错误')}")
def api_get(path, params=None, need_auth=True):
if need_auth:
ensure_login()
s = get_session()
resp = s.get(f'{BASE_URL}{path}', params=params, timeout=15)
return resp.json()
# ============================================================
# 工具函数
# ============================================================
def get_nodes():
"""获取所有节点基本信息列表"""
return api_get('/api/nodes')
def get_node_recent(uuid):
"""获取指定节点最近1分钟实时状态(嵌套对象结构)"""
return api_get(f'/api/recent/{uuid}')
def get_load_records(uuid, hours=4, load_type='all'):
"""获取指定节点负载历史记录(扁平化结构)"""
params = {'uuid': uuid, 'hours': hours}
if load_type and load_type != 'all':
params['load_type'] = load_type
return api_get('/api/records/load', params=params)
def get_ping_records(uuid=None, task_id=None, hours=4):
"""获取 Ping 历史记录,uuid 和 task_id 至少提供一个"""
params = {'hours': hours}
if uuid:
params['uuid'] = uuid
if task_id is not None:
params['task_id'] = task_id
return api_get('/api/records/ping', params=params)
def get_ping_tasks():
"""获取公开的 Ping 任务配置列表"""
return api_get('/api/task/ping')
def get_site_info():
"""获取站点公开设置属性"""
return api_get('/api/public', need_auth=False)
def get_version():
"""获取 Komari 服务端版本信息"""
return api_get('/api/version', need_auth=False)
def check_login():
"""检查当前登录状态"""
s = get_session()
resp = s.get(f'{BASE_URL}/api/me', timeout=10)
return resp.json()
# ============================================================
# MCP JSON-RPC 处理
# ============================================================
TOOLS = [
{
"name": "get_nodes",
"description": "获取所有节点的基本信息列表(名称、CPU、内存、磁盘、价格、到期时间、流量限制等)。未登录时 hidden=true 的节点不可见。",
"inputSchema": {
"type": "object",
"properties": {},
"required": []
}
},
{
"name": "get_node_recent",
"description": "获取指定节点最近1分钟的实时状态数据(CPU/内存/磁盘/网络/负载/连接数/GPU/运行时间),数据结构为嵌套对象格式。",
"inputSchema": {
"type": "object",
"properties": {
"uuid": {"type": "string", "description": "节点的唯一标识符 UUID"}
},
"required": ["uuid"]
}
},
{
"name": "get_load_records",
"description": "获取指定节点的负载历史记录(扁平化结构),可按指标类型筛选,用于生成图表和趋势分析。",
"inputSchema": {
"type": "object",
"properties": {
"uuid": {"type": "string", "description": "节点的唯一标识符 UUID"},
"hours": {"type": "number", "description": "查询时间范围(小时),默认 4"},
"load_type": {"type": "string", "description": "指标筛选类型: cpu / ram / swap / load / temp / disk / network / process / connections / all(默认)"}
},
"required": ["uuid"]
}
},
{
"name": "get_ping_records",
"description": "获取 Ping 历史记录。uuid 和 task_id 至少提供一个:仅 uuid 返回该节点参与的任务记录,仅 task_id 返回该任务下所有节点记录,同时提供则交集查询。",
"inputSchema": {
"type": "object",
"properties": {
"uuid": {"type": "string", "description": "节点 UUID(可选,与 task_id 至少提供一个)"},
"task_id": {"type": "number", "description": "Ping 任务 ID(可选,与 uuid 至少提供一个)"},
"hours": {"type": "number", "description": "查询时间范围(小时),默认 4"}
},
"required": []
}
},
{
"name": "get_ping_tasks",
"description": "获取公开的 Ping 任务配置列表,包含任务名称、类型、执行间隔、关联节点等。",
"inputSchema": {
"type": "object",
"properties": {},
"required": []
}
},
{
"name": "get_site_info",
"description": "获取站点公开设置(站点名、描述、主题、OAuth 配置、CORS、记录保留时间等),无需登录。",
"inputSchema": {
"type": "object",
"properties": {},
"required": []
}
},
{
"name": "get_version",
"description": "获取 Komari 服务端版本号和 Git Hash,无需登录。",
"inputSchema": {
"type": "object",
"properties": {},
"required": []
}
},
{
"name": "check_login",
"description": "检查当前登录状态,返回用户名、UUID、是否启用2FA、SSO 绑定等信息。",
"inputSchema": {
"type": "object",
"properties": {},
"required": []
}
}
]
TOOL_MAP = {
"get_nodes": get_nodes,
"get_node_recent": get_node_recent,
"get_load_records": get_load_records,
"get_ping_records": get_ping_records,
"get_ping_tasks": get_ping_tasks,
"get_site_info": get_site_info,
"get_version": get_version,
"check_login": check_login
}
def handle_request(req):
method = req.get("method", "")
req_id = req.get("id")
if method == "initialize":
return {
"jsonrpc": "2.0",
"id": req_id,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": {}
},
"serverInfo": {
"name": "ros-servers-status",
"version": "1.0.0"
}
}
}
if method == "ping":
return {"jsonrpc": "2.0", "id": req_id, "result": {}}
if method == "notifications/initialized":
return None
if method == "tools/list":
return {"jsonrpc": "2.0", "id": req_id, "result": {"tools": TOOLS}}
if method == "tools/call":
tool_name = req["params"]["name"]
arguments = req["params"].get("arguments", {})
tool_func = TOOL_MAP.get(tool_name)
if not tool_func:
return {"jsonrpc": "2.0", "id": req_id,
"error": {"code": -32601, "message": f"未知工具: {tool_name}"}}
try:
result = tool_func(**arguments)
text = json.dumps(result, ensure_ascii=False, indent=2)
return {"jsonrpc": "2.0", "id": req_id,
"result": {"content": [{"type": "text", "text": text}]}}
except Exception as e:
return {"jsonrpc": "2.0", "id": req_id,
"error": {"code": -32000, "message": str(e)}}
return {"jsonrpc": "2.0", "id": req_id,
"error": {"code": -32601, "message": f"未知方法: {method}"}}
def main():
for line in sys.stdin:
line = line.strip()
if not line:
continue
try:
req = json.loads(line)
resp = handle_request(req)
if resp is None:
continue
sys.stdout.write(json.dumps(resp, ensure_ascii=False) + '\n')
sys.stdout.flush()
except json.JSONDecodeError:
pass
if __name__ == '__main__':
main()