# 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](/docs/reference/connections.md#cursor-account-mcp-connection),
and connect it from [Deployed Agents](https://cursor.com/dashboard/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.

## Related

- [`skills/mcp-auth/SKILL.md`](/docs/package/skills/mcp-auth/SKILL.md): the
  companion setup procedure for coding agents
- [MCP connections](/docs/reference/connections.md): transports, account
  connectors, per-session auth, and tool filtering
- [CLI reference](/docs/reference/cli.md#mcp-oauth): OAuth command flags
  and browser callback behavior
- [Deployment](/docs/deployment.md): hosted egress and secrets
- [Troubleshooting](/docs/troubleshooting.md): authorization symptoms and
  remedies
