# MCP connections

An MCP connection gives the agent tools from an MCP server. Each file under
`bot/mcp-connections/` default-exports `defineConnection` from
`@grok/agent-sdk/connections`, and its filename becomes the server name. A
connection reaches a remote HTTP server, a local stdio process, the signed-in
Cursor account's connectors, or a peer agent by slug.

Put a server in `bot/host-connections/` when only host code should call it.
Host connections use the same shape and support `agent-sdk mcp oauth`, but the
model and playground don't see them.

## Remote MCP server

Point a connection at a remote server with a URL.

```ts
import { defineConnection } from "@grok/agent-sdk/connections";

export default defineConnection({
  url: "https://mcp.linear.app/mcp",
  headers: { authorization: "Bearer ${LINEAR_TOKEN}" },
});
```

## Credentials in headers and env

Write a credential as a `${NAME}` template in `headers` on a remote connection
or in `env` on a stdio one.

```ts
export default defineConnection({
  command: "slack-mcp-server",
  args: ["--transport", "stdio"],
  env: { SLACK_MCP_XOXB_TOKEN: "${SLACK_BOT_TOKEN}" },
});
```

On Cursor hosting, your secrets aren't in `process.env` yet when the connection
file loads. Agent SDK resolves a `${NAME}` template each time the connection
opens, so the template works locally and on hosting. Reading
`process.env.NAME` at the top of the file only works locally.

`agent-sdk validate` warns when the environment doesn't define a template's
variable. An unset variable resolves to an empty value, which the server
reports as an auth failure.

## Host MCP OAuth

For a server that speaks OAuth, set `oauth: true` and follow
[Connect a private MCP server with OAuth](https://cursor.com/docs/agent-sdk/guides/mcp-oauth.md).

## Per-session auth

For an HTTP or SSE connection whose credential depends on who the session is
for, declare an `auth` callback instead of static headers. The headers it
returns override the static ones.

```ts
export default defineConnection({
  url: "https://api.cursor.com/v1/mcp/plugins",
  auth: async (session) => ({
    headers: { Authorization: `Bearer ${await grantFor(session)}` },
  }),
  advertiseTools: true,
});
```

- The callback runs on the host before every turn, including reminder fires
  and follow-ups after a restart, so the model never sees or chooses the
  account. A callback that throws fails the turn.
- Derive the identity from `session.auth`, `session.id`, or your channel's
  session state, not from `session.continuationKey`, which the HTTP channel
  rotates after every follow-up.
- `host.mcp` calls from server tools send only the static headers, and `auth`
  can't be combined with `oauth: true`.

## Advertise tools

Set `advertiseTools: true` when the model should call an MCP server's tools by
name. Agent SDK keeps each tool's description, schemas, and MCP annotations,
and renames invalid or colliding names.

A listing or auth failure, an invalid tool name, or a name collision fails the
turn. Set `optional: true` to skip an unavailable connection instead.
Advertised tools behave like server tools, and
[direct tool calls](https://cursor.com/docs/agent-sdk/reference/tools.md#call-a-tool-without-a-model-turn) use the same
names.

In a dry-run session, read-only MCP tools run, Agent SDK stubs write tools, and
tools without effect annotations are unavailable. A read-only tool gets the
same transient retry as an authored read, and a tool with no effect annotation
runs once.

## Filter connection tools

Set `tools` to serve only the listed names, or `disallowedTools` to drop names.
When both list a name, `disallowedTools` wins. The model and `host.mcp` see
only what remains, and a filter the model sees requires `advertiseTools: true`.

```ts
export default defineConnection({
  url: "https://mcp.example.com/inventory",
  advertiseTools: true,
  tools: ["search_skus", "get_stock"],
});
```

```ts
export default defineConnection({
  url: "https://mcp.example.com/inventory",
  advertiseTools: true,
  disallowedTools: ["place_order", "delete_sku"],
});
```

Names are the server's `tools/list` names, and Agent SDK skips unknown names. A
filter that matches nothing fails the turn unless `optional: true`. Add
`effects: "read"` to keep only the listed tools the server marks as reads. On a
host connection, a name list limits `host.mcp` without advertising anything.

## Local stdio MCP server

Run a local MCP server as a child process with `command`.

```ts
export default defineConnection({
  command: "node",
  args: ["--import", "tsx", "mcp/units-server.ts"],
  // env, cwd
});
```

To run TypeScript in the agent environment, define the tools on the connection
instead. Here `tools` takes an object of handlers rather than a list of names.
Agent SDK packages them as a stdio MCP server, so you write only `execute`.

```ts
export default defineConnection({
  tools: {
    probe_agent_tool: {
      description: "Prove the tool ran in the agent environment.",
      async execute({ label }) {
        return { ok: true, label };
      },
    },
  },
});
```

## Cursor account MCP connection

`{ cursorAccount: true }` forwards the MCP connectors that the signed-in Cursor
account already authorized in the dashboard, such as Linear, Notion, and Slack.
Each tool runs on the Cursor backend with the account's stored OAuth
credentials, so raw tokens never reach the serving host, session workspaces, or
traces.

```ts
// bot/mcp-connections/account.ts
export default defineConnection({
  cursorAccount: true,
  advertiseTools: true,
});
```

The agent gets every connected HTTP or SSE connector, the same as
`servers: "*"`, unless you pass a list such as `servers: ["Linear"]`. Name the
file `account.ts`, because `cursor.ts` collides with the IDE's `cursor` MCP
namespace. The host must be [signed in](https://cursor.com/docs/agent-sdk/reference/cli.md#environment-variables), or
`serve` fails at startup.

> \[!CAUTION]
> Anyone who can talk to the agent can drive these connectors. `serve` refuses
> to start with `--allow-anonymous` and account connections unless you also pass
> `--allow-anonymous-cursor-account-mcp`, which belongs only behind a trusted
> boundary such as an SSO proxy. Prefer `--bearer-token` on shared hosts.

## Peer MCP connection

`{ agent: "<slug>" }` addresses another agent without a URL, headers, or
secrets. On a local multi-agent serve, the target is another mount slug. On
Cursor hosting, it's the lowercase deployment slug of another agent on the same
team.

The model gets the peer's `ask` and `check` tools, plus `call_tool` when the
peer has server tools, so it can delegate work:

```ts
export default defineConnection({
  agent: "weather-agent",
  description: "Delegate weather questions to the weather agent.",
});
```

Declare the connection only on the caller. In a local multi-agent serve,
unknown slugs and self-references fail at startup. A single-agent serve omits
peers with a warning unless its hosting platform sets `--peers-url`. See
[Peer agents](https://cursor.com/docs/agent-sdk/guides/agent-to-agent.md#delegate-a-question-to-a-specialist).

## Call MCP from host code

Server tools reach every connection, including host connections, through
`ctx.host.mcp`. Channel and schedule handlers use `args.host.mcp`.

```ts
// bot/tools/search_linear.ts
import { defineTool } from "@grok/agent-sdk/tools";
import { z } from "zod";

export default defineTool({
  description: "Search Linear issues.",
  inputSchema: z.object({ query: z.string() }),
  async execute({ query }, ctx) {
    return ctx.host.mcp.callTool("linear", "list_issues", { query });
  },
});
```

`host.mcp.names()` lists connection names, and `listTools(name)` and
`callTool(name, tool, args)` call into a named connection.

## Related

- [Tools](https://cursor.com/docs/agent-sdk/reference/tools.md)
- [Peer agents](https://cursor.com/docs/agent-sdk/guides/agent-to-agent.md)


---

## Sitemap

[Overview of all docs pages](/llms.txt)
