Skip to content

Deploy the Agent SDK

Both options run the same agent project and HTTP API. Channel delivery paths differ. Cursor-managed hosting is preferred for most agents.

OptionUse it whenYou manage
Cursor-managed hosting (preferred)You want the shortest path from a Git repo to a running agentAgent code, external storage, declared egress, and deployment secrets
Self-hostingYou need your own network, proxy, persistent filesystem, or process controlsAgent code, Node process, TLS, auth, secrets, durable state, monitoring, and upgrades

Cursor-managed hosting

Cursor builds the selected Git ref into a deployment. The deployment exposes a stable URL while Cursor manages its runtime lifecycle.

Before you deploy

  • Confirm managed hosting is enabled for the account and team.
  • Sign in with an account holding team-admin deployment permission.
  • Add @cursor/july to the agent project.

For a GitHub source, install the Cursor GitHub App on the repository owner and grant it access to the repository. Cursor builds through its repository integration, not your local Git credentials. Commit and push the Git ref before deploying it.

Declare hosting needs

If the agent needs extra egress or deployment secrets, add a hosting block to agent/agent.ts:

ts
import { defineAgent } from "@cursor/july";

export default defineAgent({
  hosting: {
    egressDomains: ["api.weather.example.com"],
    secretNames: ["WEATHER_API_KEY"],
  },
});

egressDomains lists outbound hosts beyond the platform's base policy. Enter hostnames without schemes, ports, or paths. One leading *. wildcard is allowed. Declared domains allow HTTPS and TLS traffic, not arbitrary TCP ports.

secretNames lists the environment variables the agent expects. Names use UPPER_SNAKE_CASE. Commit names only; set their values after creating the deployment. Names beginning with CURSOR_ are reserved.

For defineConnection({ url, oauth: true }), declare MCP_OAUTH_<CONNECTION>_* in secretNames, authorize with agent-sdk mcp oauth <connection> --store, then redeploy. See Host MCP OAuth.

Run agent-sdk validate before deploying. It reports invalid domains and secret names as warnings, so fix them even when validation exits zero.

Deploy from Git

Sign in, validate the project, and deploy it:

bash
cd my-agent
agent-sdk login
agent-sdk whoami
agent-sdk validate
agent-sdk deploy

Inside a Git checkout, deploy infers the HTTPS origin URL, current branch or detached commit, agent path, and deployment slug. Explicit flags override each value:

bash
agent-sdk deploy \
  --repo https://github.com/acme/agents \
  --ref main \
  --path agents/weather \
  --slug weather-agent

IMPORTANT

--slug doesn't retain source or Cursor-event settings. Run every repo-backed deploy from the pushed checkout, or pass --repo, --ref, --path, and any --cursor-events-repo flags again.

Deploy reads the agent from Git; it doesn't upload local files. Keep the local hosting block in sync with the selected ref. A nested --path must contain package.json and be installable from its own directory.

For a directory containing several agent projects, choose one from the TTY prompt, pass --slug <name>, or deploy each child with --all. Pass --team <id> when the signed-in account has no default team or you want another team.

--all creates a separate deployment for each child. Self-host when the agents must share one multi-agent process.

The command waits up to ten minutes for a running engine. Use --no-wait to return after Cursor accepts the deployment, then inspect it separately:

bash
agent-sdk deployments
agent-sdk deployment weather-agent
agent-sdk logs --prod --slug weather-agent

Set deployment secrets

A deployment must exist before you can set its secrets. Pass names only. Enter values at the hidden prompt, or pipe one line per name:

bash
agent-sdk secrets set weather-agent WEATHER_API_KEY
agent-sdk secrets list weather-agent
agent-sdk deploy --dir . --slug weather-agent

secrets list returns names and creation times, never values. The engine reads secret changes on its next deploy.

Do not pass NAME=VALUE on the command line. That form lands in shell history and in agent-captured terminals. The CLI refuses it unless you add --from-argv. For non-interactive input without argv, redirect a file or pipe stdin.

Credential output in terminals

Treat any command that prints a secret as a credential event. Keep it out of agent-captured terminals when you can.

CommandWhat prints
secrets set / secrets listNames only. Values never print.
rotate-pod-credentialMasked key only.
First deploy / rotate-tokenFull alias token once. Save it outside the agent transcript; it cannot be retrieved later.
deployment --jsonMay include short-lived engineAccess.headers. Treat JSON as a credential.

Do not verify secrets with echo "$SECRET", printenv, or by pasting values into chat. Use secrets list for names, then redeploy and exercise the feature that needs the secret.

Choose durable storage

Hosted filesystem state can reset during a deploy or runtime replacement. Prefer cursorHostedStorage (@cursor/july/storage/cursor-hosted) so durable records land in Cursor's Bugbot agent_serve_* tables through a control-plane HTTP proxy (pod credential auth — no database URL in the engine). Do not put BUGBOTDB_URL or AGENT_SERVE_DEPLOYMENT_ID in hosting.secretNames. Self-host with your own defineStorage backend or a persistent --state-root when the complete filesystem must survive.

