Skip to content

Commit e3734b9

Browse files
authored
Support Local MCP Connections (#1049)
# why To allow support for non-hosted MCP servers # what changed Added the option to connect over StdioClientTransport to an MCP server. Users can define their connection in the common format: ```TypeScript const notionClient = await connectToMCPServer({ command: "npx", args: ["-y", "@notionhq/notion-mcp-server"], env: { NOTION_TOKEN: process.env.NOTION_TOKEN, }, }); ``` - Added docs examples # test plan
1 parent ab5d6ed commit e3734b9

File tree

4 files changed

+53
-17
lines changed

4 files changed

+53
-17
lines changed

.changeset/nasty-buckets-push.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@browserbasehq/stagehand": patch
3+
---
4+
5+
Support local MCP server connections

.prettierignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pnpm-lock.yaml
22
README.md
33
**/*.json
4-
docs/
4+
docs/
5+
.github/

docs/best-practices/mcp-integrations.mdx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ There are two options for connecting to MCP servers:
1818
1. **Pass a URL directly** - The simplest approach for quick setup
1919
2. **Create a connection first** - Gives you more control over the connection
2020

21+
<Note>
22+
MCP client support is currently only available in TypeScript.
23+
</Note>
24+
2125
## Passing a URL
2226

2327
The simplest way to add MCP integrations is by providing server URLs directly in the agent configuration:
@@ -50,12 +54,21 @@ const supabaseClient = await connectToMCPServer(
5054
`https://server.smithery.ai/@supabase-community/supabase-mcp/mcp?api_key=${process.env.SMITHERY_API_KEY}`
5155
);
5256

57+
// You can also pass the config to start a local MCP server
58+
const notionClient = await connectToMCPServer({
59+
command: "npx",
60+
args: ["-y", "@notionhq/notion-mcp-server"],
61+
env: {
62+
NOTION_TOKEN: process.env.NOTION_TOKEN,
63+
},
64+
});
65+
5366
// Use the connected client
5467
const agent = stagehand.agent({
5568
provider: "openai",
5669
model: "computer-use-preview",
57-
integrations: [supabaseClient],
58-
instructions: `You can interact with Supabase databases. Use these tools to store and retrieve data.`,
70+
integrations: [supabaseClient, notionClient],
71+
instructions: `You can interact with Supabase databases and Notion. Use these tools to store and retrieve data.`,
5972
options: {
6073
apiKey: process.env.OPENAI_API_KEY,
6174
},

lib/mcp/connection.ts

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,49 @@ import {
33
ClientOptions,
44
} from "@modelcontextprotocol/sdk/client/index.js";
55
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
6+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
67
import { MCPConnectionError } from "../../types/stagehandErrors";
78

89
export interface ConnectToMCPServerOptions {
910
serverUrl: string | URL;
1011
clientOptions?: ClientOptions;
1112
}
1213

14+
export interface StdioServerConfig {
15+
command: string;
16+
args?: string[];
17+
env?: Record<string, string>;
18+
}
19+
1320
export const connectToMCPServer = async (
14-
serverUrlOrOptions: string | ConnectToMCPServerOptions,
21+
serverConfig: string | URL | StdioServerConfig | ConnectToMCPServerOptions,
1522
): Promise<Client> => {
16-
// Handle both string URL and options object
17-
const options: ConnectToMCPServerOptions =
18-
typeof serverUrlOrOptions === "string"
19-
? { serverUrl: serverUrlOrOptions }
20-
: serverUrlOrOptions;
23+
try {
24+
let transport;
25+
let clientOptions: ClientOptions | undefined;
2126

22-
const serverUrl = options.serverUrl.toString();
27+
// Check if it's a stdio config (has 'command' property)
28+
if (typeof serverConfig === "object" && "command" in serverConfig) {
29+
transport = new StdioClientTransport(serverConfig);
30+
} else {
31+
// Handle URL-based connection
32+
let serverUrl: string | URL;
33+
34+
if (typeof serverConfig === "string" || serverConfig instanceof URL) {
35+
serverUrl = serverConfig;
36+
} else {
37+
serverUrl = (serverConfig as ConnectToMCPServerOptions).serverUrl;
38+
clientOptions = (serverConfig as ConnectToMCPServerOptions)
39+
.clientOptions;
40+
}
41+
42+
transport = new StreamableHTTPClientTransport(new URL(serverUrl));
43+
}
2344

24-
try {
25-
const transport = new StreamableHTTPClientTransport(
26-
new URL(options.serverUrl),
27-
);
2845
const client = new Client({
2946
name: "Stagehand",
3047
version: "1.0.0",
31-
...options.clientOptions,
48+
...clientOptions,
3249
});
3350

3451
await client.connect(transport);
@@ -37,7 +54,7 @@ export const connectToMCPServer = async (
3754
await client.ping();
3855
} catch (pingError) {
3956
await client.close();
40-
throw new MCPConnectionError(serverUrl, pingError);
57+
throw new MCPConnectionError(serverConfig.toString(), pingError);
4158
}
4259

4360
return client;
@@ -46,6 +63,6 @@ export const connectToMCPServer = async (
4663
if (error instanceof MCPConnectionError) {
4764
throw error; // Re-throw our custom error
4865
}
49-
throw new MCPConnectionError(serverUrl, error);
66+
throw new MCPConnectionError(serverConfig.toString(), error);
5067
}
5168
};

0 commit comments

Comments
 (0)