feat: add MCP WebSearch page URL and enhance error handling in web search command

This commit is contained in:
clh02467605
2026-07-24 10:56:48 +08:00
parent fac2b2d18b
commit dac254af86
6 changed files with 134 additions and 8 deletions
+1
View File
@@ -20,6 +20,7 @@ runtime/src/urls.ts ← 用户面控制台 URL(cn-only)
BAILIAN_CONSOLE BAILIAN_CONSOLE_ROOT/cn-beijing
API_KEY_PAGE BAILIAN_CONSOLE/?tab=app#/api-key
TOKEN_PLAN_PAGE BAILIAN_CONSOLE_ROOT/cn-beijing?tab=plan#/efm/subscription/overview
MCP_WEBSEARCH_PAGE BAILIAN_CONSOLE?tab=mcp#/mcp-market/detail/WebSearch
core/files/upload.ts ← 文件上传 endpoint(cn-pinned)
UPLOAD_API ${REGIONS.cn}/api/v1/uploads
@@ -0,0 +1,34 @@
import { BailianError } from "bailian-cli-core";
import { MCP_WEBSEARCH_PAGE } from "bailian-cli-runtime";
/** recoginze WebSearch MCP not activated / invalid caused 404 (CLI wrapped message from server)。 */
export function isWebSearchMcpNotActivated(error: unknown): boolean {
if (!(error instanceof BailianError)) return false;
const message = error.message;
if (!/MCP request failed:\s*404\b/i.test(message)) return false;
return /未开通|MCP不存在|MCP_IS_INVALID/i.test(message);
}
/** activate hint; URL from runtime/urls.ts。 */
export function webSearchActivateHint(): string {
return [
"Activate (or re-activate) the WebSearch MCP in the Bailian MCP marketplace, then retry.",
"If it was previously on SSE, cancel and activate again to upgrade to Streamable HTTP.",
`Open: ${MCP_WEBSEARCH_PAGE}`,
].join("\n");
}
/**
* keep original message / exitCode for not activated errors, add hint only; other errors throw as is.
* do not replace server error message.
*/
export function rethrowWithWebSearchActivateHint(error: unknown): never {
if (isWebSearchMcpNotActivated(error) && error instanceof BailianError && !error.hint) {
throw new BailianError(error.message, error.exitCode, webSearchActivateHint(), {
cause: error,
api: error.api,
rawResponse: error.rawResponse,
});
}
throw error;
}
+11 -8
View File
@@ -5,8 +5,8 @@ import {
mcpWebSearchPath,
type FlagsDef,
} from "bailian-cli-core";
import { createSpinner } from "bailian-cli-runtime";
import { emitResult } from "bailian-cli-runtime";
import { createSpinner, emitResult } from "bailian-cli-runtime";
import { rethrowWithWebSearchActivateHint } from "./web-activate-hint.ts";
const WEB_SEARCH_FLAGS = {
query: { type: "string", valueHint: "<text>", description: "Search query text" },
@@ -41,11 +41,14 @@ export default defineCommand({
return;
}
const client = ctx.client.mcp(mcpWebSearchPath());
await client.initialize();
const tools = await client.listTools();
emitResult({ tools }, format);
try {
const client = ctx.client.mcp(mcpWebSearchPath());
await client.initialize();
const tools = await client.listTools();
emitResult({ tools }, format);
} catch (error) {
rethrowWithWebSearchActivateHint(error);
}
return;
}
@@ -123,7 +126,7 @@ export default defineCommand({
}
} catch (error) {
spinner.stop("Failed.");
throw error;
rethrowWithWebSearchActivateHint(error);
}
},
});
@@ -0,0 +1,81 @@
import { describe, expect, test } from "vite-plus/test";
import { BailianError, ExitCode } from "bailian-cli-core";
import { MCP_WEBSEARCH_PAGE } from "bailian-cli-runtime";
import {
isWebSearchMcpNotActivated,
rethrowWithWebSearchActivateHint,
webSearchActivateHint,
} from "../src/commands/search/web-activate-hint.ts";
describe("web-activate-hint", () => {
test("识别 404 + 未开通 / MCP不存在 / MCP_IS_INVALID", () => {
expect(
isWebSearchMcpNotActivated(
new BailianError("MCP request failed: 404 Not Found - MCP不存在或未开通"),
),
).toBe(true);
expect(
isWebSearchMcpNotActivated(new BailianError("MCP request failed: 404 - MCP不存在或未开通")),
).toBe(true);
expect(
isWebSearchMcpNotActivated(
new BailianError("MCP request failed: 404 Not Found - MCP_IS_INVALID"),
),
).toBe(true);
});
test("裸 404 或非 MCP 错误不加开通判定", () => {
expect(isWebSearchMcpNotActivated(new BailianError("MCP request failed: 404 Not Found"))).toBe(
false,
);
expect(
isWebSearchMcpNotActivated(new BailianError("MCP request failed: 405 Method Not Allowed")),
).toBe(false);
expect(isWebSearchMcpNotActivated(new Error("MCP不存在或未开通"))).toBe(false);
});
test("hint 含 MCP 广场 WebSearch 深链", () => {
expect(webSearchActivateHint()).toContain(MCP_WEBSEARCH_PAGE);
expect(webSearchActivateHint()).toMatch(/Activate|re-activate/i);
});
test("rethrow 保留原 message,补 Hint", () => {
const original = new BailianError(
"MCP request failed: 404 Not Found - MCP不存在或未开通",
ExitCode.GENERAL,
);
try {
rethrowWithWebSearchActivateHint(original);
expect.unreachable("should throw");
} catch (error) {
expect(error).toBeInstanceOf(BailianError);
const wrapped = error as BailianError;
expect(wrapped.message).toBe(original.message);
expect(wrapped.exitCode).toBe(ExitCode.GENERAL);
expect(wrapped.hint).toContain(MCP_WEBSEARCH_PAGE);
expect(wrapped.cause).toBe(original);
}
});
test("已有 hint 或非未开通错误原样抛出", () => {
const withHint = new BailianError(
"MCP request failed: 404 Not Found - MCP不存在或未开通",
ExitCode.GENERAL,
"already hinted",
);
try {
rethrowWithWebSearchActivateHint(withHint);
expect.unreachable("should throw");
} catch (error) {
expect(error).toBe(withHint);
}
const other = new BailianError("MCP request failed: 401 Unauthorized");
try {
rethrowWithWebSearchActivateHint(other);
expect.unreachable("should throw");
} catch (error) {
expect(error).toBe(other);
}
});
});
+1
View File
@@ -34,6 +34,7 @@ export {
BAILIAN_CONSOLE,
API_KEY_PAGE,
TOKEN_PLAN_PAGE,
MCP_WEBSEARCH_PAGE,
VOICE_TTS_PAGE,
} from "./urls.ts";
+6
View File
@@ -18,5 +18,11 @@ export const API_KEY_PAGE = `${BAILIAN_CONSOLE}/?tab=app#/api-key`;
/** Direct deep link to the Token Plan subscription overview and API key entry. */
export const TOKEN_PLAN_PAGE = `${BAILIAN_CONSOLE_ROOT}/cn-beijing?tab=plan#/efm/subscription/overview`;
/**
* MCP marketplace detail for the built-in WebSearch server.
* Users must activate (or re-activate for Streamable HTTP) before `search web` works.
*/
export const MCP_WEBSEARCH_PAGE = `${BAILIAN_CONSOLE}?tab=mcp#/mcp-market/detail/WebSearch`;
/** Voice TTS experience center — browse system and custom voices. */
export const VOICE_TTS_PAGE = "https://help.aliyun.com/zh/model-studio/cosyvoice-voice-list";