Use the hosted agent

The CLI handles authentication for --prod commands. External HTTP clients that hit the stable alias URL must send X-Agent-Alias-Token on every request. Authored channel auth still runs after that gate.

Most webhook providers cannot set that header. Today you need an alias-token relay (or another authenticating intermediary), a Cursor relay / Socket Mode path that does not use the public alias URL, or self-hosting where you control auth. Provider webhooks aimed straight at a managed alias without a relay are not supported yet.

Use --prod with the normal client commands:

bash
agent-sdk playground --prod --slug weather-agent
agent-sdk chat --prod --slug weather-agent
agent-sdk run --prod --slug weather-agent --message "Forecast for Paris"
agent-sdk sessions --prod --slug weather-agent
agent-sdk logs --prod --slug weather-agent

Keep playground --prod running while the playground is open. Press Ctrl-C to stop it.

The first deployment prints a reusable alias token once. Store it immediately. Run agent-sdk deployment weather-agent to retrieve the stable alias URL later. The token remains valid until rotation and can't be retrieved.

External HTTP clients send the alias token on every request:

bash
curl "$AGENT_ALIAS_URL/v1/health" \
  -H "X-Agent-Alias-Token: $AGENT_ALIAS_TOKEN"

--prod commands don't use the alias token. If it is lost or exposed, rotate it. The old token stops working immediately:

bash
agent-sdk rotate-token weather-agent

Connect GitHub

Let the hosted engine pull Cursor SCM events. Repeat --cursor-events-repo for each repository whose events should wake the agent:

bash
agent-sdk deploy --dir . \
  --slug pr-approver \
  --cursor-events-repo acme/checkout \
  --cursor-events-repo acme/payments

The agent still needs a githubChannel() declaration for the events it handles. This delivery path needs no public GitHub webhook URL. The flag requires Cursor SCM-event access for the deployment credential.

For outbound GitHub calls, use githubChannel({ cursorAccount: true }) and grant the team's Cursor GitHub App access to each repository. Alternatively, add dedicated GitHub credentials as deployment secrets. See the GitHub guide.

Connect Slack

Hosted Slack supports the team's Cursor Slack app or a dedicated Socket Mode app.

Use the Cursor Slack app when mentions and direct messages are enough:

ts
import { slackChannel } from "@cursor/july/channels/slack";

export default slackChannel({
  cursorAccount: true,
  agentName: "PrApprover",
});

The team must have the Cursor Slack app installed and Slack event relay access enabled. This mode needs no Slack token secrets. It doesn't support channel-post watches, tool approvals, or interactivity.

Use a dedicated Socket Mode app for those features or a separate bot identity:

ts
import { slackChannel } from "@cursor/july/channels/slack";

export default slackChannel({ envPrefix: "PR_APPROVER" });

Provision the dedicated app from the dashboard. Open the deployment under Deployed Agents at cursor.com/dashboard, switch to Details, and expand Slack under Integrations. Add to Slack connects the workspace with a one-time authorization, and Create Slack app creates and installs the app, then stores its tokens as deployment secrets automatically. Redeploy when prompted so the running agent picks them up. See Provision from the dashboard for the walkthrough, including workspace-admin approval.

If you created the Slack app by hand instead, set its tokens as deployment secrets yourself. The prefix selects the secret names:

bash
agent-sdk secrets set pr-approver \
  PR_APPROVER_SLACK_BOT_TOKEN \
  PR_APPROVER_SLACK_APP_TOKEN
agent-sdk deploy --dir . --slug pr-approver

Without envPrefix, a dedicated app reads SLACK_BOT_TOKEN and SLACK_APP_TOKEN. Socket Mode needs no inbound URL. See the Slack guide.

Update or stop a deployment

Redeploy the same slug after pushing a new Git ref. The stable alias continues to point at the active generation. Follow the same source rules from Deploy from Git.

bash
agent-sdk deploy --dir /path/to/my-agent --slug weather-agent
agent-sdk stop weather-agent

stop waits for the deployment to stop unless you pass --no-wait. See the CLI reference for the full command reference.

Self-host the Agent SDK

The Agent SDK runs as a Node HTTP server on Node 22.13 or newer. You can host it on a VM, container platform, or ECS.

The security model in one minute

serve binds to loopback and admits direct local callers by default. Choose one of these options before exposing it:

  1. Pass --bearer-token <secret> for a shared host.
  2. Define channel-specific auth for routes with their own credentials or signatures.
  3. Use --allow-anonymous only behind an authenticating proxy.

A static bearer token maps every holder to one principal. Use authored auth when callers need separate identities. See Channels for policy details.

Credentials

A self-hosted server can read these credentials.

