@sleep2agi/commhub-server 0.9.0-preview.47 → 0.9.0-preview.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/serve-error.test.ts +38 -0
- package/src/serve-error.ts +27 -0
- package/src/server.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sleep2agi/commhub-server",
|
|
3
|
-
"version": "0.9.0-preview.
|
|
3
|
+
"version": "0.9.0-preview.48",
|
|
4
4
|
"description": "CommHub Server — AI Agent communication hub with MCP protocol, multi-network isolation, user auth, and MCP tools (17 collaboration-core + node/provider ops tools; authoritative list: docs-site/docs/api/mcp-tools.md).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { buildServeErrorResponse, serveErrorBody } from "./serve-error.js";
|
|
3
|
+
|
|
4
|
+
// 注:「没有 error 回调时 Bun 回默认非 JSON 500」这一半没法在 bun test 里当用例:
|
|
5
|
+
// 运行器会把 fetch 里抛出的异常当成未处理错误直接判这条用例失败(实测),证据在 #695 末评。
|
|
6
|
+
describe("#695 fetch exceptions become JSON with the exception's own message", () => {
|
|
7
|
+
test("body carries THE message, both JSON-RPC-shaped and REST-shaped", () => {
|
|
8
|
+
const b = serveErrorBody(new Error("boom-695: sqlite is locked"));
|
|
9
|
+
expect(b.error.code).toBe(-32603);
|
|
10
|
+
expect(b.error.message).toBe("boom-695: sqlite is locked");
|
|
11
|
+
expect(b.message).toBe("boom-695: sqlite is locked");
|
|
12
|
+
expect(b.ok).toBe(false);
|
|
13
|
+
// 不是固定 JSON:换一个异常,message 跟着变
|
|
14
|
+
expect(serveErrorBody(new Error("other")).error.message).toBe("other");
|
|
15
|
+
expect(serveErrorBody("plain string").error.message).toBe("plain string");
|
|
16
|
+
expect(serveErrorBody(new Error("")).error.message).toBe("internal error");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("real Bun.serve: a throwing fetch answers 500 + application/json + the message (was a non-JSON default page)", async () => {
|
|
20
|
+
const server = Bun.serve({
|
|
21
|
+
port: 0,
|
|
22
|
+
fetch() { throw new Error("boom-695-live"); },
|
|
23
|
+
error: (err) => buildServeErrorResponse(err),
|
|
24
|
+
});
|
|
25
|
+
try {
|
|
26
|
+
const res = await fetch(`http://127.0.0.1:${server.port}/mcp`, { method: "POST", body: "{}" });
|
|
27
|
+
expect(res.status).toBe(500);
|
|
28
|
+
expect(res.headers.get("content-type") ?? "").toContain("application/json");
|
|
29
|
+
const json = await res.json() as { error: { code: number; message: string }; ok: boolean };
|
|
30
|
+
expect(json.error.code).toBe(-32603);
|
|
31
|
+
expect(json.error.message).toBe("boom-695-live");
|
|
32
|
+
expect(json.ok).toBe(false);
|
|
33
|
+
} finally {
|
|
34
|
+
server.stop(true);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// #695 —— Bun.serve 没注册 `error` 回调时,fetch 处理器抛出的异常会变成 Bun 的默认 500,
|
|
2
|
+
// body 不是 JSON;MCP 客户端于是只能报一个泛化的 -32603,拿不到任何一句人话。
|
|
3
|
+
// 这里把异常变成一个带 message 的 JSON 响应:/mcp 的客户端按 JSON-RPC 形状读 error.message,
|
|
4
|
+
// REST 客户端按本仓其它路由的 { ok:false, error, message } 形状读。
|
|
5
|
+
// 🔴 判据(#695 末评):同一个异常,响应里要带**那个异常的 message**,不是一个固定 JSON。
|
|
6
|
+
|
|
7
|
+
export interface ServeErrorBody {
|
|
8
|
+
jsonrpc: "2.0";
|
|
9
|
+
id: null;
|
|
10
|
+
error: { code: -32603; message: string };
|
|
11
|
+
ok: false;
|
|
12
|
+
message: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function serveErrorBody(err: unknown): ServeErrorBody {
|
|
16
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
17
|
+
const message = (raw && raw.trim()) ? raw.trim().slice(0, 500) : "internal error";
|
|
18
|
+
return { jsonrpc: "2.0", id: null, error: { code: -32603, message }, ok: false, message };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function buildServeErrorResponse(err: unknown): Response {
|
|
22
|
+
const body = serveErrorBody(err);
|
|
23
|
+
return new Response(JSON.stringify(body), {
|
|
24
|
+
status: 500,
|
|
25
|
+
headers: { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" },
|
|
26
|
+
});
|
|
27
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildServeErrorResponse } from "./serve-error.js";
|
|
1
2
|
import { redactMessageRow } from "./redact-tokens.js";
|
|
2
3
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
4
|
import { parseDbTimestampMs } from "./db-timestamp.js";
|
|
@@ -786,6 +787,9 @@ return Bun.serve({
|
|
|
786
787
|
// route. Default Bun ceiling is 128 MiB which is too permissive for
|
|
787
788
|
// a hub now exposed to the public internet.
|
|
788
789
|
maxRequestBodySize: MAX_REQUEST_CONTENT_LENGTH,
|
|
790
|
+
// #695 —— fetch 处理器抛出时不要让 Bun 回默认的非 JSON 500;带上异常 message,
|
|
791
|
+
// /mcp 客户端按 JSON-RPC 读 error.message,REST 客户端按 { ok:false, message } 读。
|
|
792
|
+
error: (err) => buildServeErrorResponse(err),
|
|
789
793
|
|
|
790
794
|
async fetch(req, server) {
|
|
791
795
|
const url = new URL(req.url, `http://localhost:${PORT}`);
|