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 . --devConnect 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: truefor connectors already authorized under MCP in the Cursor dashboard. Their tokens stay on the Cursor backend. -
Choose
urlwithoauth: truewhen the machine runningbdk serveshould hold OAuth credentials. -
Choose
urlwith staticheaderswhen 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.
Related
skills/mcp-auth/SKILL.md: the companion setup procedure for coding agents- MCP connections: transports, account connectors, per-session auth, and tool filtering
- CLI reference: OAuth command flags and browser callback behavior
- Deployment: hosted egress and secrets
- Troubleshooting: authorization symptoms and remedies