Skip to content

Commit 179f9ea

Browse files
committed
feat: add MCP WebSearch page URL and enhance error handling in web search command
1 parent f9012a6 commit 179f9ea

6 files changed

Lines changed: 134 additions & 8 deletions

File tree

docs/agents/url-change.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ runtime/src/urls.ts ← 用户面控制台 URL(cn-only)
2020
BAILIAN_CONSOLE BAILIAN_CONSOLE_ROOT/cn-beijing
2121
API_KEY_PAGE BAILIAN_CONSOLE/?tab=app#/api-key
2222
TOKEN_PLAN_PAGE BAILIAN_CONSOLE_ROOT/cn-beijing?tab=plan#/efm/subscription/overview
23+
MCP_WEBSEARCH_PAGE BAILIAN_CONSOLE?tab=mcp#/mcp-market/detail/WebSearch
2324
2425
core/files/upload.ts ← 文件上传 endpoint(cn-pinned)
2526
UPLOAD_API ${REGIONS.cn}/api/v1/uploads
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { BailianError } from "bailian-cli-core";
2+
import { MCP_WEBSEARCH_PAGE } from "bailian-cli-runtime";
3+
4+
/** recoginze WebSearch MCP not activated / invalid caused 404 (CLI wrapped message from server)。 */
5+
export function isWebSearchMcpNotActivated(error: unknown): boolean {
6+
if (!(error instanceof BailianError)) return false;
7+
const message = error.message;
8+
if (!/MCP request failed:\s*404\b/i.test(message)) return false;
9+
return /|MCP|MCP_IS_INVALID/i.test(message);
10+
}
11+
12+
/** activate hint; URL from runtime/urls.ts。 */
13+
export function webSearchActivateHint(): string {
14+
return [
15+
"Activate (or re-activate) the WebSearch MCP in the Bailian MCP marketplace, then retry.",
16+
"If it was previously on SSE, cancel and activate again to upgrade to Streamable HTTP.",
17+
`Open: ${MCP_WEBSEARCH_PAGE}`,
18+
].join("\n");
19+
}
20+
21+
/**
22+
* keep original message / exitCode for not activated errors, add hint only; other errors throw as is.
23+
* do not replace server error message.
24+
*/
25+
export function rethrowWithWebSearchActivateHint(error: unknown): never {
26+
if (isWebSearchMcpNotActivated(error) && error instanceof BailianError && !error.hint) {
27+
throw new BailianError(error.message, error.exitCode, webSearchActivateHint(), {
28+
cause: error,
29+
api: error.api,
30+
rawResponse: error.rawResponse,
31+
});
32+
}
33+
throw error;
34+
}

packages/commands/src/commands/search/web.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import {
55
mcpWebSearchPath,
66
type FlagsDef,
77
} from "bailian-cli-core";
8-
import { createSpinner } from "bailian-cli-runtime";
9-
import { emitResult } from "bailian-cli-runtime";
8+
import { createSpinner, emitResult } from "bailian-cli-runtime";
9+
import { rethrowWithWebSearchActivateHint } from "./web-activate-hint.ts";
1010

