feat(mcp): add browser OAuth for remote servers (#5316)

This commit is contained in:
chengyongru
2026-08-10 23:44:37 +08:00
committed by GitHub
parent b3b0517611
commit 8e77f3f8a4
33 changed files with 4099 additions and 198 deletions
+64 -1
View File
@@ -10,6 +10,7 @@ import type {
FilePreviewPayload,
ImageGenerationSettingsUpdate,
McpPresetsPayload,
McpOAuthFlowPayload,
MarketplaceProvider,
NanobotFeaturesPayload,
ModelConfigurationCreate,
@@ -97,7 +98,19 @@ async function request<T>(
);
if (!res.ok) {
const text = typeof res.text === "function" ? (await res.text()).trim() : "";
throw new ApiError(res.status, text || `HTTP ${res.status}`);
let message = text;
if (text.startsWith("{")) {
try {
const payload: unknown = JSON.parse(text);
if (payload && typeof payload === "object") {
const error = (payload as { error?: unknown }).error;
if (typeof error === "string" && error.trim()) message = error.trim();
}
} catch {
// Preserve non-JSON error bodies exactly as returned by the gateway.
}
}
throw new ApiError(res.status, message || `HTTP ${res.status}`);
}
const contentType = res.headers?.get?.("content-type") ?? "";
if (contentType && !contentType.toLowerCase().includes("application/json")) {
@@ -686,6 +699,56 @@ export async function fetchMcpPresets(
);
}
export async function startMcpOAuth(
transport: WebUIMutationTransport,
name: string,
reset: boolean = false,
): Promise<McpOAuthFlowPayload> {
return mutation<McpOAuthFlowPayload>(
transport,
"settings.mcp.oauth_start",
{ name, ...(reset ? { reset: true } : {}) },
30_000,
);
}
export async function fetchMcpOAuthStatus(
token: string,
flowId: string,
base: string = "",
): Promise<McpOAuthFlowPayload> {
const query = new URLSearchParams({ flow_id: flowId });
return request<McpOAuthFlowPayload>(
`${base}/api/settings/mcp-oauth/status?${query}`,
token,
undefined,
API_READ_TIMEOUT_MS,
);
}
export async function completeMcpOAuth(
transport: WebUIMutationTransport,
flowId: string,
callbackUrl: string,
): Promise<McpOAuthFlowPayload> {
return mutation<McpOAuthFlowPayload>(
transport,
"settings.mcp.oauth_complete",
{ flow_id: flowId, callback_url: callbackUrl },
);
}
export async function cancelMcpOAuth(
transport: WebUIMutationTransport,
flowId: string,
): Promise<McpOAuthFlowPayload> {
return mutation<McpOAuthFlowPayload>(
transport,
"settings.mcp.oauth_cancel",
{ flow_id: flowId },
);
}
export async function fetchProviderModels(
token: string,
provider: string,
+25
View File
@@ -956,6 +956,7 @@ export interface McpPresetInfo {
description: string;
docs_url: string;
transport: "stdio" | "streamableHttp" | "sse" | "oauth" | string;
auth?: "oauth" | null;
requires: string;
note: string;
install_supported: boolean;
@@ -976,6 +977,30 @@ export interface McpPresetInfo {
manifest?: AppManifest;
}
export type McpOAuthFlowStatus =
| "starting"
| "authorization_required"
| "connecting"
| "authorized"
| "connected"
| "failed"
| "cancelled";
export interface McpOAuthFlowPayload {
flow_id: string;
name: string;
status: McpOAuthFlowStatus;
expires_in: number;
authorization_url?: string;
completion_input?: "callback_url";
error?: string;
hot_reload?: {
ok: boolean;
message?: string;
requires_restart?: boolean;
};
}
export interface McpPresetsPayload {
presets: McpPresetInfo[];
installed_count: number;