Skip to content

MCP Connections

An MCP connection gives the agent tools from an MCP server. One file per server under agent/mcp-connections/, and the filename becomes the server name the model sees. An MCP connection default-exports defineConnection from @cursor/july/connections, and the transport comes in four shapes: remote HTTP, local stdio, the signed-in Cursor account's connectors, and peer agents on the same host.

Remote MCP server

Point an MCP connection at a remote server with a URL.

ts
import { defineConnection } from "@cursor/july/connections";

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

Tokens come from env vars. Never hardcode them in the file.

Host MCP OAuth

For servers that speak OAuth, set oauth: true and authorize with the CLI. Tokens live in ~/.config/agent-serve/mcp-auth.json. --store copies them onto the hosted deployment as MCP_OAUTH_<NAME>_* secrets.

ts
export default defineConnection({
  url: "https://mcp.example.com/inventory",
  oauth: true,
  hostOnly: true, // model cannot call; host.mcp still can
});
bash
agent-sdk mcp oauth inventory           # browser PKCE → local mcp-auth.json
agent-sdk mcp oauth inventory --store   # also upsert deployment secrets

Full walkthrough: Host MCP OAuth. Companion skill: skills/mcp-auth/SKILL.md.

Use hostOnly: true when only deterministic host tools should call the server (deploys, admin APIs). Account MCP (cursorAccount: true) stays the right choice for connectors already linked in the Cursor dashboard; omit servers (or pass "*") to forward every connected connector.

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
});

This suits small purpose-built servers, like a units converter shipped next to the agent.

To run TypeScript in the agent environment (including a repo-less cloud VM), author the tools on the connection instead. The Agent SDK packages them as stdio MCP. You write execute. The Agent SDK speaks the protocol.

ts
export default defineConnection({
  tools: {
    probe_cloud_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 the signed-in Cursor account already authorized (dashboard → MCP): Linear, Notion, Slack, and the rest. You don't configure tokens. Every tool runs on the Cursor backend with the account's stored OAuth credentials, so raw tokens never reach the serve host, session workspaces, or traces.

By default the agent gets every connected HTTP/SSE connector on the account. Pass servers: "*" (or ["*"]) for the same all-connectors behavior in an explicit form. Pass a name list when you want a smaller set.

ts
// agent/mcp-connections/cursor.ts: every connected connector
export default defineConnection({ cursorAccount: true });
// same, spelled out:
export default defineConnection({ cursorAccount: true, servers: "*" });
// only Linear:
export default defineConnection({
  cursorAccount: true,
  servers: ["Linear"],
});

The host must be signed in (agent-sdk login or CURSOR_API_KEY). serve fails fast at startup otherwise, and logs each connector's live status (connected, needsAuth, error) as it starts.

Local turns and host-side calls go through a loopback bridge guarded by a per-startup secret. Cloud-runtime turns reach the same bridge through the serve --public-url, so servers filters apply there too. A cloud-capable agent that combines a concrete servers allowlist with no --public-url fails at startup rather than running unfiltered. Backend execution covers the account's HTTP/SSE servers. Stdio servers can't run server-side, so author a { command } MCP connection for those.

CAUTION

Whoever can talk to the agent can drive these connectors, because they are ordinary agent tools. serve refuses to start when --allow-anonymous is combined with account MCP connections unless you also pass --allow-anonymous-cursor-account-mcp (trusted boundary only; for example an SSO proxy or the hosted alias token). Prefer --bearer-token on shared hosts.

Peer MCP connection

{ agent: "<slug>" } addresses another agent mounted on the same serve host. The model gets the peer's ask and check (and call_tool) tools and can delegate work to it:

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

Unknown slugs and self-references fail serve at startup. Resolution (loopback versus --public-url), loop caveats, and the delegation model are in the Agent-to-agent guide.

Every MCP connection is available in three places

One authored MCP connection serves three consumers.

  1. Cursor agent: Local or cloud turns see the MCP connection through SDK mcpServers, and the model calls its tools directly.

  2. Server tools: Deterministic host code composes MCP calls through ctx.host.mcp:

    ts
    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 });
      },
    });
  3. Channel and schedule handlers: Webhooks hit MCP servers with no model turn at all, through args.host.mcp:

    ts
    POST("/sync", {
      bodySchema: z.object({}),
      handler: async (_req, { host }) => {
        const result = await host.mcp.callTool("linear", "list_issues", {});
        return Response.json(result);
      },
    });

The host registry is small: host.mcp.names() lists MCP connection names, and listTools(name) / callTool(name, tool, args) open the client lazily on first use.

What's next

Continue with these pages: