271 lines
8.7 KiB
Python
271 lines
8.7 KiB
Python
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()
|