diff --git a/.changeset/tender-experts-obey.md b/.changeset/tender-experts-obey.md new file mode 100644 index 000000000..6e81f47fa --- /dev/null +++ b/.changeset/tender-experts-obey.md @@ -0,0 +1,5 @@ +--- +"@browserbasehq/stagehand": patch +--- + +Supports request header authentication with connectToMCPServer diff --git a/packages/core/lib/v3/mcp/connection.ts b/packages/core/lib/v3/mcp/connection.ts index 5d2f15da5..00cb982de 100644 --- a/packages/core/lib/v3/mcp/connection.ts +++ b/packages/core/lib/v3/mcp/connection.ts @@ -2,13 +2,17 @@ import { Client, ClientOptions, } from "@modelcontextprotocol/sdk/client/index.js"; -import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; +import { + StreamableHTTPClientTransport, + type StreamableHTTPClientTransportOptions, +} from "@modelcontextprotocol/sdk/client/streamableHttp.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; import { MCPConnectionError } from "../types/public/sdkErrors.js"; export interface ConnectToMCPServerOptions { serverUrl: string | URL; clientOptions?: ClientOptions; + requestOptions?: StreamableHTTPClientTransportOptions; } export interface StdioServerConfig { @@ -23,6 +27,7 @@ export const connectToMCPServer = async ( try { let transport; let clientOptions: ClientOptions | undefined; + let requestOptions: StreamableHTTPClientTransportOptions | undefined; // Check if it's a stdio config (has 'command' property) if (typeof serverConfig === "object" && "command" in serverConfig) { @@ -37,9 +42,14 @@ export const connectToMCPServer = async ( serverUrl = (serverConfig as ConnectToMCPServerOptions).serverUrl; clientOptions = (serverConfig as ConnectToMCPServerOptions) .clientOptions; + requestOptions = (serverConfig as ConnectToMCPServerOptions) + .requestOptions; } - transport = new StreamableHTTPClientTransport(new URL(serverUrl)); + transport = new StreamableHTTPClientTransport( + new URL(serverUrl), + requestOptions, + ); } const client = new Client({ diff --git a/packages/core/tests/unit/public-api/v3-core.test.ts b/packages/core/tests/unit/public-api/v3-core.test.ts index 59b5e0cea..6987d2f31 100644 --- a/packages/core/tests/unit/public-api/v3-core.test.ts +++ b/packages/core/tests/unit/public-api/v3-core.test.ts @@ -156,7 +156,11 @@ describe("V3 Core public API types", () => { | string | URL | { command: string; args?: string[]; env?: Record } - | { serverUrl: string | URL; clientOptions?: unknown }; + | { + serverUrl: string | URL; + clientOptions?: unknown; + requestOptions?: unknown; + }; it("has correct parameter types", () => { expectTypeOf( diff --git a/packages/docs/v3/best-practices/mcp-integrations.mdx b/packages/docs/v3/best-practices/mcp-integrations.mdx index ed44fbaaa..b2d6e4e66 100644 --- a/packages/docs/v3/best-practices/mcp-integrations.mdx +++ b/packages/docs/v3/best-practices/mcp-integrations.mdx @@ -67,7 +67,7 @@ const notionClient = await connectToMCPServer({ }, }); -// Use the connected client +// Use the connected clients (example with Supabase + Notion) const agent = stagehand.agent({ provider: "openai", model: "computer-use-preview", @@ -81,6 +81,23 @@ const agent = stagehand.agent({ await agent.execute("Search for restaurants in New Brunswick, NJ and save the first result to the database"); ``` +## Authenticated MCP Servers + +Some MCP servers require authentication via HTTP request headers. You can pass request headers through `requestOptions`: + +```typescript +const authenticatedClient = await connectToMCPServer({ + serverUrl: "https://mcp-server.example.com/mcp", + requestOptions: { + requestInit: { + headers: { + Authorization: `Bearer ${process.env.MCP_SERVER_API_KEY}`, + }, + }, + }, +}); +``` + ## Multiple Integrations