Skip to content

Commit 03fda0d

Browse files
feat(user/mineru_mcp): 添加 mcp 健康检查端点和环境变量脚本
1 parent c6aa807 commit 03fda0d

File tree

3 files changed

+229
-1
lines changed

3 files changed

+229
-1
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export HEALTH_CHECK_PORT=3000
2+
export MINERU_API_KEY=eyJ0eXBlIjoiSldUIiwiYWxnIjoiSFM1MTIifQ.eyJqdGkiOiI5MjIwMDY5MCIsInJvbCI6IlJPTEVfUkVHSVNURVIiLCJpc3MiOiJPcGVuWExhYiIsImlhdCI6MTc3MzMyNzM2OCwiY2xpZW50SWQiOiJsa3pkeDU3bnZ5MjJqa3BxOXgydyIsInBob25lIjoiIiwib3BlbklkIjpudWxsLCJ1dWlkIjoiZTc2ZWZmZmYtZWQyOC00YWU0LWE1ZGQtMDAyOGQxNzAxOTk1IiwiZW1haWwiOiIiLCJleHAiOjE3ODExMDMzNjh9.fu7u5TLrOOyqpC3p2jBZyIJmC5zok6sT1qw9Zf7KCd3N7ECkBAzNM7VEzdUz02eg2p8TO2990QsTiv-QFiFLEw
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#!/bin/bash
2+
# MinerU MCP DragonOS 健康检查脚本
3+
# 用法: ./check_health.sh [port]
4+
5+
set -e
6+
7+
# 配置
8+
PORT="${1:-3000}"
9+
HOST="127.0.0.1"
10+
URL="http://${HOST}:${PORT}/health"
11+
TIMEOUT=5
12+
13+
# 颜色定义
14+
RED='\033[0;31m'
15+
GREEN='\033[0;32m'
16+
YELLOW='\033[1;33m'
17+
NC='\033[0m' # No Color
18+
19+
# 辅助函数
20+
print_green() { printf '%b\n' "${GREEN}$1${NC}"; }
21+
print_red() { printf '%b\n' "${RED}$1${NC}"; }
22+
print_yellow() { printf '%b\n' "${YELLOW}$1${NC}"; }
23+
24+
echo "=== MinerU MCP DragonOS 健康检查 ==="
25+
echo "目标: ${URL}"
26+
echo ""
27+
28+
# 检查端口是否在监听
29+
check_port() {
30+
ss -tlnp 2>/dev/null | grep -q ":${PORT} "
31+
}
32+
33+
# 发送健康检查请求
34+
check_health() {
35+
# 使用 --noproxy 绕过系统代理
36+
local response http_code body
37+
response=$(curl --noproxy '*' -s -w "\n%{http_code}" --max-time "${TIMEOUT}" "${URL}" 2>&1)
38+
http_code=$(echo "$response" | tail -n 1)
39+
body=$(echo "$response" | sed '$d')
40+
41+
if [ "${http_code}" = "200" ]; then
42+
print_green "✓ HTTP 状态码: ${http_code}"
43+
echo ""
44+
echo "响应内容:"
45+
echo "$body" | python3 -m json.tool 2>/dev/null || echo "$body"
46+
echo ""
47+
validate_response "$body"
48+
return $?
49+
else
50+
print_red "✗ HTTP 状态码: ${http_code}"
51+
echo "响应: ${body}"
52+
return 1
53+
fi
54+
}
55+
56+
# 验证响应内容
57+
validate_response() {
58+
local body="$1"
59+
local errors=0
60+
61+
echo "字段验证:"
62+
63+
# 检查 status 字段
64+
local status
65+
status=$(echo "$body" | python3 -c "import sys,json; print(json.load(sys.stdin).get('status',''))" 2>/dev/null)
66+
if [ "$status" = "ok" ]; then
67+
print_green " ✓ status: ok"
68+
else
69+
print_red " ✗ status: ${status} (期望: ok)"
70+
((errors++))
71+
fi
72+
73+
# 检查 server 字段
74+
local server
75+
server=$(echo "$body" | python3 -c "import sys,json; print(json.load(sys.stdin).get('server',''))" 2>/dev/null)
76+
if [ "$server" = "mineru-mcp-dragonos" ]; then
77+
print_green " ✓ server: ${server}"
78+
else
79+
print_yellow " ? server: ${server} (期望: mineru-mcp-dragonos)"
80+
fi
81+
82+
# 检查 api_mode 字段
83+
local api_mode
84+
api_mode=$(echo "$body" | python3 -c "import sys,json; print(json.load(sys.stdin).get('api_mode',''))" 2>/dev/null)
85+
if [ "$api_mode" = "local" ] || [ "$api_mode" = "remote" ]; then
86+
print_green " ✓ api_mode: ${api_mode}"
87+
else
88+
print_red " ✗ api_mode: ${api_mode} (期望: local 或 remote)"
89+
((errors++))
90+
fi
91+
92+
# 检查 has_api_key 字段
93+
local has_api_key
94+
has_api_key=$(echo "$body" | python3 -c "import sys,json; print(json.load(sys.stdin).get('has_api_key',''))" 2>/dev/null)
95+
if [ "$has_api_key" = "True" ] || [ "$has_api_key" = "true" ]; then
96+
print_green " ✓ has_api_key: true"
97+
else
98+
print_yellow " ? has_api_key: false"
99+
fi
100+
101+
# 检查 version 字段
102+
local version
103+
version=$(echo "$body" | python3 -c "import sys,json; print(json.load(sys.stdin).get('version',''))" 2>/dev/null)
104+
if [ -n "$version" ]; then
105+
print_green " ✓ version: ${version}"
106+
else
107+
print_red " ✗ version: 缺失"
108+
((errors++))
109+
fi
110+
111+
return "$errors"
112+
}
113+
114+
# 主逻辑
115+
main() {
116+
# 1. 检查端口
117+
printf "检查端口 %s... " "$PORT"
118+
if check_port; then
119+
local pid
120+
pid=$(ss -tlnp 2>/dev/null | grep ":${PORT} " | grep -oP 'pid=\K[0-9]+' | head -1)
121+
print_green "已监听 (pid: ${pid:-unknown})"
122+
else
123+
print_red "未监听"
124+
echo ""
125+
print_red "错误: 服务未在端口 ${PORT} 启动"
126+
echo "请先启动服务: source .mineru.env && cargo run --release"
127+
exit 1
128+
fi
129+
echo ""
130+
131+
# 2. 发送健康检查请求
132+
echo "发送健康检查请求..."
133+
if check_health; then
134+
echo ""
135+
print_green "========================================"
136+
print_green "✓ 健康检查通过"
137+
print_green "========================================"
138+
exit 0
139+
else
140+
echo ""
141+
print_red "========================================"
142+
print_red "✗ 健康检查失败"
143+
print_red "========================================"
144+
exit 1
145+
fi
146+
}
147+
148+
main

