agents 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-DnmmRjyv.d.ts +231 -0
- package/dist/{client-connection-CGMuV62J.js → client-connection-D3Wcd6Q6.js} +154 -23
- package/dist/client-connection-D3Wcd6Q6.js.map +1 -0
- package/dist/{client-storage-D633wI1S.d.ts → client-storage-tusTuoSF.d.ts} +262 -13
- package/dist/experimental/forever.d.ts +7 -18
- package/dist/experimental/forever.js +4 -38
- package/dist/experimental/forever.js.map +1 -1
- package/dist/experimental/memory/session/index.d.ts +251 -0
- package/dist/experimental/memory/session/index.js +385 -0
- package/dist/experimental/memory/session/index.js.map +1 -0
- package/dist/index.d.ts +84 -22
- package/dist/index.js +324 -250
- package/dist/index.js.map +1 -1
- package/dist/mcp/client.d.ts +51 -7
- package/dist/mcp/client.js +157 -32
- package/dist/mcp/client.js.map +1 -1
- package/dist/mcp/do-oauth-client-provider.js +1 -1
- package/dist/mcp/do-oauth-client-provider.js.map +1 -1
- package/dist/mcp/index.d.ts +27 -165
- package/dist/mcp/index.js +50 -10
- package/dist/mcp/index.js.map +1 -1
- package/dist/observability/index.d.ts +72 -6
- package/dist/observability/index.js +61 -16
- package/dist/observability/index.js.map +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/workflows.d.ts +15 -1
- package/dist/workflows.js +36 -3
- package/dist/workflows.js.map +1 -1
- package/package.json +16 -11
- package/dist/agent-B4_kEsdK.d.ts +0 -60
- package/dist/client-connection-CGMuV62J.js.map +0 -1
- package/dist/mcp-DA0kDE7K.d.ts +0 -61
package/dist/mcp/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":[],"sources":["../../src/mcp/client.ts"],"sourcesContent":["import type { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport type { RequestOptions } from \"@modelcontextprotocol/sdk/shared/protocol.js\";\nimport type {\n CallToolRequest,\n CallToolResultSchema,\n CompatibilityCallToolResultSchema,\n GetPromptRequest,\n Prompt,\n ReadResourceRequest,\n Resource,\n ResourceTemplate,\n Tool\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { CfWorkerJsonSchemaValidator } from \"@modelcontextprotocol/sdk/validation/cfworker-provider.js\";\nimport { type RetryOptions, tryN } from \"../retries\";\nimport type { ToolSet } from \"ai\";\nimport type { JSONSchema7 } from \"json-schema\";\nimport { nanoid } from \"nanoid\";\nimport { Emitter, type Event, DisposableStore } from \"../core/events\";\nimport type { MCPObservabilityEvent } from \"../observability/mcp\";\nimport {\n MCPClientConnection,\n MCPConnectionState,\n type MCPTransportOptions\n} from \"./client-connection\";\nimport { toErrorMessage } from \"./errors\";\nimport type { TransportType } from \"./types\";\nimport type { MCPServerRow } from \"./client-storage\";\nimport type { AgentMcpOAuthProvider } from \"./do-oauth-client-provider\";\nimport { DurableObjectOAuthClientProvider } from \"./do-oauth-client-provider\";\n\nconst defaultClientOptions: ConstructorParameters<typeof Client>[1] = {\n jsonSchemaValidator: new CfWorkerJsonSchemaValidator()\n};\n\n/**\n * Options that can be stored in the server_options column\n * This is what gets JSON.stringify'd and stored in the database\n */\nexport type MCPServerOptions = {\n client?: ConstructorParameters<typeof Client>[1];\n transport?: {\n headers?: HeadersInit;\n type?: TransportType;\n };\n /** Retry options for connection and reconnection attempts */\n retry?: RetryOptions;\n};\n\n/**\n * Result of an OAuth callback request\n */\nexport type MCPOAuthCallbackResult =\n | { serverId: string; authSuccess: true; authError?: undefined }\n | { serverId?: string; authSuccess: false; authError: string };\n\n/**\n * Options for registering an MCP server\n */\nexport type RegisterServerOptions = {\n url: string;\n name: string;\n callbackUrl: string;\n client?: ConstructorParameters<typeof Client>[1];\n transport?: MCPTransportOptions;\n authUrl?: string;\n clientId?: string;\n /** Retry options for connection and reconnection attempts */\n retry?: RetryOptions;\n};\n\n/**\n * Result of attempting to connect to an MCP server.\n * Discriminated union ensures error is present only on failure.\n */\nexport type MCPConnectionResult =\n | {\n state: typeof MCPConnectionState.FAILED;\n error: string;\n }\n | {\n state: typeof MCPConnectionState.AUTHENTICATING;\n authUrl: string;\n clientId?: string;\n }\n | {\n state: typeof MCPConnectionState.CONNECTED;\n };\n\n/**\n * Result of discovering server capabilities.\n * success indicates whether discovery completed successfully.\n * state is the current connection state at time of return.\n * error is present when success is false.\n */\nexport type MCPDiscoverResult = {\n success: boolean;\n state: MCPConnectionState;\n error?: string;\n};\n\nexport type MCPClientOAuthCallbackConfig = {\n successRedirect?: string;\n errorRedirect?: string;\n customHandler?: (result: MCPClientOAuthResult) => Response;\n};\n\nexport type MCPClientOAuthResult =\n | { serverId: string; authSuccess: true; authError?: undefined }\n | {\n serverId?: string;\n authSuccess: false;\n /** May contain untrusted content from external OAuth providers. Escape appropriately for your output context. */\n authError: string;\n };\n\nexport type MCPClientManagerOptions = {\n storage: DurableObjectStorage;\n createAuthProvider?: (callbackUrl: string) => AgentMcpOAuthProvider;\n};\n\n/**\n * Utility class that aggregates multiple MCP clients into one\n */\nexport class MCPClientManager {\n public mcpConnections: Record<string, MCPClientConnection> = {};\n private _didWarnAboutUnstableGetAITools = false;\n private _oauthCallbackConfig?: MCPClientOAuthCallbackConfig;\n private _connectionDisposables = new Map<string, DisposableStore>();\n private _storage: DurableObjectStorage;\n private _createAuthProviderFn?: (\n callbackUrl: string\n ) => AgentMcpOAuthProvider;\n private _isRestored = false;\n\n /** @internal Protected for testing purposes. */\n protected readonly _onObservabilityEvent =\n new Emitter<MCPObservabilityEvent>();\n public readonly onObservabilityEvent: Event<MCPObservabilityEvent> =\n this._onObservabilityEvent.event;\n\n private readonly _onServerStateChanged = new Emitter<void>();\n /**\n * Event that fires whenever any MCP server state changes (registered, connected, removed, etc.)\n * This is useful for broadcasting server state to clients.\n */\n public readonly onServerStateChanged: Event<void> =\n this._onServerStateChanged.event;\n\n /**\n * @param _name Name of the MCP client\n * @param _version Version of the MCP Client\n * @param options Storage adapter for persisting MCP server state\n */\n constructor(\n private _name: string,\n private _version: string,\n options: MCPClientManagerOptions\n ) {\n if (!options.storage) {\n throw new Error(\n \"MCPClientManager requires a valid DurableObjectStorage instance\"\n );\n }\n this._storage = options.storage;\n this._createAuthProviderFn = options.createAuthProvider;\n }\n\n // SQL helper - runs a query and returns results as array\n private sql<T extends Record<string, SqlStorageValue>>(\n query: string,\n ...bindings: SqlStorageValue[]\n ): T[] {\n return [...this._storage.sql.exec<T>(query, ...bindings)];\n }\n\n // Storage operations\n private saveServerToStorage(server: MCPServerRow): void {\n this.sql(\n `INSERT OR REPLACE INTO cf_agents_mcp_servers (\n id, name, server_url, client_id, auth_url, callback_url, server_options\n ) VALUES (?, ?, ?, ?, ?, ?, ?)`,\n server.id,\n server.name,\n server.server_url,\n server.client_id ?? null,\n server.auth_url ?? null,\n server.callback_url,\n server.server_options ?? null\n );\n }\n\n private removeServerFromStorage(serverId: string): void {\n this.sql(\"DELETE FROM cf_agents_mcp_servers WHERE id = ?\", serverId);\n }\n\n private getServersFromStorage(): MCPServerRow[] {\n return this.sql<MCPServerRow>(\n \"SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers\"\n );\n }\n\n /**\n * Get the retry options for a server from stored server_options\n */\n private getServerRetryOptions(serverId: string): RetryOptions | undefined {\n const rows = this.sql<MCPServerRow>(\n \"SELECT server_options FROM cf_agents_mcp_servers WHERE id = ?\",\n serverId\n );\n if (!rows.length || !rows[0].server_options) return undefined;\n const parsed: MCPServerOptions = JSON.parse(rows[0].server_options);\n return parsed.retry;\n }\n\n private clearServerAuthUrl(serverId: string): void {\n this.sql(\n \"UPDATE cf_agents_mcp_servers SET auth_url = NULL WHERE id = ?\",\n serverId\n );\n }\n\n private failConnection(\n serverId: string,\n error: string\n ): MCPOAuthCallbackResult {\n this.clearServerAuthUrl(serverId);\n if (this.mcpConnections[serverId]) {\n this.mcpConnections[serverId].connectionState = MCPConnectionState.FAILED;\n this.mcpConnections[serverId].connectionError = error;\n }\n this._onServerStateChanged.fire();\n return { serverId, authSuccess: false, authError: error };\n }\n\n jsonSchema: typeof import(\"ai\").jsonSchema | undefined;\n\n /**\n * Create an auth provider for a server\n * @internal\n */\n private createAuthProvider(\n serverId: string,\n callbackUrl: string,\n clientName: string,\n clientId?: string\n ): AgentMcpOAuthProvider {\n if (!this._storage) {\n throw new Error(\n \"Cannot create auth provider: storage is not initialized\"\n );\n }\n const authProvider = new DurableObjectOAuthClientProvider(\n this._storage,\n clientName,\n callbackUrl\n );\n authProvider.serverId = serverId;\n if (clientId) {\n authProvider.clientId = clientId;\n }\n return authProvider;\n }\n\n /**\n * Restore MCP server connections from storage\n * This method is called on Agent initialization to restore previously connected servers\n *\n * @param clientName Name to use for OAuth client (typically the agent instance name)\n */\n async restoreConnectionsFromStorage(clientName: string): Promise<void> {\n if (this._isRestored) {\n return;\n }\n\n const servers = this.getServersFromStorage();\n\n if (!servers || servers.length === 0) {\n this._isRestored = true;\n return;\n }\n\n for (const server of servers) {\n const existingConn = this.mcpConnections[server.id];\n\n // Skip if connection already exists and is in a good state\n if (existingConn) {\n if (existingConn.connectionState === MCPConnectionState.READY) {\n console.warn(\n `[MCPClientManager] Server ${server.id} already has a ready connection. Skipping recreation.`\n );\n continue;\n }\n\n // Don't interrupt in-flight OAuth or connections\n if (\n existingConn.connectionState === MCPConnectionState.AUTHENTICATING ||\n existingConn.connectionState === MCPConnectionState.CONNECTING ||\n existingConn.connectionState === MCPConnectionState.DISCOVERING\n ) {\n // Let the existing flow complete\n continue;\n }\n\n // If failed, clean up the old connection before recreating\n if (existingConn.connectionState === MCPConnectionState.FAILED) {\n try {\n await existingConn.client.close();\n } catch (error) {\n console.warn(\n `[MCPClientManager] Error closing failed connection ${server.id}:`,\n error\n );\n }\n delete this.mcpConnections[server.id];\n this._connectionDisposables.get(server.id)?.dispose();\n this._connectionDisposables.delete(server.id);\n }\n }\n\n const parsedOptions: MCPServerOptions | null = server.server_options\n ? JSON.parse(server.server_options)\n : null;\n\n const authProvider = this._createAuthProviderFn\n ? this._createAuthProviderFn(server.callback_url)\n : this.createAuthProvider(\n server.id,\n server.callback_url,\n clientName,\n server.client_id ?? undefined\n );\n authProvider.serverId = server.id;\n if (server.client_id) {\n authProvider.clientId = server.client_id;\n }\n\n // Create the in-memory connection object (no need to save to storage - we just read from it!)\n const conn = this.createConnection(server.id, server.server_url, {\n client: parsedOptions?.client ?? {},\n transport: {\n ...(parsedOptions?.transport ?? {}),\n type: parsedOptions?.transport?.type ?? (\"auto\" as TransportType),\n authProvider\n }\n });\n\n // If auth_url exists, OAuth flow is in progress - set state and wait for callback\n if (server.auth_url) {\n conn.connectionState = MCPConnectionState.AUTHENTICATING;\n continue;\n }\n\n // Start connection in background (don't await) to avoid blocking the DO\n this._restoreServer(server.id, parsedOptions?.retry);\n }\n\n this._isRestored = true;\n }\n\n /**\n * Internal method to restore a single server connection and discovery\n */\n private async _restoreServer(\n serverId: string,\n retry?: RetryOptions\n ): Promise<void> {\n // Always try to connect - the connection logic will determine if OAuth is needed\n // If stored OAuth tokens are valid, connection will succeed automatically\n // If tokens are missing/invalid, connection will fail with Unauthorized\n // and state will be set to \"authenticating\"\n const maxAttempts = retry?.maxAttempts ?? 3;\n const baseDelayMs = retry?.baseDelayMs ?? 500;\n const maxDelayMs = retry?.maxDelayMs ?? 5000;\n\n const connectResult = await tryN(\n maxAttempts,\n async () => this.connectToServer(serverId),\n { baseDelayMs, maxDelayMs }\n ).catch((error) => {\n console.error(\n `Error connecting to ${serverId} after ${maxAttempts} attempts:`,\n error\n );\n return null;\n });\n\n if (connectResult?.state === MCPConnectionState.CONNECTED) {\n const discoverResult = await this.discoverIfConnected(serverId);\n if (discoverResult && !discoverResult.success) {\n console.error(`Error discovering ${serverId}:`, discoverResult.error);\n }\n }\n }\n\n /**\n * Connect to and register an MCP server\n *\n * @deprecated This method is maintained for backward compatibility.\n * For new code, use registerServer() and connectToServer() separately.\n *\n * @param url Server URL\n * @param options Connection options\n * @returns Object with server ID, auth URL (if OAuth), and client ID (if OAuth)\n */\n async connect(\n url: string,\n options: {\n // Allows you to reconnect to a server (in the case of an auth reconnect)\n reconnect?: {\n // server id\n id: string;\n oauthClientId?: string;\n oauthCode?: string;\n };\n // we're overriding authProvider here because we want to be able to access the auth URL\n transport?: MCPTransportOptions;\n client?: ConstructorParameters<typeof Client>[1];\n } = {}\n ): Promise<{\n id: string;\n authUrl?: string;\n clientId?: string;\n }> {\n /* Late initialization of jsonSchemaFn */\n /**\n * We need to delay loading ai sdk, because putting it in module scope is\n * causing issues with startup time.\n * The only place it's used is in getAITools, which only matters after\n * .connect() is called on at least one server.\n * So it's safe to delay loading it until .connect() is called.\n */\n await this.ensureJsonSchema();\n\n const id = options.reconnect?.id ?? nanoid(8);\n\n if (options.transport?.authProvider) {\n options.transport.authProvider.serverId = id;\n // reconnect with auth\n if (options.reconnect?.oauthClientId) {\n options.transport.authProvider.clientId =\n options.reconnect?.oauthClientId;\n }\n }\n\n // During OAuth reconnect, reuse existing connection to preserve state\n if (!options.reconnect?.oauthCode || !this.mcpConnections[id]) {\n const normalizedTransport = {\n ...options.transport,\n type: options.transport?.type ?? (\"auto\" as TransportType)\n };\n\n this.mcpConnections[id] = new MCPClientConnection(\n new URL(url),\n {\n name: this._name,\n version: this._version\n },\n {\n client: options.client ?? {},\n transport: normalizedTransport\n }\n );\n\n // Pipe connection-level observability events to the manager-level emitter\n // and track the subscription for cleanup.\n const store = new DisposableStore();\n // If we somehow already had disposables for this id, clear them first\n const existing = this._connectionDisposables.get(id);\n if (existing) existing.dispose();\n this._connectionDisposables.set(id, store);\n store.add(\n this.mcpConnections[id].onObservabilityEvent((event) => {\n this._onObservabilityEvent.fire(event);\n })\n );\n }\n\n // Initialize connection first. this will try connect\n await this.mcpConnections[id].init();\n\n // Handle OAuth completion if we have a reconnect code\n if (options.reconnect?.oauthCode) {\n try {\n await this.mcpConnections[id].completeAuthorization(\n options.reconnect.oauthCode\n );\n\n // Reinitialize connection\n await this.mcpConnections[id].init();\n } catch (error) {\n this._onObservabilityEvent.fire({\n type: \"mcp:client:connect\",\n displayMessage: `Failed to complete OAuth reconnection for ${id} for ${url}`,\n payload: {\n url: url,\n transport: options.transport?.type ?? \"auto\",\n state: this.mcpConnections[id].connectionState,\n error: toErrorMessage(error)\n },\n timestamp: Date.now(),\n id\n });\n // Re-throw to signal failure to the caller\n throw error;\n }\n }\n\n // If connection is in authenticating state, return auth URL for OAuth flow\n const authUrl = options.transport?.authProvider?.authUrl;\n if (\n this.mcpConnections[id].connectionState ===\n MCPConnectionState.AUTHENTICATING &&\n authUrl &&\n options.transport?.authProvider?.redirectUrl\n ) {\n return {\n authUrl,\n clientId: options.transport?.authProvider?.clientId,\n id\n };\n }\n\n // If connection is connected, discover capabilities\n const discoverResult = await this.discoverIfConnected(id);\n if (discoverResult && !discoverResult.success) {\n throw new Error(\n `Failed to discover server capabilities: ${discoverResult.error}`\n );\n }\n\n return {\n id\n };\n }\n\n /**\n * Create an in-memory connection object and set up observability\n * Does NOT save to storage - use registerServer() for that\n * @returns The connection object (existing or newly created)\n */\n private createConnection(\n id: string,\n url: string,\n options: {\n client?: ConstructorParameters<typeof Client>[1];\n transport: MCPTransportOptions;\n }\n ): MCPClientConnection {\n // Return existing connection if already exists\n if (this.mcpConnections[id]) {\n return this.mcpConnections[id];\n }\n\n const normalizedTransport = {\n ...options.transport,\n type: options.transport?.type ?? (\"auto\" as TransportType)\n };\n\n this.mcpConnections[id] = new MCPClientConnection(\n new URL(url),\n {\n name: this._name,\n version: this._version\n },\n {\n client: { ...defaultClientOptions, ...options.client },\n transport: normalizedTransport\n }\n );\n\n // Pipe connection-level observability events to the manager-level emitter\n const store = new DisposableStore();\n const existing = this._connectionDisposables.get(id);\n if (existing) existing.dispose();\n this._connectionDisposables.set(id, store);\n store.add(\n this.mcpConnections[id].onObservabilityEvent((event) => {\n this._onObservabilityEvent.fire(event);\n })\n );\n\n return this.mcpConnections[id];\n }\n\n /**\n * Register an MCP server connection without connecting\n * Creates the connection object, sets up observability, and saves to storage\n *\n * @param id Server ID\n * @param options Registration options including URL, name, callback URL, and connection config\n * @returns Server ID\n */\n async registerServer(\n id: string,\n options: RegisterServerOptions\n ): Promise<string> {\n // Create the in-memory connection\n this.createConnection(id, options.url, {\n client: options.client,\n transport: {\n ...options.transport,\n type: options.transport?.type ?? (\"auto\" as TransportType)\n }\n });\n\n // Save to storage (exclude authProvider since it's recreated during restore)\n const { authProvider: _, ...transportWithoutAuth } =\n options.transport ?? {};\n this.saveServerToStorage({\n id,\n name: options.name,\n server_url: options.url,\n callback_url: options.callbackUrl,\n client_id: options.clientId ?? null,\n auth_url: options.authUrl ?? null,\n server_options: JSON.stringify({\n client: options.client,\n transport: transportWithoutAuth,\n retry: options.retry\n })\n });\n\n this._onServerStateChanged.fire();\n\n return id;\n }\n\n /**\n * Connect to an already registered MCP server and initialize the connection.\n *\n * For OAuth servers, returns `{ state: \"authenticating\", authUrl, clientId? }`.\n * The user must complete the OAuth flow via the authUrl, which triggers a\n * callback handled by `handleCallbackRequest()`.\n *\n * For non-OAuth servers, establishes the transport connection and returns\n * `{ state: \"connected\" }`. Call `discoverIfConnected()` afterwards to\n * discover capabilities and transition to \"ready\" state.\n *\n * @param id Server ID (must be registered first via registerServer())\n * @returns Connection result with current state and OAuth info (if applicable)\n */\n async connectToServer(id: string): Promise<MCPConnectionResult> {\n const conn = this.mcpConnections[id];\n if (!conn) {\n throw new Error(\n `Server ${id} is not registered. Call registerServer() first.`\n );\n }\n\n const error = await conn.init();\n this._onServerStateChanged.fire();\n\n switch (conn.connectionState) {\n case MCPConnectionState.FAILED:\n return {\n state: conn.connectionState,\n error: error ?? \"Unknown connection error\"\n };\n\n case MCPConnectionState.AUTHENTICATING: {\n const authUrl = conn.options.transport.authProvider?.authUrl;\n const redirectUrl = conn.options.transport.authProvider?.redirectUrl;\n\n if (!authUrl || !redirectUrl) {\n return {\n state: MCPConnectionState.FAILED,\n error: `OAuth configuration incomplete: missing ${!authUrl ? \"authUrl\" : \"redirectUrl\"}`\n };\n }\n\n const clientId = conn.options.transport.authProvider?.clientId;\n\n // Update storage with auth URL and client ID\n const servers = this.getServersFromStorage();\n const serverRow = servers.find((s) => s.id === id);\n if (serverRow) {\n this.saveServerToStorage({\n ...serverRow,\n auth_url: authUrl,\n client_id: clientId ?? null\n });\n // Broadcast again so clients receive the auth_url\n this._onServerStateChanged.fire();\n }\n\n return {\n state: conn.connectionState,\n authUrl,\n clientId\n };\n }\n\n case MCPConnectionState.CONNECTED:\n return { state: conn.connectionState };\n\n default:\n return {\n state: MCPConnectionState.FAILED,\n error: `Unexpected connection state after init: ${conn.connectionState}`\n };\n }\n }\n\n private extractServerIdFromState(state: string | null): string | null {\n if (!state) return null;\n const parts = state.split(\".\");\n return parts.length === 2 ? parts[1] : null;\n }\n\n isCallbackRequest(req: Request): boolean {\n if (req.method !== \"GET\") {\n return false;\n }\n\n const url = new URL(req.url);\n const state = url.searchParams.get(\"state\");\n const serverId = this.extractServerIdFromState(state);\n if (!serverId) {\n return false;\n }\n\n // Match by server ID AND verify the request origin + pathname matches the registered callback URL.\n // This prevents unrelated GET requests with a `state` param from being intercepted.\n const servers = this.getServersFromStorage();\n return servers.some((server) => {\n if (server.id !== serverId) return false;\n try {\n const storedUrl = new URL(server.callback_url);\n return (\n storedUrl.origin === url.origin && storedUrl.pathname === url.pathname\n );\n } catch {\n return false;\n }\n });\n }\n\n private validateCallbackRequest(\n req: Request\n ):\n | { valid: true; serverId: string; code: string; state: string }\n | { valid: false; serverId?: string; error: string } {\n const url = new URL(req.url);\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n const error = url.searchParams.get(\"error\");\n const errorDescription = url.searchParams.get(\"error_description\");\n\n // Early validation - return errors because we can't identify the connection\n if (!state) {\n return {\n valid: false,\n error: \"Unauthorized: no state provided\"\n };\n }\n\n const serverId = this.extractServerIdFromState(state);\n if (!serverId) {\n return {\n valid: false,\n error:\n \"No serverId found in state parameter. Expected format: {nonce}.{serverId}\"\n };\n }\n\n if (error) {\n return {\n serverId: serverId,\n valid: false,\n error: errorDescription || error\n };\n }\n\n if (!code) {\n return {\n serverId: serverId,\n valid: false,\n error: \"Unauthorized: no code provided\"\n };\n }\n\n const servers = this.getServersFromStorage();\n const serverExists = servers.some((server) => server.id === serverId);\n if (!serverExists) {\n return {\n serverId: serverId,\n valid: false,\n error: `No server found with id \"${serverId}\". Was the request matched with \\`isCallbackRequest()\\`?`\n };\n }\n\n if (this.mcpConnections[serverId] === undefined) {\n return {\n serverId: serverId,\n valid: false,\n error: `No connection found for serverId \"${serverId}\".`\n };\n }\n\n return {\n valid: true,\n serverId,\n code: code,\n state: state\n };\n }\n\n async handleCallbackRequest(req: Request): Promise<MCPOAuthCallbackResult> {\n const validation = this.validateCallbackRequest(req);\n\n if (!validation.valid) {\n if (validation.serverId && this.mcpConnections[validation.serverId]) {\n return this.failConnection(validation.serverId, validation.error);\n }\n\n return {\n serverId: validation.serverId,\n authSuccess: false,\n authError: validation.error\n };\n }\n\n const { serverId, code, state } = validation;\n const conn = this.mcpConnections[serverId]; // We have a valid connection - all errors from here should fail the connection\n\n try {\n if (!conn.options.transport.authProvider) {\n throw new Error(\n \"Trying to finalize authentication for a server connection without an authProvider\"\n );\n }\n\n const authProvider = conn.options.transport.authProvider;\n authProvider.serverId = serverId;\n\n // Two-phase state validation: check first (non-destructive), consume later\n // This prevents DoS attacks where attacker consumes valid state before legitimate callback\n const stateValidation = await authProvider.checkState(state);\n if (!stateValidation.valid) {\n throw new Error(stateValidation.error || \"Invalid state\");\n }\n\n // Already authenticated - just return success\n if (\n conn.connectionState === MCPConnectionState.READY ||\n conn.connectionState === MCPConnectionState.CONNECTED\n ) {\n this.clearServerAuthUrl(serverId);\n return { serverId, authSuccess: true };\n }\n\n if (conn.connectionState !== MCPConnectionState.AUTHENTICATING) {\n throw new Error(\n `Failed to authenticate: the client is in \"${conn.connectionState}\" state, expected \"authenticating\"`\n );\n }\n\n await authProvider.consumeState(state);\n await conn.completeAuthorization(code);\n await authProvider.deleteCodeVerifier();\n this.clearServerAuthUrl(serverId);\n conn.connectionError = null;\n this._onServerStateChanged.fire();\n\n return { serverId, authSuccess: true };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return this.failConnection(serverId, message);\n }\n }\n\n /**\n * Discover server capabilities if connection is in CONNECTED or READY state.\n * Transitions to DISCOVERING then READY (or CONNECTED on error).\n * Can be called to refresh server capabilities (e.g., from a UI refresh button).\n *\n * If called while a previous discovery is in-flight for the same server,\n * the previous discovery will be aborted.\n *\n * @param serverId The server ID to discover\n * @param options Optional configuration\n * @param options.timeoutMs Timeout in milliseconds (default: 30000)\n * @returns Result with current state and optional error, or undefined if connection not found\n */\n async discoverIfConnected(\n serverId: string,\n options: { timeoutMs?: number } = {}\n ): Promise<MCPDiscoverResult | undefined> {\n const conn = this.mcpConnections[serverId];\n if (!conn) {\n this._onObservabilityEvent.fire({\n type: \"mcp:client:discover\",\n displayMessage: `Connection not found for ${serverId}`,\n payload: {},\n timestamp: Date.now(),\n id: nanoid()\n });\n return undefined;\n }\n\n // Delegate to connection's discover method which handles cancellation and timeout\n const result = await conn.discover(options);\n this._onServerStateChanged.fire();\n\n return {\n ...result,\n state: conn.connectionState\n };\n }\n\n /**\n * Establish connection in the background after OAuth completion\n * This method connects to the server and discovers its capabilities\n * @param serverId The server ID to establish connection for\n */\n async establishConnection(serverId: string): Promise<void> {\n const conn = this.mcpConnections[serverId];\n if (!conn) {\n this._onObservabilityEvent.fire({\n type: \"mcp:client:preconnect\",\n displayMessage: `Connection not found for serverId: ${serverId}`,\n payload: { serverId },\n timestamp: Date.now(),\n id: nanoid()\n });\n return;\n }\n\n // Skip if already discovering or ready - prevents duplicate work\n if (\n conn.connectionState === MCPConnectionState.DISCOVERING ||\n conn.connectionState === MCPConnectionState.READY\n ) {\n this._onObservabilityEvent.fire({\n type: \"mcp:client:connect\",\n displayMessage: `establishConnection skipped for ${serverId}, already in ${conn.connectionState} state`,\n payload: {\n url: conn.url.toString(),\n transport: conn.options.transport.type || \"unknown\",\n state: conn.connectionState\n },\n timestamp: Date.now(),\n id: nanoid()\n });\n return;\n }\n\n const retry = this.getServerRetryOptions(serverId);\n const maxAttempts = retry?.maxAttempts ?? 3;\n const baseDelayMs = retry?.baseDelayMs ?? 500;\n const maxDelayMs = retry?.maxDelayMs ?? 5000;\n\n const connectResult = await tryN(\n maxAttempts,\n async () => this.connectToServer(serverId),\n { baseDelayMs, maxDelayMs }\n );\n this._onServerStateChanged.fire();\n\n if (connectResult.state === MCPConnectionState.CONNECTED) {\n await this.discoverIfConnected(serverId);\n }\n\n this._onObservabilityEvent.fire({\n type: \"mcp:client:connect\",\n displayMessage: `establishConnection completed for ${serverId}, final state: ${conn.connectionState}`,\n payload: {\n url: conn.url.toString(),\n transport: conn.options.transport.type || \"unknown\",\n state: conn.connectionState\n },\n timestamp: Date.now(),\n id: nanoid()\n });\n }\n\n /**\n * Configure OAuth callback handling\n * @param config OAuth callback configuration\n */\n configureOAuthCallback(config: MCPClientOAuthCallbackConfig): void {\n this._oauthCallbackConfig = config;\n }\n\n /**\n * Get the current OAuth callback configuration\n * @returns The current OAuth callback configuration\n */\n getOAuthCallbackConfig(): MCPClientOAuthCallbackConfig | undefined {\n return this._oauthCallbackConfig;\n }\n\n /**\n * @returns namespaced list of tools\n */\n listTools(): NamespacedData[\"tools\"] {\n return getNamespacedData(this.mcpConnections, \"tools\");\n }\n\n /**\n * Lazy-loads the jsonSchema function from the AI SDK.\n *\n * This defers importing the \"ai\" package until it's actually needed, which helps reduce\n * initial bundle size and startup time. The jsonSchema function is required for converting\n * MCP tools into AI SDK tool definitions via getAITools().\n *\n * @internal This method is for internal use only. It's automatically called before operations\n * that need jsonSchema (like getAITools() or OAuth flows). External consumers should not need\n * to call this directly.\n */\n async ensureJsonSchema() {\n if (!this.jsonSchema) {\n const { jsonSchema } = await import(\"ai\");\n this.jsonSchema = jsonSchema;\n }\n }\n\n /**\n * @returns a set of tools that you can use with the AI SDK\n */\n getAITools(): ToolSet {\n if (!this.jsonSchema) {\n throw new Error(\"jsonSchema not initialized.\");\n }\n\n // Warn if tools are being read from non-ready connections\n for (const [id, conn] of Object.entries(this.mcpConnections)) {\n if (\n conn.connectionState !== MCPConnectionState.READY &&\n conn.connectionState !== MCPConnectionState.AUTHENTICATING\n ) {\n console.warn(\n `[getAITools] WARNING: Reading tools from connection ${id} in state \"${conn.connectionState}\". Tools may not be loaded yet.`\n );\n }\n }\n\n return Object.fromEntries(\n getNamespacedData(this.mcpConnections, \"tools\").map((tool) => {\n return [\n `tool_${tool.serverId.replace(/-/g, \"\")}_${tool.name}`,\n {\n description: tool.description,\n execute: async (args) => {\n const result = await this.callTool({\n arguments: args,\n name: tool.name,\n serverId: tool.serverId\n });\n if (result.isError) {\n const content = result.content as\n | Array<{ type: string; text?: string }>\n | undefined;\n const textContent = content?.[0];\n const message =\n textContent?.type === \"text\" && textContent.text\n ? textContent.text\n : \"Tool call failed\";\n throw new Error(message);\n }\n return result;\n },\n inputSchema: this.jsonSchema!(tool.inputSchema as JSONSchema7),\n outputSchema: tool.outputSchema\n ? this.jsonSchema!(tool.outputSchema as JSONSchema7)\n : undefined\n }\n ];\n })\n );\n }\n\n /**\n * @deprecated this has been renamed to getAITools(), and unstable_getAITools will be removed in the next major version\n * @returns a set of tools that you can use with the AI SDK\n */\n unstable_getAITools(): ToolSet {\n if (!this._didWarnAboutUnstableGetAITools) {\n this._didWarnAboutUnstableGetAITools = true;\n console.warn(\n \"unstable_getAITools is deprecated, use getAITools instead. unstable_getAITools will be removed in the next major version.\"\n );\n }\n return this.getAITools();\n }\n\n /**\n * Closes all active in-memory connections to MCP servers.\n *\n * Note: This only closes the transport connections - it does NOT remove\n * servers from storage. Servers will still be listed and their callback\n * URLs will still match incoming OAuth requests.\n *\n * Use removeServer() instead if you want to fully clean up a server\n * (closes connection AND removes from storage).\n */\n async closeAllConnections() {\n const ids = Object.keys(this.mcpConnections);\n\n // Cancel all in-flight discoveries\n for (const id of ids) {\n this.mcpConnections[id].cancelDiscovery();\n }\n\n await Promise.all(\n ids.map(async (id) => {\n await this.mcpConnections[id].client.close();\n })\n );\n // Dispose all per-connection subscriptions\n for (const id of ids) {\n const store = this._connectionDisposables.get(id);\n if (store) store.dispose();\n this._connectionDisposables.delete(id);\n delete this.mcpConnections[id];\n }\n }\n\n /**\n * Closes a connection to an MCP server\n * @param id The id of the connection to close\n */\n async closeConnection(id: string) {\n if (!this.mcpConnections[id]) {\n throw new Error(`Connection with id \"${id}\" does not exist.`);\n }\n\n // Cancel any in-flight discovery\n this.mcpConnections[id].cancelDiscovery();\n\n await this.mcpConnections[id].client.close();\n delete this.mcpConnections[id];\n\n const store = this._connectionDisposables.get(id);\n if (store) store.dispose();\n this._connectionDisposables.delete(id);\n }\n\n /**\n * Remove an MCP server - closes connection if active and removes from storage.\n */\n async removeServer(serverId: string): Promise<void> {\n if (this.mcpConnections[serverId]) {\n try {\n await this.closeConnection(serverId);\n } catch (_e) {\n // Ignore errors when closing\n }\n }\n this.removeServerFromStorage(serverId);\n this._onServerStateChanged.fire();\n }\n\n /**\n * List all MCP servers from storage\n */\n listServers(): MCPServerRow[] {\n return this.getServersFromStorage();\n }\n\n /**\n * Dispose the manager and all resources.\n */\n async dispose(): Promise<void> {\n try {\n await this.closeAllConnections();\n } finally {\n // Dispose manager-level emitters\n this._onServerStateChanged.dispose();\n this._onObservabilityEvent.dispose();\n }\n }\n\n /**\n * @returns namespaced list of prompts\n */\n listPrompts(): NamespacedData[\"prompts\"] {\n return getNamespacedData(this.mcpConnections, \"prompts\");\n }\n\n /**\n * @returns namespaced list of tools\n */\n listResources(): NamespacedData[\"resources\"] {\n return getNamespacedData(this.mcpConnections, \"resources\");\n }\n\n /**\n * @returns namespaced list of resource templates\n */\n listResourceTemplates(): NamespacedData[\"resourceTemplates\"] {\n return getNamespacedData(this.mcpConnections, \"resourceTemplates\");\n }\n\n /**\n * Namespaced version of callTool\n */\n async callTool(\n params: CallToolRequest[\"params\"] & { serverId: string },\n resultSchema?:\n | typeof CallToolResultSchema\n | typeof CompatibilityCallToolResultSchema,\n options?: RequestOptions\n ) {\n const unqualifiedName = params.name.replace(`${params.serverId}.`, \"\");\n return this.mcpConnections[params.serverId].client.callTool(\n {\n ...params,\n name: unqualifiedName\n },\n resultSchema,\n options\n );\n }\n\n /**\n * Namespaced version of readResource\n */\n readResource(\n params: ReadResourceRequest[\"params\"] & { serverId: string },\n options: RequestOptions\n ) {\n return this.mcpConnections[params.serverId].client.readResource(\n params,\n options\n );\n }\n\n /**\n * Namespaced version of getPrompt\n */\n getPrompt(\n params: GetPromptRequest[\"params\"] & { serverId: string },\n options: RequestOptions\n ) {\n return this.mcpConnections[params.serverId].client.getPrompt(\n params,\n options\n );\n }\n}\n\ntype NamespacedData = {\n tools: (Tool & { serverId: string })[];\n prompts: (Prompt & { serverId: string })[];\n resources: (Resource & { serverId: string })[];\n resourceTemplates: (ResourceTemplate & { serverId: string })[];\n};\n\nexport function getNamespacedData<T extends keyof NamespacedData>(\n mcpClients: Record<string, MCPClientConnection>,\n type: T\n): NamespacedData[T] {\n const sets = Object.entries(mcpClients).map(([name, conn]) => {\n return { data: conn[type], name };\n });\n\n const namespacedData = sets.flatMap(({ name: serverId, data }) => {\n return data.map((item) => {\n return {\n ...item,\n // we add a serverId so we can easily pull it out and send the tool call to the right server\n serverId\n };\n });\n });\n\n return namespacedData as NamespacedData[T]; // Type assertion needed due to TS limitations with conditional return types\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../../src/mcp/client.ts"],"sourcesContent":["import type { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport type { RequestOptions } from \"@modelcontextprotocol/sdk/shared/protocol.js\";\nimport type {\n CallToolRequest,\n CallToolResultSchema,\n CompatibilityCallToolResultSchema,\n GetPromptRequest,\n Prompt,\n ReadResourceRequest,\n Resource,\n ResourceTemplate,\n Tool\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { CfWorkerJsonSchemaValidator } from \"@modelcontextprotocol/sdk/validation/cfworker-provider.js\";\nimport { type RetryOptions, tryN } from \"../retries\";\nimport type { ToolSet } from \"ai\";\nimport type { JSONSchema7 } from \"json-schema\";\nimport { nanoid } from \"nanoid\";\nimport { Emitter, type Event, DisposableStore } from \"../core/events\";\nimport type { MCPObservabilityEvent } from \"../observability/mcp\";\nimport {\n MCPClientConnection,\n MCPConnectionState,\n type MCPTransportOptions\n} from \"./client-connection\";\nimport { toErrorMessage } from \"./errors\";\nimport { RPC_DO_PREFIX } from \"./rpc\";\nimport type { TransportType } from \"./types\";\nimport type { MCPServerRow } from \"./client-storage\";\nimport type { AgentMcpOAuthProvider } from \"./do-oauth-client-provider\";\nimport { DurableObjectOAuthClientProvider } from \"./do-oauth-client-provider\";\n\nconst defaultClientOptions: ConstructorParameters<typeof Client>[1] = {\n jsonSchemaValidator: new CfWorkerJsonSchemaValidator()\n};\n\n/**\n * Blocked hostname patterns for SSRF protection.\n * Prevents MCP client from connecting to internal/private network addresses.\n */\nconst BLOCKED_HOSTNAMES = new Set([\n \"localhost\",\n \"0.0.0.0\",\n \"[::1]\",\n \"[::]\",\n \"metadata.google.internal\"\n]);\n\n/**\n * Check whether a hostname looks like a private/internal IP address.\n * Blocks RFC 1918, link-local, loopback, and cloud metadata endpoints.\n */\nfunction isBlockedUrl(url: string): boolean {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return true; // Malformed URLs are blocked\n }\n\n const hostname = parsed.hostname;\n\n if (BLOCKED_HOSTNAMES.has(hostname)) return true;\n\n // IPv4 checks\n const ipv4Parts = hostname.split(\".\");\n if (ipv4Parts.length === 4 && ipv4Parts.every((p) => /^\\d{1,3}$/.test(p))) {\n const [a, b] = ipv4Parts.map(Number);\n // 10.0.0.0/8\n if (a === 10) return true;\n // 172.16.0.0/12\n if (a === 172 && b >= 16 && b <= 31) return true;\n // 192.168.0.0/16\n if (a === 192 && b === 168) return true;\n // 127.0.0.0/8 (loopback)\n if (a === 127) return true;\n // 169.254.0.0/16 (link-local / cloud metadata)\n if (a === 169 && b === 254) return true;\n // 0.0.0.0/8\n if (a === 0) return true;\n }\n\n // IPv6 private range checks\n // URL parser keeps brackets: hostname for [fc00::1] is \"[fc00::1]\"\n if (hostname.startsWith(\"[\") && hostname.endsWith(\"]\")) {\n const addr = hostname.slice(1, -1).toLowerCase();\n // fc00::/7 — unique local addresses (fc00:: through fdff::)\n if (addr.startsWith(\"fc\") || addr.startsWith(\"fd\")) return true;\n // fe80::/10 — link-local addresses\n if (addr.startsWith(\"fe80\")) return true;\n }\n\n return false;\n}\n\n/**\n * Options that can be stored in the server_options column\n * This is what gets JSON.stringify'd and stored in the database\n */\nexport type MCPServerOptions = {\n client?: ConstructorParameters<typeof Client>[1];\n transport?: {\n headers?: HeadersInit;\n type?: TransportType;\n };\n /** Retry options for connection and reconnection attempts */\n retry?: RetryOptions;\n};\n\n/**\n * Result of an OAuth callback request\n */\nexport type MCPOAuthCallbackResult =\n | { serverId: string; authSuccess: true; authError?: undefined }\n | { serverId?: string; authSuccess: false; authError: string };\n\n/**\n * Options for registering an MCP server\n */\nexport type RegisterServerOptions = {\n url: string;\n name: string;\n callbackUrl?: string;\n client?: ConstructorParameters<typeof Client>[1];\n transport?: MCPTransportOptions;\n authUrl?: string;\n clientId?: string;\n /** Retry options for connection and reconnection attempts */\n retry?: RetryOptions;\n};\n\n/**\n * Result of attempting to connect to an MCP server.\n * Discriminated union ensures error is present only on failure.\n */\nexport type MCPConnectionResult =\n | {\n state: typeof MCPConnectionState.FAILED;\n error: string;\n }\n | {\n state: typeof MCPConnectionState.AUTHENTICATING;\n authUrl: string;\n clientId?: string;\n }\n | {\n state: typeof MCPConnectionState.CONNECTED;\n };\n\n/**\n * Result of discovering server capabilities.\n * success indicates whether discovery completed successfully.\n * state is the current connection state at time of return.\n * error is present when success is false.\n */\nexport type MCPDiscoverResult = {\n success: boolean;\n state: MCPConnectionState;\n error?: string;\n};\n\nexport type MCPClientOAuthCallbackConfig = {\n successRedirect?: string;\n errorRedirect?: string;\n customHandler?: (result: MCPClientOAuthResult) => Response;\n};\n\nexport type MCPClientOAuthResult =\n | { serverId: string; authSuccess: true; authError?: undefined }\n | {\n serverId?: string;\n authSuccess: false;\n /** May contain untrusted content from external OAuth providers. Escape appropriately for your output context. */\n authError: string;\n };\n\nexport type MCPClientManagerOptions = {\n storage: DurableObjectStorage;\n createAuthProvider?: (callbackUrl: string) => AgentMcpOAuthProvider;\n};\n\n/**\n * Utility class that aggregates multiple MCP clients into one\n */\nexport class MCPClientManager {\n public mcpConnections: Record<string, MCPClientConnection> = {};\n private _didWarnAboutUnstableGetAITools = false;\n private _oauthCallbackConfig?: MCPClientOAuthCallbackConfig;\n private _connectionDisposables = new Map<string, DisposableStore>();\n private _storage: DurableObjectStorage;\n private _createAuthProviderFn?: (\n callbackUrl: string\n ) => AgentMcpOAuthProvider;\n private _isRestored = false;\n private _pendingConnections = new Map<string, Promise<void>>();\n\n /** @internal Protected for testing purposes. */\n protected readonly _onObservabilityEvent =\n new Emitter<MCPObservabilityEvent>();\n public readonly onObservabilityEvent: Event<MCPObservabilityEvent> =\n this._onObservabilityEvent.event;\n\n private readonly _onServerStateChanged = new Emitter<void>();\n /**\n * Event that fires whenever any MCP server state changes (registered, connected, removed, etc.)\n * This is useful for broadcasting server state to clients.\n */\n public readonly onServerStateChanged: Event<void> =\n this._onServerStateChanged.event;\n\n /**\n * @param _name Name of the MCP client\n * @param _version Version of the MCP Client\n * @param options Storage adapter for persisting MCP server state\n */\n constructor(\n private _name: string,\n private _version: string,\n options: MCPClientManagerOptions\n ) {\n if (!options.storage) {\n throw new Error(\n \"MCPClientManager requires a valid DurableObjectStorage instance\"\n );\n }\n this._storage = options.storage;\n this._createAuthProviderFn = options.createAuthProvider;\n }\n\n // SQL helper - runs a query and returns results as array\n private sql<T extends Record<string, SqlStorageValue>>(\n query: string,\n ...bindings: SqlStorageValue[]\n ): T[] {\n return [...this._storage.sql.exec<T>(query, ...bindings)];\n }\n\n // Storage operations\n private saveServerToStorage(server: MCPServerRow): void {\n this.sql(\n `INSERT OR REPLACE INTO cf_agents_mcp_servers (\n id, name, server_url, client_id, auth_url, callback_url, server_options\n ) VALUES (?, ?, ?, ?, ?, ?, ?)`,\n server.id,\n server.name,\n server.server_url,\n server.client_id ?? null,\n server.auth_url ?? null,\n server.callback_url,\n server.server_options ?? null\n );\n }\n\n private removeServerFromStorage(serverId: string): void {\n this.sql(\"DELETE FROM cf_agents_mcp_servers WHERE id = ?\", serverId);\n }\n\n private getServersFromStorage(): MCPServerRow[] {\n return this.sql<MCPServerRow>(\n \"SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers\"\n );\n }\n\n /**\n * Get the retry options for a server from stored server_options\n */\n private getServerRetryOptions(serverId: string): RetryOptions | undefined {\n const rows = this.sql<MCPServerRow>(\n \"SELECT server_options FROM cf_agents_mcp_servers WHERE id = ?\",\n serverId\n );\n if (!rows.length || !rows[0].server_options) return undefined;\n const parsed: MCPServerOptions = JSON.parse(rows[0].server_options);\n return parsed.retry;\n }\n\n private clearServerAuthUrl(serverId: string): void {\n this.sql(\n \"UPDATE cf_agents_mcp_servers SET auth_url = NULL WHERE id = ?\",\n serverId\n );\n }\n\n private failConnection(\n serverId: string,\n error: string\n ): MCPOAuthCallbackResult {\n this.clearServerAuthUrl(serverId);\n if (this.mcpConnections[serverId]) {\n this.mcpConnections[serverId].connectionState = MCPConnectionState.FAILED;\n this.mcpConnections[serverId].connectionError = error;\n }\n this._onServerStateChanged.fire();\n return { serverId, authSuccess: false, authError: error };\n }\n\n jsonSchema: typeof import(\"ai\").jsonSchema | undefined;\n\n /**\n * Create an auth provider for a server\n * @internal\n */\n private createAuthProvider(\n serverId: string,\n callbackUrl: string,\n clientName: string,\n clientId?: string\n ): AgentMcpOAuthProvider {\n if (!this._storage) {\n throw new Error(\n \"Cannot create auth provider: storage is not initialized\"\n );\n }\n const authProvider = new DurableObjectOAuthClientProvider(\n this._storage,\n clientName,\n callbackUrl\n );\n authProvider.serverId = serverId;\n if (clientId) {\n authProvider.clientId = clientId;\n }\n return authProvider;\n }\n\n /**\n * Get saved RPC servers from storage (servers with rpc:// URLs).\n * These are restored separately by the Agent class since they need env bindings.\n */\n getRpcServersFromStorage(): MCPServerRow[] {\n return this.getServersFromStorage().filter((s) =>\n s.server_url.startsWith(RPC_DO_PREFIX)\n );\n }\n\n /**\n * Save an RPC server to storage for hibernation recovery.\n * The bindingName is stored in server_options so the Agent can look up\n * the namespace from env during restore.\n */\n saveRpcServerToStorage(\n id: string,\n name: string,\n normalizedName: string,\n bindingName: string,\n props?: Record<string, unknown>\n ): void {\n this.saveServerToStorage({\n id,\n name,\n server_url: `${RPC_DO_PREFIX}${normalizedName}`,\n client_id: null,\n auth_url: null,\n callback_url: \"\",\n server_options: JSON.stringify({ bindingName, props })\n });\n }\n\n /**\n * Restore MCP server connections from storage\n * This method is called on Agent initialization to restore previously connected servers.\n * RPC servers (rpc:// URLs) are skipped here -- they are restored by the Agent class\n * which has access to env bindings.\n *\n * @param clientName Name to use for OAuth client (typically the agent instance name)\n */\n async restoreConnectionsFromStorage(clientName: string): Promise<void> {\n if (this._isRestored) {\n return;\n }\n\n const servers = this.getServersFromStorage();\n\n if (!servers || servers.length === 0) {\n this._isRestored = true;\n return;\n }\n\n for (const server of servers) {\n if (server.server_url.startsWith(RPC_DO_PREFIX)) {\n continue;\n }\n\n const existingConn = this.mcpConnections[server.id];\n\n // Skip if connection already exists and is in a good state\n if (existingConn) {\n if (existingConn.connectionState === MCPConnectionState.READY) {\n console.warn(\n `[MCPClientManager] Server ${server.id} already has a ready connection. Skipping recreation.`\n );\n continue;\n }\n\n // Don't interrupt in-flight OAuth or connections\n if (\n existingConn.connectionState === MCPConnectionState.AUTHENTICATING ||\n existingConn.connectionState === MCPConnectionState.CONNECTING ||\n existingConn.connectionState === MCPConnectionState.DISCOVERING\n ) {\n // Let the existing flow complete\n continue;\n }\n\n // If failed, clean up the old connection before recreating\n if (existingConn.connectionState === MCPConnectionState.FAILED) {\n try {\n await existingConn.client.close();\n } catch (error) {\n console.warn(\n `[MCPClientManager] Error closing failed connection ${server.id}:`,\n error\n );\n }\n delete this.mcpConnections[server.id];\n this._connectionDisposables.get(server.id)?.dispose();\n this._connectionDisposables.delete(server.id);\n }\n }\n\n const parsedOptions: MCPServerOptions | null = server.server_options\n ? JSON.parse(server.server_options)\n : null;\n\n let authProvider: AgentMcpOAuthProvider | undefined;\n if (server.callback_url) {\n authProvider = this._createAuthProviderFn\n ? this._createAuthProviderFn(server.callback_url)\n : this.createAuthProvider(\n server.id,\n server.callback_url,\n clientName,\n server.client_id ?? undefined\n );\n authProvider.serverId = server.id;\n if (server.client_id) {\n authProvider.clientId = server.client_id;\n }\n }\n\n // Create the in-memory connection object (no need to save to storage - we just read from it!)\n const conn = this.createConnection(server.id, server.server_url, {\n client: parsedOptions?.client ?? {},\n transport: {\n ...(parsedOptions?.transport ?? {}),\n type: parsedOptions?.transport?.type ?? (\"auto\" as TransportType),\n authProvider\n }\n });\n\n // If auth_url exists, OAuth flow is in progress - set state and wait for callback\n if (server.auth_url) {\n conn.connectionState = MCPConnectionState.AUTHENTICATING;\n continue;\n }\n\n // Start connection in background (don't await) to avoid blocking the DO\n this._trackConnection(\n server.id,\n this._restoreServer(server.id, parsedOptions?.retry)\n );\n }\n\n this._isRestored = true;\n }\n\n /**\n * Track a pending connection promise for a server.\n * The promise is removed from the map when it settles.\n */\n private _trackConnection(serverId: string, promise: Promise<void>): void {\n const tracked = promise.finally(() => {\n // Only delete if it's still the same promise (not replaced by a newer one)\n if (this._pendingConnections.get(serverId) === tracked) {\n this._pendingConnections.delete(serverId);\n }\n });\n this._pendingConnections.set(serverId, tracked);\n }\n\n /**\n * Wait for all in-flight connection and discovery operations to settle.\n * This is useful when you need MCP tools to be available before proceeding,\n * e.g. before calling getAITools() after the agent wakes from hibernation.\n *\n * Returns once every pending connection has either connected and discovered,\n * failed, or timed out. Never rejects.\n *\n * @param options.timeout - Maximum time in milliseconds to wait.\n * `0` returns immediately without waiting.\n * `undefined` (default) waits indefinitely.\n */\n async waitForConnections(options?: { timeout?: number }): Promise<void> {\n if (this._pendingConnections.size === 0) {\n return;\n }\n if (options?.timeout != null && options.timeout <= 0) {\n return;\n }\n const settled = Promise.allSettled(this._pendingConnections.values());\n if (options?.timeout != null && options.timeout > 0) {\n let timerId: ReturnType<typeof setTimeout>;\n const timer = new Promise<void>((resolve) => {\n timerId = setTimeout(resolve, options.timeout);\n });\n await Promise.race([settled, timer]);\n clearTimeout(timerId!);\n } else {\n await settled;\n }\n }\n\n /**\n * Internal method to restore a single server connection and discovery\n */\n private async _restoreServer(\n serverId: string,\n retry?: RetryOptions\n ): Promise<void> {\n // Always try to connect - the connection logic will determine if OAuth is needed\n // If stored OAuth tokens are valid, connection will succeed automatically\n // If tokens are missing/invalid, connection will fail with Unauthorized\n // and state will be set to \"authenticating\"\n const maxAttempts = retry?.maxAttempts ?? 3;\n const baseDelayMs = retry?.baseDelayMs ?? 500;\n const maxDelayMs = retry?.maxDelayMs ?? 5000;\n\n const connectResult = await tryN(\n maxAttempts,\n async () => this.connectToServer(serverId),\n { baseDelayMs, maxDelayMs }\n ).catch((error) => {\n console.error(\n `Error connecting to ${serverId} after ${maxAttempts} attempts:`,\n error\n );\n return null;\n });\n\n if (connectResult?.state === MCPConnectionState.CONNECTED) {\n const discoverResult = await this.discoverIfConnected(serverId);\n if (discoverResult && !discoverResult.success) {\n console.error(`Error discovering ${serverId}:`, discoverResult.error);\n }\n }\n }\n\n /**\n * Connect to and register an MCP server\n *\n * @deprecated This method is maintained for backward compatibility.\n * For new code, use registerServer() and connectToServer() separately.\n *\n * @param url Server URL\n * @param options Connection options\n * @returns Object with server ID, auth URL (if OAuth), and client ID (if OAuth)\n */\n async connect(\n url: string,\n options: {\n // Allows you to reconnect to a server (in the case of an auth reconnect)\n reconnect?: {\n // server id\n id: string;\n oauthClientId?: string;\n oauthCode?: string;\n };\n // we're overriding authProvider here because we want to be able to access the auth URL\n transport?: MCPTransportOptions;\n client?: ConstructorParameters<typeof Client>[1];\n } = {}\n ): Promise<{\n id: string;\n authUrl?: string;\n clientId?: string;\n }> {\n /* Late initialization of jsonSchemaFn */\n /**\n * We need to delay loading ai sdk, because putting it in module scope is\n * causing issues with startup time.\n * The only place it's used is in getAITools, which only matters after\n * .connect() is called on at least one server.\n * So it's safe to delay loading it until .connect() is called.\n */\n await this.ensureJsonSchema();\n\n const id = options.reconnect?.id ?? nanoid(8);\n\n if (options.transport?.authProvider) {\n options.transport.authProvider.serverId = id;\n // reconnect with auth\n if (options.reconnect?.oauthClientId) {\n options.transport.authProvider.clientId =\n options.reconnect?.oauthClientId;\n }\n }\n\n if (isBlockedUrl(url)) {\n throw new Error(\n `Blocked URL: ${url} — MCP client connections to private/internal addresses are not allowed`\n );\n }\n\n // During OAuth reconnect, reuse existing connection to preserve state\n if (!options.reconnect?.oauthCode || !this.mcpConnections[id]) {\n const normalizedTransport = {\n ...options.transport,\n type: options.transport?.type ?? (\"auto\" as TransportType)\n };\n\n this.mcpConnections[id] = new MCPClientConnection(\n new URL(url),\n {\n name: this._name,\n version: this._version\n },\n {\n client: options.client ?? {},\n transport: normalizedTransport\n }\n );\n\n // Pipe connection-level observability events to the manager-level emitter\n // and track the subscription for cleanup.\n const store = new DisposableStore();\n // If we somehow already had disposables for this id, clear them first\n const existing = this._connectionDisposables.get(id);\n if (existing) existing.dispose();\n this._connectionDisposables.set(id, store);\n store.add(\n this.mcpConnections[id].onObservabilityEvent((event) => {\n this._onObservabilityEvent.fire(event);\n })\n );\n }\n\n // Initialize connection first. this will try connect\n await this.mcpConnections[id].init();\n\n // Handle OAuth completion if we have a reconnect code\n if (options.reconnect?.oauthCode) {\n try {\n await this.mcpConnections[id].completeAuthorization(\n options.reconnect.oauthCode\n );\n\n // Reinitialize connection\n await this.mcpConnections[id].init();\n } catch (error) {\n this._onObservabilityEvent.fire({\n type: \"mcp:client:connect\",\n payload: {\n url: url,\n transport: options.transport?.type ?? \"auto\",\n state: this.mcpConnections[id].connectionState,\n error: toErrorMessage(error)\n },\n timestamp: Date.now()\n });\n // Re-throw to signal failure to the caller\n throw error;\n }\n }\n\n // If connection is in authenticating state, return auth URL for OAuth flow\n const authUrl = options.transport?.authProvider?.authUrl;\n if (\n this.mcpConnections[id].connectionState ===\n MCPConnectionState.AUTHENTICATING &&\n authUrl &&\n options.transport?.authProvider?.redirectUrl\n ) {\n return {\n authUrl,\n clientId: options.transport?.authProvider?.clientId,\n id\n };\n }\n\n // If connection is connected, discover capabilities\n const discoverResult = await this.discoverIfConnected(id);\n if (discoverResult && !discoverResult.success) {\n throw new Error(\n `Failed to discover server capabilities: ${discoverResult.error}`\n );\n }\n\n return {\n id\n };\n }\n\n /**\n * Create an in-memory connection object and set up observability\n * Does NOT save to storage - use registerServer() for that\n * @returns The connection object (existing or newly created)\n */\n private createConnection(\n id: string,\n url: string,\n options: {\n client?: ConstructorParameters<typeof Client>[1];\n transport: MCPTransportOptions;\n }\n ): MCPClientConnection {\n // Return existing connection if already exists\n if (this.mcpConnections[id]) {\n return this.mcpConnections[id];\n }\n\n const normalizedTransport = {\n ...options.transport,\n type: options.transport?.type ?? (\"auto\" as TransportType)\n };\n\n this.mcpConnections[id] = new MCPClientConnection(\n new URL(url),\n {\n name: this._name,\n version: this._version\n },\n {\n client: { ...defaultClientOptions, ...options.client },\n transport: normalizedTransport\n }\n );\n\n // Pipe connection-level observability events to the manager-level emitter\n const store = new DisposableStore();\n const existing = this._connectionDisposables.get(id);\n if (existing) existing.dispose();\n this._connectionDisposables.set(id, store);\n store.add(\n this.mcpConnections[id].onObservabilityEvent((event) => {\n this._onObservabilityEvent.fire(event);\n })\n );\n\n return this.mcpConnections[id];\n }\n\n /**\n * Register an MCP server connection without connecting\n * Creates the connection object, sets up observability, and saves to storage\n *\n * @param id Server ID\n * @param options Registration options including URL, name, callback URL, and connection config\n * @returns Server ID\n */\n async registerServer(\n id: string,\n options: RegisterServerOptions\n ): Promise<string> {\n if (isBlockedUrl(options.url)) {\n throw new Error(\n `Blocked URL: ${options.url} — MCP client connections to private/internal addresses are not allowed`\n );\n }\n\n // Create the in-memory connection\n this.createConnection(id, options.url, {\n client: options.client,\n transport: {\n ...options.transport,\n type: options.transport?.type ?? (\"auto\" as TransportType)\n }\n });\n\n // Save to storage (exclude authProvider since it's recreated during restore)\n const { authProvider: _, ...transportWithoutAuth } =\n options.transport ?? {};\n this.saveServerToStorage({\n id,\n name: options.name,\n server_url: options.url,\n callback_url: options.callbackUrl ?? \"\",\n client_id: options.clientId ?? null,\n auth_url: options.authUrl ?? null,\n server_options: JSON.stringify({\n client: options.client,\n transport: transportWithoutAuth,\n retry: options.retry\n })\n });\n\n this._onServerStateChanged.fire();\n\n return id;\n }\n\n /**\n * Connect to an already registered MCP server and initialize the connection.\n *\n * For OAuth servers, returns `{ state: \"authenticating\", authUrl, clientId? }`.\n * The user must complete the OAuth flow via the authUrl, which triggers a\n * callback handled by `handleCallbackRequest()`.\n *\n * For non-OAuth servers, establishes the transport connection and returns\n * `{ state: \"connected\" }`. Call `discoverIfConnected()` afterwards to\n * discover capabilities and transition to \"ready\" state.\n *\n * @param id Server ID (must be registered first via registerServer())\n * @returns Connection result with current state and OAuth info (if applicable)\n */\n async connectToServer(id: string): Promise<MCPConnectionResult> {\n const conn = this.mcpConnections[id];\n if (!conn) {\n throw new Error(\n `Server ${id} is not registered. Call registerServer() first.`\n );\n }\n\n const error = await conn.init();\n this._onServerStateChanged.fire();\n\n switch (conn.connectionState) {\n case MCPConnectionState.FAILED:\n return {\n state: conn.connectionState,\n error: error ?? \"Unknown connection error\"\n };\n\n case MCPConnectionState.AUTHENTICATING: {\n const authUrl = conn.options.transport.authProvider?.authUrl;\n const redirectUrl = conn.options.transport.authProvider?.redirectUrl;\n\n if (!authUrl || !redirectUrl) {\n return {\n state: MCPConnectionState.FAILED,\n error: `OAuth configuration incomplete: missing ${!authUrl ? \"authUrl\" : \"redirectUrl\"}`\n };\n }\n\n const clientId = conn.options.transport.authProvider?.clientId;\n\n // Update storage with auth URL and client ID\n const servers = this.getServersFromStorage();\n const serverRow = servers.find((s) => s.id === id);\n if (serverRow) {\n this.saveServerToStorage({\n ...serverRow,\n auth_url: authUrl,\n client_id: clientId ?? null\n });\n // Broadcast again so clients receive the auth_url\n this._onServerStateChanged.fire();\n }\n\n this._onObservabilityEvent.fire({\n type: \"mcp:client:authorize\",\n payload: { serverId: id, authUrl, clientId },\n timestamp: Date.now()\n });\n\n return {\n state: conn.connectionState,\n authUrl,\n clientId\n };\n }\n\n case MCPConnectionState.CONNECTED:\n return { state: conn.connectionState };\n\n default:\n return {\n state: MCPConnectionState.FAILED,\n error: `Unexpected connection state after init: ${conn.connectionState}`\n };\n }\n }\n\n private extractServerIdFromState(state: string | null): string | null {\n if (!state) return null;\n const parts = state.split(\".\");\n return parts.length === 2 ? parts[1] : null;\n }\n\n isCallbackRequest(req: Request): boolean {\n if (req.method !== \"GET\") {\n return false;\n }\n\n const url = new URL(req.url);\n const state = url.searchParams.get(\"state\");\n const serverId = this.extractServerIdFromState(state);\n if (!serverId) {\n return false;\n }\n\n // Match by server ID AND verify the request origin + pathname matches the registered callback URL.\n // This prevents unrelated GET requests with a `state` param from being intercepted.\n const servers = this.getServersFromStorage();\n return servers.some((server) => {\n if (server.id !== serverId) return false;\n try {\n const storedUrl = new URL(server.callback_url);\n return (\n storedUrl.origin === url.origin && storedUrl.pathname === url.pathname\n );\n } catch {\n return false;\n }\n });\n }\n\n private validateCallbackRequest(\n req: Request\n ):\n | { valid: true; serverId: string; code: string; state: string }\n | { valid: false; serverId?: string; error: string } {\n const url = new URL(req.url);\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n const error = url.searchParams.get(\"error\");\n const errorDescription = url.searchParams.get(\"error_description\");\n\n // Early validation - return errors because we can't identify the connection\n if (!state) {\n return {\n valid: false,\n error: \"Unauthorized: no state provided\"\n };\n }\n\n const serverId = this.extractServerIdFromState(state);\n if (!serverId) {\n return {\n valid: false,\n error:\n \"No serverId found in state parameter. Expected format: {nonce}.{serverId}\"\n };\n }\n\n if (error) {\n return {\n serverId: serverId,\n valid: false,\n error: errorDescription || error\n };\n }\n\n if (!code) {\n return {\n serverId: serverId,\n valid: false,\n error: \"Unauthorized: no code provided\"\n };\n }\n\n const servers = this.getServersFromStorage();\n const serverExists = servers.some((server) => server.id === serverId);\n if (!serverExists) {\n return {\n serverId: serverId,\n valid: false,\n error: `No server found with id \"${serverId}\". Was the request matched with \\`isCallbackRequest()\\`?`\n };\n }\n\n if (this.mcpConnections[serverId] === undefined) {\n return {\n serverId: serverId,\n valid: false,\n error: `No connection found for serverId \"${serverId}\".`\n };\n }\n\n return {\n valid: true,\n serverId,\n code: code,\n state: state\n };\n }\n\n async handleCallbackRequest(req: Request): Promise<MCPOAuthCallbackResult> {\n const validation = this.validateCallbackRequest(req);\n\n if (!validation.valid) {\n if (validation.serverId && this.mcpConnections[validation.serverId]) {\n return this.failConnection(validation.serverId, validation.error);\n }\n\n return {\n serverId: validation.serverId,\n authSuccess: false,\n authError: validation.error\n };\n }\n\n const { serverId, code, state } = validation;\n const conn = this.mcpConnections[serverId]; // We have a valid connection - all errors from here should fail the connection\n\n try {\n if (!conn.options.transport.authProvider) {\n throw new Error(\n \"Trying to finalize authentication for a server connection without an authProvider\"\n );\n }\n\n const authProvider = conn.options.transport.authProvider;\n authProvider.serverId = serverId;\n\n // Two-phase state validation: check first (non-destructive), consume later\n // This prevents DoS attacks where attacker consumes valid state before legitimate callback\n const stateValidation = await authProvider.checkState(state);\n if (!stateValidation.valid) {\n throw new Error(stateValidation.error || \"Invalid state\");\n }\n\n // Already authenticated - just return success\n if (\n conn.connectionState === MCPConnectionState.READY ||\n conn.connectionState === MCPConnectionState.CONNECTED\n ) {\n this.clearServerAuthUrl(serverId);\n return { serverId, authSuccess: true };\n }\n\n if (conn.connectionState !== MCPConnectionState.AUTHENTICATING) {\n throw new Error(\n `Failed to authenticate: the client is in \"${conn.connectionState}\" state, expected \"authenticating\"`\n );\n }\n\n await authProvider.consumeState(state);\n await conn.completeAuthorization(code);\n await authProvider.deleteCodeVerifier();\n this.clearServerAuthUrl(serverId);\n conn.connectionError = null;\n this._onServerStateChanged.fire();\n\n return { serverId, authSuccess: true };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return this.failConnection(serverId, message);\n }\n }\n\n /**\n * Discover server capabilities if connection is in CONNECTED or READY state.\n * Transitions to DISCOVERING then READY (or CONNECTED on error).\n * Can be called to refresh server capabilities (e.g., from a UI refresh button).\n *\n * If called while a previous discovery is in-flight for the same server,\n * the previous discovery will be aborted.\n *\n * @param serverId The server ID to discover\n * @param options Optional configuration\n * @param options.timeoutMs Timeout in milliseconds (default: 30000)\n * @returns Result with current state and optional error, or undefined if connection not found\n */\n async discoverIfConnected(\n serverId: string,\n options: { timeoutMs?: number } = {}\n ): Promise<MCPDiscoverResult | undefined> {\n const conn = this.mcpConnections[serverId];\n if (!conn) {\n this._onObservabilityEvent.fire({\n type: \"mcp:client:discover\",\n payload: {},\n timestamp: Date.now()\n });\n return undefined;\n }\n\n // Delegate to connection's discover method which handles cancellation and timeout\n const result = await conn.discover(options);\n this._onServerStateChanged.fire();\n\n return {\n ...result,\n state: conn.connectionState\n };\n }\n\n /**\n * Establish connection in the background after OAuth completion.\n * This method connects to the server and discovers its capabilities.\n * The connection is automatically tracked so that `waitForConnections()`\n * will include it.\n * @param serverId The server ID to establish connection for\n */\n async establishConnection(serverId: string): Promise<void> {\n const promise = this._doEstablishConnection(serverId);\n this._trackConnection(serverId, promise);\n return promise;\n }\n\n private async _doEstablishConnection(serverId: string): Promise<void> {\n const conn = this.mcpConnections[serverId];\n if (!conn) {\n this._onObservabilityEvent.fire({\n type: \"mcp:client:preconnect\",\n payload: { serverId },\n timestamp: Date.now()\n });\n return;\n }\n\n // Skip if already discovering or ready - prevents duplicate work\n if (\n conn.connectionState === MCPConnectionState.DISCOVERING ||\n conn.connectionState === MCPConnectionState.READY\n ) {\n this._onObservabilityEvent.fire({\n type: \"mcp:client:connect\",\n payload: {\n url: conn.url.toString(),\n transport: conn.options.transport.type || \"unknown\",\n state: conn.connectionState\n },\n timestamp: Date.now()\n });\n return;\n }\n\n const retry = this.getServerRetryOptions(serverId);\n const maxAttempts = retry?.maxAttempts ?? 3;\n const baseDelayMs = retry?.baseDelayMs ?? 500;\n const maxDelayMs = retry?.maxDelayMs ?? 5000;\n\n const connectResult = await tryN(\n maxAttempts,\n async () => this.connectToServer(serverId),\n { baseDelayMs, maxDelayMs }\n );\n this._onServerStateChanged.fire();\n\n if (connectResult.state === MCPConnectionState.CONNECTED) {\n await this.discoverIfConnected(serverId);\n }\n\n this._onObservabilityEvent.fire({\n type: \"mcp:client:connect\",\n payload: {\n url: conn.url.toString(),\n transport: conn.options.transport.type || \"unknown\",\n state: conn.connectionState\n },\n timestamp: Date.now()\n });\n }\n\n /**\n * Configure OAuth callback handling\n * @param config OAuth callback configuration\n */\n configureOAuthCallback(config: MCPClientOAuthCallbackConfig): void {\n this._oauthCallbackConfig = config;\n }\n\n /**\n * Get the current OAuth callback configuration\n * @returns The current OAuth callback configuration\n */\n getOAuthCallbackConfig(): MCPClientOAuthCallbackConfig | undefined {\n return this._oauthCallbackConfig;\n }\n\n /**\n * @returns namespaced list of tools\n */\n listTools(): NamespacedData[\"tools\"] {\n return getNamespacedData(this.mcpConnections, \"tools\");\n }\n\n /**\n * Lazy-loads the jsonSchema function from the AI SDK.\n *\n * This defers importing the \"ai\" package until it's actually needed, which helps reduce\n * initial bundle size and startup time. The jsonSchema function is required for converting\n * MCP tools into AI SDK tool definitions via getAITools().\n *\n * @internal This method is for internal use only. It's automatically called before operations\n * that need jsonSchema (like getAITools() or OAuth flows). External consumers should not need\n * to call this directly.\n */\n async ensureJsonSchema() {\n if (!this.jsonSchema) {\n const { jsonSchema } = await import(\"ai\");\n this.jsonSchema = jsonSchema;\n }\n }\n\n /**\n * @returns a set of tools that you can use with the AI SDK\n */\n getAITools(): ToolSet {\n if (!this.jsonSchema) {\n throw new Error(\"jsonSchema not initialized.\");\n }\n\n // Warn if tools are being read from non-ready connections\n for (const [id, conn] of Object.entries(this.mcpConnections)) {\n if (\n conn.connectionState !== MCPConnectionState.READY &&\n conn.connectionState !== MCPConnectionState.AUTHENTICATING\n ) {\n console.warn(\n `[getAITools] WARNING: Reading tools from connection ${id} in state \"${conn.connectionState}\". Tools may not be loaded yet.`\n );\n }\n }\n\n const entries: [string, ToolSet[string]][] = [];\n for (const tool of getNamespacedData(this.mcpConnections, \"tools\")) {\n try {\n const toolKey = `tool_${tool.serverId.replace(/-/g, \"\")}_${tool.name}`;\n entries.push([\n toolKey,\n {\n description: tool.description,\n execute: async (args) => {\n const result = await this.callTool({\n arguments: args,\n name: tool.name,\n serverId: tool.serverId\n });\n if (result.isError) {\n const content = result.content as\n | Array<{ type: string; text?: string }>\n | undefined;\n const textContent = content?.[0];\n const message =\n textContent?.type === \"text\" && textContent.text\n ? textContent.text\n : \"Tool call failed\";\n throw new Error(message);\n }\n return result;\n },\n inputSchema: tool.inputSchema\n ? this.jsonSchema!(tool.inputSchema as JSONSchema7)\n : this.jsonSchema!({ type: \"object\" } as JSONSchema7),\n outputSchema: tool.outputSchema\n ? this.jsonSchema!(tool.outputSchema as JSONSchema7)\n : undefined\n }\n ]);\n } catch (e) {\n console.warn(\n `[getAITools] Skipping tool \"${tool.name}\" from \"${tool.serverId}\": ${e}`\n );\n }\n }\n return Object.fromEntries(entries);\n }\n\n /**\n * @deprecated this has been renamed to getAITools(), and unstable_getAITools will be removed in the next major version\n * @returns a set of tools that you can use with the AI SDK\n */\n unstable_getAITools(): ToolSet {\n if (!this._didWarnAboutUnstableGetAITools) {\n this._didWarnAboutUnstableGetAITools = true;\n console.warn(\n \"unstable_getAITools is deprecated, use getAITools instead. unstable_getAITools will be removed in the next major version.\"\n );\n }\n return this.getAITools();\n }\n\n /**\n * Closes all active in-memory connections to MCP servers.\n *\n * Note: This only closes the transport connections - it does NOT remove\n * servers from storage. Servers will still be listed and their callback\n * URLs will still match incoming OAuth requests.\n *\n * Use removeServer() instead if you want to fully clean up a server\n * (closes connection AND removes from storage).\n */\n async closeAllConnections() {\n const ids = Object.keys(this.mcpConnections);\n\n // Clear all pending connection tracking\n this._pendingConnections.clear();\n\n // Cancel all in-flight discoveries\n for (const id of ids) {\n this.mcpConnections[id].cancelDiscovery();\n }\n\n await Promise.all(\n ids.map(async (id) => {\n await this.mcpConnections[id].client.close();\n })\n );\n // Dispose all per-connection subscriptions\n for (const id of ids) {\n const store = this._connectionDisposables.get(id);\n if (store) store.dispose();\n this._connectionDisposables.delete(id);\n delete this.mcpConnections[id];\n }\n }\n\n /**\n * Closes a connection to an MCP server\n * @param id The id of the connection to close\n */\n async closeConnection(id: string) {\n if (!this.mcpConnections[id]) {\n throw new Error(`Connection with id \"${id}\" does not exist.`);\n }\n\n // Cancel any in-flight discovery\n this.mcpConnections[id].cancelDiscovery();\n\n // Remove from pending so waitForConnections() doesn't block on a closed server\n this._pendingConnections.delete(id);\n\n await this.mcpConnections[id].client.close();\n delete this.mcpConnections[id];\n\n const store = this._connectionDisposables.get(id);\n if (store) store.dispose();\n this._connectionDisposables.delete(id);\n }\n\n /**\n * Remove an MCP server - closes connection if active and removes from storage.\n */\n async removeServer(serverId: string): Promise<void> {\n if (this.mcpConnections[serverId]) {\n try {\n await this.closeConnection(serverId);\n } catch (_e) {\n // Ignore errors when closing\n }\n }\n this.removeServerFromStorage(serverId);\n this._onServerStateChanged.fire();\n }\n\n /**\n * List all MCP servers from storage\n */\n listServers(): MCPServerRow[] {\n return this.getServersFromStorage();\n }\n\n /**\n * Dispose the manager and all resources.\n */\n async dispose(): Promise<void> {\n try {\n await this.closeAllConnections();\n } finally {\n // Dispose manager-level emitters\n this._onServerStateChanged.dispose();\n this._onObservabilityEvent.dispose();\n }\n }\n\n /**\n * @returns namespaced list of prompts\n */\n listPrompts(): NamespacedData[\"prompts\"] {\n return getNamespacedData(this.mcpConnections, \"prompts\");\n }\n\n /**\n * @returns namespaced list of tools\n */\n listResources(): NamespacedData[\"resources\"] {\n return getNamespacedData(this.mcpConnections, \"resources\");\n }\n\n /**\n * @returns namespaced list of resource templates\n */\n listResourceTemplates(): NamespacedData[\"resourceTemplates\"] {\n return getNamespacedData(this.mcpConnections, \"resourceTemplates\");\n }\n\n /**\n * Namespaced version of callTool\n */\n async callTool(\n params: CallToolRequest[\"params\"] & { serverId: string },\n resultSchema?:\n | typeof CallToolResultSchema\n | typeof CompatibilityCallToolResultSchema,\n options?: RequestOptions\n ) {\n const { serverId, ...mcpParams } = params;\n const unqualifiedName = mcpParams.name.replace(`${serverId}.`, \"\");\n return this.mcpConnections[serverId].client.callTool(\n {\n ...mcpParams,\n name: unqualifiedName\n },\n resultSchema,\n options\n );\n }\n\n /**\n * Namespaced version of readResource\n */\n readResource(\n params: ReadResourceRequest[\"params\"] & { serverId: string },\n options: RequestOptions\n ) {\n return this.mcpConnections[params.serverId].client.readResource(\n params,\n options\n );\n }\n\n /**\n * Namespaced version of getPrompt\n */\n getPrompt(\n params: GetPromptRequest[\"params\"] & { serverId: string },\n options: RequestOptions\n ) {\n return this.mcpConnections[params.serverId].client.getPrompt(\n params,\n options\n );\n }\n}\n\ntype NamespacedData = {\n tools: (Tool & { serverId: string })[];\n prompts: (Prompt & { serverId: string })[];\n resources: (Resource & { serverId: string })[];\n resourceTemplates: (ResourceTemplate & { serverId: string })[];\n};\n\nexport function getNamespacedData<T extends keyof NamespacedData>(\n mcpClients: Record<string, MCPClientConnection>,\n type: T\n): NamespacedData[T] {\n const sets = Object.entries(mcpClients).map(([name, conn]) => {\n return { data: conn[type], name };\n });\n\n const namespacedData = sets.flatMap(({ name: serverId, data }) => {\n return data.map((item) => {\n return {\n ...item,\n // we add a serverId so we can easily pull it out and send the tool call to the right server\n serverId\n };\n });\n });\n\n return namespacedData as NamespacedData[T]; // Type assertion needed due to TS limitations with conditional return types\n}\n"],"mappings":"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"}
|
|
@@ -115,7 +115,7 @@ var DurableObjectOAuthClientProvider = class {
|
|
|
115
115
|
async consumeState(state) {
|
|
116
116
|
const parts = state.split(".");
|
|
117
117
|
if (parts.length !== 2) {
|
|
118
|
-
console.warn(`[OAuth] consumeState called with invalid state format
|
|
118
|
+
console.warn(`[OAuth] consumeState called with invalid state format`);
|
|
119
119
|
return;
|
|
120
120
|
}
|
|
121
121
|
const [nonce] = parts;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"do-oauth-client-provider.js","names":[],"sources":["../../src/mcp/do-oauth-client-provider.ts"],"sourcesContent":["import type { OAuthClientProvider } from \"@modelcontextprotocol/sdk/client/auth.js\";\nimport type {\n OAuthClientInformation,\n OAuthClientInformationFull,\n OAuthClientMetadata,\n OAuthTokens\n} from \"@modelcontextprotocol/sdk/shared/auth.js\";\nimport { nanoid } from \"nanoid\";\n\nconst STATE_EXPIRATION_MS = 10 * 60 * 1000; // 10 minutes\n\ninterface StoredState {\n nonce: string;\n serverId: string;\n createdAt: number;\n}\n\n// A slight extension to the standard OAuthClientProvider interface because `redirectToAuthorization` doesn't give us the interface we need\n// This allows us to track authentication for a specific server and associated dynamic client registration\nexport interface AgentMcpOAuthProvider extends OAuthClientProvider {\n authUrl: string | undefined;\n clientId: string | undefined;\n serverId: string | undefined;\n checkState(\n state: string\n ): Promise<{ valid: boolean; serverId?: string; error?: string }>;\n consumeState(state: string): Promise<void>;\n deleteCodeVerifier(): Promise<void>;\n}\n\n/**\n * @deprecated Use {@link AgentMcpOAuthProvider} instead.\n */\nexport type AgentsOAuthProvider = AgentMcpOAuthProvider;\n\nexport class DurableObjectOAuthClientProvider implements AgentMcpOAuthProvider {\n private _authUrl_: string | undefined;\n private _serverId_: string | undefined;\n private _clientId_: string | undefined;\n\n constructor(\n public storage: DurableObjectStorage,\n public clientName: string,\n public baseRedirectUrl: string\n ) {\n if (!storage) {\n throw new Error(\n \"DurableObjectOAuthClientProvider requires a valid DurableObjectStorage instance\"\n );\n }\n }\n\n get clientMetadata(): OAuthClientMetadata {\n return {\n client_name: this.clientName,\n client_uri: this.clientUri,\n grant_types: [\"authorization_code\", \"refresh_token\"],\n redirect_uris: [this.redirectUrl],\n response_types: [\"code\"],\n token_endpoint_auth_method: \"none\"\n };\n }\n\n get clientUri() {\n return new URL(this.redirectUrl).origin;\n }\n\n get redirectUrl() {\n return this.baseRedirectUrl;\n }\n\n get clientId() {\n if (!this._clientId_) {\n throw new Error(\"Trying to access clientId before it was set\");\n }\n return this._clientId_;\n }\n\n set clientId(clientId_: string) {\n this._clientId_ = clientId_;\n }\n\n get serverId() {\n if (!this._serverId_) {\n throw new Error(\"Trying to access serverId before it was set\");\n }\n return this._serverId_;\n }\n\n set serverId(serverId_: string) {\n this._serverId_ = serverId_;\n }\n\n keyPrefix(clientId: string) {\n return `/${this.clientName}/${this.serverId}/${clientId}`;\n }\n\n clientInfoKey(clientId: string) {\n return `${this.keyPrefix(clientId)}/client_info/`;\n }\n\n async clientInformation(): Promise<OAuthClientInformation | undefined> {\n if (!this._clientId_) {\n return undefined;\n }\n return (\n (await this.storage.get<OAuthClientInformation>(\n this.clientInfoKey(this.clientId)\n )) ?? undefined\n );\n }\n\n async saveClientInformation(\n clientInformation: OAuthClientInformationFull\n ): Promise<void> {\n await this.storage.put(\n this.clientInfoKey(clientInformation.client_id),\n clientInformation\n );\n this.clientId = clientInformation.client_id;\n }\n\n tokenKey(clientId: string) {\n return `${this.keyPrefix(clientId)}/token`;\n }\n\n async tokens(): Promise<OAuthTokens | undefined> {\n if (!this._clientId_) {\n return undefined;\n }\n return (\n (await this.storage.get<OAuthTokens>(this.tokenKey(this.clientId))) ??\n undefined\n );\n }\n\n async saveTokens(tokens: OAuthTokens): Promise<void> {\n await this.storage.put(this.tokenKey(this.clientId), tokens);\n }\n\n get authUrl() {\n return this._authUrl_;\n }\n\n stateKey(nonce: string) {\n return `/${this.clientName}/${this.serverId}/state/${nonce}`;\n }\n\n async state(): Promise<string> {\n const nonce = nanoid();\n const state = `${nonce}.${this.serverId}`;\n const storedState: StoredState = {\n nonce,\n serverId: this.serverId,\n createdAt: Date.now()\n };\n await this.storage.put(this.stateKey(nonce), storedState);\n return state;\n }\n\n async checkState(\n state: string\n ): Promise<{ valid: boolean; serverId?: string; error?: string }> {\n const parts = state.split(\".\");\n if (parts.length !== 2) {\n return { valid: false, error: \"Invalid state format\" };\n }\n\n const [nonce, serverId] = parts;\n const key = this.stateKey(nonce);\n const storedState = await this.storage.get<StoredState>(key);\n\n if (!storedState) {\n return { valid: false, error: \"State not found or already used\" };\n }\n\n if (storedState.serverId !== serverId) {\n await this.storage.delete(key);\n return { valid: false, error: \"State serverId mismatch\" };\n }\n\n const age = Date.now() - storedState.createdAt;\n if (age > STATE_EXPIRATION_MS) {\n await this.storage.delete(key);\n return { valid: false, error: \"State expired\" };\n }\n\n return { valid: true, serverId };\n }\n\n async consumeState(state: string): Promise<void> {\n const parts = state.split(\".\");\n if (parts.length !== 2) {\n // This should never happen since checkState validates format first.\n // Log for debugging but don't throw - state consumption is best-effort.\n console.warn(\n `[OAuth] consumeState called with invalid state format: ${state.substring(0, 20)}...`\n );\n return;\n }\n const [nonce] = parts;\n await this.storage.delete(this.stateKey(nonce));\n }\n\n async redirectToAuthorization(authUrl: URL): Promise<void> {\n this._authUrl_ = authUrl.toString();\n }\n\n async invalidateCredentials(\n scope: \"all\" | \"client\" | \"tokens\" | \"verifier\"\n ): Promise<void> {\n if (!this._clientId_) return;\n\n const deleteKeys: string[] = [];\n\n if (scope === \"all\" || scope === \"client\") {\n deleteKeys.push(this.clientInfoKey(this.clientId));\n }\n if (scope === \"all\" || scope === \"tokens\") {\n deleteKeys.push(this.tokenKey(this.clientId));\n }\n if (scope === \"all\" || scope === \"verifier\") {\n deleteKeys.push(this.codeVerifierKey(this.clientId));\n }\n\n if (deleteKeys.length > 0) {\n await this.storage.delete(deleteKeys);\n }\n }\n\n codeVerifierKey(clientId: string) {\n return `${this.keyPrefix(clientId)}/code_verifier`;\n }\n\n async saveCodeVerifier(verifier: string): Promise<void> {\n const key = this.codeVerifierKey(this.clientId);\n\n // Don't overwrite existing verifier to preserve first PKCE verifier\n const existing = await this.storage.get<string>(key);\n if (existing) {\n return;\n }\n\n await this.storage.put(key, verifier);\n }\n\n async codeVerifier(): Promise<string> {\n const codeVerifier = await this.storage.get<string>(\n this.codeVerifierKey(this.clientId)\n );\n if (!codeVerifier) {\n throw new Error(\"No code verifier found\");\n }\n return codeVerifier;\n }\n\n async deleteCodeVerifier(): Promise<void> {\n await this.storage.delete(this.codeVerifierKey(this.clientId));\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"do-oauth-client-provider.js","names":[],"sources":["../../src/mcp/do-oauth-client-provider.ts"],"sourcesContent":["import type { OAuthClientProvider } from \"@modelcontextprotocol/sdk/client/auth.js\";\nimport type {\n OAuthClientInformation,\n OAuthClientInformationFull,\n OAuthClientMetadata,\n OAuthTokens\n} from \"@modelcontextprotocol/sdk/shared/auth.js\";\nimport { nanoid } from \"nanoid\";\n\nconst STATE_EXPIRATION_MS = 10 * 60 * 1000; // 10 minutes\n\ninterface StoredState {\n nonce: string;\n serverId: string;\n createdAt: number;\n}\n\n// A slight extension to the standard OAuthClientProvider interface because `redirectToAuthorization` doesn't give us the interface we need\n// This allows us to track authentication for a specific server and associated dynamic client registration\nexport interface AgentMcpOAuthProvider extends OAuthClientProvider {\n authUrl: string | undefined;\n clientId: string | undefined;\n serverId: string | undefined;\n checkState(\n state: string\n ): Promise<{ valid: boolean; serverId?: string; error?: string }>;\n consumeState(state: string): Promise<void>;\n deleteCodeVerifier(): Promise<void>;\n}\n\n/**\n * @deprecated Use {@link AgentMcpOAuthProvider} instead.\n */\nexport type AgentsOAuthProvider = AgentMcpOAuthProvider;\n\nexport class DurableObjectOAuthClientProvider implements AgentMcpOAuthProvider {\n private _authUrl_: string | undefined;\n private _serverId_: string | undefined;\n private _clientId_: string | undefined;\n\n constructor(\n public storage: DurableObjectStorage,\n public clientName: string,\n public baseRedirectUrl: string\n ) {\n if (!storage) {\n throw new Error(\n \"DurableObjectOAuthClientProvider requires a valid DurableObjectStorage instance\"\n );\n }\n }\n\n get clientMetadata(): OAuthClientMetadata {\n return {\n client_name: this.clientName,\n client_uri: this.clientUri,\n grant_types: [\"authorization_code\", \"refresh_token\"],\n redirect_uris: [this.redirectUrl],\n response_types: [\"code\"],\n token_endpoint_auth_method: \"none\"\n };\n }\n\n get clientUri() {\n return new URL(this.redirectUrl).origin;\n }\n\n get redirectUrl() {\n return this.baseRedirectUrl;\n }\n\n get clientId() {\n if (!this._clientId_) {\n throw new Error(\"Trying to access clientId before it was set\");\n }\n return this._clientId_;\n }\n\n set clientId(clientId_: string) {\n this._clientId_ = clientId_;\n }\n\n get serverId() {\n if (!this._serverId_) {\n throw new Error(\"Trying to access serverId before it was set\");\n }\n return this._serverId_;\n }\n\n set serverId(serverId_: string) {\n this._serverId_ = serverId_;\n }\n\n keyPrefix(clientId: string) {\n return `/${this.clientName}/${this.serverId}/${clientId}`;\n }\n\n clientInfoKey(clientId: string) {\n return `${this.keyPrefix(clientId)}/client_info/`;\n }\n\n async clientInformation(): Promise<OAuthClientInformation | undefined> {\n if (!this._clientId_) {\n return undefined;\n }\n return (\n (await this.storage.get<OAuthClientInformation>(\n this.clientInfoKey(this.clientId)\n )) ?? undefined\n );\n }\n\n async saveClientInformation(\n clientInformation: OAuthClientInformationFull\n ): Promise<void> {\n await this.storage.put(\n this.clientInfoKey(clientInformation.client_id),\n clientInformation\n );\n this.clientId = clientInformation.client_id;\n }\n\n tokenKey(clientId: string) {\n return `${this.keyPrefix(clientId)}/token`;\n }\n\n async tokens(): Promise<OAuthTokens | undefined> {\n if (!this._clientId_) {\n return undefined;\n }\n return (\n (await this.storage.get<OAuthTokens>(this.tokenKey(this.clientId))) ??\n undefined\n );\n }\n\n async saveTokens(tokens: OAuthTokens): Promise<void> {\n await this.storage.put(this.tokenKey(this.clientId), tokens);\n }\n\n get authUrl() {\n return this._authUrl_;\n }\n\n stateKey(nonce: string) {\n return `/${this.clientName}/${this.serverId}/state/${nonce}`;\n }\n\n async state(): Promise<string> {\n const nonce = nanoid();\n const state = `${nonce}.${this.serverId}`;\n const storedState: StoredState = {\n nonce,\n serverId: this.serverId,\n createdAt: Date.now()\n };\n await this.storage.put(this.stateKey(nonce), storedState);\n return state;\n }\n\n async checkState(\n state: string\n ): Promise<{ valid: boolean; serverId?: string; error?: string }> {\n const parts = state.split(\".\");\n if (parts.length !== 2) {\n return { valid: false, error: \"Invalid state format\" };\n }\n\n const [nonce, serverId] = parts;\n const key = this.stateKey(nonce);\n const storedState = await this.storage.get<StoredState>(key);\n\n if (!storedState) {\n return { valid: false, error: \"State not found or already used\" };\n }\n\n if (storedState.serverId !== serverId) {\n await this.storage.delete(key);\n return { valid: false, error: \"State serverId mismatch\" };\n }\n\n const age = Date.now() - storedState.createdAt;\n if (age > STATE_EXPIRATION_MS) {\n await this.storage.delete(key);\n return { valid: false, error: \"State expired\" };\n }\n\n return { valid: true, serverId };\n }\n\n async consumeState(state: string): Promise<void> {\n const parts = state.split(\".\");\n if (parts.length !== 2) {\n // This should never happen since checkState validates format first.\n // Log for debugging but don't throw - state consumption is best-effort.\n console.warn(`[OAuth] consumeState called with invalid state format`);\n return;\n }\n const [nonce] = parts;\n await this.storage.delete(this.stateKey(nonce));\n }\n\n async redirectToAuthorization(authUrl: URL): Promise<void> {\n this._authUrl_ = authUrl.toString();\n }\n\n async invalidateCredentials(\n scope: \"all\" | \"client\" | \"tokens\" | \"verifier\"\n ): Promise<void> {\n if (!this._clientId_) return;\n\n const deleteKeys: string[] = [];\n\n if (scope === \"all\" || scope === \"client\") {\n deleteKeys.push(this.clientInfoKey(this.clientId));\n }\n if (scope === \"all\" || scope === \"tokens\") {\n deleteKeys.push(this.tokenKey(this.clientId));\n }\n if (scope === \"all\" || scope === \"verifier\") {\n deleteKeys.push(this.codeVerifierKey(this.clientId));\n }\n\n if (deleteKeys.length > 0) {\n await this.storage.delete(deleteKeys);\n }\n }\n\n codeVerifierKey(clientId: string) {\n return `${this.keyPrefix(clientId)}/code_verifier`;\n }\n\n async saveCodeVerifier(verifier: string): Promise<void> {\n const key = this.codeVerifierKey(this.clientId);\n\n // Don't overwrite existing verifier to preserve first PKCE verifier\n const existing = await this.storage.get<string>(key);\n if (existing) {\n return;\n }\n\n await this.storage.put(key, verifier);\n }\n\n async codeVerifier(): Promise<string> {\n const codeVerifier = await this.storage.get<string>(\n this.codeVerifierKey(this.clientId)\n );\n if (!codeVerifier) {\n throw new Error(\"No code verifier found\");\n }\n return codeVerifier;\n }\n\n async deleteCodeVerifier(): Promise<void> {\n await this.storage.delete(this.codeVerifierKey(this.clientId));\n }\n}\n"],"mappings":"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"}
|
package/dist/mcp/index.d.ts
CHANGED
|
@@ -1,181 +1,22 @@
|
|
|
1
|
-
import { a as BaseTransportType, c as
|
|
1
|
+
import { C as ServeOptions, S as McpClientOptions, _ as WorkerTransportOptions, a as RPCClientTransport, b as BaseTransportType, c as RPCServerTransportOptions, d as createMcpHandler, f as experimental_createMcpHandler, g as WorkerTransport, h as TransportState, l as RPC_DO_PREFIX, m as getMcpAuthContext, o as RPCClientTransportOptions, p as McpAuthContext, s as RPCServerTransport, u as CreateMcpHandlerOptions, v as SSEEdgeClientTransport, x as MaybePromise, y as StreamableHTTPEdgeClientTransport } from "../client-storage-tusTuoSF.js";
|
|
2
2
|
import { MCPClientOAuthCallbackConfig, MCPClientOAuthResult, MCPConnectionResult, MCPDiscoverResult, MCPServerOptions } from "./client.js";
|
|
3
3
|
import { Agent, Connection, ConnectionContext } from "../index.js";
|
|
4
|
-
import {
|
|
5
|
-
import { StreamableHTTPClientTransport, StreamableHTTPClientTransportOptions } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
6
|
-
import { ElicitRequest, ElicitRequestSchema, ElicitResult, ElicitResult as ElicitResult$1, InitializeRequestParams, JSONRPCMessage, MessageExtraInfo, RequestId } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { ElicitRequest, ElicitRequestSchema, ElicitResult, ElicitResult as ElicitResult$1, JSONRPCMessage, MessageExtraInfo } from "@modelcontextprotocol/sdk/types.js";
|
|
7
5
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
6
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
9
|
-
import { Transport, TransportSendOptions } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
10
|
-
import { EventStore } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
11
7
|
|
|
12
|
-
//#region src/mcp/client-transports.d.ts
|
|
13
|
-
/**
|
|
14
|
-
* @deprecated Use SSEClientTransport from @modelcontextprotocol/sdk/client/sse.js instead. This alias will be removed in the next major version.
|
|
15
|
-
*/
|
|
16
|
-
declare class SSEEdgeClientTransport extends SSEClientTransport {
|
|
17
|
-
constructor(url: URL, options: SSEClientTransportOptions);
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* @deprecated Use StreamableHTTPClientTransport from @modelcontextprotocol/sdk/client/streamableHttp.js instead. This alias will be removed in the next major version.
|
|
21
|
-
*/
|
|
22
|
-
declare class StreamableHTTPEdgeClientTransport extends StreamableHTTPClientTransport {
|
|
23
|
-
constructor(url: URL, options: StreamableHTTPClientTransportOptions);
|
|
24
|
-
}
|
|
25
|
-
//#endregion
|
|
26
|
-
//#region src/mcp/worker-transport.d.ts
|
|
27
|
-
interface MCPStorageApi {
|
|
28
|
-
get(): Promise<TransportState | undefined> | TransportState | undefined;
|
|
29
|
-
set(state: TransportState): Promise<void> | void;
|
|
30
|
-
}
|
|
31
|
-
interface TransportState {
|
|
32
|
-
sessionId?: string;
|
|
33
|
-
initialized: boolean;
|
|
34
|
-
initializeParams?: InitializeRequestParams;
|
|
35
|
-
}
|
|
36
|
-
interface WorkerTransportOptions {
|
|
37
|
-
/**
|
|
38
|
-
* Function that generates a session ID for the transport.
|
|
39
|
-
* The session ID SHOULD be globally unique and cryptographically secure.
|
|
40
|
-
* Return undefined to disable session management (stateless mode).
|
|
41
|
-
*/
|
|
42
|
-
sessionIdGenerator?: () => string;
|
|
43
|
-
/**
|
|
44
|
-
* Enable traditional Request/Response mode, this will disable streaming.
|
|
45
|
-
*/
|
|
46
|
-
enableJsonResponse?: boolean;
|
|
47
|
-
/**
|
|
48
|
-
* Callback fired when a new session is initialized.
|
|
49
|
-
*/
|
|
50
|
-
onsessioninitialized?: (sessionId: string) => void;
|
|
51
|
-
/**
|
|
52
|
-
* Callback fired when a session is closed via DELETE request.
|
|
53
|
-
*/
|
|
54
|
-
onsessionclosed?: (sessionId: string) => void;
|
|
55
|
-
corsOptions?: CORSOptions;
|
|
56
|
-
/**
|
|
57
|
-
* Optional storage api for persisting transport state.
|
|
58
|
-
* Use this to store session state in Durable Object/Agent storage
|
|
59
|
-
* so it survives hibernation/restart.
|
|
60
|
-
*/
|
|
61
|
-
storage?: MCPStorageApi;
|
|
62
|
-
/**
|
|
63
|
-
* Event store for resumability support.
|
|
64
|
-
* If provided, enables clients to reconnect and resume messages using Last-Event-ID.
|
|
65
|
-
*/
|
|
66
|
-
eventStore?: EventStore;
|
|
67
|
-
/**
|
|
68
|
-
* Retry interval in milliseconds to suggest to clients in SSE retry field.
|
|
69
|
-
* Controls client reconnection timing for polling behavior.
|
|
70
|
-
*/
|
|
71
|
-
retryInterval?: number;
|
|
72
|
-
}
|
|
73
|
-
declare class WorkerTransport implements Transport {
|
|
74
|
-
started: boolean;
|
|
75
|
-
private initialized;
|
|
76
|
-
private sessionIdGenerator?;
|
|
77
|
-
private enableJsonResponse;
|
|
78
|
-
private onsessioninitialized?;
|
|
79
|
-
private onsessionclosed?;
|
|
80
|
-
private standaloneSseStreamId;
|
|
81
|
-
private streamMapping;
|
|
82
|
-
private requestToStreamMapping;
|
|
83
|
-
private requestResponseMap;
|
|
84
|
-
private corsOptions?;
|
|
85
|
-
private storage?;
|
|
86
|
-
private stateRestored;
|
|
87
|
-
private eventStore?;
|
|
88
|
-
private retryInterval?;
|
|
89
|
-
private initializeParams?;
|
|
90
|
-
sessionId?: string;
|
|
91
|
-
onclose?: () => void;
|
|
92
|
-
onerror?: (error: Error) => void;
|
|
93
|
-
onmessage?: (message: JSONRPCMessage, extra?: MessageExtraInfo) => void;
|
|
94
|
-
constructor(options?: WorkerTransportOptions);
|
|
95
|
-
/**
|
|
96
|
-
* Restore transport state from persistent storage.
|
|
97
|
-
* This is automatically called on start.
|
|
98
|
-
*/
|
|
99
|
-
private restoreState;
|
|
100
|
-
/**
|
|
101
|
-
* Persist current transport state to storage.
|
|
102
|
-
*/
|
|
103
|
-
private saveState;
|
|
104
|
-
start(): Promise<void>;
|
|
105
|
-
/**
|
|
106
|
-
* Validates the MCP-Protocol-Version header on incoming requests.
|
|
107
|
-
*
|
|
108
|
-
* This performs a simple check: if a version header is present, it must be
|
|
109
|
-
* in the SUPPORTED_PROTOCOL_VERSIONS list. We do not track the negotiated
|
|
110
|
-
* version or enforce version consistency across requests - the SDK handles
|
|
111
|
-
* version negotiation during initialization, and we simply reject any
|
|
112
|
-
* explicitly unsupported versions.
|
|
113
|
-
*
|
|
114
|
-
* - Header present and supported: Accept
|
|
115
|
-
* - Header present and unsupported: 400 Bad Request
|
|
116
|
-
* - Header missing: Accept (version validation is optional)
|
|
117
|
-
*/
|
|
118
|
-
private validateProtocolVersion;
|
|
119
|
-
private getHeaders;
|
|
120
|
-
handleRequest(request: Request, parsedBody?: unknown): Promise<Response>;
|
|
121
|
-
private handleGetRequest;
|
|
122
|
-
private handlePostRequest;
|
|
123
|
-
private handleDeleteRequest;
|
|
124
|
-
private handleOptionsRequest;
|
|
125
|
-
private handleUnsupportedRequest;
|
|
126
|
-
private validateSession;
|
|
127
|
-
close(): Promise<void>;
|
|
128
|
-
/**
|
|
129
|
-
* Close an SSE stream for a specific request, triggering client reconnection.
|
|
130
|
-
* Use this to implement polling behavior during long-running operations -
|
|
131
|
-
* client will reconnect after the retry interval specified in the priming event.
|
|
132
|
-
*/
|
|
133
|
-
closeSSEStream(requestId: RequestId): void;
|
|
134
|
-
send(message: JSONRPCMessage, options?: TransportSendOptions): Promise<void>;
|
|
135
|
-
}
|
|
136
|
-
//#endregion
|
|
137
|
-
//#region src/mcp/auth-context.d.ts
|
|
138
|
-
interface McpAuthContext {
|
|
139
|
-
props: Record<string, unknown>;
|
|
140
|
-
}
|
|
141
|
-
declare function getMcpAuthContext(): McpAuthContext | undefined;
|
|
142
|
-
//#endregion
|
|
143
|
-
//#region src/mcp/handler.d.ts
|
|
144
|
-
interface CreateMcpHandlerOptions extends WorkerTransportOptions {
|
|
145
|
-
/**
|
|
146
|
-
* The route path that this MCP handler should respond to.
|
|
147
|
-
* If specified, the handler will only process requests that match this route.
|
|
148
|
-
* @default "/mcp"
|
|
149
|
-
*/
|
|
150
|
-
route?: string;
|
|
151
|
-
/**
|
|
152
|
-
* An optional auth context to use for handling MCP requests.
|
|
153
|
-
* If not provided, the handler will look for props in the execution context.
|
|
154
|
-
*/
|
|
155
|
-
authContext?: McpAuthContext;
|
|
156
|
-
/**
|
|
157
|
-
* An optional transport to use for handling MCP requests.
|
|
158
|
-
* If not provided, a WorkerTransport will be created with the provided WorkerTransportOptions.
|
|
159
|
-
*/
|
|
160
|
-
transport?: WorkerTransport;
|
|
161
|
-
}
|
|
162
|
-
declare function createMcpHandler(server: McpServer | Server, options?: CreateMcpHandlerOptions): (request: Request, env: unknown, ctx: ExecutionContext) => Promise<Response>;
|
|
163
|
-
/**
|
|
164
|
-
* @deprecated This has been renamed to createMcpHandler, and experimental_createMcpHandler will be removed in the next major version
|
|
165
|
-
*/
|
|
166
|
-
declare function experimental_createMcpHandler(server: McpServer | Server, options?: CreateMcpHandlerOptions): (request: Request, env: unknown, ctx: ExecutionContext) => Promise<Response>;
|
|
167
|
-
//#endregion
|
|
168
8
|
//#region src/mcp/index.d.ts
|
|
169
9
|
declare abstract class McpAgent<Env extends Cloudflare.Env = Cloudflare.Env, State = unknown, Props extends Record<string, unknown> = Record<string, unknown>> extends Agent<Env, State, Props> {
|
|
170
10
|
private _transport?;
|
|
171
11
|
props?: Props;
|
|
12
|
+
shouldSendProtocolMessages(_connection: Connection, ctx: ConnectionContext): boolean;
|
|
172
13
|
abstract server: MaybePromise<McpServer | Server>;
|
|
173
14
|
abstract init(): Promise<void>;
|
|
174
15
|
setInitializeRequest(initializeRequest: JSONRPCMessage): Promise<void>;
|
|
175
16
|
getInitializeRequest(): Promise<JSONRPCMessage | undefined>;
|
|
176
17
|
/** Read the transport type for this agent.
|
|
177
|
-
* This relies on the naming scheme being `sse:${sessionId}
|
|
178
|
-
*
|
|
18
|
+
* This relies on the naming scheme being `sse:${sessionId}`,
|
|
19
|
+
* `streamable-http:${sessionId}`, or `rpc:${sessionId}`.
|
|
179
20
|
*/
|
|
180
21
|
getTransportType(): BaseTransportType;
|
|
181
22
|
/** Read the sessionId for this agent.
|
|
@@ -185,6 +26,20 @@ declare abstract class McpAgent<Env extends Cloudflare.Env = Cloudflare.Env, Sta
|
|
|
185
26
|
getSessionId(): string;
|
|
186
27
|
/** Get the unique WebSocket. SSE transport only. */
|
|
187
28
|
getWebSocket(): Connection<unknown> | null;
|
|
29
|
+
/**
|
|
30
|
+
* Returns options for configuring the RPC server transport.
|
|
31
|
+
* Override this method to customize RPC transport behavior (e.g., timeout).
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* class MyMCP extends McpAgent {
|
|
36
|
+
* protected getRpcTransportOptions() {
|
|
37
|
+
* return { timeout: 120000 }; // 2 minutes
|
|
38
|
+
* }
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
protected getRpcTransportOptions(): RPCServerTransportOptions;
|
|
188
43
|
/** Returns a new transport matching the type of the Agent. */
|
|
189
44
|
private initTransport;
|
|
190
45
|
/** Update and store the props */
|
|
@@ -207,6 +62,13 @@ declare abstract class McpAgent<Env extends Cloudflare.Env = Cloudflare.Env, Sta
|
|
|
207
62
|
private _waitForElicitationResponse;
|
|
208
63
|
/** Handle elicitation responses */
|
|
209
64
|
private _handleElicitationResponse;
|
|
65
|
+
/**
|
|
66
|
+
* Handle an RPC message for MCP
|
|
67
|
+
* This method is called by the RPC stub to process MCP messages
|
|
68
|
+
* @param message The JSON-RPC message(s) to handle
|
|
69
|
+
* @returns The response message(s) or undefined
|
|
70
|
+
*/
|
|
71
|
+
handleMcpMessage(message: JSONRPCMessage | JSONRPCMessage[]): Promise<JSONRPCMessage | JSONRPCMessage[] | undefined>;
|
|
210
72
|
/** Return a handler for the given path for this MCP.
|
|
211
73
|
* Defaults to Streamable HTTP transport.
|
|
212
74
|
*/
|
|
@@ -229,5 +91,5 @@ declare abstract class McpAgent<Env extends Cloudflare.Env = Cloudflare.Env, Sta
|
|
|
229
91
|
};
|
|
230
92
|
}
|
|
231
93
|
//#endregion
|
|
232
|
-
export { type CreateMcpHandlerOptions, type ElicitRequest, ElicitRequestSchema, type ElicitResult, type MCPClientOAuthCallbackConfig, type MCPClientOAuthResult, type MCPConnectionResult, type MCPDiscoverResult, type MCPServerOptions, McpAgent, type McpAuthContext, SSEEdgeClientTransport, StreamableHTTPEdgeClientTransport, type TransportState, WorkerTransport, type WorkerTransportOptions, createMcpHandler, experimental_createMcpHandler, getMcpAuthContext };
|
|
94
|
+
export { type CreateMcpHandlerOptions, type ElicitRequest, ElicitRequestSchema, type ElicitResult, type MCPClientOAuthCallbackConfig, type MCPClientOAuthResult, type MCPConnectionResult, type MCPDiscoverResult, type MCPServerOptions, McpAgent, type McpAuthContext, type McpClientOptions, RPCClientTransport, type RPCClientTransportOptions, RPCServerTransport, type RPCServerTransportOptions, RPC_DO_PREFIX, SSEEdgeClientTransport, StreamableHTTPEdgeClientTransport, type TransportState, WorkerTransport, type WorkerTransportOptions, createMcpHandler, experimental_createMcpHandler, getMcpAuthContext };
|
|
233
95
|
//# sourceMappingURL=index.d.ts.map
|