CredentialUsed forProvide it as
Cursor API keymodel turns, cloud runtime, Cursor account MCP connectionsagent-sdk login (stores a revocable key), CURSOR_API_KEY, or --api-key / serve({ apiKey })
Slack tokensSlack channels<PREFIX>_SLACK_BOT_TOKEN + <PREFIX>_SLACK_APP_TOKEN per agent
GitHub webhook secretdelivery signature verificationGITHUB_WEBHOOK_SECRET, same value on server and signer
GitHub APIoutbound API callsa GitHub App (GITHUB_APP_ID + GITHUB_APP_PRIVATE_KEY + installation id) or GITHUB_TOKEN / gh auth login
MCP connection tokensauthored MCP connectionsenv vars your mcp-connections/*.ts read, or host OAuth secrets from agent-sdk mcp oauth <name> --store (MCP_OAUTH_*; see Host MCP OAuth)

Use a dedicated Cursor key per host. agent-sdk whoami shows the active credential. logout removes the stored key from the host; revoke the key in the Cursor dashboard to invalidate it. See CLI authentication for credential resolution.

State

Place --state-root on a persistent volume outside the agent repository, and back it up. Sessions survive restarts only when their state does. See Storage and Sessions for persistence and layout details.

A single box

A single-host deployment needs one supervised serve process on a private network. Export the Cursor key and a generated bearer token in the supervisor environment:

bash
export CURSOR_API_KEY="<cursor-api-key>"
export AGENT_SDK_BEARER_TOKEN="$(openssl rand -hex 32)"

# the server: all agents under one port
agent-sdk serve --dir /srv/agents --port 3000 --host 127.0.0.1 \
  --state-root /var/lib/agent-serve \
  --bearer-token "$AGENT_SDK_BEARER_TOKEN"

Slack Socket Mode needs no inbound network. For GitHub, prefer --cursor-events --repo owner/repo on serve so the host pulls events through Cursor without a public webhook URL.

Webhook forwarding is the fallback. It needs one additional process. Before starting or restarting serve, export the same strong GITHUB_WEBHOOK_SECRET in both supervisor environments. Then install the extension, authenticate gh, and start the forwarder. Repository forwarding requires repo-admin access; organization forwarding with --org requires org-owner access.

bash
export GITHUB_WEBHOOK_SECRET="$(openssl rand -hex 32)"
agent-sdk github doctor --install
gh auth login

# GitHub agents only: ONE forwarder relaying live deliveries to loopback
GITHUB_TOKEN= GH_TOKEN= \
  agent-sdk github forward --dir /srv/agents --repo owner/repo

Run long-lived processes under a supervisor. systemd survives reboots; tmux survives only SSH disconnects. On a TTY, press Enter to reload agent code. Humans reach the playground through a private network or tunnel. Keep --bearer-token on because tunneled requests arrive from loopback and IP-based policies can't tell them apart.

Health checks: GET /v1/health at the host level (made for ALB and ECS checks), and each agent also serves /<slug>/v1/health.

Containers

Build the image with Node 22.13 or newer, the agent source, and its package dependencies. Run agent-sdk serve as a non-root user:

bash
agent-sdk serve --dir /srv/agents --mode multi \
  --host 0.0.0.0 --port 3000 \
  --state-root /var/lib/agent-serve \
  --bearer-token "$AGENT_SDK_BEARER_TOKEN"

Mount the state root as a persistent volume and inject secrets at startup. Install git and gh when channels need host-side GitHub work. Don't put secrets in the image.

Serve many agents from one process

Point serve at a folder of agent projects and every child mounts under its directory name on one port. One process, one state root, one credential:

bash
agent-sdk serve --dir /srv/agents
# index at /, each agent at /<slug>/v1/*, /<slug>/playground

Only mount what you mean to run. Every mounted agent's channels are live, and webhook-driven agents spend model budget on every wake. --mode single serves exactly one agent at the unslugged /v1/* when the agent is the whole host. See the HTTP API for route layout and the Slack guide for multi-agent token setup.

The production flags

Use these settings in production:

FlagIn production
--devLeave off. Dev mode admits unsigned loopback GitHub deliveries, widens playground session listing on loopback, and never auto-fires schedules.
--bearer-tokenSet on shared hosts unless an authenticating proxy is the trust boundary and you use --allow-anonymous instead.
--allow-anonymousUse only behind an authenticating network boundary. It also widens playground session access so Slack and webhook sessions appear.
--state-rootPlace on a persistent volume outside any repo.
--public-urlSet when cloud-runtime turns must call back into peers on this host.
--no-playgroundSet when no human needs the UI.
--no-docsSet to remove the documentation site at /docs.
--no-schedulesSet on secondary hosts so schedules run exactly once.

Schedules fire on their cron cadence (UTC) in production mode. They have no cross-host coordination, so enable them on exactly one serving process per project.

Restarts and upgrades

Restarts preserve sessions, event streams, and SDK conversation state under the state root. Parked approvals and in-memory reminders don't survive a restart; re-run or recreate them afterward.

Observability

Use agent-sdk logs for runtime output, OpenTelemetry for OTLP traces and metrics, hooks for in-process subscribers, and session traces for incident review.

What's next

Continue with these pages: