mirror of
https://github.com/binaricat/Netcatty.git
synced 2026-11-09 15:52:14 +00:00
* feat: add optional Streamable HTTP for External MCP Serve the existing catalog tools and approval path on a loopback HTTP endpoint so clients that cannot spawn the stdio launcher, such as Codex in WSL, can connect with a bearer token. The listener stays off until the user enables it. Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com> * fix: close HTTP sockets before stop and export the Codex bearer Destroy tracked connections before awaiting server.close so disable does not hang on a half-open request. Prefix the Codex snippet with export so the child process receives the token. Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com> * fix: advertise a real client host and return 413 intact Wildcard binds no longer copy 127.0.0.1 into client configs; the URL stays empty until a reachable client address is set. Oversized POSTs receive a JSON-RPC 413 before the socket is closed. Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com> * fix: recheck the bearer token after the HTTP body arrives A half-open POST already passed auth before readBody. Recheck the live token before dispatch so a rotation during that wait cannot still run a tool. Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com> * fix: expire idle Streamable HTTP sessions JSON responses do not keep a socket open, so a client that never sends DELETE used to pin an MCP session until the listener stopped. Sweep sessions that have been idle for 15 minutes, and skip any session with a request still in flight. Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com> * fix: return 404 when an HTTP session id is unknown GET and DELETE with a nonempty session id that is no longer active now return 404, matching POST and the Streamable HTTP session contract. A missing session id stays 400. Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com> * fix: accept bracketed IPv6 origins Node reports URL.hostname as [::1], so an otherwise valid loopback or wildcard Origin was rejected. Compare the address with the brackets removed. Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com> * fix: release rejected HTTP handshakes that never open a session A 406 or 415 from the SDK returns normally before onsessioninitialized. Detach the approval client and close the MCP server when that happens so rejected initializes do not accumulate. Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
59 lines
2.3 KiB
TypeScript
59 lines
2.3 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import en from "./en.ts";
|
|
import ru from "./ru.ts";
|
|
import es from "./es.ts";
|
|
import zhCN from "./zh-CN.ts";
|
|
import zhTW from "./zh-TW.ts";
|
|
|
|
const KEYS = [
|
|
"ai.externalMcp.http.title",
|
|
"ai.externalMcp.http.description",
|
|
"ai.externalMcp.http.enable",
|
|
"ai.externalMcp.http.port",
|
|
"ai.externalMcp.http.portHint",
|
|
"ai.externalMcp.http.allowCustomBind",
|
|
"ai.externalMcp.http.allowCustomBind.description",
|
|
"ai.externalMcp.http.listenAddress",
|
|
"ai.externalMcp.http.listenAddress.placeholder",
|
|
"ai.externalMcp.http.clientHost",
|
|
"ai.externalMcp.http.clientHost.description",
|
|
"ai.externalMcp.http.clientHost.placeholder",
|
|
"ai.externalMcp.http.clientHostRequired",
|
|
"ai.externalMcp.http.invalidClientHost",
|
|
"ai.externalMcp.http.warning",
|
|
"ai.externalMcp.http.url",
|
|
"ai.externalMcp.http.token",
|
|
"ai.externalMcp.http.regenerate",
|
|
"ai.externalMcp.http.regenerateHint",
|
|
"ai.externalMcp.http.wslHint",
|
|
"ai.externalMcp.http.codexSnippet",
|
|
"ai.externalMcp.http.claudeCommand",
|
|
"ai.externalMcp.http.genericSnippet",
|
|
"ai.externalMcp.http.envLine",
|
|
"ai.externalMcp.http.notListening",
|
|
"ai.externalMcp.http.listening",
|
|
"ai.externalMcp.http.portInUse",
|
|
"ai.externalMcp.http.requiresExternalMcp",
|
|
"ai.externalMcp.http.invalidPort",
|
|
"ai.externalMcp.http.invalidAddress",
|
|
"ai.externalMcp.http.listenFailed",
|
|
"ai.externalMcp.http.legacySseNote",
|
|
] as const;
|
|
|
|
test("External MCP HTTP strings exist in every locale", () => {
|
|
for (const [name, messages] of Object.entries({ en, es, "zh-CN": zhCN, "zh-TW": zhTW, ru })) {
|
|
const missing = KEYS.filter((key) => !messages[key]);
|
|
assert.deepEqual(missing, [], `${name} is missing HTTP MCP labels`);
|
|
assert.match(messages["ai.externalMcp.http.portInUse"], /\{port\}/);
|
|
}
|
|
assert.match(zhTW["ai.externalMcp.http.port"], /連接埠/);
|
|
assert.match(zhTW["ai.externalMcp.http.listenAddress"], /位址/);
|
|
assert.match(zhTW["ai.externalMcp.http.clientHost"], /位址/);
|
|
assert.doesNotMatch(zhTW["ai.externalMcp.http.clientHost"], /地址/);
|
|
assert.doesNotMatch(zhTW["ai.externalMcp.http.port"], /端口/);
|
|
assert.doesNotMatch(zhTW["ai.externalMcp.http.listenAddress"], /地址/);
|
|
assert.match(zhTW["ai.externalMcp.http.wslHint"], /鏡像網路/);
|
|
});
|