user/apps/mineru-mcp-dragonos/src/lib.rs

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use std::{
1313
collections::{HashMap, HashSet},
1414
env,
1515
path::{Path, PathBuf},
16+
sync::Arc,
1617
time::{Duration, Instant},
1718
};
1819
use thiserror::Error;
@@ -21,6 +22,15 @@ use tracing::{error, info, warn};
2122
use uuid::Uuid;
2223
use walkdir::WalkDir;
2324

25+
// HTTP 健康检查相关
26+
use axum::{
27+
extract::State,
28+
response::Json as AxumJson,
29+
routing::get,
30+
Router,
31+
};
32+
use std::sync::atomic::{AtomicBool, Ordering};
33+
2434
#[derive(Debug, Clone)]
2535
pub struct Settings {
2636
mineru_api_base: String,
@@ -196,6 +206,18 @@ pub struct MineruServer {
196206
settings: Settings,
197207
client: reqwest::Client,
198208
tool_router: ToolRouter<Self>,
209+
healthy: Arc<AtomicBool>,
210+
}
211+
212+
#[derive(Debug, Serialize)]
213+
pub struct HealthCheckResponse {
214+
pub status: String,
215+
pub server: String,
216+
pub timestamp: String,
217+
pub api_mode: String,
218+
pub api_base: String,
219+
pub has_api_key: bool,
220+
pub version: &'static str,
199221
}
200222

201223
#[tool_router]
@@ -209,6 +231,7 @@ impl MineruServer {
209231
settings,
210232
client,
211233
tool_router: Self::tool_router(),
234+
healthy: Arc::new(AtomicBool::new(true)),
212235
})
213236
}
214237

