MCP 服务:ds 余额查询 + ros 邮件/QQ/服务器状态
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user