Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
064731e
feat: Implement enhanced error handling and retry mechanisms
google-labs-jules[bot] May 29, 2025
52a6977
fix: Correct syntax error in mcp-proxy.ts
google-labs-jules[bot] May 29, 2025
a20a0c2
fix: Resolve TS18048 errors for currentProxyConfig
google-labs-jules[bot] May 29, 2025
8688f1e
fix: Resolve TS2451 redeclaration error for initialActiveServers
google-labs-jules[bot] May 29, 2025
dac76ea
feat: Configure proxy retry settings via environment variables
google-labs-jules[bot] May 29, 2025
64c841b
fix: Correct scope of defaultEnvProxySettings in config.ts
google-labs-jules[bot] May 29, 2025
a2ae361
refactor: change tool name seperator from -- to __
ptbsare Jun 5, 2025
76109f8
perf: improve tool call logging
ptbsare Jun 5, 2025
d160e0c
fix: revert -- seperator change
ptbsare Jun 19, 2025
be4b869
feat: add stdio tool retry and improve readme
ptbsare Jun 23, 2025
1cb290f
perf: improve connection error
ptbsare Jun 23, 2025
03094cd
feat: logger level
ptbsare Jun 23, 2025
3566805
doc: improve readme for logger level
ptbsare Jun 23, 2025
0bb1f66
perf(logging): improve inactive logging
ptbsare Jun 24, 2025
5284c4d
refactor: improve retry loggic
ptbsare Jun 27, 2025
d2d7d7d
feat: Make server-tool name separator configurable SERVER_TOOLNAME_SE…
ptbsare Jun 27, 2025
72dfb3b
feat!: Make server-tool name separator configurable
ptbsare Jun 27, 2025
da4ba02
Fix: Ensure McpError is returned to client in all failure scenarios
ptbsare Jun 28, 2025
89d80ba
fix: 404 Error POSTING session not found as connection error retry sse
ptbsare Jun 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 227 additions & 3 deletions README.md

Large diffs are not rendered by default.

