# How to give Cursor access to your API without giving it your keys

> Connect Cursor to your API through Porticol, keep the key out of the agent, and hold a risky call for approval.

Canonical: https://porticol.app/docs/guides/cursor-api-access

[View as Markdown](https://porticol.app/docs/guides/cursor-api-access.md)

Cursor's agent can read your project, run a shell command and call an API, which is useful until a production key is in reach. One key in a file is enough for the agent to send the call on its own.

Porticol Guard puts the key in the keychain, gives Cursor the API over MCP, and holds a risky call until a person approves it.

## 1. Install

macOS or Linux:

```sh
curl -fsSL https://porticol.app/install.sh | sh
```

Windows, in PowerShell:

```powershell
irm https://porticol.app/install.ps1 | iex
```

Open Porticol. Local use needs no account and no sign-in. An account is asked for only when you invite someone, sync, share or use a team gateway.

## 2. Connect Cursor

On the start screen, choose **Connect my coding agent**. Porticol finds Cursor, shows the exact lines it will add to `~/.cursor/mcp.json`, and writes nothing until you click **Add Porticol**. **Remove** restores the file.

By hand, add the server to `~/.cursor/mcp.json` (`%USERPROFILE%\.cursor\mcp.json` on Windows):

```json
{
  "mcpServers": {
    "porticol": {
      "type": "stdio",
      "command": "porticol-cli",
      "args": ["mcp"]
    }
  }
}
```

Reload MCP or restart Cursor, then ask the agent to list your Porticol environments. The first call proves the connection.

Cursor has no setting that forces its traffic through a proxy. For shell and MCP calls, let Porticol add its hooks to `~/.cursor/hooks.json`: `beforeShellExecution` and `beforeMCPExecution` call `porticol-cli guard hook cursor`, with `failClosed: true`, so a hook that crashes or times out denies the call.

## 3. Write a policy

Guard reads one YAML policy. Start in shadow mode, where Guard logs what it would do and lets the call through, then switch to `enforce` when the rules match how your team works. The policy below is in `enforce`, because the walkthrough holds a call.

```yaml
version: 1
mode: enforce
hosts:
  api.example.com: { env: production, key: "{{api_key}}" }
rules:
  - match: { method: [GET, HEAD] }
    action: allow
  - match: { env: production, method: [POST, PUT, PATCH] }
    action: ask
agents:
  cursor: { up_to: production }
  "*": { up_to: staging }
```

`{{api_key}}` names a secret variable of the production environment. Its value stays in the keychain, and Porticol adds it to the request.

The first matching rule wins. A call above the agent's ceiling is denied before any rule. On a protected environment, a call no rule matches asks by default, so a DELETE to production is held.

## 4. The held call

When Cursor sends a DELETE to production, `send` returns:

```json
{
  "status": "pending",
  "approval_id": "8f0c…",
  "reason": "DELETE to production needs a person.",
  "message": "A person must approve this call in Porticol. Call send again with the same request and approval_id once they have. Do not loop and do not ask for a secret."
}
```

The approval card shows the agent's name, the environment, the exact request with keys masked, and the agent's reason in its own words. Choose **Allow once**, **Allow for today** or **Deny**. A system notification fires while the app is in the background. From a terminal, `porticol-cli approve <id>` takes the four-letter code shown on the card; the agent's shell has no terminal, so it cannot approve its own call.

The approval is bound to a SHA-256 of the request the policy decided: the method, URL, environment, headers, auth, protocol, settings and body. It is single use and expires after 10 minutes. Cursor retries once with the same request and `approval_id`; a changed request does not match. A denied call returns `{"status": "denied"}` and is not retried.

## 5. Read the log

Every call lands in Agent activity: the agent, method, URL, environment, decision and the rule behind it. The same entries sit in a hash-chained log. `porticol-cli guard log verify` checks the chain, and the log exports as JSON or CSV. Free keeps 30 days on this computer; on the server, Team keeps 90 days and Business 180, with export.

## The honest limit for Cursor

Cursor sits at level 1 with hooks, not level 2. The `failClosed` hooks can ask and deny on shell and MCP calls, but they are not a forced network path: a command that avoids the hook, or an unsandboxed shell outside Cursor's control, can still reach the network. Level 1 stops mistakes, not an agent that finds a key and runs `curl` itself. Use `porticol-cli guard scan` to find production keys on the laptop, and put the production key on the gateway when the call must be stopped for certain. Windows runs levels 1 and 3 only. Cursor's name in the log is self-reported.

## Questions

**Is Cursor protected at the same level as Claude Code?** No. Claude Code has a sandbox that can force traffic through the proxy on macOS and Linux. Cursor has no such setting, so Guard can hold and deny the calls it sees, not every call the machine can make.

**What happens when a hook fails?** With `failClosed: true`, a crash or a timeout denies the call. That is deliberate: a guard that fails open is not a guard.

**Does Cursor see the key?** No. Porticol injects the key when it sends the request and masks every result. A key already in the project is a separate problem, and `porticol-cli guard scan` finds those.
