Skip to content

Connect a private MCP server with OAuth ​

Host MCP OAuth lets an Agent SDK 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.

ts
// bot/mcp-connections/inventory.ts
import { defineConnection } from "@grok/agent-sdk/connections";

export default defineConnection({
  url: "https://mcp.example.com/inventory",
  oauth: true,
  advertiseTools: true,
});
text
agent-sdk mcp oauth inventory
agent-sdk 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.

text
agent-sdk serve --dir . --dev

Connect authorizes the process that is running now.

A turn waits for Connect only when a person can click it:

  • A playground or HTTP chat turn.
  • A Slack message, when the channel shows Connect. slackChannel({ toolApprovals: true }) shows Connect, and so does buildToolApprovalEvents in events.

The wait stops after 10 minutes, or when the turn is interrupted. Other turns do not wait. These are schedules, reminders, webhooks, evals, and Slack channels without Connect. A required connection fails the call with Reconnect <connection>. An optional connection is skipped, also in the playground. Authorize those connections ahead of time with mcp oauth.

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.

ts
// bot/host-connections/inventory.ts
import { defineConnection } from "@grok/agent-sdk/connections";

export default defineConnection({
  url: "https://mcp.example.com/inventory",
  oauth: true,
  tools: ["lookup_stock"],
});
ts
// bot/tools/get_stock.ts
import { defineTool } from "@grok/agent-sdk/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.

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 agent-sdk 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.