@@ -987,14 +1010,69 @@ fn init_tracing() {
9871010
tracing_subscriber::fmt().with_env_filter(filter).init();
9881011
}
9891012

1013+
/// 启动 HTTP 健康检查服务器
1014+
pub async fn run_health_check_server(
1015+
server: MineruServer,
1016+
port: u16,
1017+
) -> Result<(), Box<dyn std::error::Error>> {
1018+
let app = Router::new()
1019+
.route("/health", get(health_handler))
1020+
.route("/", get(root_handler))
1021+
.with_state(server);
1022+
1023+
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}")).await?;
1024+
info!("健康检查服务器启动在 http://0.0.0.0:{}", port);
1025+
axum::serve(listener, app).await?;
1026+
Ok(())
1027+
}
1028+
1029+
async fn health_handler(State(server): State<MineruServer>) -> AxumJson<HealthCheckResponse> {
1030+
let is_healthy = server.healthy.load(Ordering::Relaxed);
1031+
AxumJson(HealthCheckResponse {
1032+
status: if is_healthy { "ok".to_string() } else { "error".to_string() },
1033+
server: "mineru-mcp-dragonos".to_string(),
1034+
timestamp: chrono::Utc::now().to_rfc3339(),
1035+
api_mode: if server.settings.use_local_api { "local".to_string() } else { "remote".to_string() },
1036+
api_base: if server.settings.use_local_api {
1037+
server.settings.local_mineru_api_base.clone()
1038+
} else {
1039+
server.settings.mineru_api_base.clone()
1040+
},
1041+
has_api_key: server.settings.mineru_api_key.is_some(),
1042+
version: env!("CARGO_PKG_VERSION"),
1043+
})
1044+
}
1045+
1046+
async fn root_handler() -> &'static str {
1047+
"MinerU MCP DragonOS Server - Health Check: GET /health"
1048+
}
1049+
9901050
pub async fn run() -> Result<(), Box<dyn std::error::Error>> {
9911051
init_tracing();
9921052
let settings = Settings::from_env();
9931053
if !settings.use_local_api && settings.mineru_api_key.is_none() {
9941054
warn!("MINERU_API_KEY 未设置,远程解析将失败");
9951055
}
9961056
let server = MineruServer::new(settings)?;
997-
let service = server.serve(stdio()).await.inspect_err(|err| {
1057+
1058+
// 从环境变量读取健康检查端口,默认 3000
1059+
let health_port = env::var("HEALTH_CHECK_PORT")
1060+
.ok()
1061+
.and_then(|v| v.parse::<u16>().ok())
1062+
.unwrap_or(3000);
1063+
1064+
// 启动 HTTP 健康检查服务器
1065+
let server_clone = server.clone();
1066+
tokio::spawn(async move {
1067+
if let Err(err) = run_health_check_server(server_clone, health_port).await {
1068+
error!("健康检查服务器错误: {err}");
1069+
}
1070+
});
1071+
1072+
// 启动 MCP stdio 服务
1073+
let service = server.serve(stdio())
1074+
.await
1075+
.inspect_err(|err| {
9981076
error!("启动MCP服务失败: {err}");
9991077
})?;
10001078
service.waiting().await?;

0 commit comments

Comments
 (0)