Appearance
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.
| Option | Use it when | You manage |
|---|---|---|
| Cursor-managed hosting (preferred) | You want the shortest path from a Git repo to a running agent | Agent code, external storage, declared egress, and deployment secrets |
| Self-hosting | You need your own network, proxy, persistent filesystem, or process controls | Agent 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/julyto 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 deployInside 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-agentIMPORTANT
--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-agentSet 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-agentsecrets 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.
| Command | What prints |
|---|---|
secrets set / secrets list | Names only. Values never print. |
rotate-pod-credential | Masked key only. |
First deploy / rotate-token | Full alias token once. Save it outside the agent transcript; it cannot be retrieved later. |
deployment --json | May 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-agentKeep 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-agentConnect 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/paymentsThe 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-approverWithout 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-agentstop 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:
- Pass
--bearer-token <secret>for a shared host. - Define channel-specific auth for routes with their own credentials or signatures.
- Use
--allow-anonymousonly 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.
| Credential | Used for | Provide it as |
|---|---|---|
| Cursor API key | model turns, cloud runtime, Cursor account MCP connections | agent-sdk login (stores a revocable key), CURSOR_API_KEY, or --api-key / serve({ apiKey }) |
| Slack tokens | Slack channels | <PREFIX>_SLACK_BOT_TOKEN + <PREFIX>_SLACK_APP_TOKEN per agent |
| GitHub webhook secret | delivery signature verification | GITHUB_WEBHOOK_SECRET, same value on server and signer |
| GitHub API | outbound API calls | a GitHub App (GITHUB_APP_ID + GITHUB_APP_PRIVATE_KEY + installation id) or GITHUB_TOKEN / gh auth login |
| MCP connection tokens | authored MCP connections | env 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/repoRun 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>/playgroundOnly 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:
| Flag | In production |
|---|---|
--dev | Leave off. Dev mode admits unsigned loopback GitHub deliveries, widens playground session listing on loopback, and never auto-fires schedules. |
--bearer-token | Set on shared hosts unless an authenticating proxy is the trust boundary and you use --allow-anonymous instead. |
--allow-anonymous | Use only behind an authenticating network boundary. It also widens playground session access so Slack and webhook sessions appear. |
--state-root | Place on a persistent volume outside any repo. |
--public-url | Set when cloud-runtime turns must call back into peers on this host. |
--no-playground | Set when no human needs the UI. |
--no-docs | Set to remove the documentation site at /docs. |
--no-schedules | Set 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:
- CLI reference: deploy, inspect, stop, and rotate hosted agents
- Storage: preserve supported records across engine replacements
- Channels: the auth policies in detail
- GitHub guide: delivery paths without a public URL
- Troubleshooting: the symptom table for when a deploy misbehaves