1111
const WEB_SEARCH_FLAGS = {
1212
query: { type: "string", valueHint: "<text>", description: "Search query text" },
@@ -41,11 +41,14 @@ export default defineCommand({
4141
return;
4242
}
4343

44-
const client = ctx.client.mcp(mcpWebSearchPath());
45-
await client.initialize();
46-
const tools = await client.listTools();
47-
48-
emitResult({ tools }, format);
44+
try {
45+
const client = ctx.client.mcp(mcpWebSearchPath());
46+
await client.initialize();
47+
const tools = await client.listTools();
48+
emitResult({ tools }, format);
49+
} catch (error) {
50+
rethrowWithWebSearchActivateHint(error);
51+
}
4952
return;
5053
}
5154

@@ -123,7 +126,7 @@ export default defineCommand({
123126
}
124127
} catch (error) {
125128
spinner.stop("Failed.");
126-
throw error;
129+
rethrowWithWebSearchActivateHint(error);
127130
}
128131
},
129132
});
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { describe, expect, test } from "vite-plus/test";
2+
import { BailianError, ExitCode } from "bailian-cli-core";
3+
import { MCP_WEBSEARCH_PAGE } from "bailian-cli-runtime";
4+
import {
5+
isWebSearchMcpNotActivated,
6+
rethrowWithWebSearchActivateHint,
7+
webSearchActivateHint,
8+
} from "../src/commands/search/web-activate-hint.ts";
9+
10+
describe("web-activate-hint", () => {
11+
test("识别 404 + 未开通 / MCP不存在 / MCP_IS_INVALID", () => {
12+
expect(
13+
isWebSearchMcpNotActivated(
14+
new BailianError("MCP request failed: 404 Not Found - MCP不存在或未开通"),
15+
),
16+
).toBe(true);
17+
expect(
18+
isWebSearchMcpNotActivated(new BailianError("MCP request failed: 404 - MCP不存在或未开通")),
19+
).toBe(true);
20+
expect(
21+
isWebSearchMcpNotActivated(
22+
new BailianError("MCP request failed: 404 Not Found - MCP_IS_INVALID"),
23+
),
24+
).toBe(true);
25+
});
26+
27+
test("裸 404 或非 MCP 错误不加开通判定", () => {
28+
expect(isWebSearchMcpNotActivated(new BailianError("MCP request failed: 404 Not Found"))).toBe(
29+
false,
30+
);
31+
expect(
32+
isWebSearchMcpNotActivated(new BailianError("MCP request failed: 405 Method Not Allowed")),
33+
).toBe(false);
34+
expect(isWebSearchMcpNotActivated(new Error("MCP不存在或未开通"))).toBe(false);
35+
});
36+
37+
test("hint 含 MCP 广场 WebSearch 深链", () => {
38+
expect(webSearchActivateHint()).toContain(MCP_WEBSEARCH_PAGE);
39+
expect(webSearchActivateHint()).toMatch(/Activate|re-activate/i);
40+
});
41+
42+
test("rethrow 保留原 message,补 Hint", () => {
43+
const original = new BailianError(
44+
"MCP request failed: 404 Not Found - MCP不存在或未开通",
45+
ExitCode.GENERAL,
46+
);
47+
try {
48+
rethrowWithWebSearchActivateHint(original);
49+
expect.unreachable("should throw");
50+
} catch (error) {
51+
expect(error).toBeInstanceOf(BailianError);
52+
const wrapped = error as BailianError;
53+
expect(wrapped.message).toBe(original.message);
54+
expect(wrapped.exitCode).toBe(ExitCode.GENERAL);
55+
expect(wrapped.hint).toContain(MCP_WEBSEARCH_PAGE);
56+
expect(wrapped.cause).toBe(original);
57+
}
58+
});
59+
60+
test("已有 hint 或非未开通错误原样抛出", () => {
61+
const withHint = new BailianError(
62+
"MCP request failed: 404 Not Found - MCP不存在或未开通",
63+
ExitCode.GENERAL,
64+
"already hinted",
65+
);
66+
try {
67+
rethrowWithWebSearchActivateHint(withHint);
68+
expect.unreachable("should throw");
69+
} catch (error) {
70+
expect(error).toBe(withHint);
71+
}
72+
73+
const other = new BailianError("MCP request failed: 401 Unauthorized");
74+
try {
75+
rethrowWithWebSearchActivateHint(other);
76+
expect.unreachable("should throw");
77+
} catch (error) {
78+
expect(error).toBe(other);
79+
}
80+
});
81+
});

packages/runtime/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export {
3434
BAILIAN_CONSOLE,
3535
API_KEY_PAGE,
3636
TOKEN_PLAN_PAGE,
37+
MCP_WEBSEARCH_PAGE,
3738
VOICE_TTS_PAGE,
3839
} from "./urls.ts";
3940

packages/runtime/src/urls.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,11 @@ export const API_KEY_PAGE = `${BAILIAN_CONSOLE}/?tab=app#/api-key`;
1818
/** Direct deep link to the Token Plan subscription overview and API key entry. */
1919
export const TOKEN_PLAN_PAGE = `${BAILIAN_CONSOLE_ROOT}/cn-beijing?tab=plan#/efm/subscription/overview`;
2020

21+
/**
22+
* MCP marketplace detail for the built-in WebSearch server.
23+
* Users must activate (or re-activate for Streamable HTTP) before `search web` works.
24+
*/
25+
export const MCP_WEBSEARCH_PAGE = `${BAILIAN_CONSOLE}?tab=mcp#/mcp-market/detail/WebSearch`;
26+
2127
/** Voice TTS experience center — browse system and custom voices. */
2228
export const VOICE_TTS_PAGE = "https://help.aliyun.com/zh/model-studio/cosyvoice-voice-list";

0 commit comments

Comments
 (0)