Skip to main content

Command Palette

Search for a command to run...

BDK

Connect a private MCP server with OAuth

Host MCP OAuth lets a BDK process authorize a remote MCP server without putting tokens in the agent's source or workspace. Use it when the server speaks OAuth and its credentials should belong to the serve host instead of a person's Cursor account.

Use a private MCP tool locally

Declare the remote server, authorize it once in the browser, and start the agent. The next local turn can call the server's named tools with the credentials stored on that host.

// bot/mcp-connections/inventory.tsimport { defineConnection } from "@cursor/bdk/connections";export default defineConnection({  url: "https://mcp.example.com/inventory",  oauth: true,  advertiseTools: true,});
bdk mcp oauth inventorybdk run --dir . \  --message "Which east-coast warehouse has SKU-142 in stock?"

The filename is the connection name used by the CLI and ctx.host.mcp. Changing the server URL invalidates its saved authorization, so run mcp oauth again after moving the endpoint.

Connect on first use

You can also let the first unauthenticated call ask for authorization. The turn parks and the playground shows Connect. Finish the browser flow, return to the playground, and confirm Connected; the same call retries and the turn continues without restarting the agent.

bdk serve --dir . --dev

Connect authorizes the process that is running now.

Keep privileged tools behind a wrapper

Put a connection under bot/host-connections/ when the model should not receive the MCP server's tools directly. A server tool can expose one constrained operation while ctx.host.mcp keeps the broader server on the host.

// bot/host-connections/inventory.tsimport { defineConnection } from "@cursor/bdk/connections";export default defineConnection({  url: "https://mcp.example.com/inventory",  oauth: true,  tools: ["lookup_stock"],});
// bot/tools/get_stock.tsimport { defineTool } from "@cursor/bdk/tools";import { z } from "zod";export default defineTool({  description: "Read stock for one approved region.",  inputSchema: z.object({    sku: z.string(),    region: z.enum(["us-east", "us-west"]),  }),  async execute(input, ctx) {    const result = await ctx.host.mcp.callTool(      "inventory",      "lookup_stock",      input    );    return {      ok: result.isError !== true,      stock: result.structuredContent ?? null,    };  },});

The model sees get_stock, not the unrestricted inventory server. Use this shape when code should own an allowlist or another side-effect gate.

Use an OAuth server from a hosted agent

Tokens from mcp oauth stay in the CLI config directory of the machine that authorized them, so a hosted deployment doesn't receive them. For a Cursor-managed agent, add the server under MCP in the Cursor dashboard, reach it through a cursorAccount: true connection, and connect it from Deployed Agents in the dashboard.

Anyone able to use that agent can reach the MCP capabilities you expose, so narrow the connection's servers list and keep privileged operations behind a host-side wrapper.

Choose where credentials live

Use the credential shape that matches who should own access:

  • Choose cursorAccount: true for connectors already authorized under MCP in the Cursor dashboard. Their tokens stay on the Cursor backend.

  • Choose url with oauth: true when the machine running bdk serve should hold OAuth credentials.

  • Choose url with static headers when the server uses an API key or bearer token instead of OAuth.

Host OAuth and per-session auth are different identity models and cannot be combined on one connection. Use per-session auth when each caller needs a different downstream identity.