GET /api/mcp/config returns 403 for non-admin users, but the previous
client returned the error body as MCPConfig, causing MCPServerList to
crash with 'Cannot convert undefined or null to object' on
Object.entries(config.mcp_servers).
- api.ts: introduce MCPConfigRequestError; loadMCPConfig and
updateMCPConfig now throw it (carrying status + isAdminRequired)
instead of letting non-2xx bodies leak through as parsed config
- tool-settings-page.tsx: render a friendly 'admin privileges required'
empty state when the React Query error is an admin-required
MCPConfigRequestError; keep MCPServerList resilient with
Object.entries(servers ?? {}) and an empty-state for no servers
- i18n: add settings.tools.adminRequired and settings.tools.empty in
en-US, zh-CN and the Translations type
- tests: cover 403 / 5xx / instanceof / detail-fallback for both
loadMCPConfig and updateMCPConfig in tests/unit/core/mcp/api.test.ts
Refs: #3527
55 lines
1.4 KiB
TypeScript
55 lines
1.4 KiB
TypeScript
import { fetch } from "@/core/api/fetcher";
|
|
import { getBackendBaseURL } from "@/core/config";
|
|
|
|
import type { MCPConfig } from "./types";
|
|
|
|
export class MCPConfigRequestError extends Error {
|
|
readonly status: number;
|
|
constructor(status: number, message: string) {
|
|
super(message);
|
|
this.name = "MCPConfigRequestError";
|
|
this.status = status;
|
|
}
|
|
get isAdminRequired(): boolean {
|
|
return this.status === 403;
|
|
}
|
|
}
|
|
|
|
async function readErrorDetail(
|
|
response: Response,
|
|
fallback: string,
|
|
): Promise<string> {
|
|
const error = (await response.json().catch(() => ({}))) as {
|
|
detail?: unknown;
|
|
};
|
|
return typeof error.detail === "string" ? error.detail : fallback;
|
|
}
|
|
|
|
export async function loadMCPConfig() {
|
|
const response = await fetch(`${getBackendBaseURL()}/api/mcp/config`);
|
|
if (!response.ok) {
|
|
throw new MCPConfigRequestError(
|
|
response.status,
|
|
await readErrorDetail(response, "Failed to load MCP configuration"),
|
|
);
|
|
}
|
|
return response.json() as Promise<MCPConfig>;
|
|
}
|
|
|
|
export async function updateMCPConfig(config: MCPConfig) {
|
|
const response = await fetch(`${getBackendBaseURL()}/api/mcp/config`, {
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(config),
|
|
});
|
|
if (!response.ok) {
|
|
throw new MCPConfigRequestError(
|
|
response.status,
|
|
await readErrorDetail(response, "Failed to update MCP configuration"),
|
|
);
|
|
}
|
|
return response.json();
|
|
}
|