UNPKG

mcp-use

Version:

Opinionated MCP Framework for TypeScript (@modelcontextprotocol/sdk compatible) - Build MCP Agents, Clients and Servers with support for ChatGPT Apps, Code Mode, OAuth, Notifications, Sampling, Observability and more.

543 lines (541 loc) 15.4 kB
import { ElicitationDeclinedError, ElicitationTimeoutError, ElicitationValidationError } from "./chunk-KUEVOU4M.js"; import { createReadableStreamFromGenerator, streamEventsToAISDK, streamEventsToAISDKWithTools } from "./chunk-LGDFGYRL.js"; import "./chunk-GXNAXUDI.js"; import { PROMPTS } from "./chunk-DTHLI4WJ.js"; import { AcquireActiveMCPServerTool, AddMCPServerFromConfigTool, ConnectMCPServerTool, ListMCPServersTool, MCPAgent, ObservabilityManager, ReleaseMCPServerConnectionTool, RemoteAgent, ServerManager, createLLMFromString, getSupportedProviders, isValidLLMString, parseLLMString } from "./chunk-FDT46IKB.js"; import "./chunk-JRGQRPTN.js"; import { BaseCodeExecutor, E2BCodeExecutor, MCPClient, StdioConnector, VMCodeExecutor, isVMAvailable, loadConfigFile } from "./chunk-T7EN7JPJ.js"; import { BaseAdapter } from "./chunk-MFSO5PUW.js"; import "./chunk-JQKKMUCT.js"; import { ErrorBoundary, Image, McpUseProvider, ThemeProvider, WidgetControls, useMcp, useWidget, useWidgetProps, useWidgetState, useWidgetTheme } from "./chunk-UNSBX4AO.js"; import "./chunk-MUC4N6UU.js"; import { BaseConnector, HttpConnector, MCPSession } from "./chunk-XSB7YRNQ.js"; import { Tel, Telemetry, VERSION, getPackageVersion, setTelemetrySource } from "./chunk-CN263ZGG.js"; import { Logger, logger } from "./chunk-FRUZDWXH.js"; import { BrowserOAuthClientProvider, onMcpAuthorization } from "./chunk-J75I2C26.js"; import { __name } from "./chunk-3GQAWCBQ.js"; // src/oauth-helper.ts var OAuthHelper = class { static { __name(this, "OAuthHelper"); } config; discovery; state; clientRegistration; constructor(config) { this.config = config; this.state = { isRequired: false, isAuthenticated: false, isAuthenticating: false, isCompletingOAuth: false, authError: null, oauthTokens: null }; } /** * Get current OAuth state */ getState() { return { ...this.state }; } /** * Check if a server requires authentication by pinging the URL */ async checkAuthRequired(serverUrl) { console.log("\u{1F50D} [OAuthHelper] Checking auth requirement for:", serverUrl); try { const response = await fetch(serverUrl, { method: "GET", headers: { Accept: "text/event-stream", "Cache-Control": "no-cache" }, redirect: "manual", signal: AbortSignal.timeout(1e4) // 10 second timeout }); console.log("\u{1F50D} [OAuthHelper] Auth check response:", { status: response.status, statusText: response.statusText, url: serverUrl }); if (response.status === 401 || response.status === 403 || response.status === 400) { console.log("\u{1F510} [OAuthHelper] Authentication required for:", serverUrl); return true; } console.log( "\u2705 [OAuthHelper] No authentication required for:", serverUrl ); return false; } catch (error) { const err = error; console.warn( "\u26A0\uFE0F [OAuthHelper] Could not check auth requirement for:", serverUrl, error ); if (err.name === "TypeError" && (err.message?.includes("CORS") || err.message?.includes("Failed to fetch"))) { console.log( "\u{1F50D} [OAuthHelper] CORS blocked direct check, using heuristics for:", serverUrl ); return this.checkAuthByHeuristics(serverUrl); } if (err.name === "AbortError") { console.log( "\u23F0 [OAuthHelper] Request timeout, assuming no auth required for:", serverUrl ); return false; } return this.checkAuthByHeuristics(serverUrl); } } /** * Fallback heuristics for determining auth requirements when direct checking fails */ checkAuthByHeuristics(serverUrl) { console.log( "\u{1F50D} [OAuthHelper] Using heuristics to determine auth for:", serverUrl ); const authRequiredPatterns = [ /api\.githubcopilot\.com/i, // GitHub Copilot /api\.github\.com/i, // GitHub API /[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.googleapis\.com/i, // Google APIs (DNS-safe, max 63 chars per label) /api\.openai\.com/i, // OpenAI /api\.anthropic\.com/i, // Anthropic /[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.atlassian\.net/i, // Atlassian (Jira, Confluence) /[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.slack\.com/i, // Slack /api\.notion\.com/i, // Notion /api\.linear\.app/i // Linear ]; const noAuthPatterns = [ /localhost/i, // Local development /127\.0\.0\.1/, // Local development /\.local/i, // Local development /mcp\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.com/i // Generic MCP server pattern (often public) ]; for (const pattern of noAuthPatterns) { if (pattern.test(serverUrl)) { console.log( "\u2705 [OAuthHelper] Heuristic: No auth required (matches no-auth pattern):", serverUrl ); return false; } } for (const pattern of authRequiredPatterns) { if (pattern.test(serverUrl)) { console.log( "\u{1F510} [OAuthHelper] Heuristic: Auth required (matches auth pattern):", serverUrl ); return true; } } console.log( "\u2753 [OAuthHelper] Heuristic: Unknown pattern, assuming no auth required:", serverUrl ); return false; } /** * Discover OAuth configuration from a server */ async discoverOAuthConfig(serverUrl) { try { const discoveryUrl = `${serverUrl}/.well-known/oauth-authorization-server`; console.log( "\u{1F50D} [OAuthHelper] Attempting OAuth discovery at:", discoveryUrl ); const response = await fetch(discoveryUrl); if (!response.ok) { console.error("\u274C [OAuthHelper] OAuth discovery failed:", { status: response.status, statusText: response.statusText, url: discoveryUrl }); throw new Error( `OAuth discovery failed: ${response.status} ${response.statusText}` ); } this.discovery = await response.json(); console.log("\u2705 [OAuthHelper] OAuth discovery successful:", { authorization_endpoint: this.discovery?.authorization_endpoint, token_endpoint: this.discovery?.token_endpoint, registration_endpoint: this.discovery?.registration_endpoint }); return this.discovery; } catch (error) { console.error("\u274C [OAuthHelper] OAuth discovery error:", error); throw new Error(`Failed to discover OAuth configuration: ${error}`); } } /** * Register a new OAuth client dynamically */ async registerClient(_serverUrl) { if (!this.discovery) { throw new Error( "OAuth discovery not performed. Call discoverOAuthConfig first." ); } if (!this.discovery.registration_endpoint) { throw new Error("Server does not support dynamic client registration"); } try { const registrationData = { client_name: this.config.clientName || "MCP Use Example", redirect_uris: [this.config.redirectUri], grant_types: ["authorization_code"], response_types: ["code"], token_endpoint_auth_method: "none", // Use public client (no secret) scope: this.config.scope || "read write" }; console.log("\u{1F510} [OAuthHelper] Registering OAuth client dynamically:", { registration_endpoint: this.discovery.registration_endpoint, client_name: registrationData.client_name, redirect_uri: this.config.redirectUri }); const response = await fetch(this.discovery.registration_endpoint, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(registrationData) }); if (!response.ok) { const errorText = await response.text(); throw new Error( `Client registration failed: ${response.status} ${response.statusText} - ${errorText}` ); } this.clientRegistration = await response.json(); console.log("\u2705 [OAuthHelper] Client registered successfully:", { client_id: this.clientRegistration?.client_id, client_secret: this.clientRegistration?.client_secret ? "***" : "none" }); return this.clientRegistration; } catch (error) { console.error("\u274C [OAuthHelper] Client registration failed:", error); throw new Error(`Failed to register OAuth client: ${error}`); } } /** * Generate authorization URL for OAuth flow */ generateAuthUrl(serverUrl, additionalParams) { if (!this.discovery) { throw new Error( "OAuth discovery not performed. Call discoverOAuthConfig first." ); } if (!this.clientRegistration) { throw new Error("Client not registered. Call registerClient first."); } const params = new URLSearchParams({ client_id: this.clientRegistration.client_id, redirect_uri: this.config.redirectUri, response_type: "code", scope: this.config.scope || "read", state: this.config.state || this.generateState(), ...additionalParams }); return `${this.discovery.authorization_endpoint}?${params.toString()}`; } /** * Exchange authorization code for access token */ async exchangeCodeForToken(serverUrl, code, codeVerifier) { if (!this.discovery) { throw new Error( "OAuth discovery not performed. Call discoverOAuthConfig first." ); } if (!this.clientRegistration) { throw new Error("Client not registered. Call registerClient first."); } const body = new URLSearchParams({ grant_type: "authorization_code", client_id: this.clientRegistration.client_id, code, redirect_uri: this.config.redirectUri }); if (codeVerifier) { body.append("code_verifier", codeVerifier); } const response = await fetch(this.discovery.token_endpoint, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: body.toString() }); if (!response.ok) { const error = await response.text(); throw new Error( `Token exchange failed: ${response.status} ${response.statusText} - ${error}` ); } return await response.json(); } /** * Handle OAuth callback and extract authorization code */ handleCallback() { const urlParams = new URLSearchParams(window.location.search); const code = urlParams.get("code"); const state = urlParams.get("state"); if (!code || !state) { return null; } const url = new URL(window.location.href); url.searchParams.delete("code"); url.searchParams.delete("state"); window.history.replaceState({}, "", url.toString()); return { code, state }; } /** * Start OAuth flow by opening popup window (similar to your implementation) */ async startOAuthFlow(serverUrl) { this.setState({ isAuthenticating: true, authError: null }); try { await this.discoverOAuthConfig(serverUrl); await this.registerClient(serverUrl); const authUrl = this.generateAuthUrl(serverUrl); const authWindow = window.open( authUrl, "mcp-oauth", "width=500,height=600,scrollbars=yes,resizable=yes,status=yes,location=yes" ); if (!authWindow) { throw new Error( "Failed to open authentication window. Please allow popups for this site and try again." ); } console.log("\u2705 [OAuthHelper] OAuth popup opened successfully"); } catch (error) { console.error("\u274C [OAuthHelper] Failed to start OAuth flow:", error); this.setState({ isAuthenticating: false, authError: error instanceof Error ? error.message : "Failed to start authentication" }); throw error; } } /** * Complete OAuth flow by exchanging code for token */ async completeOAuthFlow(serverUrl, code) { this.setState({ isCompletingOAuth: true, authError: null }); try { const tokenResponse = await this.exchangeCodeForToken(serverUrl, code); this.setState({ isAuthenticating: false, isAuthenticated: true, isCompletingOAuth: false, authError: null, oauthTokens: tokenResponse }); console.log("\u2705 [OAuthHelper] OAuth flow completed successfully"); return tokenResponse; } catch (error) { console.error("\u274C [OAuthHelper] Failed to complete OAuth flow:", error); this.setState({ isAuthenticating: false, isCompletingOAuth: false, authError: error instanceof Error ? error.message : "Failed to complete authentication" }); throw error; } } /** * Reset authentication state */ resetAuth() { this.setState({ isRequired: false, isAuthenticated: false, isAuthenticating: false, isCompletingOAuth: false, authError: null, oauthTokens: null }); } /** * Set OAuth state (internal method) */ setState(newState) { this.state = { ...this.state, ...newState }; } /** * Generate a random state parameter for CSRF protection */ generateState() { return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); } }; var LINEAR_OAUTH_CONFIG = { // No clientId needed - will use dynamic client registration redirectUri: typeof window !== "undefined" ? window.location.origin + window.location.pathname : "http://localhost:5173", scope: "read write", clientName: "MCP Use Example" }; function createOAuthMCPConfig(serverUrl, accessToken) { return { mcpServers: { linear: { url: serverUrl, authToken: accessToken, transport: "sse" } } }; } __name(createOAuthMCPConfig, "createOAuthMCPConfig"); export { AcquireActiveMCPServerTool, AddMCPServerFromConfigTool, BaseAdapter, BaseCodeExecutor, BaseConnector, BrowserOAuthClientProvider, Tel as BrowserTelemetry, ConnectMCPServerTool, E2BCodeExecutor, ElicitationDeclinedError, ElicitationTimeoutError, ElicitationValidationError, ErrorBoundary, HttpConnector, Image, LINEAR_OAUTH_CONFIG, ListMCPServersTool, Logger, MCPAgent, MCPClient, MCPSession, McpUseProvider, OAuthHelper, ObservabilityManager, PROMPTS, ReleaseMCPServerConnectionTool, RemoteAgent, ServerManager, StdioConnector, Tel, Telemetry, ThemeProvider, VERSION, VMCodeExecutor, WidgetControls, createLLMFromString, createOAuthMCPConfig, createReadableStreamFromGenerator, getPackageVersion, getSupportedProviders, isVMAvailable, isValidLLMString, loadConfigFile, logger, onMcpAuthorization, parseLLMString, setTelemetrySource as setBrowserTelemetrySource, setTelemetrySource, streamEventsToAISDK, streamEventsToAISDKWithTools, useMcp, useWidget, useWidgetProps, useWidgetState, useWidgetTheme };