提交qiming-mcp-proxy
This commit is contained in:
451
qiming-mcp-proxy/docs/LOG_CONFIGURATION.md
Normal file
451
qiming-mcp-proxy/docs/LOG_CONFIGURATION.md
Normal file
@@ -0,0 +1,451 @@
|
||||
# mcp-proxy 日志配置指南
|
||||
|
||||
## 概述
|
||||
|
||||
mcp-proxy 支持灵活的日志配置,可以通过配置文件或环境变量进行配置,非常适合与 Tauri 等客户端集成。
|
||||
|
||||
## 日志功能特性
|
||||
|
||||
- ✅ **按日期滚动**:每天自动创建新的日志文件
|
||||
- ✅ **自动清理**:保留最近 N 天的日志文件
|
||||
- ✅ **多级别过滤**:支持 trace/debug/info/warn/error
|
||||
- ✅ **双输出**:同时输出到控制台和文件
|
||||
- ✅ **结构化日志**:使用 tracing 框架
|
||||
- ✅ **环境变量覆盖**:支持运行时动态配置
|
||||
|
||||
## 配置方式
|
||||
|
||||
### 方式 1: 环境变量(推荐用于 Tauri 集成)
|
||||
|
||||
环境变量具有最高优先级,会覆盖配置文件中的设置。
|
||||
|
||||
```bash
|
||||
# 设置日志目录(Tauri 可以传递应用数据目录)
|
||||
export MCP_PROXY_LOG_DIR="/path/to/logs"
|
||||
|
||||
# 设置日志级别
|
||||
export MCP_PROXY_LOG_LEVEL="info"
|
||||
|
||||
# 设置服务器端口
|
||||
export MCP_PROXY_PORT="18099"
|
||||
```
|
||||
|
||||
**Windows (PowerShell)**:
|
||||
```powershell
|
||||
$env:MCP_PROXY_LOG_DIR = "C:\Users\YourName\AppData\Roaming\YourApp\logs"
|
||||
$env:MCP_PROXY_LOG_LEVEL = "info"
|
||||
$env:MCP_PROXY_PORT = "18099"
|
||||
```
|
||||
|
||||
**Windows (CMD)**:
|
||||
```cmd
|
||||
set MCP_PROXY_LOG_DIR=C:\Users\YourName\AppData\Roaming\YourApp\logs
|
||||
set MCP_PROXY_LOG_LEVEL=info
|
||||
set MCP_PROXY_PORT=18099
|
||||
```
|
||||
|
||||
### 方式 2: 配置文件
|
||||
|
||||
创建 `config.yml` 文件:
|
||||
|
||||
```yaml
|
||||
server:
|
||||
port: 18099
|
||||
|
||||
log:
|
||||
level: "info"
|
||||
path: "./logs"
|
||||
retain_days: 7
|
||||
```
|
||||
|
||||
配置文件查找顺序:
|
||||
1. `/app/config.yml` (Docker 容器内)
|
||||
2. `./config.yml` (当前工作目录)
|
||||
3. 环境变量 `BOT_SERVER_CONFIG` 指定的路径
|
||||
|
||||
## Tauri 集成示例
|
||||
|
||||
### Rust 端 (nuwax-agent)
|
||||
|
||||
```rust
|
||||
use std::process::Command;
|
||||
use std::path::PathBuf;
|
||||
use tauri::api::path::app_data_dir;
|
||||
|
||||
pub async fn start_mcp_proxy(
|
||||
tauri_config: &tauri::Config,
|
||||
port: u16,
|
||||
) -> Result<Child> {
|
||||
// 获取 Tauri 应用数据目录
|
||||
let app_data = app_data_dir(tauri_config)
|
||||
.ok_or_else(|| anyhow::anyhow!("无法获取应用数据目录"))?;
|
||||
|
||||
// 创建日志目录
|
||||
let log_dir = app_data.join("logs").join("mcp-proxy");
|
||||
std::fs::create_dir_all(&log_dir)?;
|
||||
|
||||
let log_dir_str = log_dir.to_string_lossy().to_string();
|
||||
|
||||
tracing::info!("MCP Proxy 日志目录: {}", log_dir_str);
|
||||
|
||||
// 查找 mcp-proxy 可执行文件
|
||||
let mcp_proxy_path = resolve_npm_bin("mcp-proxy").await?;
|
||||
|
||||
// 启动进程,传递环境变量
|
||||
let mut cmd = tokio::process::Command::new(&mcp_proxy_path);
|
||||
|
||||
cmd.env("MCP_PROXY_LOG_DIR", &log_dir_str);
|
||||
cmd.env("MCP_PROXY_LOG_LEVEL", "info");
|
||||
cmd.env("MCP_PROXY_PORT", port.to_string());
|
||||
|
||||
// Windows: 隐藏 CMD 窗口
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||
}
|
||||
|
||||
// 捕获 stdout/stderr 用于调试
|
||||
cmd.stdout(std::process::Stdio::piped());
|
||||
cmd.stderr(std::process::Stdio::piped());
|
||||
|
||||
let mut child = cmd.spawn()?;
|
||||
|
||||
// 读取并记录输出
|
||||
if let Some(stdout) = child.stdout.take() {
|
||||
tokio::spawn(async move {
|
||||
let reader = tokio::io::BufReader::new(stdout);
|
||||
let mut lines = reader.lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
tracing::info!("[mcp-proxy stdout] {}", line);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(stderr) = child.stderr.take() {
|
||||
tokio::spawn(async move {
|
||||
let reader = tokio::io::BufReader::new(stderr);
|
||||
let mut lines = reader.lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
tracing::error!("[mcp-proxy stderr] {}", line);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(child)
|
||||
}
|
||||
```
|
||||
|
||||
### TypeScript/JavaScript 端
|
||||
|
||||
```typescript
|
||||
import { Command } from '@tauri-apps/api/shell';
|
||||
import { appDataDir, join } from '@tauri-apps/api/path';
|
||||
|
||||
async function startMcpProxy(port: number = 18099) {
|
||||
// 获取应用数据目录
|
||||
const appData = await appDataDir();
|
||||
const logDir = await join(appData, 'logs', 'mcp-proxy');
|
||||
|
||||
console.log(`MCP Proxy 日志目录: ${logDir}`);
|
||||
|
||||
// 创建命令
|
||||
const command = new Command('mcp-proxy', [], {
|
||||
env: {
|
||||
MCP_PROXY_LOG_DIR: logDir,
|
||||
MCP_PROXY_LOG_LEVEL: 'info',
|
||||
MCP_PROXY_PORT: port.toString(),
|
||||
}
|
||||
});
|
||||
|
||||
// 监听输出
|
||||
command.on('output', (data) => {
|
||||
console.log('[mcp-proxy]', data);
|
||||
});
|
||||
|
||||
command.on('error', (error) => {
|
||||
console.error('[mcp-proxy error]', error);
|
||||
});
|
||||
|
||||
// 执行命令
|
||||
const child = await command.spawn();
|
||||
|
||||
console.log(`MCP Proxy 已启动,PID: ${child.pid}`);
|
||||
|
||||
return child;
|
||||
}
|
||||
```
|
||||
|
||||
## 日志文件格式
|
||||
|
||||
### 文件命名规则
|
||||
|
||||
```
|
||||
日志目录/
|
||||
├── log.2026-02-12 # 2026年2月12日的日志
|
||||
├── log.2026-02-13 # 2026年2月13日的日志
|
||||
└── log.2026-02-14 # 2026年2月14日的日志(当前)
|
||||
```
|
||||
|
||||
**自动清理**:默认保留最近 5 天的日志,可通过 `retain_days` 配置。
|
||||
|
||||
### 日志内容示例
|
||||
|
||||
```
|
||||
2026-02-12T14:30:15.123456Z INFO mcp_proxy: ========================================
|
||||
2026-02-12T14:30:15.123789Z INFO mcp_proxy: MCP-Proxy 服务启动
|
||||
2026-02-12T14:30:15.124012Z INFO mcp_proxy: 命令: proxy (HTTP 服务器模式)
|
||||
2026-02-12T14:30:15.124234Z INFO mcp_proxy: 版本: 0.1.39
|
||||
2026-02-12T14:30:15.124456Z INFO mcp_proxy: 配置信息:
|
||||
2026-02-12T14:30:15.124678Z INFO mcp_proxy: - 监听端口: 18099
|
||||
2026-02-12T14:30:15.124890Z INFO mcp_proxy: - 日志目录: /path/to/logs
|
||||
2026-02-12T14:30:15.125012Z INFO mcp_proxy: - 日志级别: info
|
||||
2026-02-12T14:30:15.125234Z INFO mcp_proxy: - 日志保留: 7 天
|
||||
2026-02-12T14:30:15.125456Z INFO mcp_proxy: 环境变量覆盖:
|
||||
2026-02-12T14:30:15.125678Z INFO mcp_proxy: - MCP_PROXY_LOG_DIR: /custom/log/path
|
||||
2026-02-12T14:30:15.125890Z INFO mcp_proxy: ========================================
|
||||
2026-02-12T14:30:15.234567Z INFO mcp_proxy: 尝试绑定到地址: 0.0.0.0:18099
|
||||
2026-02-12T14:30:15.345678Z INFO mcp_proxy: 成功绑定到地址: 0.0.0.0:18099
|
||||
2026-02-12T14:30:15.456789Z INFO mcp_proxy: 初始化应用状态...
|
||||
2026-02-12T14:30:15.567890Z INFO mcp_proxy: 应用状态初始化完成
|
||||
2026-02-12T14:30:15.678901Z INFO mcp_proxy: 初始化路由...
|
||||
2026-02-12T14:30:15.789012Z INFO mcp_proxy: 路由初始化完成
|
||||
2026-02-12T14:30:15.890123Z INFO mcp_proxy: ✅ 服务启动成功,监听地址: 0.0.0.0:18099
|
||||
2026-02-12T14:30:15.901234Z INFO mcp_proxy: ✅ 健康检查端点: http://0.0.0.0:18099/health
|
||||
2026-02-12T14:30:15.912345Z INFO mcp_proxy: ✅ MCP 服务列表: http://0.0.0.0:18099/mcp
|
||||
2026-02-12T14:30:16.023456Z INFO mcp_proxy: ✅ MCP服务状态检查定时任务已启动
|
||||
2026-02-12T14:30:16.134567Z INFO mcp_proxy: 系统信息:
|
||||
2026-02-12T14:30:16.145678Z INFO mcp_proxy: - 操作系统: windows
|
||||
2026-02-12T14:30:16.156789Z INFO mcp_proxy: - 架构: x86_64
|
||||
2026-02-12T14:30:16.167890Z INFO mcp_proxy: - 工作目录: "C:\\Users\\YourName\\AppData\\Local"
|
||||
2026-02-12T14:30:16.178901Z INFO mcp_proxy: 🚀 HTTP 服务器启动,等待连接...
|
||||
```
|
||||
|
||||
## 日志级别说明
|
||||
|
||||
| 级别 | 用途 | 建议场景 |
|
||||
|------|------|---------|
|
||||
| `trace` | 最详细的调试信息 | 开发调试 |
|
||||
| `debug` | 调试信息 | 开发和测试 |
|
||||
| `info` | 一般信息 | **生产环境推荐** |
|
||||
| `warn` | 警告信息 | 最小日志 |
|
||||
| `error` | 错误信息 | 仅记录错误 |
|
||||
|
||||
## 启动时的 stderr 输出
|
||||
|
||||
即使日志写入文件,mcp-proxy 也会在启动时向 **stderr** 输出关键信息:
|
||||
|
||||
```
|
||||
========================================
|
||||
MCP-Proxy 启动中...
|
||||
版本: 0.1.39
|
||||
配置加载完成:
|
||||
- 端口: 18099
|
||||
- 日志目录: /path/to/logs
|
||||
- 日志级别: info
|
||||
- 日志保留天数: 7
|
||||
========================================
|
||||
```
|
||||
|
||||
这确保即使日志系统初始化失败,也能看到基本的启动信息。
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 问题 1: 日志文件未创建
|
||||
|
||||
**可能原因**:
|
||||
- 日志目录路径不存在或无权限
|
||||
- 环境变量设置错误
|
||||
|
||||
**解决方案**:
|
||||
```bash
|
||||
# 检查日志目录是否存在
|
||||
ls -la /path/to/logs
|
||||
|
||||
# 检查环境变量
|
||||
echo $MCP_PROXY_LOG_DIR
|
||||
|
||||
# 手动创建目录
|
||||
mkdir -p /path/to/logs
|
||||
chmod 755 /path/to/logs
|
||||
```
|
||||
|
||||
### 问题 2: 看不到日志输出
|
||||
|
||||
**可能原因**:
|
||||
- 日志级别设置过高(如 `error`)
|
||||
- Windows 下 CMD 窗口被隐藏
|
||||
|
||||
**解决方案**:
|
||||
```bash
|
||||
# 降低日志级别
|
||||
export MCP_PROXY_LOG_LEVEL="debug"
|
||||
|
||||
# 或使用 RUST_LOG 环境变量
|
||||
export RUST_LOG="mcp_proxy=debug"
|
||||
```
|
||||
|
||||
### 问题 3: 日志文件过多
|
||||
|
||||
**可能原因**:
|
||||
- `retain_days` 设置过大
|
||||
|
||||
**解决方案**:
|
||||
```bash
|
||||
# 设置保留天数(通过配置文件)
|
||||
# config.yml
|
||||
log:
|
||||
retain_days: 3 # 只保留 3 天
|
||||
|
||||
# 或手动清理旧日志
|
||||
find /path/to/logs -name "log.*" -mtime +7 -delete
|
||||
```
|
||||
|
||||
## 完整示例:nuwax-agent 集成
|
||||
|
||||
```rust
|
||||
// nuwax-agent-core/src/service/mcp_proxy.rs
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use tokio::process::Child;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct McpProxyConfig {
|
||||
pub port: u16,
|
||||
pub log_dir: PathBuf,
|
||||
pub log_level: String,
|
||||
}
|
||||
|
||||
pub async fn start_mcp_proxy(config: McpProxyConfig) -> Result<Child> {
|
||||
// 确保日志目录存在
|
||||
std::fs::create_dir_all(&config.log_dir)
|
||||
.context("创建 MCP Proxy 日志目录失败")?;
|
||||
|
||||
tracing::info!("启动 MCP Proxy:");
|
||||
tracing::info!(" - 端口: {}", config.port);
|
||||
tracing::info!(" - 日志目录: {}", config.log_dir.display());
|
||||
tracing::info!(" - 日志级别: {}", config.log_level);
|
||||
|
||||
// 查找可执行文件
|
||||
let mcp_proxy_path = which::which("mcp-proxy")
|
||||
.or_else(|_| {
|
||||
// Fallback: 尝试 npm 全局路径
|
||||
let home = std::env::var("HOME")
|
||||
.or_else(|_| std::env::var("USERPROFILE"))?;
|
||||
|
||||
#[cfg(windows)]
|
||||
let npm_bin = PathBuf::from(&home).join(".local/bin/mcp-proxy.cmd");
|
||||
|
||||
#[cfg(not(windows))]
|
||||
let npm_bin = PathBuf::from(&home).join(".local/bin/mcp-proxy");
|
||||
|
||||
if npm_bin.exists() {
|
||||
Ok(npm_bin)
|
||||
} else {
|
||||
Err(which::Error::CannotFindBinaryPath)
|
||||
}
|
||||
})
|
||||
.context("找不到 mcp-proxy 可执行文件")?;
|
||||
|
||||
tracing::info!("mcp-proxy 路径: {}", mcp_proxy_path.display());
|
||||
|
||||
// 构建命令
|
||||
let mut cmd = tokio::process::Command::new(&mcp_proxy_path);
|
||||
|
||||
// 设置环境变量
|
||||
cmd.env("MCP_PROXY_LOG_DIR", config.log_dir.to_string_lossy().as_ref());
|
||||
cmd.env("MCP_PROXY_LOG_LEVEL", &config.log_level);
|
||||
cmd.env("MCP_PROXY_PORT", config.port.to_string());
|
||||
|
||||
// 捕获输出
|
||||
cmd.stdout(std::process::Stdio::piped());
|
||||
cmd.stderr(std::process::Stdio::piped());
|
||||
|
||||
// Windows: 隐藏 CMD 窗口
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||
}
|
||||
|
||||
// 启动进程
|
||||
let mut child = cmd.spawn()
|
||||
.context("启动 mcp-proxy 进程失败")?;
|
||||
|
||||
// 异步读取 stdout
|
||||
if let Some(stdout) = child.stdout.take() {
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
|
||||
tokio::spawn(async move {
|
||||
let reader = BufReader::new(stdout);
|
||||
let mut lines = reader.lines();
|
||||
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
tracing::info!("[mcp-proxy] {}", line);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 异步读取 stderr
|
||||
if let Some(stderr) = child.stderr.take() {
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
|
||||
tokio::spawn(async move {
|
||||
let reader = BufReader::new(stderr);
|
||||
let mut lines = reader.lines();
|
||||
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
tracing::error!("[mcp-proxy stderr] {}", line);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tracing::info!("MCP Proxy 进程已启动,PID: {:?}", child.id());
|
||||
|
||||
Ok(child)
|
||||
}
|
||||
```
|
||||
|
||||
## 环境变量参考
|
||||
|
||||
| 环境变量 | 类型 | 默认值 | 说明 |
|
||||
|---------|------|--------|------|
|
||||
| `MCP_PROXY_PORT` | u16 | `3000` | HTTP 服务监听端口 |
|
||||
| `MCP_PROXY_LOG_DIR` | String | `./logs` | 日志文件目录 |
|
||||
| `MCP_PROXY_LOG_LEVEL` | String | `info` | 日志级别 (trace/debug/info/warn/error) |
|
||||
| `RUST_LOG` | String | - | Rust 标准日志环境变量(更细粒度控制) |
|
||||
| `BOT_SERVER_CONFIG` | String | - | 自定义配置文件路径 |
|
||||
|
||||
## 高级用法:RUST_LOG
|
||||
|
||||
`RUST_LOG` 环境变量提供更细粒度的日志控制:
|
||||
|
||||
```bash
|
||||
# 只显示 mcp_proxy 模块的 debug 级别日志
|
||||
export RUST_LOG="mcp_proxy=debug"
|
||||
|
||||
# 显示多个模块的日志
|
||||
export RUST_LOG="mcp_proxy=debug,tokio=info,hyper=warn"
|
||||
|
||||
# 显示所有模块的 trace 级别日志(非常详细)
|
||||
export RUST_LOG="trace"
|
||||
|
||||
# 组合使用
|
||||
export RUST_LOG="debug,mcp_proxy=trace,hyper::proto=error"
|
||||
```
|
||||
|
||||
**注意**:`RUST_LOG` 优先级高于 `MCP_PROXY_LOG_LEVEL`。
|
||||
|
||||
## 总结
|
||||
|
||||
- ✅ 使用 `MCP_PROXY_LOG_DIR` 环境变量指定日志目录(Tauri 集成推荐)
|
||||
- ✅ 日志文件按日期自动滚动,默认保留 5 天
|
||||
- ✅ 关键启动信息会输出到 stderr,即使日志系统失败也能看到
|
||||
- ✅ 支持通过环境变量动态配置,无需修改配置文件
|
||||
- ✅ Windows 下可以隐藏 CMD 窗口,但仍然捕获日志输出
|
||||
|
||||
更多信息请参考:
|
||||
- [Tracing 文档](https://docs.rs/tracing)
|
||||
- [Tracing Subscriber 文档](https://docs.rs/tracing-subscriber)
|
||||
@@ -0,0 +1,511 @@
|
||||
# mcp-proxy 启动失败分析
|
||||
|
||||
## 问题描述
|
||||
|
||||
从 nuwax-agent 日志中发现,mcp-proxy 启动后健康检查持续失败:
|
||||
|
||||
```
|
||||
2026-02-12T06:19:01.052725Z ERROR [McpProxy] 健康检查失败:
|
||||
MCP Proxy 健康检查超时: 等待 15s 后 http://127.0.0.1:18099/mcp 仍未就绪
|
||||
```
|
||||
|
||||
**启动日志**:
|
||||
```
|
||||
2026-02-12T06:18:41.088275Z INFO [McpProxy] 可执行文件路径: C:\Users\MECHREVO\.local\bin\mcp-proxy.cmd
|
||||
2026-02-12T06:18:41.088293Z INFO [McpProxy] 监听地址: 127.0.0.1:18099
|
||||
2026-02-12T06:18:41.088337Z INFO [McpProxy] Windows: 添加 npm 全局 bin 到 PATH: C:\Users\MECHREVO\AppData\Roaming\npm
|
||||
... (15 秒后)
|
||||
2026-02-12T06:19:01.052725Z ERROR [McpProxy] 健康检查失败
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 关键问题
|
||||
|
||||
### 1. 缺少子进程输出
|
||||
|
||||
由于 CMD 窗口可能被隐藏或者 nuwax-agent 没有捕获 stdout/stderr,**看不到 mcp-proxy 的实际错误信息**。
|
||||
|
||||
这就像在黑暗中调试:
|
||||
```
|
||||
nuwax-agent: "mcp-proxy,你启动了吗?"
|
||||
[15 秒沉默]
|
||||
nuwax-agent: "超时了,你失败了!"
|
||||
mcp-proxy: (可能早就崩溃了,但没人知道为什么)
|
||||
```
|
||||
|
||||
### 2. 健康检查端点可能不对
|
||||
|
||||
日志显示健康检查 URL:
|
||||
```
|
||||
http://127.0.0.1:18099/mcp
|
||||
```
|
||||
|
||||
**需要验证**:mcp-proxy 的健康检查端点是否真的是 `/mcp`?
|
||||
|
||||
根据 mcp-proxy 代码,让我检查实际的路由:
|
||||
|
||||
---
|
||||
|
||||
## 诊断步骤
|
||||
|
||||
### 步骤 1: 检查 mcp-proxy 实际的健康检查路由
|
||||
|
||||
从 mcp-proxy 的代码分析(假设基于之前的代码结构):
|
||||
|
||||
**可能的健康检查端点**:
|
||||
- `/health` - 标准健康检查
|
||||
- `/` - 根路径
|
||||
- `/mcp` - MCP 服务列表
|
||||
- `/api/health` - API 健康检查
|
||||
|
||||
**需要确认**: nuwax-agent 使用的 `/mcp` 端点是否正确。
|
||||
|
||||
### 步骤 2: 手动测试 mcp-proxy
|
||||
|
||||
在 Windows 测试机上手动运行:
|
||||
|
||||
```powershell
|
||||
# 1. 手动启动 mcp-proxy
|
||||
cd C:\Users\MECHREVO\.local\bin
|
||||
.\mcp-proxy.cmd --help
|
||||
|
||||
# 2. 查看可用命令
|
||||
.\mcp-proxy.cmd server --help
|
||||
|
||||
# 3. 手动启动服务器(查看完整输出)
|
||||
.\mcp-proxy.cmd server --port 18099
|
||||
|
||||
# 4. 在另一个终端测试健康检查
|
||||
curl http://127.0.0.1:18099/health
|
||||
curl http://127.0.0.1:18099/mcp
|
||||
curl http://127.0.0.1:18099/
|
||||
|
||||
# 5. 检查进程
|
||||
tasklist | findstr node
|
||||
netstat -ano | findstr "18099"
|
||||
```
|
||||
|
||||
### 步骤 3: 检查依赖问题
|
||||
|
||||
mcp-proxy 可能缺少 Node.js 依赖:
|
||||
|
||||
```powershell
|
||||
# 检查 mcp-proxy 的依赖
|
||||
cd C:\Users\MECHREVO\.local\bin\node_modules\mcp-stdio-proxy
|
||||
npm list
|
||||
|
||||
# 重新安装(如果有问题)
|
||||
npm install -g mcp-stdio-proxy --force
|
||||
```
|
||||
|
||||
### 步骤 4: 检查端口占用
|
||||
|
||||
```powershell
|
||||
# 检查 18099 端口是否被占用
|
||||
netstat -ano | findstr "18099"
|
||||
|
||||
# 如果被占用,查看占用进程
|
||||
tasklist /FI "PID eq <PID>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 可能的失败原因
|
||||
|
||||
### 原因 1: Node.js 模块缺失 ⭐ 最可能
|
||||
|
||||
**症状**: npm 包安装成功,但 node_modules 不完整
|
||||
|
||||
**可能性**:
|
||||
- npm 安装过程中网络问题
|
||||
- 包的 postinstall 脚本失败
|
||||
- Windows 长路径问题(路径超过 260 字符)
|
||||
|
||||
**验证**:
|
||||
```powershell
|
||||
# 检查 mcp-stdio-proxy 的 node_modules
|
||||
dir C:\Users\MECHREVO\.local\bin\node_modules\mcp-stdio-proxy\node_modules
|
||||
|
||||
# 检查 package.json 中的依赖
|
||||
type C:\Users\MECHREVO\.local\bin\node_modules\mcp-stdio-proxy\package.json
|
||||
```
|
||||
|
||||
**解决**:
|
||||
```powershell
|
||||
# 清理并重新安装
|
||||
npm uninstall -g mcp-stdio-proxy
|
||||
npm cache clean --force
|
||||
npm install -g mcp-stdio-proxy --verbose
|
||||
```
|
||||
|
||||
### 原因 2: 健康检查 URL 不匹配
|
||||
|
||||
**症状**: mcp-proxy 启动成功,但 nuwax-agent 访问了错误的端点
|
||||
|
||||
**验证**: 需要查看 mcp-proxy 的实际路由
|
||||
|
||||
从 mcp-proxy 源码看,可能的路由:
|
||||
```rust
|
||||
// mcp-proxy/src/main.rs 或 server 模块
|
||||
.route("/", get(root_handler))
|
||||
.route("/health", get(health_check))
|
||||
.route("/mcp", get(list_mcp_services)) // 这个可能不是健康检查!
|
||||
```
|
||||
|
||||
**/mcp 可能是服务列表端点,不是健康检查!**
|
||||
|
||||
**正确的健康检查应该是**:
|
||||
```rust
|
||||
// 应该访问
|
||||
http://127.0.0.1:18099/health
|
||||
// 而不是
|
||||
http://127.0.0.1:18099/mcp
|
||||
```
|
||||
|
||||
### 原因 3: 环境变量问题
|
||||
|
||||
**症状**: mcp-proxy 需要特定的环境变量
|
||||
|
||||
日志显示添加了 PATH:
|
||||
```
|
||||
Windows: 添加 npm 全局 bin 到 PATH: C:\Users\MECHREVO\AppData\Roaming\npm
|
||||
```
|
||||
|
||||
但可能还需要其他环境变量(如果 mcp-proxy 有子进程)。
|
||||
|
||||
### 原因 4: 配置文件缺失
|
||||
|
||||
**症状**: mcp-proxy 启动时需要配置文件,但找不到
|
||||
|
||||
从日志中看到:
|
||||
```
|
||||
WARN [McpProxy] mcpServers 配置为空,跳过启动
|
||||
```
|
||||
|
||||
**这可能是关键**!如果 `mcpServers` 配置为空,mcp-proxy 可能:
|
||||
- 不启动 HTTP 服务器
|
||||
- 或者启动了但没有实际的服务端点
|
||||
|
||||
**需要确认**:
|
||||
1. mcp-proxy 是否需要配置文件?
|
||||
2. 配置文件应该在哪里?
|
||||
3. 空配置时 mcp-proxy 的行为是什么?
|
||||
|
||||
### 原因 5: Windows 防火墙
|
||||
|
||||
**症状**: 本地回环连接被防火墙阻止
|
||||
|
||||
**验证**:
|
||||
```powershell
|
||||
# 检查防火墙规则
|
||||
netsh advfirewall firewall show rule name=all | findstr "18099"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 深入分析:mcpServers 配置为空
|
||||
|
||||
从日志:
|
||||
```
|
||||
2026-02-12T06:18:32.797802Z WARN [McpProxy] mcpServers 配置为空,跳过启动
|
||||
```
|
||||
|
||||
**这说明**:
|
||||
1. nuwax-agent 读取配置发现 `mcpServers` 为空
|
||||
2. nuwax-agent 可能**根本没有启动 mcp-proxy 进程**
|
||||
3. 或者启动了但 mcp-proxy 立即退出了
|
||||
|
||||
**查看完整的启动流程**:
|
||||
```
|
||||
06:18:32.797761 INFO [BinPath] mcp-proxy 找到: C:\Users\...\mcp-proxy.cmd
|
||||
06:18:32.797802 WARN [McpProxy] mcpServers 配置为空,跳过启动
|
||||
06:18:32.797825 INFO [Services] MCP Proxy 启动命令已发送
|
||||
|
||||
06:18:41.088275 INFO [McpProxy] 可执行文件路径: C:\Users\...\mcp-proxy.cmd
|
||||
06:18:41.088293 INFO [McpProxy] 监听地址: 127.0.0.1:18099
|
||||
... (启动了?)
|
||||
|
||||
06:19:01.052725 ERROR [McpProxy] 健康检查失败
|
||||
```
|
||||
|
||||
**矛盾点**:
|
||||
- 前面说"跳过启动"
|
||||
- 后面又有启动日志和健康检查失败
|
||||
|
||||
**可能的情况**:
|
||||
1. **第一次启动**(06:18:32)被跳过了(配置为空)
|
||||
2. **第二次启动**(06:18:41)实际执行了,但失败了
|
||||
|
||||
让我查看日志中是否有多次重启:
|
||||
|
||||
从日志确实看到多次重启:
|
||||
```
|
||||
06:18:11 开始重启所有服务
|
||||
06:18:32 第一次尝试启动 MCP Proxy (配置为空,跳过)
|
||||
06:18:35 再次重启所有服务
|
||||
06:18:40 第二次尝试启动 MCP Proxy (实际启动了)
|
||||
06:19:01 健康检查失败
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 推荐的修复方案
|
||||
|
||||
### 方案 A: 捕获 mcp-proxy 的 stdout/stderr ⭐ 最优先
|
||||
|
||||
**问题**: 看不到 mcp-proxy 的实际错误
|
||||
|
||||
**解决**: 使用前面文档中的 `start_service_with_logging` 函数
|
||||
|
||||
**nuwax-agent 代码修改**:
|
||||
```rust
|
||||
// nuwax-agent-core/src/service/mcp_proxy.rs
|
||||
|
||||
pub async fn start_mcp_proxy(config: &McpProxyConfig) -> Result<Child> {
|
||||
let cmd_path = resolve_npm_bin("mcp-proxy").await?;
|
||||
|
||||
// 准备启动参数
|
||||
let mut env_vars = vec![
|
||||
("MCP_PORT".to_string(), config.port.to_string()),
|
||||
];
|
||||
|
||||
// 如果有配置文件,添加环境变量
|
||||
if let Some(config_path) = &config.config_file {
|
||||
env_vars.push(("MCP_CONFIG".to_string(), config_path.clone()));
|
||||
}
|
||||
|
||||
// 使用带日志捕获的启动函数
|
||||
let child = crate::utils::process::start_service_with_logging(
|
||||
&cmd_path,
|
||||
"McpProxy",
|
||||
Some(env_vars),
|
||||
Some(vec!["server".to_string()]), // 子命令
|
||||
).await?;
|
||||
|
||||
tracing::info!("[McpProxy] 进程已启动,PID: {:?}", child.id());
|
||||
|
||||
// 健康检查(使用正确的端点)
|
||||
let health_url = format!("http://127.0.0.1:{}/health", config.port);
|
||||
wait_for_http_ready(&health_url, Duration::from_secs(30)).await
|
||||
.context("MCP Proxy 健康检查超时")?;
|
||||
|
||||
Ok(child)
|
||||
}
|
||||
```
|
||||
|
||||
**效果**: 现在可以在日志中看到 mcp-proxy 的所有输出!
|
||||
|
||||
### 方案 B: 修复健康检查端点
|
||||
|
||||
**问题**: 健康检查可能用错了端点
|
||||
|
||||
**验证**:
|
||||
1. 查看 mcp-proxy 的路由定义
|
||||
2. 确认健康检查端点是 `/health` 还是 `/mcp`
|
||||
|
||||
**建议**: 尝试多个端点
|
||||
|
||||
```rust
|
||||
async fn check_mcp_proxy_health(port: u16) -> Result<()> {
|
||||
let endpoints = vec![
|
||||
"/health",
|
||||
"/",
|
||||
"/mcp",
|
||||
"/api/health",
|
||||
];
|
||||
|
||||
for endpoint in &endpoints {
|
||||
let url = format!("http://127.0.0.1:{}{}", port, endpoint);
|
||||
tracing::debug!("Trying health check: {}", url);
|
||||
|
||||
match reqwest::get(&url).await {
|
||||
Ok(resp) if resp.status().is_success() => {
|
||||
tracing::info!("Health check succeeded: {}", url);
|
||||
return Ok(());
|
||||
}
|
||||
Ok(resp) => {
|
||||
tracing::warn!("Health check {} returned: {}", url, resp.status());
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::debug!("Health check {} failed: {}", url, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anyhow::bail!("All health check endpoints failed")
|
||||
}
|
||||
```
|
||||
|
||||
### 方案 C: 处理空配置情况
|
||||
|
||||
**问题**: `mcpServers` 为空时,mcp-proxy 可能不应该启动
|
||||
|
||||
**建议**: 在 nuwax-agent 中明确处理
|
||||
|
||||
```rust
|
||||
pub async fn start_mcp_proxy(config: &McpProxyConfig) -> Result<Option<Child>> {
|
||||
// 检查配置
|
||||
if config.mcp_servers.is_empty() {
|
||||
tracing::info!("[McpProxy] mcpServers 配置为空,跳过启动");
|
||||
return Ok(None); // 返回 None 而不是启动失败
|
||||
}
|
||||
|
||||
// 启动服务
|
||||
let child = start_service_with_logging(...).await?;
|
||||
|
||||
Ok(Some(child))
|
||||
}
|
||||
```
|
||||
|
||||
### 方案 D: 增加超时时间和重试
|
||||
|
||||
**问题**: 15 秒超时可能不够
|
||||
|
||||
**建议**:
|
||||
```rust
|
||||
// 增加超时到 30 秒
|
||||
wait_for_http_ready(&health_url, Duration::from_secs(30)).await?;
|
||||
|
||||
// 或者添加重试逻辑
|
||||
for attempt in 1..=3 {
|
||||
match start_and_check_mcp_proxy(config).await {
|
||||
Ok(child) => return Ok(child),
|
||||
Err(e) if attempt < 3 => {
|
||||
tracing::warn!("MCP Proxy 启动失败 (尝试 {}/3): {}", attempt, e);
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 需要 mcp-proxy 仓库确认的信息
|
||||
|
||||
为了完全解决这个问题,需要从 mcp-proxy 代码中确认:
|
||||
|
||||
### 1. 健康检查端点
|
||||
|
||||
**问题**: 正确的健康检查 URL 是什么?
|
||||
|
||||
**需要检查的文件**:
|
||||
- `mcp-proxy/src/server.rs` 或 `mcp-proxy/src/main.rs`
|
||||
- 查找 `Router` 或 `axum::Router` 配置
|
||||
- 查找 `health` 相关的路由
|
||||
|
||||
**期望的代码**:
|
||||
```rust
|
||||
// mcp-proxy/src/server.rs
|
||||
let app = Router::new()
|
||||
.route("/health", get(health_check)) // ← 这个!
|
||||
.route("/mcp", get(list_services))
|
||||
// ...
|
||||
```
|
||||
|
||||
### 2. 空配置行为
|
||||
|
||||
**问题**: 当没有配置 MCP 服务时,mcp-proxy 会怎么做?
|
||||
|
||||
**可能的行为**:
|
||||
1. 启动 HTTP 服务器,但没有 MCP 服务
|
||||
2. 直接退出(因为没有服务可代理)
|
||||
3. 报错
|
||||
|
||||
**需要确认**: mcp-proxy 的预期行为
|
||||
|
||||
### 3. 必需的环境变量
|
||||
|
||||
**问题**: mcp-proxy 是否需要特定的环境变量?
|
||||
|
||||
**需要检查**:
|
||||
- 配置文件路径(`MCP_CONFIG`?)
|
||||
- 日志级别(`RUST_LOG`?)
|
||||
- 其他配置
|
||||
|
||||
### 4. 启动子命令
|
||||
|
||||
**问题**: 正确的启动命令是什么?
|
||||
|
||||
**可能的形式**:
|
||||
```bash
|
||||
mcp-proxy # 默认启动
|
||||
mcp-proxy server # 明确的 server 子命令
|
||||
mcp-proxy server --port 18099
|
||||
mcp-proxy --config config.json server
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 立即可以做的
|
||||
|
||||
### 在 mcp-proxy 仓库
|
||||
|
||||
1. **添加详细的启动日志**
|
||||
```rust
|
||||
// mcp-proxy/src/main.rs
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
tracing::info!("mcp-proxy starting...");
|
||||
tracing::info!("Version: {}", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
let config = load_config()?;
|
||||
tracing::info!("Configuration loaded: {:?}", config);
|
||||
|
||||
if config.mcp_servers.is_empty() {
|
||||
tracing::warn!("No MCP servers configured");
|
||||
// 决定是继续还是退出
|
||||
}
|
||||
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], config.port));
|
||||
tracing::info!("Starting HTTP server on {}", addr);
|
||||
|
||||
// 启动服务器...
|
||||
tracing::info!("HTTP server started successfully");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
2. **统一健康检查端点**
|
||||
```rust
|
||||
// 确保有一个明确的健康检查端点
|
||||
.route("/health", get(|| async { "OK" }))
|
||||
```
|
||||
|
||||
3. **添加版本信息端点**
|
||||
```rust
|
||||
.route("/version", get(|| async {
|
||||
Json(json!({
|
||||
"version": env!("CARGO_PKG_VERSION"),
|
||||
"name": env!("CARGO_PKG_NAME"),
|
||||
}))
|
||||
}))
|
||||
```
|
||||
|
||||
### 在 nuwax-agent 仓库
|
||||
|
||||
1. **立即实现日志捕获**(最优先)
|
||||
2. **增加健康检查超时时间** (15s → 30s)
|
||||
3. **尝试多个健康检查端点**
|
||||
4. **空配置时跳过启动,不报错**
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
| 问题 | 严重性 | 解决方案 | 优先级 |
|
||||
|------|--------|----------|--------|
|
||||
| **无法看到错误日志** | 🔴 高 | 捕获 stdout/stderr | P0 |
|
||||
| **健康检查端点可能错误** | 🟡 中 | 尝试多个端点 | P1 |
|
||||
| **空配置时行为不明** | 🟡 中 | 明确处理逻辑 | P1 |
|
||||
| **超时时间太短** | 🟢 低 | 增加到 30s | P2 |
|
||||
|
||||
**下一步行动**:
|
||||
1. ✅ 已创建完整的修复文档
|
||||
2. ⏳ 需要在 Windows 上手动测试 mcp-proxy
|
||||
3. ⏳ 需要确认 mcp-proxy 的健康检查端点
|
||||
4. ⏳ 在 nuwax-agent 中实现日志捕获
|
||||
412
qiming-mcp-proxy/docs/analysis/NPM_DEPENDENCY_UPDATE_ANALYSIS.md
Normal file
412
qiming-mcp-proxy/docs/analysis/NPM_DEPENDENCY_UPDATE_ANALYSIS.md
Normal file
@@ -0,0 +1,412 @@
|
||||
# mcp-proxy npm 包依赖更新机制分析
|
||||
|
||||
## 问题
|
||||
**用户关注**: mcp-proxy 打包到 npm 时,内部依赖的平台二进制文件是否是最新的(下载的)?
|
||||
|
||||
## 快速回答
|
||||
❌ **当前机制**: cargo-dist 生成的 npm 包中的二进制文件**不是**从 npm registry 下载的最新版本,而是**构建时打包进去的**。
|
||||
|
||||
✅ **这实际上是正确的行为**: 这确保了版本一致性和可靠性。
|
||||
|
||||
---
|
||||
|
||||
## cargo-dist npm 安装器工作机制
|
||||
|
||||
### 1. 构建阶段(GitHub Actions)
|
||||
|
||||
```yaml
|
||||
# .github/workflows/release.yml
|
||||
targets = [
|
||||
"aarch64-apple-darwin",
|
||||
"aarch64-unknown-linux-gnu",
|
||||
"x86_64-apple-darwin",
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"x86_64-pc-windows-msvc"
|
||||
]
|
||||
```
|
||||
|
||||
**流程**:
|
||||
```
|
||||
Tag 推送 (v0.1.39)
|
||||
↓
|
||||
GitHub Actions 触发
|
||||
↓
|
||||
为每个平台构建二进制文件
|
||||
├─ Linux x86_64: mcp-proxy (ELF)
|
||||
├─ Linux ARM64: mcp-proxy (ELF)
|
||||
├─ macOS x86_64: mcp-proxy (Mach-O)
|
||||
├─ macOS ARM64: mcp-proxy (Mach-O)
|
||||
└─ Windows: mcp-proxy.exe (PE)
|
||||
↓
|
||||
cargo-dist 打包成 tarball
|
||||
├─ mcp-stdio-proxy-aarch64-apple-darwin.tar.xz
|
||||
├─ mcp-stdio-proxy-x86_64-unknown-linux-gnu.tar.xz
|
||||
└─ mcp-stdio-proxy-x86_64-pc-windows-msvc.zip
|
||||
↓
|
||||
生成 npm 安装器包
|
||||
└─ mcp-stdio-proxy-0.1.39-npm-package.tar.gz
|
||||
```
|
||||
|
||||
### 2. npm 包结构
|
||||
|
||||
cargo-dist 生成的 npm 包包含:
|
||||
|
||||
```
|
||||
mcp-stdio-proxy-0.1.39-npm-package.tar.gz
|
||||
└── package/
|
||||
├── package.json
|
||||
│ ├── "version": "0.1.39"
|
||||
│ ├── "name": "mcp-stdio-proxy"
|
||||
│ ├── "bin": { "mcp-proxy": "./install.js" }
|
||||
│ └── "artifactDownloadUrl": "https://github.com/.../v0.1.39"
|
||||
├── install.js # 安装脚本
|
||||
└── (可能的其他元数据)
|
||||
```
|
||||
|
||||
**package.json 关键字段**:
|
||||
```json
|
||||
{
|
||||
"name": "mcp-stdio-proxy",
|
||||
"version": "0.1.39",
|
||||
"bin": {
|
||||
"mcp-proxy": "./install.js"
|
||||
},
|
||||
"artifactDownloadUrl": "https://github.com/nuwax-ai/mcp-proxy/releases/download/v0.1.39"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 用户安装流程
|
||||
|
||||
```bash
|
||||
npm install -g mcp-stdio-proxy@0.1.39
|
||||
```
|
||||
|
||||
**实际发生的事情**:
|
||||
|
||||
1. **下载 npm 包**:
|
||||
```
|
||||
npm registry → mcp-stdio-proxy-0.1.39-npm-package.tar.gz
|
||||
```
|
||||
|
||||
2. **运行 install.js**:
|
||||
```javascript
|
||||
// install.js (由 cargo-dist 生成)
|
||||
const version = "0.1.39";
|
||||
const platform = detectPlatform(); // e.g., "x86_64-pc-windows-msvc"
|
||||
const artifactUrl = `${baseUrl}/mcp-stdio-proxy-${platform}.tar.xz`;
|
||||
|
||||
// 下载平台特定的二进制包
|
||||
download(artifactUrl);
|
||||
extract(artifact);
|
||||
symlinkBinary("mcp-proxy");
|
||||
```
|
||||
|
||||
3. **下载的二进制文件来源**:
|
||||
```
|
||||
https://github.com/nuwax-ai/mcp-proxy/releases/download/v0.1.39/mcp-stdio-proxy-x86_64-pc-windows-msvc.zip
|
||||
```
|
||||
或者(如果 OSS 同步成功):
|
||||
```
|
||||
https://nuwa-packages.oss-rg-china-mainland.aliyuncs.com/mcp-stdio-proxy/v0.1.39/mcp-stdio-proxy-x86_64-pc-windows-msvc.zip
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 关键点:版本锁定机制
|
||||
|
||||
### ✅ 版本是锁定的(这是正确的)
|
||||
|
||||
| 组件 | 版本来源 | 更新时机 |
|
||||
|------|---------|---------|
|
||||
| **npm 包版本** | Cargo.toml `version = "0.1.39"` | 手动更新 + Git Tag |
|
||||
| **二进制文件** | 该版本构建时编译的二进制 | 构建时生成 |
|
||||
| **依赖的 crate** | Cargo.lock 锁定 | 更新 Cargo.lock |
|
||||
| **下载 URL** | package.json `artifactDownloadUrl` | 自动生成(包含版本号) |
|
||||
|
||||
**示例**:
|
||||
```
|
||||
用户安装: npm install -g mcp-stdio-proxy@0.1.39
|
||||
↓
|
||||
下载 npm 包(包含版本信息)
|
||||
↓
|
||||
install.js 读取 artifactDownloadUrl
|
||||
↓
|
||||
下载: https://.../v0.1.39/mcp-stdio-proxy-x86_64-pc-windows-msvc.zip
|
||||
↓
|
||||
解压得到的二进制文件就是 v0.1.39 构建时的版本
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 依赖更新策略
|
||||
|
||||
### 场景 A: Rust 依赖更新(如 rmcp, tokio 等)
|
||||
|
||||
**问题**: 如果 `rmcp` 发布了新版本,用户安装 `mcp-stdio-proxy@0.1.39` 会用到新版本吗?
|
||||
|
||||
**答案**: ❌ 不会,因为二进制文件已经编译好了
|
||||
|
||||
**更新方法**:
|
||||
```bash
|
||||
# 1. 在项目中更新依赖
|
||||
cd mcp-proxy
|
||||
cargo update -p rmcp
|
||||
|
||||
# 2. 测试
|
||||
cargo test
|
||||
|
||||
# 3. 更新版本号
|
||||
# 编辑 mcp-proxy/Cargo.toml
|
||||
version = "0.1.40"
|
||||
|
||||
# 4. 提交并打标签
|
||||
git commit -am "chore: bump version to 0.1.40 with updated rmcp"
|
||||
git tag v0.1.40
|
||||
git push origin v0.1.40
|
||||
|
||||
# 5. GitHub Actions 自动构建并发布
|
||||
# 新的 npm 包 mcp-stdio-proxy@0.1.40 将包含更新后的 rmcp
|
||||
```
|
||||
|
||||
### 场景 B: 系统依赖(如 OpenSSL、glibc)
|
||||
|
||||
**问题**: 构建的二进制依赖的系统库版本是什么?
|
||||
|
||||
**答案**: 取决于 GitHub Actions runner 的环境
|
||||
|
||||
**当前配置** (`.github/workflows/release.yml`):
|
||||
```yaml
|
||||
matrix:
|
||||
runner: "ubuntu-22.04" # Ubuntu 22.04 的 glibc 版本
|
||||
```
|
||||
|
||||
**潜在问题**:
|
||||
- 如果在 Ubuntu 22.04 上构建,二进制可能依赖较新的 glibc
|
||||
- 在旧系统(如 CentOS 7)上可能无法运行
|
||||
|
||||
**解决方案**:
|
||||
```yaml
|
||||
# 使用容器构建以控制依赖版本
|
||||
matrix:
|
||||
container:
|
||||
image: "quay.io/pypa/manylinux2014_x86_64" # 兼容性更好
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 当前流程的优缺点
|
||||
|
||||
### ✅ 优点
|
||||
|
||||
1. **版本一致性**:
|
||||
- `mcp-stdio-proxy@0.1.39` 永远下载的是 v0.1.39 构建时的二进制
|
||||
- 不会因为依赖更新导致意外行为
|
||||
|
||||
2. **确定性构建**:
|
||||
- Cargo.lock 锁定所有依赖版本
|
||||
- 可复现的构建结果
|
||||
|
||||
3. **无运行时编译**:
|
||||
- 用户安装时不需要 Rust 工具链
|
||||
- 安装速度快(直接下载预编译二进制)
|
||||
|
||||
4. **平台特定优化**:
|
||||
- 为每个平台单独编译优化
|
||||
- 无跨平台兼容性损失
|
||||
|
||||
### ❌ 潜在问题
|
||||
|
||||
1. **依赖无法自动更新**:
|
||||
- 用户不能通过 `npm update` 获取新的 Rust 依赖
|
||||
- 必须发布新版本
|
||||
|
||||
2. **二进制体积大**:
|
||||
- 每个平台的二进制都需要打包
|
||||
- npm 包本身较小(只是安装器),但下载的二进制较大
|
||||
|
||||
3. **系统兼容性**:
|
||||
- 需要确保目标系统有合适的运行时库
|
||||
- 可能在旧系统上无法运行
|
||||
|
||||
---
|
||||
|
||||
## 对比其他方案
|
||||
|
||||
### 方案 A: 当前方案(cargo-dist)
|
||||
|
||||
```
|
||||
npm install -g mcp-stdio-proxy
|
||||
↓
|
||||
下载安装器 npm 包(~1KB)
|
||||
↓
|
||||
install.js 下载平台二进制(~10MB)
|
||||
↓
|
||||
解压到 ~/.npm/bin/
|
||||
```
|
||||
|
||||
**特点**: 预编译二进制,版本锁定
|
||||
|
||||
---
|
||||
|
||||
### 方案 B: 纯 npm 包(不适用)
|
||||
|
||||
```
|
||||
npm install -g mcp-stdio-proxy
|
||||
↓
|
||||
下载 JavaScript 代码
|
||||
↓
|
||||
运行时执行
|
||||
```
|
||||
|
||||
**特点**: 不适用,因为 mcp-proxy 是 Rust 项目
|
||||
|
||||
---
|
||||
|
||||
### 方案 C: npm 包 + 源码编译(不推荐)
|
||||
|
||||
```
|
||||
npm install -g mcp-stdio-proxy
|
||||
↓
|
||||
下载源码 + package.json
|
||||
↓
|
||||
postinstall: cargo build --release
|
||||
↓
|
||||
编译二进制
|
||||
```
|
||||
|
||||
**特点**:
|
||||
- ❌ 需要用户有 Rust 工具链
|
||||
- ❌ 安装时间长(编译需要几分钟)
|
||||
- ✅ 可以获取最新依赖
|
||||
- ✅ 针对用户系统优化
|
||||
|
||||
---
|
||||
|
||||
## 建议
|
||||
|
||||
### ✅ 保持当前方案
|
||||
|
||||
**理由**:
|
||||
1. cargo-dist 是 Rust 社区标准方案
|
||||
2. 版本锁定是**优点**而非缺点(确保稳定性)
|
||||
3. 用户体验好(无需 Rust 工具链,安装快)
|
||||
|
||||
### 🔧 优化建议
|
||||
|
||||
#### 1. 明确依赖更新流程
|
||||
|
||||
创建文档说明如何更新依赖并发布新版本:
|
||||
|
||||
```markdown
|
||||
# 依赖更新指南
|
||||
|
||||
## 更新 Rust 依赖
|
||||
1. `cargo update -p <crate_name>`
|
||||
2. `cargo test` 确保无破坏性更改
|
||||
3. 更新 `Cargo.toml` 版本号
|
||||
4. `git tag v<new_version>` 触发发布
|
||||
|
||||
## 发布检查清单
|
||||
- [ ] 所有测试通过
|
||||
- [ ] CHANGELOG.md 已更新
|
||||
- [ ] 版本号已同步(Cargo.toml)
|
||||
- [ ] Git tag 格式正确(v0.1.40)
|
||||
```
|
||||
|
||||
#### 2. 添加版本信息到二进制
|
||||
|
||||
```rust
|
||||
// mcp-proxy/src/main.rs
|
||||
#[derive(Parser)]
|
||||
#[command(version = env!("CARGO_PKG_VERSION"))]
|
||||
struct Cli {
|
||||
// ...
|
||||
}
|
||||
|
||||
// 运行时显示依赖版本
|
||||
fn print_version_info() {
|
||||
println!("mcp-proxy {}", env!("CARGO_PKG_VERSION"));
|
||||
println!(" rmcp: {}", env!("CARGO_PKG_VERSION_rmcp"));
|
||||
println!(" tokio: {}", env!("CARGO_PKG_VERSION_tokio"));
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 自动化依赖检查
|
||||
|
||||
```yaml
|
||||
# .github/workflows/dependency-check.yml
|
||||
name: Dependency Check
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 1' # 每周一检查
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
check-outdated:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: cargo outdated --exit-code 1
|
||||
```
|
||||
|
||||
#### 4. 添加系统兼容性说明
|
||||
|
||||
在 README.md 中明确说明:
|
||||
|
||||
```markdown
|
||||
## 系统要求
|
||||
|
||||
### Linux
|
||||
- glibc >= 2.31 (Ubuntu 20.04+, Debian 11+, RHEL 8+)
|
||||
- 或使用 musl 构建版本(待支持)
|
||||
|
||||
### macOS
|
||||
- macOS 10.15+ (Catalina or later)
|
||||
|
||||
### Windows
|
||||
- Windows 10 / Windows Server 2019 或更高版本
|
||||
- 需要 Visual C++ Redistributable 2015-2022
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
### 问题回答
|
||||
|
||||
**Q: mcp-proxy 打包的内部平台依赖是否是最新的(下载的)?**
|
||||
|
||||
**A**:
|
||||
- ❌ **不是"下载的最新"**: 二进制文件在构建时编译,版本锁定
|
||||
- ✅ **这是正确的行为**: 确保版本一致性和可靠性
|
||||
- 🔄 **更新方式**: 通过发布新版本(如 v0.1.40)获取更新的依赖
|
||||
|
||||
### 版本管理流程
|
||||
|
||||
```
|
||||
Rust 依赖更新
|
||||
↓
|
||||
cargo update
|
||||
↓
|
||||
测试验证
|
||||
↓
|
||||
版本号递增 (0.1.39 → 0.1.40)
|
||||
↓
|
||||
Git Tag (v0.1.40)
|
||||
↓
|
||||
GitHub Actions 构建
|
||||
↓
|
||||
发布到 npm (mcp-stdio-proxy@0.1.40)
|
||||
↓
|
||||
用户 npm install -g mcp-stdio-proxy@latest
|
||||
↓
|
||||
获取包含最新依赖的二进制
|
||||
```
|
||||
|
||||
### 最佳实践
|
||||
|
||||
1. ✅ **保持当前 cargo-dist 方案**
|
||||
2. ✅ **定期检查和更新依赖**
|
||||
3. ✅ **清晰的版本发布流程**
|
||||
4. ✅ **文档化系统要求**
|
||||
5. ✅ **自动化依赖检查(GitHub Actions)**
|
||||
661
qiming-mcp-proxy/docs/analysis/NUWAX_AGENT_WINDOWS_CMD_FIX.md
Normal file
661
qiming-mcp-proxy/docs/analysis/NUWAX_AGENT_WINDOWS_CMD_FIX.md
Normal file
@@ -0,0 +1,661 @@
|
||||
# nuwax-agent Windows CMD 窗口隐藏完整解决方案
|
||||
|
||||
## 问题总结
|
||||
|
||||
### 现象
|
||||
在 Windows 上运行 nuwax-agent (Tauri 应用) 时,会弹出多个 CMD 命令行窗口,影响用户体验。
|
||||
|
||||
### 受影响的服务
|
||||
所有通过 npm 全局安装的 Node.js 服务(以 `.cmd` 文件形式存在):
|
||||
|
||||
1. **mcp-proxy.cmd** - MCP 代理服务
|
||||
```
|
||||
C:\Users\MECHREVO\.local\bin\mcp-proxy.cmd
|
||||
```
|
||||
|
||||
2. **nuwax-file-server.cmd** - 文件服务器
|
||||
```
|
||||
C:\Users\MECHREVO\.local\bin\nuwax-file-server.cmd
|
||||
```
|
||||
|
||||
3. **其他潜在的 npm 全局包**
|
||||
- `nuwaxcode`
|
||||
- `claude-code-acp-ts`
|
||||
- 任何未来通过 `npm install -g` 安装的服务
|
||||
|
||||
### 根本原因
|
||||
|
||||
#### 技术细节
|
||||
Windows 上的 npm 全局包会生成批处理包装文件:
|
||||
```
|
||||
~/.local/bin/
|
||||
├── package-name.cmd # Windows 批处理文件
|
||||
├── package-name # Unix shell 脚本(Windows 不使用)
|
||||
└── package-name.ps1 # PowerShell 脚本(可选)
|
||||
```
|
||||
|
||||
当 Rust 代码使用 `std::process::Command` 或 `tokio::process::Command` 启动这些 `.cmd` 文件时:
|
||||
|
||||
**默认行为**:
|
||||
```rust
|
||||
Command::new("C:\\...\\mcp-proxy.cmd").spawn()?;
|
||||
// ❌ 会弹出 CMD 窗口
|
||||
```
|
||||
|
||||
**需要显式设置**:
|
||||
```rust
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||
}
|
||||
// ✅ 隐藏 CMD 窗口
|
||||
```
|
||||
|
||||
#### 层级关系说明
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ nuwax-agent.exe (Tauri GUI 应用) │
|
||||
└─────────────────┬───────────────────┘
|
||||
│
|
||||
┌───────────┴───────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ mcp-proxy.cmd │ │ file-server.cmd │ ❌ 问题1: nuwax-agent 启动时未隐藏
|
||||
└─────┬───────────┘ └─────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ MCP 服务子进程 │ ✅ 已修复 (mcp-proxy v0.1.39)
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
**结论**:
|
||||
- **mcp-proxy v0.1.39** 修复了:mcp-proxy 启动 MCP 子进程时的 CMD 窗口
|
||||
- **nuwax-agent 需要修复**:启动 npm 全局包时的 CMD 窗口
|
||||
|
||||
---
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 方案 1: 统一的进程启动工具函数 ✅ 推荐
|
||||
|
||||
#### 1.1 创建通用工具模块
|
||||
|
||||
**文件**: `nuwax-agent-core/src/utils/process.rs` (新建)
|
||||
|
||||
```rust
|
||||
//! 进程启动工具函数
|
||||
//!
|
||||
//! 提供跨平台的进程启动功能,Windows 上自动隐藏 CMD 窗口
|
||||
|
||||
use std::process::Command as StdCommand;
|
||||
use tokio::process::Command as TokioCommand;
|
||||
|
||||
/// 创建隐藏 CMD 窗口的同步 Command(Windows)
|
||||
///
|
||||
/// # 用法
|
||||
/// ```rust
|
||||
/// let mut cmd = create_hidden_command("C:\\path\\to\\script.cmd");
|
||||
/// cmd.env("PORT", "8080");
|
||||
/// cmd.args(&["--production"]);
|
||||
/// cmd.spawn()?;
|
||||
/// ```
|
||||
#[cfg(windows)]
|
||||
pub fn create_hidden_command(program: impl AsRef<std::ffi::OsStr>) -> StdCommand {
|
||||
use std::os::windows::process::CommandExt;
|
||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||
|
||||
let mut cmd = StdCommand::new(program);
|
||||
|
||||
// 注意:必须在所有 env/args 调用之后才能确保标志生效
|
||||
// 这里先设置,但调用者不应该再次修改
|
||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||
|
||||
cmd
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn create_hidden_command(program: impl AsRef<std::ffi::OsStr>) -> StdCommand {
|
||||
StdCommand::new(program)
|
||||
}
|
||||
|
||||
/// 创建隐藏 CMD 窗口的异步 Command(Windows)
|
||||
#[cfg(windows)]
|
||||
pub fn create_hidden_tokio_command(program: impl AsRef<std::ffi::OsStr>) -> TokioCommand {
|
||||
use std::os::windows::process::CommandExt;
|
||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||
|
||||
let mut cmd = TokioCommand::new(program);
|
||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||
|
||||
cmd
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn create_hidden_tokio_command(program: impl AsRef<std::ffi::OsStr>) -> TokioCommand {
|
||||
TokioCommand::new(program)
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.2 使用方式
|
||||
|
||||
**在服务启动代码中**:
|
||||
|
||||
```rust
|
||||
use crate::utils::process::create_hidden_tokio_command;
|
||||
|
||||
// FileServer 启动
|
||||
pub async fn start_file_server(config: &FileServerConfig) -> Result<Child> {
|
||||
let cmd_path = "C:\\Users\\...\\nuwax-file-server.cmd";
|
||||
|
||||
let mut cmd = create_hidden_tokio_command(cmd_path);
|
||||
cmd.env("PORT", &config.port.to_string());
|
||||
cmd.env("NODE_ENV", "production");
|
||||
cmd.args(&["--production"]);
|
||||
|
||||
// 启动进程(CMD 窗口已自动隐藏)
|
||||
let child = cmd.spawn()?;
|
||||
|
||||
Ok(child)
|
||||
}
|
||||
|
||||
// McpProxy 启动
|
||||
pub async fn start_mcp_proxy(config: &McpProxyConfig) -> Result<Child> {
|
||||
let cmd_path = "C:\\Users\\...\\mcp-proxy.cmd";
|
||||
|
||||
let mut cmd = create_hidden_tokio_command(cmd_path);
|
||||
cmd.env("MCP_PORT", &config.port.to_string());
|
||||
|
||||
let child = cmd.spawn()?;
|
||||
|
||||
Ok(child)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 方案 2: 增强版 - 带日志捕获 ✅ 强烈推荐
|
||||
|
||||
由于 CMD 窗口隐藏后无法看到错误信息,需要捕获子进程的 stdout/stderr:
|
||||
|
||||
**文件**: `nuwax-agent-core/src/utils/process.rs`
|
||||
|
||||
```rust
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
use tokio::process::{Child, Command as TokioCommand};
|
||||
use std::process::Stdio;
|
||||
use anyhow::Result;
|
||||
|
||||
/// 启动服务并捕获输出日志
|
||||
///
|
||||
/// # 特性
|
||||
/// - Windows: 自动隐藏 CMD 窗口
|
||||
/// - 捕获 stdout/stderr 并记录到日志
|
||||
/// - 返回子进程句柄
|
||||
///
|
||||
/// # 参数
|
||||
/// - `cmd_path`: 可执行文件路径(如 `C:\...\mcp-proxy.cmd`)
|
||||
/// - `service_name`: 服务名称(用于日志标识)
|
||||
/// - `env_vars`: 环境变量 `Vec<(String, String)>`
|
||||
/// - `args`: 命令行参数 `Vec<String>`
|
||||
pub async fn start_service_with_logging(
|
||||
cmd_path: impl AsRef<std::ffi::OsStr>,
|
||||
service_name: &str,
|
||||
env_vars: Option<Vec<(String, String)>>,
|
||||
args: Option<Vec<String>>,
|
||||
) -> Result<Child> {
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
let mut cmd = TokioCommand::new(&cmd_path);
|
||||
|
||||
// 设置环境变量
|
||||
if let Some(envs) = env_vars {
|
||||
for (key, value) in envs {
|
||||
cmd.env(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置参数
|
||||
if let Some(args) = args {
|
||||
cmd.args(&args);
|
||||
}
|
||||
|
||||
// 捕获 stdout/stderr
|
||||
cmd.stdout(Stdio::piped());
|
||||
cmd.stderr(Stdio::piped());
|
||||
|
||||
// Windows: 隐藏 CMD 窗口(必须在最后设置)
|
||||
#[cfg(windows)]
|
||||
{
|
||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||
}
|
||||
|
||||
let mut child = cmd.spawn()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to spawn {}: {}", service_name, e))?;
|
||||
|
||||
// 异步读取并记录 stdout
|
||||
if let Some(stdout) = child.stdout.take() {
|
||||
let service_name = service_name.to_string();
|
||||
tokio::spawn(async move {
|
||||
let reader = BufReader::new(stdout);
|
||||
let mut lines = reader.lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
tracing::info!("[{} stdout] {}", service_name, line);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 异步读取并记录 stderr
|
||||
if let Some(stderr) = child.stderr.take() {
|
||||
let service_name = service_name.to_string();
|
||||
tokio::spawn(async move {
|
||||
let reader = BufReader::new(stderr);
|
||||
let mut lines = reader.lines();
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
tracing::error!("[{} stderr] {}", service_name, line);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tracing::info!("[{}] 进程已启动", service_name);
|
||||
|
||||
Ok(child)
|
||||
}
|
||||
```
|
||||
|
||||
#### 使用示例
|
||||
|
||||
```rust
|
||||
use crate::utils::process::start_service_with_logging;
|
||||
|
||||
// FileServer 启动
|
||||
pub async fn start_file_server(config: &FileServerConfig) -> Result<Child> {
|
||||
let cmd_path = resolve_npm_bin("nuwax-file-server").await?;
|
||||
|
||||
let env_vars = vec![
|
||||
("PORT".to_string(), config.port.to_string()),
|
||||
("NODE_ENV".to_string(), "production".to_string()),
|
||||
("LOG_LEVEL".to_string(), "info".to_string()),
|
||||
];
|
||||
|
||||
let args = vec!["--production".to_string()];
|
||||
|
||||
let child = start_service_with_logging(
|
||||
&cmd_path,
|
||||
"FileServer",
|
||||
Some(env_vars),
|
||||
Some(args),
|
||||
).await?;
|
||||
|
||||
// 健康检查
|
||||
wait_for_port_ready(config.port, Duration::from_secs(10)).await?;
|
||||
|
||||
Ok(child)
|
||||
}
|
||||
|
||||
// McpProxy 启动
|
||||
pub async fn start_mcp_proxy(config: &McpProxyConfig) -> Result<Child> {
|
||||
let cmd_path = resolve_npm_bin("mcp-proxy").await?;
|
||||
|
||||
let env_vars = vec![
|
||||
("MCP_PORT".to_string(), config.port.to_string()),
|
||||
];
|
||||
|
||||
let child = start_service_with_logging(
|
||||
&cmd_path,
|
||||
"McpProxy",
|
||||
Some(env_vars),
|
||||
None,
|
||||
).await?;
|
||||
|
||||
// 健康检查
|
||||
let health_url = format!("http://127.0.0.1:{}/mcp", config.port);
|
||||
wait_for_http_ready(&health_url, Duration::from_secs(15)).await?;
|
||||
|
||||
Ok(child)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 方案 3: Node.js 检测逻辑改进 ✅ 必须
|
||||
|
||||
解决日志中出现的 Node.js 重复安装问题。
|
||||
|
||||
**文件**: `nuwax-agent-core/src/dependency/node.rs`
|
||||
|
||||
```rust
|
||||
use std::process::Stdio;
|
||||
use tokio::time::Duration;
|
||||
use anyhow::{Result, Context};
|
||||
|
||||
/// 检查 Node.js 是否真正可用
|
||||
pub async fn is_nodejs_available() -> bool {
|
||||
#[cfg(windows)]
|
||||
let node_cmd = "node.exe";
|
||||
#[cfg(not(windows))]
|
||||
let node_cmd = "node";
|
||||
|
||||
match tokio::process::Command::new(node_cmd)
|
||||
.arg("--version")
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status()
|
||||
.await
|
||||
{
|
||||
Ok(status) if status.success() => {
|
||||
tracing::info!("Node.js is available in PATH");
|
||||
true
|
||||
}
|
||||
Ok(status) => {
|
||||
tracing::warn!("Node.js command exists but failed: {:?}", status);
|
||||
false
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::debug!("Node.js not found in PATH: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取 Node.js 版本
|
||||
pub async fn get_nodejs_version() -> Result<String> {
|
||||
#[cfg(windows)]
|
||||
let node_cmd = "node.exe";
|
||||
#[cfg(not(windows))]
|
||||
let node_cmd = "node";
|
||||
|
||||
let output = tokio::process::Command::new(node_cmd)
|
||||
.arg("--version")
|
||||
.output()
|
||||
.await
|
||||
.context("Failed to get Node.js version")?;
|
||||
|
||||
if !output.status.success() {
|
||||
anyhow::bail!("Node.js version check failed");
|
||||
}
|
||||
|
||||
let version = String::from_utf8(output.stdout)
|
||||
.context("Invalid UTF-8 in Node.js version")?
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
Ok(version)
|
||||
}
|
||||
|
||||
/// 检查 npm 包是否已全局安装
|
||||
pub async fn is_npm_package_installed(package_name: &str) -> Result<bool> {
|
||||
if !is_nodejs_available().await {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
let npm_cmd = "npm.cmd";
|
||||
#[cfg(not(windows))]
|
||||
let npm_cmd = "npm";
|
||||
|
||||
let output = tokio::process::Command::new(npm_cmd)
|
||||
.args(&["list", "-g", package_name, "--depth=0"])
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status()
|
||||
.await?;
|
||||
|
||||
Ok(output.success())
|
||||
}
|
||||
|
||||
/// 确保 npm 包已安装(带完整的前置检查)
|
||||
pub async fn ensure_npm_package(package_name: &str) -> Result<()> {
|
||||
tracing::info!("Ensuring npm package: {}", package_name);
|
||||
|
||||
// 1. 检查 Node.js 是否可用
|
||||
if !is_nodejs_available().await {
|
||||
tracing::warn!("Node.js not available, attempting to install...");
|
||||
|
||||
// 尝试安装 Node.js
|
||||
install_nodejs().await
|
||||
.context("Failed to install Node.js")?;
|
||||
|
||||
// 再次验证
|
||||
if !is_nodejs_available().await {
|
||||
anyhow::bail!("Node.js installation succeeded but still not available");
|
||||
}
|
||||
|
||||
// 显示安装的版本
|
||||
if let Ok(version) = get_nodejs_version().await {
|
||||
tracing::info!("Node.js {} is now available", version);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 检查包是否已安装
|
||||
if is_npm_package_installed(package_name).await? {
|
||||
tracing::info!("{} is already installed (global)", package_name);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 3. 安装包(带重试限制)
|
||||
let max_retries = 3;
|
||||
for attempt in 1..=max_retries {
|
||||
tracing::info!(
|
||||
"Installing {} (attempt {}/{})",
|
||||
package_name, attempt, max_retries
|
||||
);
|
||||
|
||||
match install_npm_package_global(package_name).await {
|
||||
Ok(_) => {
|
||||
tracing::info!("{} installed successfully", package_name);
|
||||
|
||||
// 验证安装
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
if is_npm_package_installed(package_name).await? {
|
||||
return Ok(());
|
||||
} else {
|
||||
tracing::warn!("{} installed but verification failed", package_name);
|
||||
}
|
||||
}
|
||||
Err(e) if attempt < max_retries => {
|
||||
tracing::warn!(
|
||||
"Install attempt {}/{} failed: {}",
|
||||
attempt, max_retries, e
|
||||
);
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
}
|
||||
Err(e) => {
|
||||
anyhow::bail!(
|
||||
"Failed to install {} after {} attempts: {}",
|
||||
package_name, max_retries, e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anyhow::bail!("Failed to verify {} installation", package_name)
|
||||
}
|
||||
|
||||
/// 安装 npm 全局包
|
||||
async fn install_npm_package_global(package_name: &str) -> Result<()> {
|
||||
#[cfg(windows)]
|
||||
let npm_cmd = "npm.cmd";
|
||||
#[cfg(not(windows))]
|
||||
let npm_cmd = "npm";
|
||||
|
||||
let output = tokio::process::Command::new(npm_cmd)
|
||||
.args(&["install", "-g", package_name])
|
||||
.output()
|
||||
.await
|
||||
.context("Failed to run npm install")?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
anyhow::bail!("npm install failed: {}", stderr);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 实施步骤
|
||||
|
||||
### 阶段 1: 核心工具函数 (1 小时)
|
||||
|
||||
1. **创建 `utils/process.rs` 模块**
|
||||
```bash
|
||||
# 在 nuwax-agent-core 中
|
||||
mkdir -p src/utils
|
||||
touch src/utils/process.rs
|
||||
touch src/utils/mod.rs
|
||||
```
|
||||
|
||||
2. **实现 `start_service_with_logging` 函数**
|
||||
- 复制上面的代码
|
||||
- 添加到 `src/utils/process.rs`
|
||||
|
||||
3. **在 `lib.rs` 中导出**
|
||||
```rust
|
||||
// src/lib.rs
|
||||
pub mod utils;
|
||||
```
|
||||
|
||||
### 阶段 2: 修改服务启动代码 (2 小时)
|
||||
|
||||
4. **FileServer 服务**
|
||||
- 文件: `src/service/file_server.rs`
|
||||
- 找到: `Command::new(...)` 或 `TokioCommand::new(...)`
|
||||
- 替换为: `start_service_with_logging(...)`
|
||||
|
||||
5. **McpProxy 服务**
|
||||
- 文件: `src/service/mcp_proxy.rs`
|
||||
- 同样的修改
|
||||
|
||||
6. **其他 `.cmd` 服务**
|
||||
- 搜索所有 `Command::new` 并检查是否是 `.cmd` 文件
|
||||
- 统一使用工具函数
|
||||
|
||||
### 阶段 3: Node.js 检测改进 (1 小时)
|
||||
|
||||
7. **改进 Node.js 检测**
|
||||
- 文件: `src/dependency/node.rs`
|
||||
- 添加 `is_nodejs_available()` 函数
|
||||
- 修改 `ensure_npm_package()` 添加前置检查
|
||||
|
||||
### 阶段 4: 测试验证 (1 小时)
|
||||
|
||||
8. **Windows 测试**
|
||||
- 编译 nuwax-agent
|
||||
- 启动应用
|
||||
- 验证:
|
||||
- ✅ 无 CMD 窗口弹出
|
||||
- ✅ 日志中能看到服务的 stdout/stderr
|
||||
- ✅ mcp-proxy 健康检查成功
|
||||
- ✅ Node.js 不会重复安装
|
||||
|
||||
9. **macOS/Linux 测试**
|
||||
- 确保修改不影响其他平台
|
||||
|
||||
---
|
||||
|
||||
## 预期效果
|
||||
|
||||
### 修复前
|
||||
```
|
||||
用户启动 nuwax-agent
|
||||
↓
|
||||
[弹窗1] CMD 窗口 - nuwax-file-server.cmd
|
||||
[弹窗2] CMD 窗口 - mcp-proxy.cmd
|
||||
[弹窗3] CMD 窗口 - 其他服务...
|
||||
↓
|
||||
用户体验差 😢
|
||||
```
|
||||
|
||||
### 修复后
|
||||
```
|
||||
用户启动 nuwax-agent
|
||||
↓
|
||||
所有服务静默启动(无 CMD 窗口)
|
||||
↓
|
||||
日志文件中可以看到所有服务输出
|
||||
↓
|
||||
用户体验好 ✅
|
||||
```
|
||||
|
||||
### 日志示例
|
||||
|
||||
**修复后的日志**:
|
||||
```
|
||||
2026-02-12T06:27:06 INFO [FileServer] 进程已启动
|
||||
2026-02-12T06:27:07 INFO [FileServer stdout] 服务运行在: http://localhost:60000
|
||||
2026-02-12T06:27:08 INFO [FileServer stdout] 环境: production
|
||||
2026-02-12T06:27:09 INFO [McpProxy] 进程已启动
|
||||
2026-02-12T06:27:10 INFO [McpProxy stdout] MCP Proxy listening on 127.0.0.1:18099
|
||||
2026-02-12T06:27:11 INFO [McpProxy stdout] Health check: OK
|
||||
```
|
||||
|
||||
**如果服务启动失败**:
|
||||
```
|
||||
2026-02-12T06:27:10 INFO [McpProxy] 进程已启动
|
||||
2026-02-12T06:27:10 ERROR [McpProxy stderr] Error: Cannot find module 'express'
|
||||
2026-02-12T06:27:10 ERROR [McpProxy stderr] at Function.Module._resolveFilename (node:internal/modules/cjs/loader:1048:15)
|
||||
2026-02-12T06:27:11 ERROR [McpProxy] 健康检查失败: Connection refused
|
||||
```
|
||||
→ 现在可以清楚地看到错误原因!
|
||||
|
||||
---
|
||||
|
||||
## 参考资料
|
||||
|
||||
### Windows Process Creation Flags
|
||||
|
||||
- `CREATE_NO_WINDOW (0x08000000)`: 隐藏控制台窗口
|
||||
- 文档: https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
|
||||
|
||||
### Rust 标准库
|
||||
|
||||
- `std::os::windows::process::CommandExt`: https://doc.rust-lang.org/std/os/windows/process/trait.CommandExt.html
|
||||
|
||||
### mcp-proxy v0.1.39 修复
|
||||
|
||||
- Commit: `aa42573 - fix: hide CMD windows on Windows platform for Tauri integration`
|
||||
- 文件: `mcp-streamable-proxy/src/server_builder.rs:230-241`
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 为什么不能在创建 Command 时就设置 CREATE_NO_WINDOW?
|
||||
|
||||
A: 某些 Rust 库(如 `tokio::process::Command`)在内部可能会重新配置命令参数,导致早期设置的标志被覆盖。最佳实践是在所有配置(env, args, stdin/stdout/stderr)之后最后设置。
|
||||
|
||||
### Q2: 隐藏 CMD 窗口后如何调试启动失败?
|
||||
|
||||
A: 使用方案 2 的 `start_service_with_logging` 函数,它会捕获 stdout/stderr 并记录到日志文件中。
|
||||
|
||||
### Q3: 是否需要在 macOS/Linux 上做特殊处理?
|
||||
|
||||
A: 不需要。macOS/Linux 上启动 shell 脚本不会弹窗口,`create_hidden_command` 函数在非 Windows 平台上只是简单的 `Command::new()`。
|
||||
|
||||
### Q4: 如果用户已经安装了 Node.js,还会触发自动安装吗?
|
||||
|
||||
A: 修复后不会。改进的 `is_nodejs_available()` 函数会先检查 `node --version` 是否成功,只有真正不可用时才会触发安装。
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
| 问题 | 现状 | 修复后 |
|
||||
|------|------|--------|
|
||||
| **CMD 窗口弹出** | ❌ 多个窗口弹出 | ✅ 完全隐藏 |
|
||||
| **mcp-proxy 启动失败** | ❌ 15s 超时,看不到错误 | ✅ 日志中能看到详细错误 |
|
||||
| **Node.js 重复安装** | ❌ 已安装仍尝试安装 10 次 | ✅ 正确检测,不重复安装 |
|
||||
| **调试困难** | ❌ 窗口隐藏后无法调试 | ✅ 完整的 stdout/stderr 日志 |
|
||||
|
||||
**实施时间**: 约 5 小时(包括测试)
|
||||
**影响范围**: nuwax-agent Windows 版本
|
||||
**风险**: 低(只影响进程启动方式,不改变业务逻辑)
|
||||
@@ -0,0 +1,249 @@
|
||||
# Windows CREATE_NO_WINDOW 处理确认报告
|
||||
|
||||
## 检查日期
|
||||
2026-02-12
|
||||
|
||||
## 检查范围
|
||||
mcp-proxy 代码库中所有启动子进程的地方
|
||||
|
||||
---
|
||||
|
||||
## ✅ 检查结果:全部已正确处理
|
||||
|
||||
### 文件 1: `mcp-proxy/src/client/core/command.rs`
|
||||
- **状态**: ✅ 已处理
|
||||
- **场景**: 本地命令模式(stdio CLI)
|
||||
- **实现方式**: `process-wrap` + `CreationFlags`
|
||||
|
||||
```rust
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use process_wrap::CreationFlags;
|
||||
// CREATE_NO_WINDOW = 0x08000000
|
||||
// 隐藏控制台窗口,避免在 GUI 应用(如 Tauri)中显示 CMD 窗口
|
||||
wrapped_cmd.wrap(CreationFlags(0x08000000));
|
||||
wrapped_cmd.wrap(JobObject);
|
||||
}
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 使用 `process-wrap` 统一 API
|
||||
- ✅ 同时使用 `JobObject` 管理进程树
|
||||
- ✅ 位置正确(在 CommandWrap::with_new 闭包之后)
|
||||
- ✅ 跨平台条件编译
|
||||
|
||||
---
|
||||
|
||||
### 文件 2: `mcp-sse-proxy/src/server_builder.rs`
|
||||
- **状态**: ✅ 已处理
|
||||
- **场景**: SSE 协议代理启动 MCP 服务子进程
|
||||
- **实现方式**: `process-wrap::tokio` + `CreationFlags`
|
||||
|
||||
```rust
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use process_wrap::tokio::CreationFlags;
|
||||
// CREATE_NO_WINDOW = 0x08000000
|
||||
// 隐藏控制台窗口,避免在 GUI 应用(如 Tauri)中显示 CMD 窗口
|
||||
wrapped_cmd.wrap(CreationFlags(0x08000000));
|
||||
wrapped_cmd.wrap(JobObject);
|
||||
}
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 使用 async 版本的 `process-wrap::tokio`
|
||||
- ✅ 同时使用 `JobObject` 管理进程树
|
||||
- ✅ 位置正确(在 TokioCommandWrap::with_new 闭包之后)
|
||||
- ✅ 详细的中英文注释
|
||||
|
||||
---
|
||||
|
||||
### 文件 3: `mcp-streamable-proxy/src/server_builder.rs`
|
||||
- **状态**: ✅ 已处理(v0.1.39 修复)
|
||||
- **场景**: Streamable HTTP 协议代理启动 MCP 服务子进程
|
||||
- **实现方式**: 标准库 `CommandExt` + `creation_flags`
|
||||
|
||||
```rust
|
||||
// Windows: 隐藏控制台窗口,避免在 GUI 应用(如 Tauri)中显示 CMD 窗口
|
||||
// 注意:必须在所有 env/args 配置之后设置,确保不被覆盖
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
// CREATE_NO_WINDOW = 0x08000000
|
||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||
}
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 使用标准库 API(无需额外依赖)
|
||||
- ✅ 位置正确(在所有 env/args 之后)
|
||||
- ✅ 明确的警告注释说明顺序重要性
|
||||
- ✅ 定义为局部常量,代码清晰
|
||||
|
||||
**修复历史**:
|
||||
- v0.1.38 之前: CREATE_NO_WINDOW 设置在 env/args 之前(可能失效)
|
||||
- v0.1.39: 修复为在所有配置之后设置
|
||||
|
||||
---
|
||||
|
||||
## 代码覆盖率
|
||||
|
||||
### mcp-proxy 相关子进程启动点
|
||||
|
||||
| 组件 | 文件 | 启动场景 | Windows 处理 |
|
||||
|------|------|---------|------------|
|
||||
| **mcp-proxy CLI** | client/core/command.rs | 启动本地 MCP 服务(stdio 模式) | ✅ CreationFlags |
|
||||
| **mcp-sse-proxy** | mcp-sse-proxy/src/server_builder.rs | 启动 MCP 服务子进程(SSE 模式) | ✅ CreationFlags |
|
||||
| **mcp-streamable-proxy** | mcp-streamable-proxy/src/server_builder.rs | 启动 MCP 服务子进程(HTTP 模式) | ✅ creation_flags |
|
||||
|
||||
### 其他组件(不在检查范围)
|
||||
|
||||
| 组件 | 说明 | 是否需要处理 |
|
||||
|------|------|------------|
|
||||
| document-parser | Python 子进程(uv、MinerU) | ⚠️ 建议检查 |
|
||||
| voice-cli | Python 子进程(TTS、Whisper) | ⚠️ 建议检查 |
|
||||
|
||||
---
|
||||
|
||||
## 实现模式对比
|
||||
|
||||
### 模式 A: process-wrap + CreationFlags
|
||||
**使用位置**: mcp-proxy, mcp-sse-proxy
|
||||
|
||||
```rust
|
||||
use process_wrap::tokio::{TokioCommandWrap, CreationFlags, JobObject, KillOnDrop};
|
||||
|
||||
let mut wrapped_cmd = TokioCommandWrap::with_new(command, |cmd| {
|
||||
cmd.args(&args);
|
||||
cmd.envs(&env);
|
||||
});
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
wrapped_cmd.wrap(CreationFlags(0x08000000));
|
||||
wrapped_cmd.wrap(JobObject);
|
||||
}
|
||||
|
||||
wrapped_cmd.wrap(KillOnDrop);
|
||||
let process = TokioChildProcess::new(wrapped_cmd)?;
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- 统一的 API 风格
|
||||
- 自动进程树管理(JobObject)
|
||||
- 自动清理(KillOnDrop)
|
||||
|
||||
---
|
||||
|
||||
### 模式 B: 标准库 CommandExt
|
||||
**使用位置**: mcp-streamable-proxy
|
||||
|
||||
```rust
|
||||
use tokio::process::Command;
|
||||
|
||||
let mut cmd = Command::new(command);
|
||||
cmd.args(&args);
|
||||
cmd.envs(&env);
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||
}
|
||||
|
||||
let child = cmd.spawn()?;
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- 无需额外依赖
|
||||
- 标准库稳定性
|
||||
- 更直接的控制
|
||||
|
||||
---
|
||||
|
||||
## 测试建议
|
||||
|
||||
### 手动测试步骤(Windows)
|
||||
|
||||
1. **编译最新版本**:
|
||||
```bash
|
||||
cargo build --release -p mcp-stdio-proxy
|
||||
```
|
||||
|
||||
2. **测试场景 A - CLI 模式**:
|
||||
```bash
|
||||
# 启动一个本地 MCP 服务
|
||||
mcp-proxy convert <local-mcp-service> --verbose
|
||||
```
|
||||
**预期**: 无 CMD 窗口弹出
|
||||
|
||||
3. **测试场景 B - Server 模式**:
|
||||
```bash
|
||||
# 启动 mcp-proxy 服务器
|
||||
mcp-proxy server --port 18099
|
||||
```
|
||||
**预期**: 启动 MCP 服务插件时无 CMD 窗口弹出
|
||||
|
||||
4. **测试场景 C - Tauri 集成**:
|
||||
- 从 nuwax-agent 启动 mcp-proxy
|
||||
**预期**: 无 CMD 窗口(这个需要 nuwax-agent 也正确设置)
|
||||
|
||||
### 自动化测试(建议添加)
|
||||
|
||||
```rust
|
||||
#[cfg(all(test, windows))]
|
||||
mod windows_tests {
|
||||
use super::*;
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_no_window_flag_is_set() {
|
||||
// 测试 CREATE_NO_WINDOW 标志是否生效
|
||||
// 可以通过检查进程树中是否有 conhost.exe 来验证
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 结论
|
||||
|
||||
### ✅ 所有子进程启动点已正确处理
|
||||
|
||||
1. **mcp-proxy/client/core/command.rs** - ✅ 已处理
|
||||
2. **mcp-sse-proxy/src/server_builder.rs** - ✅ 已处理
|
||||
3. **mcp-streamable-proxy/src/server_builder.rs** - ✅ 已处理(v0.1.39)
|
||||
|
||||
### 关键要点
|
||||
|
||||
1. **CREATE_NO_WINDOW 值**: 所有实现都使用 `0x08000000`
|
||||
2. **设置顺序**: 所有实现都在 env/args 之后设置
|
||||
3. **条件编译**: 所有实现都使用 `#[cfg(windows)]`
|
||||
4. **注释完整**: 所有实现都包含说明目的的注释
|
||||
|
||||
### 仍需关注
|
||||
|
||||
1. **nuwax-agent 侧**: 启动 mcp-proxy.cmd 本身时需要设置 CREATE_NO_WINDOW
|
||||
2. **document-parser**: Python 子进程可能需要相同处理
|
||||
3. **voice-cli**: Python 子进程可能需要相同处理
|
||||
4. **测试验证**: 需要在 Windows 环境实际测试确认效果
|
||||
|
||||
---
|
||||
|
||||
## 版本信息
|
||||
|
||||
- **检查版本**: v0.1.39
|
||||
- **修复文件**: mcp-streamable-proxy/src/server_builder.rs
|
||||
- **修复内容**: 将 CREATE_NO_WINDOW 移到最后设置
|
||||
- **向后兼容**: 是(仅影响 Windows 平台行为)
|
||||
|
||||
---
|
||||
|
||||
## 签署
|
||||
|
||||
**检查人员**: Claude (Sonnet 4.5)
|
||||
**检查日期**: 2026-02-12
|
||||
**检查方法**: 代码审查 + 全局搜索 + Agent 验证
|
||||
**结论**: ✅ 所有 mcp-proxy 相关子进程启动点已正确处理 Windows CREATE_NO_WINDOW
|
||||
183
qiming-mcp-proxy/docs/analysis/WINDOWS_FIX_SUMMARY.md
Normal file
183
qiming-mcp-proxy/docs/analysis/WINDOWS_FIX_SUMMARY.md
Normal file
@@ -0,0 +1,183 @@
|
||||
# Windows 测试问题修复总结
|
||||
|
||||
## 测试环境
|
||||
- 版本: v0.1.37
|
||||
- 平台: Windows
|
||||
|
||||
## 发现的问题
|
||||
|
||||
### 1. ✅ 隐藏 CMD 窗口没有效果
|
||||
**问题分析**:
|
||||
- 在 `mcp-streamable-proxy/src/server_builder.rs` 中,`CREATE_NO_WINDOW` 标志设置得太早
|
||||
- 后续的 `cmd.env()` 和 `cmd.args()` 调用可能导致标志被重置或无效
|
||||
|
||||
**修复方案**:
|
||||
- 将 `CREATE_NO_WINDOW` 设置移到所有 `env()` 和 `args()` 调用之后
|
||||
- 确保在创建 `TokioChildProcess` 之前最后一步设置
|
||||
|
||||
**已修复文件**:
|
||||
- `/Users/apple/workspace/mcp-proxy/mcp-streamable-proxy/src/server_builder.rs`
|
||||
|
||||
**状态**: ✅ 已修复
|
||||
|
||||
---
|
||||
|
||||
### 2. ❓ mcp-proxy 启动不成功
|
||||
|
||||
**需要的信息**:
|
||||
1. 完整的错误日志(请提供)
|
||||
2. 使用的命令行参数
|
||||
3. 配置文件内容(如果有)
|
||||
|
||||
**可能的原因**:
|
||||
- Node.js 可执行文件路径问题
|
||||
- PATH 环境变量未正确继承
|
||||
- 子进程创建失败
|
||||
- MCP 服务配置错误
|
||||
|
||||
**诊断步骤**:
|
||||
```bash
|
||||
# 1. 检查日志文件
|
||||
cat logs/mcp-proxy.log
|
||||
|
||||
# 2. 使用详细模式运行
|
||||
mcp-proxy --verbose
|
||||
|
||||
# 3. 检查 Node.js 是否可用
|
||||
where node # Windows
|
||||
node --version
|
||||
|
||||
# 4. 检查 PATH 环境变量
|
||||
echo %PATH%
|
||||
```
|
||||
|
||||
**状态**: ⏳ 等待错误日志
|
||||
|
||||
---
|
||||
|
||||
### 3. ❓ 如果用户已经安装 Node.js 无法进入后续安装流程
|
||||
|
||||
**问题不清楚**:
|
||||
这个描述需要更多上下文。可能的理解:
|
||||
|
||||
**理解 A**: mcp-proxy 尝试安装 Node.js,但检测到已安装的 Node.js 后卡住
|
||||
- 问题: mcp-proxy 本身不包含 Node.js 安装逻辑
|
||||
- 代码中只有 PATH 继承和 npm 路径添加
|
||||
- 需要确认是否是其他工具(如 MCP 服务本身)的行为
|
||||
|
||||
**理解 B**: 某个 MCP 服务需要 Node.js,但检测逻辑有问题
|
||||
- 可能的原因: Node.js 在 PATH 中但可执行文件名不对(node.exe vs node)
|
||||
- 可能的原因: Node.js 版本不兼容
|
||||
|
||||
**理解 C**: npm 包安装过程卡住
|
||||
- 可能的原因: npm 缓存问题
|
||||
- 可能的原因: 网络连接问题
|
||||
- 可能的原因: 权限问题
|
||||
|
||||
**需要的信息**:
|
||||
1. 具体是什么"后续安装流程"?
|
||||
2. 卡在哪个步骤?有什么错误提示?
|
||||
3. 这个安装流程是 mcp-proxy 触发的还是某个 MCP 服务?
|
||||
4. Node.js 安装路径是什么?
|
||||
|
||||
**可能的修复方案**:
|
||||
如果是 Node.js 可执行文件检测问题,可以添加检测逻辑:
|
||||
|
||||
```rust
|
||||
// 检查 Node.js 是否可用
|
||||
fn is_node_available() -> bool {
|
||||
#[cfg(windows)]
|
||||
let node_cmd = "node.exe";
|
||||
#[cfg(not(windows))]
|
||||
let node_cmd = "node";
|
||||
|
||||
std::process::Command::new(node_cmd)
|
||||
.arg("--version")
|
||||
.output()
|
||||
.is_ok()
|
||||
}
|
||||
```
|
||||
|
||||
**状态**: ⏳ 等待详细描述
|
||||
|
||||
---
|
||||
|
||||
## 当前代码状态
|
||||
|
||||
### PATH 环境变量处理
|
||||
✅ 正确继承父进程 PATH
|
||||
✅ Windows 上自动添加 `%APPDATA%\npm` 到 PATH
|
||||
✅ 支持配置文件中自定义 PATH
|
||||
|
||||
### Windows CMD 窗口隐藏
|
||||
✅ `mcp-streamable-proxy`: 使用 `CREATE_NO_WINDOW` (已修复顺序)
|
||||
✅ `mcp-sse-proxy`: 使用 `CreationFlags(0x08000000)`
|
||||
✅ `mcp-proxy/client/core/command.rs`: 使用 `CreationFlags(0x08000000)`
|
||||
|
||||
---
|
||||
|
||||
## 下一步行动
|
||||
|
||||
### 立即需要
|
||||
1. **问题 2**: 提供完整的 mcp-proxy 启动错误日志
|
||||
2. **问题 3**: 详细描述"无法进入后续安装流程"的具体情况
|
||||
|
||||
### 测试验证
|
||||
1. 编译新版本并在 Windows 上测试
|
||||
2. 验证 CMD 窗口是否成功隐藏
|
||||
3. 测试各种 Node.js 安装场景:
|
||||
- 未安装 Node.js
|
||||
- 通过官方安装包安装
|
||||
- 通过 nvm-windows 安装
|
||||
- 通过 fnm 安装
|
||||
- Node.js 在自定义路径
|
||||
|
||||
### 构建测试版本
|
||||
```bash
|
||||
# 更新版本号
|
||||
# 编辑 mcp-proxy/Cargo.toml, 修改 version = "0.1.38"
|
||||
|
||||
# 构建
|
||||
cargo build --release -p mcp-proxy
|
||||
|
||||
# 生成可执行文件位置
|
||||
# target/release/mcp-proxy.exe
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 请提供以下信息
|
||||
|
||||
为了准确诊断和修复剩余的两个问题,请提供:
|
||||
|
||||
### 问题 2 (mcp-proxy 启动失败)
|
||||
```bash
|
||||
# 运行这些命令并提供输出
|
||||
mcp-proxy --version
|
||||
mcp-proxy <你的命令参数> --verbose
|
||||
|
||||
# 如果有日志文件,提供内容
|
||||
type logs\mcp-proxy.log
|
||||
```
|
||||
|
||||
### 问题 3 (Node.js 安装流程)
|
||||
请描述:
|
||||
1. 完整的操作步骤(从头到尾做了什么)
|
||||
2. 期望的行为是什么
|
||||
3. 实际发生了什么
|
||||
4. 有什么错误提示或卡住的界面
|
||||
5. 你的 Node.js 安装方式和路径
|
||||
|
||||
### 系统信息
|
||||
```powershell
|
||||
# Windows 版本
|
||||
systeminfo | findstr /B /C:"OS Name" /C:"OS Version"
|
||||
|
||||
# Node.js 信息
|
||||
where node
|
||||
node --version
|
||||
npm --version
|
||||
|
||||
# PATH 环境变量
|
||||
echo %PATH%
|
||||
```
|
||||
287
qiming-mcp-proxy/docs/analysis/WINDOWS_FIX_v0.1.39.md
Normal file
287
qiming-mcp-proxy/docs/analysis/WINDOWS_FIX_v0.1.39.md
Normal file
@@ -0,0 +1,287 @@
|
||||
# Windows 测试问题修复总结 (v0.1.39)
|
||||
|
||||
## 测试环境
|
||||
- **版本**: v0.1.37 → v0.1.39
|
||||
- **平台**: Windows
|
||||
- **日志文件**: nuwax-agent.log.2026-02-12
|
||||
|
||||
---
|
||||
|
||||
## 发现的问题及修复状态
|
||||
|
||||
### 1. ✅ 隐藏 CMD 窗口没有效果
|
||||
|
||||
**问题描述**:
|
||||
- 在 Windows 上启动 MCP 子进程时,控制台窗口仍然显示
|
||||
|
||||
**根本原因**:
|
||||
- `mcp-streamable-proxy/src/server_builder.rs` 中,`CREATE_NO_WINDOW` 标志设置得太早
|
||||
- 后续的 `cmd.env()` 和 `cmd.args()` 调用导致标志可能失效
|
||||
|
||||
**修复方案**:
|
||||
- 将 `CREATE_NO_WINDOW` 设置移到所有命令配置(env/args)之后
|
||||
- 确保在创建 `TokioChildProcess` 之前作为最后一步设置
|
||||
|
||||
**修复代码**:
|
||||
```rust
|
||||
// 在所有 env() 和 args() 调用之后
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||
}
|
||||
```
|
||||
|
||||
**修复文件**:
|
||||
- `/Users/apple/workspace/mcp-proxy/mcp-streamable-proxy/src/server_builder.rs:180-241`
|
||||
|
||||
**状态**: ✅ 已修复,待测试验证
|
||||
|
||||
---
|
||||
|
||||
### 2. ❌ mcp-proxy 启动失败
|
||||
|
||||
**问题描述**:
|
||||
- mcp-proxy 进程启动后,健康检查超时失败
|
||||
- 错误: "等待 15s 后 http://127.0.0.1:18099/mcp 仍未就绪"
|
||||
|
||||
**日志证据**:
|
||||
```
|
||||
2026-02-12T06:18:41.088275Z INFO [McpProxy] 可执行文件路径: C:\Users\MECHREVO\.local\bin\mcp-proxy.cmd
|
||||
2026-02-12T06:18:41.088293Z INFO [McpProxy] 监听地址: 127.0.0.1:18099
|
||||
2026-02-12T06:19:01.052725Z ERROR [McpProxy] 健康检查失败: MCP Proxy 健康检查超时
|
||||
```
|
||||
|
||||
**分析**:
|
||||
1. **进程启动成功**: mcp-proxy.cmd 被调用
|
||||
2. **HTTP 服务未启动**: 15秒后健康检查端点仍不可用
|
||||
3. **日志缺失**: 由于 CMD 窗口隐藏,子进程的 stdout/stderr 未被捕获
|
||||
|
||||
**可能的原因**:
|
||||
- mcp-proxy 子进程内部 panic/crash
|
||||
- 缺少必要的运行时依赖
|
||||
- 配置文件解析错误
|
||||
- 端口 18099 被占用
|
||||
- Node.js 环境变量缺失
|
||||
|
||||
**诊断步骤** (需要用户执行):
|
||||
```powershell
|
||||
# 1. 检查 mcp-proxy 版本
|
||||
C:\Users\MECHREVO\.local\bin\mcp-proxy.cmd --version
|
||||
|
||||
# 2. 手动启动服务器(查看完整错误)
|
||||
C:\Users\MECHREVO\.local\bin\mcp-proxy.cmd server --port 18099
|
||||
|
||||
# 3. 检查端口占用
|
||||
netstat -ano | findstr "18099"
|
||||
|
||||
# 4. 验证 Node.js 环境
|
||||
node --version
|
||||
npm list -g mcp-stdio-proxy
|
||||
|
||||
# 5. 查看批处理文件内容
|
||||
type C:\Users\MECHREVO\.local\bin\mcp-proxy.cmd
|
||||
```
|
||||
|
||||
**建议修复** (针对 nuwax-agent):
|
||||
```rust
|
||||
// 需要捕获子进程的 stdout/stderr
|
||||
let mut child = Command::new(mcp_proxy_path)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
// 异步读取并记录输出
|
||||
tokio::spawn(async move {
|
||||
let mut stdout = BufReader::new(child.stdout.take().unwrap()).lines();
|
||||
while let Ok(Some(line)) = stdout.next_line().await {
|
||||
tracing::info!("[McpProxy stdout] {}", line);
|
||||
}
|
||||
});
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut stderr = BufReader::new(child.stderr.take().unwrap()).lines();
|
||||
while let Ok(Some(line)) = stderr.next_line().await {
|
||||
tracing::error!("[McpProxy stderr] {}", line);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**状态**: ⏳ 等待用户提供诊断信息(这是 nuwax-agent 的问题,不是 mcp-proxy 代码的问题)
|
||||
|
||||
---
|
||||
|
||||
### 3. ⚠️ Node.js 检测逻辑问题
|
||||
|
||||
**问题描述**:
|
||||
- 第一次启动时,即使系统已安装 Node.js,仍然出现多次重复安装尝试
|
||||
- 第二次启动时,应用安装了自己的 Node.js 到 `~/.local/bin`
|
||||
|
||||
**日志证据**:
|
||||
```
|
||||
# 第一次启动 - 10 次重复尝试
|
||||
2026-02-12T06:14:50.437049Z INFO [resolve_node_bin] npm -> fallback to PATH
|
||||
2026-02-12T06:15:22.509303Z INFO [Dependency] 开始全局安装 npm 包: mcp-stdio-proxy
|
||||
2026-02-12T06:15:29.360632Z INFO [Dependency] 开始全局安装 npm 包: mcp-stdio-proxy
|
||||
...(共 10 次)
|
||||
|
||||
# 第二次启动 - 成功
|
||||
2026-02-12T06:16:55.747776Z INFO [NodeInstall] 开始自动安装 Node.js...
|
||||
2026-02-12T06:17:00.275214Z INFO [resolve_node_bin] npm -> "C:\\Users\\MECHREVO\\.local\\bin\\npm.cmd"
|
||||
2026-02-12T06:17:13.857546Z INFO [Dependency] mcp-stdio-proxy 全局安装成功
|
||||
```
|
||||
|
||||
**根本原因**:
|
||||
1. **缺少 Node.js 可用性检测**: 直接假设 PATH 中的 npm 可用
|
||||
2. **未验证命令执行结果**: `fallback to PATH` 后未检查 npm 命令是否真正可用
|
||||
3. **重试逻辑问题**: 安装失败后一直重试,未触发 Node.js 安装
|
||||
|
||||
**建议修复** (针对 nuwax-agent):
|
||||
|
||||
```rust
|
||||
/// 检查 Node.js 是否真正可用
|
||||
async fn is_node_available() -> bool {
|
||||
let node_cmd = if cfg!(windows) { "node.exe" } else { "node" };
|
||||
|
||||
match tokio::process::Command::new(node_cmd)
|
||||
.arg("--version")
|
||||
.output()
|
||||
.await
|
||||
{
|
||||
Ok(output) if output.status.success() => {
|
||||
let version = String::from_utf8_lossy(&output.stdout);
|
||||
tracing::info!("Found Node.js: {}", version.trim());
|
||||
true
|
||||
}
|
||||
Ok(_) => {
|
||||
tracing::warn!("Node.js command exists but failed");
|
||||
false
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!("Node.js not found: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 确保 npm 包安装(带前置检查)
|
||||
async fn ensure_npm_package(package_name: &str) -> Result<()> {
|
||||
// 1. 先检查 Node.js 是否可用
|
||||
if !is_node_available().await {
|
||||
tracing::info!("Node.js not available, installing...");
|
||||
install_nodejs().await?;
|
||||
|
||||
// 再次验证
|
||||
if !is_node_available().await {
|
||||
anyhow::bail!("Failed to install Node.js");
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 检查包是否已安装
|
||||
if is_package_installed(package_name).await? {
|
||||
tracing::info!("{} is already installed", package_name);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 3. 安装包(带重试限制)
|
||||
let max_retries = 3;
|
||||
for attempt in 1..=max_retries {
|
||||
match install_npm_package(package_name).await {
|
||||
Ok(_) => return Ok(()),
|
||||
Err(e) if attempt < max_retries => {
|
||||
tracing::warn!("Install attempt {}/{} failed: {}", attempt, max_retries, e);
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
}
|
||||
Err(e) => anyhow::bail!("Failed to install {} after {} attempts: {}", package_name, max_retries, e),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
**状态**: ⚠️ 这是 nuwax-agent 的问题,已提供修复建议
|
||||
|
||||
---
|
||||
|
||||
## 修改文件清单
|
||||
|
||||
### mcp-proxy 仓库
|
||||
1. ✅ `mcp-streamable-proxy/src/server_builder.rs`
|
||||
- 移动 CREATE_NO_WINDOW 到最后设置
|
||||
|
||||
2. ✅ `mcp-proxy/Cargo.toml`
|
||||
- 版本号: 0.1.38 → 0.1.39
|
||||
|
||||
3. ✅ `WINDOWS_ISSUE_ANALYSIS.md`
|
||||
- 新增:完整的问题分析文档
|
||||
|
||||
4. ✅ `WINDOWS_FIX_SUMMARY.md`
|
||||
- 新增:修复总结和诊断指南
|
||||
|
||||
---
|
||||
|
||||
## 验证检查清单
|
||||
|
||||
### 立即需要用户验证
|
||||
- [ ] 手动运行 `mcp-proxy.cmd server --port 18099` 并提供完整输出
|
||||
- [ ] 检查端口 18099 是否被占用
|
||||
- [ ] 验证 Node.js 和 npm 全局包安装情况
|
||||
|
||||
### 编译新版本后验证
|
||||
- [ ] CMD 窗口是否成功隐藏(问题 1)
|
||||
- [ ] mcp-proxy 是否能正常启动(问题 2,取决于诊断结果)
|
||||
- [ ] Node.js 检测是否正常(问题 3,需要 nuwax-agent 修复)
|
||||
|
||||
---
|
||||
|
||||
## 构建和发布
|
||||
|
||||
### 构建命令
|
||||
```bash
|
||||
# 构建发布版本
|
||||
cargo build --release -p mcp-stdio-proxy
|
||||
|
||||
# 生成的二进制文件
|
||||
# target/release/mcp-proxy (或 mcp-proxy.exe on Windows)
|
||||
```
|
||||
|
||||
### 版本信息
|
||||
- **当前版本**: v0.1.39
|
||||
- **修复内容**: Windows CMD 窗口隐藏问题
|
||||
- **待验证**: 需要在 Windows 环境测试
|
||||
|
||||
---
|
||||
|
||||
## 后续行动
|
||||
|
||||
### 短期(立即)
|
||||
1. **等待用户诊断信息**: 运行诊断命令并提供输出
|
||||
2. **编译测试**: 在 Windows 上编译 v0.1.39 并测试
|
||||
|
||||
### 中期(本周)
|
||||
1. **与 nuwax-agent 团队协作**:
|
||||
- 分享子进程输出捕获建议
|
||||
- 分享 Node.js 检测逻辑改进建议
|
||||
2. **完善文档**: 添加 Windows 平台特定的故障排除指南
|
||||
|
||||
### 长期(下一版本)
|
||||
1. **考虑添加 mcp-proxy 自检命令**:
|
||||
```bash
|
||||
mcp-proxy doctor # 检查环境、依赖、配置
|
||||
```
|
||||
2. **改进错误信息**: 提供更详细的启动失败诊断
|
||||
3. **添加 Windows 集成测试**: 自动化测试 CMD 窗口隐藏等功能
|
||||
|
||||
---
|
||||
|
||||
## 联系信息
|
||||
|
||||
如有问题,请提供:
|
||||
1. 完整的错误日志
|
||||
2. 运行诊断命令的输出
|
||||
3. Windows 版本和系统信息
|
||||
4. Node.js/npm 版本信息
|
||||
|
||||
GitHub Issue: https://github.com/nuwax-ai/mcp-proxy/issues
|
||||
257
qiming-mcp-proxy/docs/analysis/WINDOWS_ISSUE_ANALYSIS.md
Normal file
257
qiming-mcp-proxy/docs/analysis/WINDOWS_ISSUE_ANALYSIS.md
Normal file
@@ -0,0 +1,257 @@
|
||||
# Windows 测试问题完整分析与修复方案
|
||||
|
||||
## 日志分析结果
|
||||
|
||||
基于 `nuwax-agent.log.2026-02-12` 的分析,我发现了以下关键问题:
|
||||
|
||||
### 问题 1: ✅ 隐藏 CMD 窗口 - 已修复
|
||||
**状态**: 已在 `mcp-streamable-proxy` 中修复
|
||||
|
||||
### 问题 2: ❌ mcp-proxy 启动失败
|
||||
|
||||
**错误日志**:
|
||||
```
|
||||
2026-02-12T06:19:01.052725Z ERROR nuwax_agent_core::service:
|
||||
[McpProxy] 健康检查失败: MCP Proxy 健康检查超时: 等待 15s 后 http://127.0.0.1:18099/mcp 仍未就绪
|
||||
```
|
||||
|
||||
**详细分析**:
|
||||
|
||||
1. **mcp-proxy 进程启动成功**:
|
||||
```
|
||||
2026-02-12T06:18:41.088275Z INFO nuwax_agent_core::service: [McpProxy] 可执行文件路径: C:\Users\MECHREVO\.local\bin\mcp-proxy.cmd
|
||||
2026-02-12T06:18:41.088293Z INFO nuwax_agent_core::service: [McpProxy] 监听地址: 127.0.0.1:18099
|
||||
```
|
||||
|
||||
2. **但是 HTTP 健康检查失败**:
|
||||
- 15秒后 `http://127.0.0.1:18099/mcp` 仍然无法访问
|
||||
- 这说明 mcp-proxy 进程虽然启动了,但 HTTP 服务器没有正常运行
|
||||
|
||||
**可能的原因**:
|
||||
|
||||
1. **mcp-proxy 自身的启动错误** (最可能):
|
||||
- 子进程可能有 panic/crash
|
||||
- 缺少必要的依赖
|
||||
- 配置文件解析错误
|
||||
- 端口被占用
|
||||
|
||||
2. **日志输出被隐藏**:
|
||||
- 由于使用了 CMD 窗口隐藏,mcp-proxy 的 stderr/stdout 可能没有被正确捕获
|
||||
- Tauri 应用需要配置子进程的输出重定向
|
||||
|
||||
3. **Node.js 环境问题**:
|
||||
- mcp-proxy.cmd 是一个 Windows 批处理文件,依赖 Node.js
|
||||
- 虽然 PATH 中有 Node.js,但可能存在其他环境变量缺失
|
||||
|
||||
**诊断步骤**:
|
||||
|
||||
```powershell
|
||||
# 1. 手动测试 mcp-proxy 是否能运行
|
||||
C:\Users\MECHREVO\.local\bin\mcp-proxy.cmd --version
|
||||
|
||||
# 2. 尝试手动启动服务器
|
||||
C:\Users\MECHREVO\.local\bin\mcp-proxy.cmd server
|
||||
|
||||
# 3. 检查端口占用
|
||||
netstat -ano | findstr "18099"
|
||||
|
||||
# 4. 查看 Node.js 全局包安装情况
|
||||
npm list -g mcp-stdio-proxy
|
||||
|
||||
# 5. 检查 mcp-proxy.cmd 内容
|
||||
type C:\Users\MECHREVO\.local\bin\mcp-proxy.cmd
|
||||
```
|
||||
|
||||
### 问题 3: ⚠️ Node.js 安装检测逻辑问题
|
||||
|
||||
**观察到的行为**:
|
||||
|
||||
```
|
||||
# 第一次启动 - Node.js 未安装
|
||||
2026-02-12T06:14:50.437049Z INFO agent_tauri_client_lib: [resolve_node_bin] npm -> fallback to PATH
|
||||
|
||||
# 多次重复尝试安装 mcp-stdio-proxy(说明检测失败)
|
||||
2026-02-12T06:15:22.509303Z INFO agent_tauri_client_lib: [Dependency] 开始全局安装 npm 包: mcp-stdio-proxy
|
||||
2026-02-12T06:15:29.360632Z INFO agent_tauri_client_lib: [Dependency] 开始全局安装 npm 包: mcp-stdio-proxy
|
||||
2026-02-12T06:15:30.769366Z INFO agent_tauri_client_lib: [Dependency] 开始全局安装 npm 包: mcp-stdio-proxy
|
||||
...(共 10 次重复)
|
||||
|
||||
# 第二次启动 - Node.js 已安装
|
||||
2026-02-12T06:16:55.747776Z INFO agent_tauri_client_lib: [NodeInstall] 开始自动安装 Node.js...
|
||||
2026-02-12T06:16:59.168654Z INFO nuwax_agent_core::dependency::node: Found local Node.js: v22.14.0
|
||||
2026-02-12T06:17:00.275214Z INFO agent_tauri_client_lib: [resolve_node_bin] npm -> "C:\\Users\\MECHREVO\\.local\\bin\\npm.cmd"
|
||||
2026-02-12T06:17:13.857546Z INFO agent_tauri_client_lib: [Dependency] mcp-stdio-proxy 全局安装成功
|
||||
```
|
||||
|
||||
**问题分析**:
|
||||
|
||||
1. **第一次启动时** (Node.js 未安装):
|
||||
- 程序没有先安装 Node.js
|
||||
- 直接尝试使用 PATH 中的 npm(可能来自系统已安装的 Node.js)
|
||||
- 但是这个 npm 可能不可用或有问题,导致安装失败
|
||||
- 触发了多次重试(10 次)
|
||||
|
||||
2. **第二次启动时** (Node.js 已通过应用安装):
|
||||
- 检测到 `~/.local/bin/node` 存在
|
||||
- 使用正确的 npm 路径
|
||||
- 安装成功
|
||||
|
||||
**根本原因**:
|
||||
- **缺少 Node.js 可用性检测**: 应该在使用 npm 之前,先检查 `node` 命令是否真正可用
|
||||
- **检测逻辑不完整**: `fallback to PATH` 意味着直接使用系统 PATH 中的 npm,但没有验证它是否能正常工作
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 1. mcp-proxy 启动失败的修复
|
||||
|
||||
这个问题不在 `mcp-proxy` 代码库中,而在调用方(nuwax-agent)。但我们可以提供诊断指导:
|
||||
|
||||
**建议给 nuwax-agent 团队**:
|
||||
|
||||
```rust
|
||||
// 在启动 mcp-proxy 时,需要捕获子进程的 stdout/stderr
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
let mut child = Command::new(mcp_proxy_path)
|
||||
.args(&["server", "--port", "18099"])
|
||||
.stdout(Stdio::piped()) // 捕获标准输出
|
||||
.stderr(Stdio::piped()) // 捕获标准错误
|
||||
.spawn()?;
|
||||
|
||||
// 读取并记录输出
|
||||
let stdout = child.stdout.take().unwrap();
|
||||
let stderr = child.stderr.take().unwrap();
|
||||
|
||||
// 使用异步任务读取输出
|
||||
tokio::spawn(async move {
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
let mut reader = BufReader::new(stdout).lines();
|
||||
while let Ok(Some(line)) = reader.next_line().await {
|
||||
tracing::info!("[McpProxy stdout] {}", line);
|
||||
}
|
||||
});
|
||||
|
||||
tokio::spawn(async move {
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
let mut reader = BufReader::new(stderr).lines();
|
||||
while let Ok(Some(line)) = reader.next_line().await {
|
||||
tracing::error!("[McpProxy stderr] {}", line);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**临时解决方案(给用户)**:
|
||||
|
||||
```powershell
|
||||
# 手动启动 mcp-proxy 来查看错误信息
|
||||
cd C:\Users\MECHREVO\.local\bin
|
||||
.\mcp-proxy.cmd server --port 18099
|
||||
|
||||
# 如果报错,记录错误信息
|
||||
```
|
||||
|
||||
### 2. Node.js 检测逻辑增强
|
||||
|
||||
虽然这个逻辑在 nuwax-agent 中,但我们可以提供参考实现:
|
||||
|
||||
```rust
|
||||
/// 检查 Node.js 是否真正可用
|
||||
async fn is_node_available() -> bool {
|
||||
#[cfg(windows)]
|
||||
let node_cmd = "node.exe";
|
||||
#[cfg(not(windows))]
|
||||
let node_cmd = "node";
|
||||
|
||||
match tokio::process::Command::new(node_cmd)
|
||||
.arg("--version")
|
||||
.output()
|
||||
.await
|
||||
{
|
||||
Ok(output) if output.status.success() => {
|
||||
let version = String::from_utf8_lossy(&output.stdout);
|
||||
tracing::info!("Found Node.js: {}", version.trim());
|
||||
true
|
||||
}
|
||||
Ok(_) => {
|
||||
tracing::warn!("Node.js command exists but failed");
|
||||
false
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!("Node.js not found: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 修正后的 npm 安装流程
|
||||
async fn ensure_npm_package(package_name: &str) -> Result<()> {
|
||||
// 1. 先检查 Node.js 是否可用
|
||||
if !is_node_available().await {
|
||||
tracing::info!("Node.js not available, installing...");
|
||||
install_nodejs().await?;
|
||||
|
||||
// 再次验证
|
||||
if !is_node_available().await {
|
||||
anyhow::bail!("Failed to install Node.js");
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 检查包是否已安装
|
||||
if is_package_installed(package_name).await? {
|
||||
tracing::info!("{} is already installed", package_name);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 3. 安装包
|
||||
install_npm_package(package_name).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 3. mcp-streamable-proxy CREATE_NO_WINDOW 修复
|
||||
|
||||
**已修复**: 将 `CREATE_NO_WINDOW` 标志移到所有命令配置的最后。
|
||||
|
||||
### 4. mcp-sse-proxy CREATE_NO_WINDOW 验证
|
||||
|
||||
**当前状态**: 使用 `CreationFlags(0x08000000)` + `JobObject`,应该是正确的。
|
||||
|
||||
需要验证:
|
||||
```rust
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use process_wrap::tokio::CreationFlags;
|
||||
wrapped_cmd.wrap(CreationFlags(0x08000000));
|
||||
wrapped_cmd.wrap(JobObject);
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
### 问题优先级
|
||||
|
||||
1. **高优先级 - mcp-proxy 启动失败**:
|
||||
- 需要 nuwax-agent 团队捕获子进程输出
|
||||
- 需要用户手动运行 mcp-proxy 来诊断具体错误
|
||||
|
||||
2. **中优先级 - Node.js 检测逻辑**:
|
||||
- 需要在 nuwax-agent 中添加 Node.js 可用性检测
|
||||
- 避免重复安装尝试
|
||||
|
||||
3. **低优先级 - CMD 窗口隐藏**:
|
||||
- mcp-streamable-proxy 已修复
|
||||
- 需要编译新版本并测试
|
||||
|
||||
### 下一步行动
|
||||
|
||||
1. **立即诊断** - 用户手动运行:
|
||||
```powershell
|
||||
C:\Users\MECHREVO\.local\bin\mcp-proxy.cmd server --port 18099
|
||||
```
|
||||
|
||||
2. **提供完整错误日志**: 运行上述命令并提供输出
|
||||
|
||||
3. **编译测试新版本**: 测试 CREATE_NO_WINDOW 修复效果
|
||||
|
||||
4. **联系 nuwax-agent 团队**: 提供子进程输出捕获和 Node.js 检测的修复建议
|
||||
Reference in New Issue
Block a user