提交qiming-mcp-proxy

This commit is contained in:
Codex
2026-06-01 13:03:20 +08:00
parent 9e9486b7c2
commit afb3d9f4e6
394 changed files with 124494 additions and 0 deletions

View 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)

View File

@@ -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 中实现日志捕获

View 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**

View 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 窗口的同步 CommandWindows
///
/// # 用法
/// ```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 窗口的异步 CommandWindows
#[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 版本
**风险**: 低(只影响进程启动方式,不改变业务逻辑)

View File

@@ -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

View 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%
```

View 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

View 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 检测的修复建议