From d3d32d637b35bb0b76c9b2347ed80d7cb881fb2e Mon Sep 17 00:00:00 2001 From: agent_coder Date: Fri, 10 Jul 2026 05:44:21 +0300 Subject: [PATCH] =?UTF-8?q?fix(mcp):=20no-response=20=D0=B4=D0=B8=D0=B0?= =?UTF-8?q?=D0=B3=D0=BD=D0=BE=D1=81=D1=82=D0=B8=D0=BA=D0=B0=20=E2=80=94=20?= =?UTF-8?q?=D0=BD=D0=B5=20=D0=BE=D1=82=D0=B4=D0=B0=D0=B2=D0=B0=D1=82=D1=8C?= =?UTF-8?q?=20=D1=81=D1=8B=D1=80=D0=BE=D0=B9=20error.message=20(=D1=83?= =?UTF-8?q?=D1=82=D0=B5=D1=87=D0=BA=D0=B0=20host=20=D0=B2=20=D0=BC=D0=BE?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D1=8C)=20(=D0=B2=D0=BD=D1=83=D1=82=D1=80.=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B2=D1=8C=D1=8E=20#437)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit no-response ветка формата использовала error.code ?? error.message: при отсутствии code axios-сообщения сетевых ошибок содержат host:port ('connect ECONNREFUSED 127.0.0.1:3000', 'getaddrinfo ENOTFOUND host'), что нарушает инвариант #437 «host никогда не попадает в видимое модели сообщение». Теперь только error.code (?? 'network error'); полный нативный текст уходит в stderr под DEBUG. code проставлен фактически для всех реальных no-response ошибок. Тест обновлён: сырое host-содержащее сообщение -> нейтральный reason, плюс ассерт что host в сообщении отсутствует. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/mcp/src/client.ts | 11 ++++++++++- packages/mcp/test/unit/error-diagnostics.test.mjs | 10 +++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/mcp/src/client.ts b/packages/mcp/src/client.ts index 2e59023f..e03b93b4 100644 --- a/packages/mcp/src/client.ts +++ b/packages/mcp/src/client.ts @@ -336,8 +336,17 @@ export function formatDocmostAxiosError(error: any): void { } } else { // No response at all (ECONNREFUSED / ETIMEDOUT / ECONNRESET / DNS / timeout). - const reason = error.code ?? error.message ?? "network error"; + // Use ONLY error.code, never the raw error.message: axios network messages + // embed host:port ("connect ECONNREFUSED 127.0.0.1:3000", "getaddrinfo + // ENOTFOUND host") and #437's invariant is that the host never reaches the + // model-visible message. code is set for essentially every real no-response + // error (ECONNREFUSED/ETIMEDOUT/ECONNRESET/ENOTFOUND/ECONNABORTED); the full + // native message still goes to stderr under DEBUG. + const reason = error.code ?? "network error"; message = `${methodPath} failed: ${reason} (no response from server)`; + if (process.env.DEBUG) { + console.error("Docmost request failed; no response:", error.message); + } } if (message.length > ERROR_MESSAGE_CAP) { diff --git a/packages/mcp/test/unit/error-diagnostics.test.mjs b/packages/mcp/test/unit/error-diagnostics.test.mjs index be01f294..ab1bdd56 100644 --- a/packages/mcp/test/unit/error-diagnostics.test.mjs +++ b/packages/mcp/test/unit/error-diagnostics.test.mjs @@ -180,18 +180,22 @@ test("no response uses error.code + path + 'no response from server'", () => { ); }); -test("no response falls back to error.message when code is absent", () => { +test("no response with no code falls back to a neutral reason (raw message not leaked — it may embed host:port)", () => { const err = makeAxiosError({ method: "post", url: "/comments/create", status: undefined, - message: "timeout of 30000ms exceeded", + // A raw axios network message like "connect ECONNREFUSED 127.0.0.1:3000" + // embeds the host; #437's invariant is that it never reaches the message. + message: "connect ECONNREFUSED 10.0.0.5:3000", }); formatDocmostAxiosError(err); assert.equal( err.message, - "POST /comments/create failed: timeout of 30000ms exceeded (no response from server)", + "POST /comments/create failed: network error (no response from server)", ); + // And the host must NOT appear anywhere in the model-visible message. + assert.ok(!err.message.includes("10.0.0.5")); }); test("path drops the host and the query string", () => {