Skip to content

Commit 83c06e6

Browse files
committed
Merge branch 'feature/error-handling-retries'
2 parents ee9ac7f + 89d80ba commit 83c06e6

File tree

9 files changed

+1454
-310
lines changed

9 files changed

+1454
-310
lines changed

README.md

Lines changed: 227 additions & 3 deletions
Large diffs are not rendered by default.

README_ZH.md

Lines changed: 140 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,18 +122,18 @@
122122
```json
123123
{
124124
"tools": {
125-
"unique-server-key1--tool-name-from-server": {
125+
"unique-server-key1__tool-name-from-server": {
126126
"enabled": true,
127127
"displayName": "我的自定义工具名称",
128128
"description": "一个更友好的描述。"
129129
},
130-
"another-sse-server--another-tool": {
130+
"another-sse-server__another-tool": {
131131
"enabled": false
132132
}
133133
}
134134
}
135135
```
136-
- 键的格式为 `<server_key>--<original_tool_name>`
136+
- 键的格式为 `<server_key><separator><original_tool_name>`,其中 `<separator>``SERVER_TOOLNAME_SEPERATOR` 环境变量的值(默认为 `__`
137137
- `enabled`: (可选, 默认: `true`) 设置为 `false` 以向连接到代理的客户端隐藏此工具。
138138
- `displayName`: (可选) 在客户端 UI 中覆盖工具的名称。
139139
- `description`: (可选) 覆盖工具的描述。
@@ -175,6 +175,143 @@
175175
export TOOLS_FOLDER=/srv/mcp_tools
176176
```
177177

178+
- **`SERVER_TOOLNAME_SEPERATOR`**: (可选) 定义用于组合服务器名称和工具名称以生成工具唯一键的分隔符(例如 `server-key__tool-name`)。此键在内部和 `tool_config.json` 文件中使用。
179+
- 默认值:`__`
180+
- 必须至少包含 2 个字符,且只能包含字母(a-z, A-Z)、数字(0-9)、连字符(`-`)和下划线(`_`)。
181+
- 如果提供的值无效,将使用默认值(`__`)并记录警告。
182+
```bash
183+
export SERVER_TOOLNAME_SEPERATOR="___" # 示例:使用三个下划线
184+
```
185+
186+
- **`LOGGING`**: (可选) 控制服务器输出的最低日志级别。
187+
- 可能的值(不区分大小写):`error`, `warn`, `info`, `debug`
188+
- 将显示指定级别及以上的所有日志。
189+
- 默认值:`info`
190+
```bash
191+
export LOGGING="debug"
192+
```
193+
194+
- **`RETRY_SSE_TOOL_CALL`**: (可选) 控制 SSE 工具调用失败时是否自动重连并重试。设置为 `"true"` 启用,`"false"` 禁用。默认: `true`。有关详细信息,请参阅“增强的可靠性特性”部分。
195+
```bash
196+
export RETRY_SSE_TOOL_CALL="true"
197+
```
198+
- **`SSE_TOOL_CALL_MAX_RETRIES`**: (可选) SSE 工具调用最大重试次数(在初始失败后)。默认: `2`。有关详细信息,请参阅“增强的可靠性特性”部分。
199+
```bash
200+
export SSE_TOOL_CALL_MAX_RETRIES="2"
201+
```
202+
- **`SSE_TOOL_CALL_RETRY_DELAY_BASE_MS`**: (可选) SSE 工具调用重试延迟基准(毫秒),用于指数退避。默认: `300`。有关详细信息,请参阅“增强的可靠性特性”部分。
203+
```bash
204+
export SSE_TOOL_CALL_RETRY_DELAY_BASE_MS="300"
205+
```
206+
- **`RETRY_HTTP_TOOL_CALL`**: (可选) 控制 HTTP 工具调用连接错误时是否重试。设置为 `"true"` 启用,`"false"` 禁用。默认: `true`。有关详细信息,请参阅“增强的可靠性特性”部分。
207+
```bash
208+
export RETRY_HTTP_TOOL_CALL="true"
209+
```
210+
- **`HTTP_TOOL_CALL_MAX_RETRIES`**: (可选) HTTP 工具调用最大重试次数(在初始失败后)。默认: `2`。有关详细信息,请参阅“增强的可靠性特性”部分。
211+
```bash
212+
export HTTP_TOOL_CALL_MAX_RETRIES="3"
213+
```
214+
- **`HTTP_TOOL_CALL_RETRY_DELAY_BASE_MS`**: (可选) HTTP 工具调用重试延迟基准(毫秒),用于指数退避。默认: `300`。有关详细信息,请参阅“增强的可靠性特性”部分。
215+
```bash
216+
export HTTP_TOOL_CALL_RETRY_DELAY_BASE_MS="500"
217+
```
218+
- **`RETRY_STDIO_TOOL_CALL`**: (可选) 控制 Stdio 工具调用连接错误时是否重试(尝试重启进程)。设置为 `"true"` 启用,`"false"` 禁用。默认: `true`。有关详细信息,请参阅“增强的可靠性特性”部分。
219+
```bash
220+
export RETRY_STDIO_TOOL_CALL="true"
221+
```
222+
- **`STDIO_TOOL_CALL_MAX_RETRIES`**: (可选) Stdio 工具调用最大重试次数(在初始失败后)。默认: `2`。有关详细信息,请参阅“增强的可靠性特性”部分。
223+
```bash
224+
export STDIO_TOOL_CALL_MAX_RETRIES="5"
225+
```
226+
- **`STDIO_TOOL_CALL_RETRY_DELAY_BASE_MS`**: (可选) Stdio 工具调用重试延迟基准(毫秒),用于指数退避。默认: `300`。有关详细信息,请参阅“增强的可靠性特性”部分。
227+
```bash
228+
export STDIO_TOOL_CALL_RETRY_DELAY_BASE_MS="1000"
229+
```
230+
231+
## 增强的可靠性特性
232+
233+
MCP 代理服务器包含多项特性,用以提升其自身弹性以及与后端 MCP 服务交互的可靠性,确保更平稳的操作和更一致的工具执行。
234+
235+
### 1. 错误传播
236+
代理服务器确保从后端 MCP 服务产生的错误能够一致地传播给请求客户端。这些错误被格式化为标准的 JSON-RPC 错误响应,使客户端更容易统一处理它们。
237+
238+
### 2. SSE 工具调用的连接重试
239+
当对基于 SSE 的后端服务器执行 `tools/call` 操作时,如果底层连接丢失或遇到错误(包括超时),代理服务器将实现重试机制。
240+
241+
**重试机制:**
242+
如果初始 SSE 工具调用因连接错误或超时而失败,代理将尝试重新建立与 SSE 后端的连接。如果重新连接成功,它将使用指数退避策略重试原始的 `tools/call` 请求,类似于 HTTP 和 Stdio 重试。这意味着每次后续重试尝试之前的延迟会指数级增加,并加入少量抖动(随机性)。
243+
244+
**配置:**
245+
这些设置主要通过环境变量控制。如果 `config/mcp_server.json``proxy` 对象下存在这些特定键的值,它们将被环境变量覆盖。
246+
247+
- **`RETRY_SSE_TOOL_CALL`** (环境变量):
248+
- 设置为 `"true"` 以启用 SSE 工具调用的重试。
249+
- 设置为 `"false"` 以禁用此功能。
250+
- **默认行为:** `true` (如果环境变量未设置、为空或为无效值)。
251+
252+
- **`SSE_TOOL_CALL_MAX_RETRIES`** (环境变量):
253+
- 指定在初次失败尝试*之后*的最大重试次数。例如,如果设置为 `"2"`,则会有一次初始尝试和最多两次重试尝试,总共最多三次尝试。
254+
- **默认行为:** `2` (如果环境变量未设置、为空或不是一个有效的整数)。
255+
256+
- **`SSE_TOOL_CALL_RETRY_DELAY_BASE_MS`** (环境变量):
257+
- 用于指数退避计算的基准延迟(以毫秒为单位)。第 *n* 次重试(0索引)之前的延迟大约是 `SSE_TOOL_CALL_RETRY_DELAY_BASE_MS * (2^n) + 抖动`
258+
- **默认行为:** `300` (毫秒) (如果环境变量未设置、为空或不是一个有效的整数)。
259+
260+
**示例 (环境变量):**
261+
```bash
262+
export RETRY_SSE_TOOL_CALL="true"
263+
export SSE_TOOL_CALL_MAX_RETRIES="3"
264+
export SSE_TOOL_CALL_RETRY_DELAY_BASE_MS="500"
265+
```
266+
267+
### 3. HTTP 工具调用的请求重试
268+
对于定向到基于 HTTP 的后端服务器的 `tools/call` 操作,代理服务器为连接错误(例如,“failed to fetch”、网络超时)实现了一套重试机制。
269+
270+
**重试机制:**
271+
如果初始 HTTP 请求因连接错误而失败,代理将使用指数退避策略重试该请求。这意味着每次后续重试尝试之前的延迟会指数级增加,并加入少量抖动(随机性)以防止“惊群效应”。
272+
273+
**配置:**
274+
这些设置主要通过环境变量控制。
275+
276+
- **`RETRY_HTTP_TOOL_CALL`** (环境变量):
277+
- 设置为 `"true"` 以启用 HTTP 工具调用的重试。
278+
- 设置为 `"false"` 以禁用此功能。
279+
- **默认行为:** `true` (如果环境变量未设置、为空或为无效值)。
280+
281+
- **`HTTP_TOOL_CALL_MAX_RETRIES`** (环境变量):
282+
- 指定在初次失败尝试*之后*的最大重试次数。例如,如果设置为 `"2"`,则会有一次初始尝试和最多两次重试尝试,总共最多三次尝试。
283+
- **默认行为:** `2` (如果环境变量未设置、为空或不是一个有效的整数)。
284+
285+
- **`HTTP_TOOL_CALL_RETRY_DELAY_BASE_MS`** (环境变量):
286+
- 用于指数退避计算的基准延迟(以毫秒为单位)。第 *n* 次重试(0索引)之前的延迟大约是 `HTTP_TOOL_CALL_RETRY_DELAY_BASE_MS * (2^n) + 抖动`
287+
- **默认行为:** `300` (毫秒) (如果环境变量未设置、为空或不是一个有效的整数)。
288+
289+
### 4. Stdio 工具调用的连接重试
290+
对于指向基于 Stdio 的后端服务器的 `tools/call` 操作,代理实现了针对连接错误(例如,进程崩溃或无响应)的重试机制。
291+
292+
**重试机制:**
293+
如果初始 Stdio 连接或工具调用失败,代理将尝试重新启动 Stdio 进程并重试请求。此机制类似于 HTTP 重试,使用指数退避策略。
294+
295+
**配置:**
296+
这些设置主要由环境变量控制。
297+
298+
- **`RETRY_STDIO_TOOL_CALL`** (环境变量):
299+
- 设置为 `"true"` 以启用 Stdio 工具调用重试。
300+
- 设置为 `"false"` 以禁用此功能。
301+
- **默认行为:** `true` (如果环境变量未设置、为空或为无效值)。
302+
303+
- **`STDIO_TOOL_CALL_MAX_RETRIES`** (环境变量):
304+
- 指定在初次失败尝试*之后*的最大重试尝试次数。例如,如果设置为 `"2"`,则将有一次初始尝试和最多两次重试尝试,总共最多三次尝试。
305+
- **默认行为:** `2` (如果环境变量未设置、为空或不是一个有效的整数)。
306+
307+
- **`STDIO_TOOL_CALL_RETRY_DELAY_BASE_MS`** (环境变量):
308+
- 用于指数退避计算的基准延迟(以毫秒为单位)。第 *n* 次重试(从 0 开始索引)之前的延迟大约是 `STDIO_TOOL_CALL_RETRY_DELAY_BASE_MS * (2^n) + 抖动`
309+
- **默认行为:** `300` (毫秒) (如果环境变量未设置、为空或不是一个有效的整数)。
310+
311+
**环境变量解析通用说明:**
312+
- 布尔环境变量(`RETRY_SSE_TOOL_CALL``RETRY_HTTP_TOOL_CALL``RETRY_STDIO_TOOL_CALL`)如果其小写值恰好是 `"true"`,则被视为 `true`。任何其他值(包括空或未设置)将应用默认值,或者如果默认值为 `false` 则为 `false`(尽管对于这些特定变量,默认值为 `true`)。
313+
- 数字环境变量(`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`)被解析为十进制整数。如果解析失败(例如,值不是数字,或变量为空/未设置),则使用默认值。
314+
178315
## 开发
179316

180317
安装依赖:

public/tools.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,37 @@ const saveToolConfigButton = document.getElementById('save-tool-config-button');
44
// const saveToolStatus = document.getElementById('save-tool-status'); // Removed: Declared in script.js
55
// Note: Assumes currentToolConfig and discoveredTools variables are globally accessible from script.js or passed.
66
// Note: Assumes triggerReload function is globally accessible from script.js or passed.
7+
let serverToolnameSeparator = '__'; // Default separator
78

89
// --- Tool Configuration Management ---
910
async function loadToolData() {
1011
if (!saveToolStatus || !toolListDiv) return; // Guard
1112
saveToolStatus.textContent = 'Loading tool data...';
1213
window.toolDataLoaded = false; // Reset flag during load attempt (use global flag)
1314
try {
14-
// Fetch both discovered tools and tool config concurrently
15-
const [toolsResponse, configResponse] = await Promise.all([
15+
// Fetch discovered tools, tool config, and environment info concurrently
16+
const [toolsResponse, configResponse, envResponse] = await Promise.all([
1617
fetch('/admin/tools/list'),
17-
fetch('/admin/tools/config')
18+
fetch('/admin/tools/config'),
19+
fetch('/admin/environment') // Fetch environment info
1820
]);
1921

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

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

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

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

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

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

8289
if (window.currentServerConfig && window.currentServerConfig.mcpServers && window.currentServerConfig.mcpServers[serverKeyForConfigOnlyTool]) {

0 commit comments

Comments
 (0)