# Cursor Enterprise Rollout — Push ACP to Every Seat with Team Hooks or an Enterprise hooks.json

How a Cursor Enterprise admin puts ACP's policy hook on every seat: team hooks distributed from the dashboard and synced to clients every thirty minutes, or an enterprise hooks.json dropped by MDM at the system path; the MCP Configuration allowlist entry; per-person enrollment; and what the Admin API audit log records.

# Roll ACP out to every Cursor seat

Cursor is the one harness whose admin console distributes the hook *configuration* itself, on a schedule, without device management. The script still has to be on disk. Cursor's [hooks](https://cursor.com/docs/hooks), [deployment patterns](https://cursor.com/docs/enterprise/deployment-patterns), [model and integration management](https://cursor.com/docs/enterprise/model-and-integration-management), and [Admin API](https://cursor.com/docs/account/teams/admin-api) pages are the source for every path and name below. Read the [shape all four harnesses share](/docs/enterprise) first if you haven't.

## Before you push

1. Claim your company domain on the ACP workspace's Team page so enrollments land in your workspace ([why](/docs/enterprise#who-each-call-is-attributed-to)).
2. Decide the posture for unenrolled seats. The wrapper below sets `ACP_REQUIRE_ENROLLMENT=1`, so a seat with no credential blocks each call with the enrollment step. Drop the variable for the self-serve posture (run, warn, log).
3. Check who already ran the self-serve installer. Their `~/.cursor/hooks.json` has a user-level ACP entry that will run *in addition to* the managed one. Have them remove it, or accept a doubled hook latency during transition.

## Step 1: Put the script on the machine

Cursor's docs are explicit: "Cursor does not deploy or manage files through your MDM solution." Your MDM drops two files into a directory users can't write to. macOS:

```bash
COMMIT=<sha>            # a commit of github.com/agentic-control-plane/claude-code-acp-plugin
install -d -m 755 /etc/acp
curl -sf "https://raw.githubusercontent.com/agentic-control-plane/claude-code-acp-plugin/$COMMIT/bin/govern.mjs" -o /etc/acp/govern.mjs
echo "<expected-sha256>  /etc/acp/govern.mjs" | shasum -a 256 -c

cat > /etc/acp/cursor-hook.sh <<'SH'
#!/bin/sh
ACP_CLIENT=cursor ACP_REQUIRE_ENROLLMENT=1 exec node /etc/acp/govern.mjs
SH
chmod 755 /etc/acp/cursor-hook.sh
chmod 644 /etc/acp/govern.mjs
```

Linux is identical. Windows, into `C:\ProgramData\acp\`: the same `govern.mjs`, plus `cursor-hook.cmd`:

```bat
@echo off
set ACP_CLIENT=cursor
set ACP_REQUIRE_ENROLLMENT=1
node "C:\ProgramData\acp\govern.mjs"
```

Node 18 or newer must be on the signed-in user's PATH. The script has no other dependency.

## Step 2: The hook file

The same entries the self-serve installer writes to `~/.cursor/hooks.json`, pointed at the managed path:

```json
{
  "version": 1,
  "hooks": {
    "preToolUse": [
      {
        "matcher": ".*",
        "hooks": [
          { "type": "command", "command": "/etc/acp/cursor-hook.sh", "timeout": 5 }
        ]
      }
    ],
    "postToolUse": [
      {
        "matcher": ".*",
        "hooks": [
          { "type": "command", "command": "/etc/acp/cursor-hook.sh", "timeout": 5 }
        ]
      }
    ]
  }
}
```

`preToolUse` fires before every tool execution and honors a deny; `postToolUse` records the result. Cursor also exposes narrower events, `beforeShellExecution` and `beforeMCPExecution` among them, which carry richer input for their one tool class; the ACP script speaks the `preToolUse` contract, which covers all of them.

Two Cursor options are worth a deliberate choice, and the sample leaves both at their defaults:

- **`failClosed`** (default `false`): whether a hook that errors or times out blocks the action. ACP's script fails loud, not closed, when the gateway is unreachable in an interactive session, on the principle that a broken hook should never brick a fleet silently. Set `failClosed: true` only if that is the posture you want on hook *errors*, which is a different question from policy denies.
- **`matcher`**: `".*"` is what the self-serve installer uses. Cursor's matcher is a pattern against the tool; leave it broad and let workspace policy decide.

## Step 3: Deliver it

| Channel | Where | Notes |
|---|---|---|
| Team hooks (dashboard) | Cursor dashboard, Enterprise plans | Cursor delivers configured hooks to every member's client on login and syncs every thirty minutes. OS targeting is supported. No access to individual machines needed, for the config. |
| Enterprise `hooks.json` (MDM) | macOS `/Library/Application Support/Cursor/hooks.json` · Linux and WSL `/etc/cursor/hooks.json` · Windows `C:\ProgramData\Cursor\hooks.json` | Highest-precedence layer. Your MDM writes it alongside the script. |

Precedence, highest first: Enterprise, Team, Project, User. Layers merge, so higher layers win on conflicting responses but don't suppress lower-layer hooks. That's why step 0 asks about existing user-level entries.

Team and enterprise hooks also run in Cursor's cloud agents; user-level hooks don't, because cloud agents have no home directory.

## Step 4: Allowlist the MCP endpoint

Under **MCP Configuration** in the dashboard (Enterprise), add ACP as a URL entry:

```
https://api.agenticcontrolplane.com/mcp
```

Once any allowlist exists, only matching servers can run. Cursor's docs are clear that "adding a server to the allowlist does not push it to users' machines." To distribute the server itself, link it to your Team Marketplace, or let each user add it; either way sign-in is MCP OAuth in the browser, so attribution comes with it.

The same page lets you set a sandbox network mode and a tool filter per server. ACP's server exposes read-only introspection tools plus one that drafts a rule for a human to confirm; none of them applies policy by itself.

## Step 5: Each person enrolls once

The pushed config identifies no one. Each developer opens [cloud.agenticcontrolplane.com/plugin/authorize](https://cloud.agenticcontrolplane.com/plugin/authorize), signs in with their work identity, and pastes the one line it shows:

```bash
echo 'gsk_…' > ~/.acp/credentials
```

Until they do, with `ACP_REQUIRE_ENROLLMENT=1` every tool call is denied with that instruction in the message.

## What you'll see

- Console rows with client `cursor`, one per person.
- On an unenrolled seat: `[ACP] Not enrolled…` and the call doesn't run.
- In Cursor's own audit log, via the Admin API's `/teams/audit-logs`: a `team_hook` event when the hook was pushed and `mcp_server_config` when the allowlist changed. Those are the receipts that the rollout itself happened, separate from ACP's per-call ledger.

## What's verified and what isn't

The hook entries above are the ones the self-serve installer writes today and the [Cursor integration](/integrations/cursor) documents. The enterprise and team channels are described from Cursor's published docs; we have not yet exercised them on a Cursor Enterprise seat ourselves. The issue tracking that is [gatewaystack-connect #943](https://github.com/davidcrowe/gatewaystack-connect/issues/943).

## Related

- [Enterprise rollout: the shape all harnesses share](/docs/enterprise)
- [Cursor integration](/integrations/cursor) — the self-serve install
- [Cursor native controls](/controls/cursor)
- Cursor: [Hooks](https://cursor.com/docs/hooks) · [Deployment patterns](https://cursor.com/docs/enterprise/deployment-patterns) · [Model and integration management](https://cursor.com/docs/enterprise/model-and-integration-management) · [Admin API](https://cursor.com/docs/account/teams/admin-api)