143 changes: 140 additions & 3 deletions README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,18 +122,18 @@
```json
{
"tools": {
"unique-server-key1--tool-name-from-server": {
"unique-server-key1__tool-name-from-server": {
"enabled": true,
"displayName": "我的自定义工具名称",
"description": "一个更友好的描述。"
},
"another-sse-server--another-tool": {
"another-sse-server__another-tool": {
"enabled": false
}
}
}
```
- 键的格式为 `<server_key>--<original_tool_name>`。
- 键的格式为 `<server_key><separator><original_tool_name>`,其中 `<separator>` 是 `SERVER_TOOLNAME_SEPERATOR` 环境变量的值(默认为 `__`)
- `enabled`: (可选, 默认: `true`) 设置为 `false` 以向连接到代理的客户端隐藏此工具。
- `displayName`: (可选) 在客户端 UI 中覆盖工具的名称。
- `description`: (可选) 覆盖工具的描述。
Expand Down Expand Up @@ -175,6 +175,143 @@
export TOOLS_FOLDER=/srv/mcp_tools
```

- **`SERVER_TOOLNAME_SEPERATOR`**: (可选) 定义用于组合服务器名称和工具名称以生成工具唯一键的分隔符(例如 `server-key__tool-name`)。此键在内部和 `tool_config.json` 文件中使用。
- 默认值:`__`。
- 必须至少包含 2 个字符,且只能包含字母(a-z, A-Z)、数字(0-9)、连字符(`-`)和下划线(`_`)。
- 如果提供的值无效,将使用默认值(`__`)并记录警告。
```bash
export SERVER_TOOLNAME_SEPERATOR="___" # 示例:使用三个下划线
```

- **`LOGGING`**: (可选) 控制服务器输出的最低日志级别。
- 可能的值(不区分大小写):`error`, `warn`, `info`, `debug`。
- 将显示指定级别及以上的所有日志。
- 默认值:`info`。
```bash
export LOGGING="debug"
```

- **`RETRY_SSE_TOOL_CALL`**: (可选) 控制 SSE 工具调用失败时是否自动重连并重试。设置为 `"true"` 启用,`"false"` 禁用。默认: `true`。有关详细信息,请参阅“增强的可靠性特性”部分。
```bash
export RETRY_SSE_TOOL_CALL="true"
```
- **`SSE_TOOL_CALL_MAX_RETRIES`**: (可选) SSE 工具调用最大重试次数(在初始失败后)。默认: `2`。有关详细信息,请参阅“增强的可靠性特性”部分。
```bash
export SSE_TOOL_CALL_MAX_RETRIES="2"
```
- **`SSE_TOOL_CALL_RETRY_DELAY_BASE_MS`**: (可选) SSE 工具调用重试延迟基准(毫秒),用于指数退避。默认: `300`。有关详细信息,请参阅“增强的可靠性特性”部分。
```bash
export SSE_TOOL_CALL_RETRY_DELAY_BASE_MS="300"
```
- **`RETRY_HTTP_TOOL_CALL`**: (可选) 控制 HTTP 工具调用连接错误时是否重试。设置为 `"true"` 启用,`"false"` 禁用。默认: `true`。有关详细信息,请参阅“增强的可靠性特性”部分。
```bash
export RETRY_HTTP_TOOL_CALL="true"
```
- **`HTTP_TOOL_CALL_MAX_RETRIES`**: (可选) HTTP 工具调用最大重试次数(在初始失败后)。默认: `2`。有关详细信息,请参阅“增强的可靠性特性”部分。
```bash
export HTTP_TOOL_CALL_MAX_RETRIES="3"
```
- **`HTTP_TOOL_CALL_RETRY_DELAY_BASE_MS`**: (可选) HTTP 工具调用重试延迟基准(毫秒),用于指数退避。默认: `300`。有关详细信息,请参阅“增强的可靠性特性”部分。
```bash
export HTTP_TOOL_CALL_RETRY_DELAY_BASE_MS="500"
```
- **`RETRY_STDIO_TOOL_CALL`**: (可选) 控制 Stdio 工具调用连接错误时是否重试(尝试重启进程)。设置为 `"true"` 启用,`"false"` 禁用。默认: `true`。有关详细信息,请参阅“增强的可靠性特性”部分。
```bash
export RETRY_STDIO_TOOL_CALL="true"
```
- **`STDIO_TOOL_CALL_MAX_RETRIES`**: (可选) Stdio 工具调用最大重试次数(在初始失败后)。默认: `2`。有关详细信息,请参阅“增强的可靠性特性”部分。
```bash
export STDIO_TOOL_CALL_MAX_RETRIES="5"
```
- **`STDIO_TOOL_CALL_RETRY_DELAY_BASE_MS`**: (可选) Stdio 工具调用重试延迟基准(毫秒),用于指数退避。默认: `300`。有关详细信息,请参阅“增强的可靠性特性”部分。
```bash
export STDIO_TOOL_CALL_RETRY_DELAY_BASE_MS="1000"
```

## 增强的可靠性特性

MCP 代理服务器包含多项特性,用以提升其自身弹性以及与后端 MCP 服务交互的可靠性,确保更平稳的操作和更一致的工具执行。

### 1. 错误传播
代理服务器确保从后端 MCP 服务产生的错误能够一致地传播给请求客户端。这些错误被格式化为标准的 JSON-RPC 错误响应,使客户端更容易统一处理它们。

### 2. SSE 工具调用的连接重试
当对基于 SSE 的后端服务器执行 `tools/call` 操作时,如果底层连接丢失或遇到错误(包括超时),代理服务器将实现重试机制。

**重试机制:**
如果初始 SSE 工具调用因连接错误或超时而失败,代理将尝试重新建立与 SSE 后端的连接。如果重新连接成功,它将使用指数退避策略重试原始的 `tools/call` 请求,类似于 HTTP 和 Stdio 重试。这意味着每次后续重试尝试之前的延迟会指数级增加,并加入少量抖动(随机性)。

**配置:**
这些设置主要通过环境变量控制。如果 `config/mcp_server.json` 中 `proxy` 对象下存在这些特定键的值,它们将被环境变量覆盖。

- **`RETRY_SSE_TOOL_CALL`** (环境变量):
- 设置为 `"true"` 以启用 SSE 工具调用的重试。
- 设置为 `"false"` 以禁用此功能。
- **默认行为:** `true` (如果环境变量未设置、为空或为无效值)。

- **`SSE_TOOL_CALL_MAX_RETRIES`** (环境变量):
- 指定在初次失败尝试*之后*的最大重试次数。例如,如果设置为 `"2"`,则会有一次初始尝试和最多两次重试尝试,总共最多三次尝试。
- **默认行为:** `2` (如果环境变量未设置、为空或不是一个有效的整数)。

- **`SSE_TOOL_CALL_RETRY_DELAY_BASE_MS`** (环境变量):
- 用于指数退避计算的基准延迟(以毫秒为单位)。第 *n* 次重试(0索引)之前的延迟大约是 `SSE_TOOL_CALL_RETRY_DELAY_BASE_MS * (2^n) + 抖动`。
- **默认行为:** `300` (毫秒) (如果环境变量未设置、为空或不是一个有效的整数)。

**示例 (环境变量):**
```bash
export RETRY_SSE_TOOL_CALL="true"
export SSE_TOOL_CALL_MAX_RETRIES="3"
export SSE_TOOL_CALL_RETRY_DELAY_BASE_MS="500"
```

### 3. HTTP 工具调用的请求重试
对于定向到基于 HTTP 的后端服务器的 `tools/call` 操作,代理服务器为连接错误(例如,“failed to fetch”、网络超时)实现了一套重试机制。

**重试机制:**
如果初始 HTTP 请求因连接错误而失败,代理将使用指数退避策略重试该请求。这意味着每次后续重试尝试之前的延迟会指数级增加,并加入少量抖动(随机性)以防止“惊群效应”。

**配置:**
这些设置主要通过环境变量控制。

- **`RETRY_HTTP_TOOL_CALL`** (环境变量):
- 设置为 `"true"` 以启用 HTTP 工具调用的重试。
- 设置为 `"false"` 以禁用此功能。
- **默认行为:** `true` (如果环境变量未设置、为空或为无效值)。

- **`HTTP_TOOL_CALL_MAX_RETRIES`** (环境变量):
- 指定在初次失败尝试*之后*的最大重试次数。例如,如果设置为 `"2"`,则会有一次初始尝试和最多两次重试尝试,总共最多三次尝试。
- **默认行为:** `2` (如果环境变量未设置、为空或不是一个有效的整数)。

- **`HTTP_TOOL_CALL_RETRY_DELAY_BASE_MS`** (环境变量):
- 用于指数退避计算的基准延迟(以毫秒为单位)。第 *n* 次重试(0索引)之前的延迟大约是 `HTTP_TOOL_CALL_RETRY_DELAY_BASE_MS * (2^n) + 抖动`。
- **默认行为:** `300` (毫秒) (如果环境变量未设置、为空或不是一个有效的整数)。

### 4. Stdio 工具调用的连接重试
对于指向基于 Stdio 的后端服务器的 `tools/call` 操作,代理实现了针对连接错误(例如,进程崩溃或无响应)的重试机制。

**重试机制:**
如果初始 Stdio 连接或工具调用失败,代理将尝试重新启动 Stdio 进程并重试请求。此机制类似于 HTTP 重试,使用指数退避策略。

**配置:**
这些设置主要由环境变量控制。

- **`RETRY_STDIO_TOOL_CALL`** (环境变量):
- 设置为 `"true"` 以启用 Stdio 工具调用重试。
- 设置为 `"false"` 以禁用此功能。
- **默认行为:** `true` (如果环境变量未设置、为空或为无效值)。

- **`STDIO_TOOL_CALL_MAX_RETRIES`** (环境变量):
- 指定在初次失败尝试*之后*的最大重试尝试次数。例如,如果设置为 `"2"`,则将有一次初始尝试和最多两次重试尝试,总共最多三次尝试。
- **默认行为:** `2` (如果环境变量未设置、为空或不是一个有效的整数)。

- **`STDIO_TOOL_CALL_RETRY_DELAY_BASE_MS`** (环境变量):
- 用于指数退避计算的基准延迟(以毫秒为单位)。第 *n* 次重试(从 0 开始索引)之前的延迟大约是 `STDIO_TOOL_CALL_RETRY_DELAY_BASE_MS * (2^n) + 抖动`。
- **默认行为:** `300` (毫秒) (如果环境变量未设置、为空或不是一个有效的整数)。

**环境变量解析通用说明:**
- 布尔环境变量(`RETRY_SSE_TOOL_CALL`,`RETRY_HTTP_TOOL_CALL`,`RETRY_STDIO_TOOL_CALL`)如果其小写值恰好是 `"true"`,则被视为 `true`。任何其他值(包括空或未设置)将应用默认值,或者如果默认值为 `false` 则为 `false`(尽管对于这些特定变量,默认值为 `true`)。
- 数字环境变量(`SSE_TOOL_CALL_MAX_RETRIES`,`SSE_TOOL_CALL_RETRY_DELAY_BASE_MS`,`HTTP_TOOL_CALL_MAX_RETRIES`,`HTTP_TOOL_CALL_RETRY_DELAY_BASE_MS`,`STDIO_TOOL_CALL_MAX_RETRIES`,`STDIO_TOOL_CALL_RETRY_DELAY_BASE_MS`)被解析为十进制整数。如果解析失败(例如,值不是数字,或变量为空/未设置),则使用默认值。

## 开发

安装依赖:
Expand Down
19 changes: 13 additions & 6 deletions public/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,37 @@ const saveToolConfigButton = document.getElementById('save-tool-config-button');
// const saveToolStatus = document.getElementById('save-tool-status'); // Removed: Declared in script.js
// Note: Assumes currentToolConfig and discoveredTools variables are globally accessible from script.js or passed.
// Note: Assumes triggerReload function is globally accessible from script.js or passed.
let serverToolnameSeparator = '__'; // Default separator

// --- Tool Configuration Management ---
async function loadToolData() {
if (!saveToolStatus || !toolListDiv) return; // Guard
saveToolStatus.textContent = 'Loading tool data...';
window.toolDataLoaded = false; // Reset flag during load attempt (use global flag)
try {
// Fetch both discovered tools and tool config concurrently
const [toolsResponse, configResponse] = await Promise.all([
// Fetch discovered tools, tool config, and environment info concurrently
const [toolsResponse, configResponse, envResponse] = await Promise.all([
fetch('/admin/tools/list'),
fetch('/admin/tools/config')
fetch('/admin/tools/config'),
fetch('/admin/environment') // Fetch environment info
]);

if (!toolsResponse.ok) throw new Error(`Failed to fetch discovered tools: ${toolsResponse.statusText}`);
if (!configResponse.ok) throw new Error(`Failed to fetch tool config: ${configResponse.statusText}`);
if (!envResponse.ok) throw new Error(`Failed to fetch environment info: ${envResponse.statusText}`); // Check env response

const toolsResult = await toolsResponse.json();
window.discoveredTools = toolsResult.tools || []; // Expecting { tools: [...] } (use global var)

window.currentToolConfig = await configResponse.json(); // Use global var
if (!window.currentToolConfig || typeof window.currentToolConfig !== 'object' || !window.currentToolConfig.tools) {
console.warn("Received invalid tool configuration format, initializing empty.", window.currentToolConfig);
window.currentToolConfig = { tools: {} }; // Initialize if invalid or empty
window.currentToolConfig = { tools: {} }; // Initialize if invalid or empty
}

const envResult = await envResponse.json(); // Parse environment info
serverToolnameSeparator = envResult.serverToolnameSeparator || '__'; // Update separator
console.log(`Using server toolname separator from backend: "${serverToolnameSeparator}"`);

renderTools(); // Render using both discovered and configured data
window.toolDataLoaded = true; // Set global flag only after successful load and render
Expand Down Expand Up @@ -66,7 +72,7 @@ function renderTools() {

// Render discovered tools first, merging with config
discoveredTools.forEach(tool => {
const toolKey = `${tool.serverName}--${tool.name}`; // Unique key
const toolKey = `${tool.serverName}${serverToolnameSeparator}${tool.name}`; // Use the fetched separator
const config = currentToolConfig.tools[toolKey] || {}; // Get config or empty object
// For discovered tools, their server is considered active by the proxy at connection time
renderToolEntry(toolKey, tool, config, false, true); // isConfigOnly = false, isServerActive = true
Expand All @@ -76,7 +82,8 @@ function renderTools() {
// Render any remaining configured tools that were not discovered
configuredToolKeys.forEach(toolKey => {
const config = currentToolConfig.tools[toolKey];
const serverKeyForConfigOnlyTool = toolKey.split('--')[0];
// Use the fetched separator for splitting
const serverKeyForConfigOnlyTool = toolKey.split(serverToolnameSeparator)[0];
let isServerActiveForConfigOnlyTool = true; // Default to true if server config not found or active flag is missing/true

if (window.currentServerConfig && window.currentServerConfig.mcpServers && window.currentServerConfig.mcpServers[serverKeyForConfigOnlyTool]) {
Expand Down
Loading