MCP bridge loses upstream isError, logs failures as SUCCESS and caches them
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức phù hợp với người mới
- 74/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- node.js, redis, typescript
- Lĩnh vực
- api, backend, observability
Hướng nghiên cứu
Start in packages/backend/src/mcp-server/dynamic-mcp-tools.ts at the MCP dispatch branch near line 912, then follow the common success path near line 508 and renderResult. Compare the upstream result from packages/backend/src/connectors/engines/mcp-client.engine.ts with the executor response. Add regression coverage for error propagation, audit status and cache behavior, including a REST payload with an isError field; done when MCP tool errors remain errors and are not cached.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Description
When an upstream MCP tool returns a normal MCP tool-error result (isError: true, rather than throwing a JSON-RPC/HTTP error), the bridge engine preserves it, but DynamicMcpTools treats the entire result envelope as an ordinary API response body.
The client-facing executor result loses the top-level error flag. The upstream error is JSON-encoded inside a text item, the invocation is recorded as SUCCESS, and, when responseMapping.cacheTtl is enabled, that error is cached and served on subsequent calls without retrying the remote tool. An AI client relying on the protocol's error flag sees a successful call.
Steps to Reproduce
At main commit 59c2a06e331aa58584167d17c0692e956a862335 (v0.45.3):
- Use a registered MCP bridge tool whose upstream returns this valid tool result:
{"content":[{"type":"text","text":"Customer does not exist"}],"isError":true}
- Invoke it through
DynamicMcpTools.executeToolwithresponseMapping: { cacheTtl: 60 }. - Check the returned top-level
isError, the invocation's audit status and the Redis write. - Invoke the same tool again with the same arguments. The second invocation reads the cached error.
Self-contained executor-boundary reproduction
With development dependencies installed, save the following as .git/repro-bridge-error.cjs and run node .git/repro-bridge-error.cjs from the repository root. It uses the real executor, a valid upstream-result fixture, and doubles for its external dependencies. The assertions describe the current broken behavior.
// Run from the repo root. The executor is real; external dependencies are doubles.
const assert = require('node:assert/strict');
const { resolve } = require('node:path');
require('ts-node').register({ project: 'packages/backend/tsconfig.json', transpileOnly: true,
compilerOptions: { emitDecoratorMetadata: false } });
const metricsPath = require.resolve(resolve('packages/backend/src/common/metrics.ts'));
require.cache[metricsPath] = { id: metricsPath, filename: metricsPath, loaded: true,
exports: { recordToolCall: () => {} } };
const { DynamicMcpTools } = require(resolve('packages/backend/src/mcp-server/dynamic-mcp-tools.ts'));
const remoteResult = { content: [{ type: 'text', text: 'Customer does not exist' }], isError: true };
const tool = {
id: 't', connectorId: 'c', organizationId: 'o', name: 'lookup_customer',
description: 'Reproduction', parameters: { type: 'object', properties: {} },
connectorType: 'MCP', connectorConfig: { baseUrl: 'https://mcp.example.com/mcp', authType: 'NONE' },
endpointMapping: { method: 'lookup_customer', path: '/mcp' }, responseMapping: { cacheTtl: 60 },
};
(async () => {
for (const cloud of [false, true]) for (const cacheTtl of [0, 60]) {
tool.responseMapping = { cacheTtl };
let engineCalls = 0;
const cache = new Map(), auditRows = [];
const executor = new DynamicMcpTools(
{ getTool: () => tool, getToolForOrg: () => tool },
{ logInvocation: async row => auditRows.push(row) },
{ get: async key => cache.get(key), set: async (key, value) => cache.set(key, value) },
{ checkLicenseActive: async () => {} }, { isCloud: () => cloud }, {}, {}, {}, {},
{ execute: async () => { engineCalls++; return remoteResult; } }, {},
{ scheduleObservationalIngest: async () => {} },
);
const context = { organizationId: 'o', connectorIds: ['c'] };
const first = await executor.executeTool(tool.name, {}, context);
const second = await executor.executeTool(tool.name, {}, context);
assert.equal(first.isError, undefined);
assert.equal(second.isError, undefined);
assert.equal(JSON.parse(first.content[0].text).isError, true);
assert.equal(auditRows[0].status, 'SUCCESS');
assert.equal(cache.size, cacheTtl ? 1 : 0);
assert.equal(engineCalls, cacheTtl ? 1 : 2);
console.log(JSON.stringify({ mode: cloud ? 'cloud' : 'self-hosted', cacheTtl,
outerIsError: first.isError ?? null, innerIsError: JSON.parse(first.content[0].text).isError,
auditStatus: auditRows[0].status, cacheEntries: cache.size, engineCallsForTwoInvocations: engineCalls }));
}
})().catch(error => { console.error(error); process.exitCode = 1; });
Expected Behavior
- The client-facing MCP result retains
isError: trueand the upstream error content. - The invocation is classified as an error in audit/observability.
- A failed tool result does not enter the normal successful-response cache.
Actual Behavior
The client-facing executor result has the following shape. The error flag is nested inside the serialized body and the executor's internal structured field, instead of being on the returned MCP result:
{
"content": [{"type":"text","text":"{\n \"content\": [ ... ],\n \"isError\": true\n}"}],
"structured": {"content":[{"type":"text","text":"Customer does not exist"}],"isError":true}
}
Condensed observed output from the reproduction:
{"mode":"self-hosted","cacheTtl":0,"outerIsError":null,"innerIsError":true,"auditStatus":"SUCCESS","cacheEntries":0,"engineCallsForTwoInvocations":2}
{"mode":"self-hosted","cacheTtl":60,"outerIsError":null,"innerIsError":true,"auditStatus":"SUCCESS","cacheEntries":1,"engineCallsForTwoInvocations":1}
{"mode":"cloud","cacheTtl":0,"outerIsError":null,"innerIsError":true,"auditStatus":"SUCCESS","cacheEntries":0,"engineCallsForTwoInvocations":2}
{"mode":"cloud","cacheTtl":60,"outerIsError":null,"innerIsError":true,"auditStatus":"SUCCESS","cacheEntries":1,"engineCallsForTwoInvocations":1}
Here outerIsError: null means the property is absent (the script prints first.isError ?? null). This also happens without caching; enabling caching additionally makes the failure persist until expiry.
Relevant Code
- McpClientEngine.execute returns the upstream
CallToolResultintact. - The MCP dispatch branch wraps that result as
body. - The common success path audits/caches it as success, and
renderResultserializes it without lifting the MCP error flag.
This concerns output/error handling, separate from the input-schema flattening work in #961. The fix should be specific to MCP result envelopes: an arbitrary REST response containing a business-data field named isError should not change classification.
Connector Type
MCP Bridge
Deployment Method / Environment
Local development; Windows, Node.js v24.17.0. Source files used in the reproduction are unchanged from the main commit above.
I also exercised the real McpClientEngine against a local HTTP fake MCP server: the engine returned isError: true, and the executor then exhibited the same missing flag, success audit entry and cached error. Audit, Redis, licensing, metrics and KG were doubles; I did not run a full Docker Compose stack or a deployed Cloud instance. The shared executor path was exercised with both Cloud/self-hosted deployment doubles.
Suggested Regression Coverage
- An MCP tool-error result stays an error, with caching disabled and enabled.
- Audit records an error and the successful-response cache receives no write.
- A second invocation can reach the remote tool after a failed first call.
- A REST payload with an
isErrordata field keeps its existing behavior.
Reporting for triage; no assignment requested.
- Ngôn ngữ chính
- TypeScript
- Star
- 856
- Fork
- 82
- Merge trung bình
- 7 giờ 52 phút
- Pull request đã merge (30 ngày)
- 220
Chuẩn bị môi trường
Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của HelpCode-ai/anythingmcp
-
Docs: document all SMTP_* and other undocumented env vars for self-hostingCó thể đã có người làm @notlakshitha đã nhận 9 ngày trước. Đang mởenhancement
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
HelpCode-ai/anythingmcp#1049 · 3 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
good first issue hacktoberfest
Độ khó 2/5 Nửa ngày Mức phù hợp với người mới 70/100
HelpCode-ai/anythingmcp#996 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
good first issue hacktoberfest
Độ khó 2/5 Nửa ngày Mức phù hợp với người mới 72/100
HelpCode-ai/anythingmcp#995 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
enhancement good first issue hacktoberfest help wanted
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
HelpCode-ai/anythingmcp#982 · 6 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 55/100
HelpCode-ai/anythingmcp#1169 ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của HelpCode-ai/anythingmcp
Issue tương tự
-
createAI: the ai peer range allows ai@6, which the provider cannot work withCó thể đã có người làm @akhil9tiet đã nhận hôm nay. Đang mởbug
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 83/100
cloudflare/agents#2690 · 3 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
[fullsend] E2E: theme — global-header ignores app.branding.fullLogo (RHDHBUGS-3670)Có thể đã có người làm @fullsend-ai-coder đã nhận hôm nay. Đang mởe2e-failure ready-to-code
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 78/100
redhat-developer/rhdh-plugin-export-overlays#4503 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
needs triage 📥
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 86/100
ethereum/ethereum-org-website#19601 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
[Bug]: Circular app layout navigation and help icons are shifted upward in the official demoĐang mởbug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
cloudscape-design/components#5184 ·
Maintainer thường phản hồi trong vòng 2 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
agilepathway/label-checker#805 ·
Maintainer thường phản hồi trong vòng 1 ngày