Skip to content

MCP Client Setup — Cursor, Windsurf, and Cline

Connect AI code editors to the DepthFusion MCP server at https://mcp.tonracein.com. All three editors below use the Streamable HTTP transport (POST /mcp, MCP spec 2025-03-26).

Token: Set DEPTHFUSION_MCP_TOKEN in your shell environment before launching your editor. Never paste a real token in any config file committed to version control. Your token lives in ~/.claude/depthfusion.env — look for the DEPTHFUSION_MCP_TOKEN= line.


Getting capability_denied / write calls rejected? Read this first (S-331)

The static DEPTHFUSION_MCP_TOKEN / DEPTHFUSION_API_TOKEN path above gets you connected, but it is intentionally zero-capability — it can never hold write_own_records, no matter how it's configured (S-321 AC-1, by design, not a bug). If a write tool call (publish_context, record_decision, etc.) is denied, this is why. Every static-token client on this host hits this, including kitabu and agent-ops's own sessions — see the per-client adoption checklist for who has and hasn't migrated.

The fix — adopt OIDC device-code auth (3 steps):

  1. Log in: depthfusion auth login --show-token — opens an Entra device-code prompt and exports a real, capability-bearing token.
  2. Confirm the app role is on the ID token — Entra must have the groups claim enabled for ID tokens specifically (not just access tokens), or the login will succeed with no groups/roles claim and you'll be right back to zero capabilities. See the prerequisite note below.
  3. Switch to depthfusion auth token for non-interactive renewal — once logged in, use this instead of re-running auth login in scripts/cron/CI so the token renews silently instead of prompting.

Full detail on all three steps: OIDC Device-Code Login (cross-project clients) below.


Prerequisites

  • A DepthFusion MCP token (contact your admin or generate one with openssl rand -hex 32 for self-hosted).
  • Your editor version must support the MCP 2025-03-26 Streamable HTTP transport:
  • Cursor ≥ 0.43 (Settings → Beta → Enable MCP)
  • Windsurf ≥ 1.0 (built-in MCP support)
  • Cline ≥ 3.0 (VSCode extension)

Cursor

Cursor stores MCP server definitions in ~/.cursor/mcp.json (global) or .cursor/mcp.json at the project root (workspace-scoped).

{
  "mcpServers": {
    "depthfusion": {
      "type": "http",
      "url": "https://mcp.tonracein.com/mcp",
      "headers": {
        "Authorization": "Bearer ${DEPTHFUSION_MCP_TOKEN}"
      }
    }
  }
}

Steps:

  1. Open a terminal and confirm the env var is set: echo $DEPTHFUSION_MCP_TOKEN
  2. Create or edit ~/.cursor/mcp.json with the snippet above.
  3. Restart Cursor (or run Cursor: Restart MCP Servers from the command palette).
  4. Open the MCP panel (View → MCP) — depthfusion should appear as Connected.

Windsurf

Windsurf reads MCP configuration from ~/.codeium/windsurf/mcp_config.json.

{
  "mcpServers": {
    "depthfusion": {
      "serverType": "http",
      "url": "https://mcp.tonracein.com/mcp",
      "headers": {
        "Authorization": "Bearer ${DEPTHFUSION_MCP_TOKEN}"
      }
    }
  }
}

Steps:

  1. Confirm the env var is set in the shell you use to launch Windsurf: echo $DEPTHFUSION_MCP_TOKEN
  2. Create or edit ~/.codeium/windsurf/mcp_config.json with the snippet above.
  3. Restart Windsurf.
  4. Open Cascade → click the plug icon → verify depthfusion is listed and active.

Cline (VSCode Extension)

Cline persists MCP settings in VSCode's settings.json. Open the Cline settings panel (Cline: Open Settings from the command palette) and add under cline.mcpServers:

{
  "cline.mcpServers": {
    "depthfusion": {
      "type": "streamableHttp",
      "url": "https://mcp.tonracein.com/mcp",
      "headers": {
        "Authorization": "Bearer ${DEPTHFUSION_MCP_TOKEN}"
      },
      "disabled": false,
      "alwaysAllow": []
    }
  }
}

Steps:

  1. Confirm the env var is set: echo $DEPTHFUSION_MCP_TOKEN
  2. In VSCode, open Settings (JSON) and add the block above (merge into any existing cline.mcpServers).
  3. Reload the VSCode window (Developer: Reload Window).
  4. Open the Cline sidebar → MCP Servers — depthfusion should appear as Connected.

Verifying the Connection

All three editors issue an initialize JSON-RPC call on connect. You can replicate the same round-trip from the command line to confirm your token works:

# Replace $DEPTHFUSION_MCP_TOKEN with your token value (or export it first)
curl -s -X POST https://mcp.tonracein.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer ${DEPTHFUSION_MCP_TOKEN}" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-03-26",
      "capabilities": {},
      "clientInfo": {"name": "curl-test", "version": "1.0"}
    }
  }'

Expected response (initialize):

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-03-26",
    "capabilities": {"tools": {}},
    "serverInfo": {"name": "depthfusion", "version": "2.0.0"}
  }
}

Verify tools/list round-trip:

curl -s -X POST https://mcp.tonracein.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer ${DEPTHFUSION_MCP_TOKEN}" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'

Expected: a JSON response listing 31 tools (e.g. depthfusion_recall_relevant, depthfusion_status, …).

Smoke-test transcript (live server, 2026-08-03):

$ curl -s -X POST https://mcp.tonracein.com/mcp \
    -H "Content-Type: application/json" \
    -H "Accept: application/json, text/event-stream" \
    -H "Authorization: Bearer ${DEPTHFUSION_MCP_TOKEN}" \
    -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl-test","version":"1.0"}}}'

{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-03-26","capabilities":{"tools":{}},"serverInfo":{"name":"depthfusion","version":"2.0.0"}}}

$ curl -s -X POST https://mcp.tonracein.com/mcp \
    -H "Content-Type: application/json" \
    -H "Accept: application/json, text/event-stream" \
    -H "Authorization: Bearer ${DEPTHFUSION_MCP_TOKEN}" \
    -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' | python3 -c \
    "import json,sys; d=json.load(sys.stdin); print(f'tool_count={len(d[\"result\"][\"tools\"])}')"

tool_count=31

If you receive 401 Unauthorized or {"detail":{"error":"invalid_token",...}}, re-check the token.


OIDC Device-Code Login (cross-project clients)

The static DEPTHFUSION_MCP_TOKEN / DEPTHFUSION_API_TOKEN path above is intentionally capability-limited: a legacy-token principal always carries zero capability grants (identity/legacy_shim.py), so it can never hold write_own_records no matter how it is configured. This is by design, not a bug — it is the project's push toward OIDC-based identity. Cross-project clients (e.g. agent-ops) that need real read/write capabilities should use the OIDC device-code flow instead.

This is a separate identity from the static MCP token above. The device-code flow is for clients that need real, grantable capabilities (read/write), not just connectivity.

What gets exported: the ID token, not the access token. depthfusion auth login exports and caches the OIDC ID token, not the OAuth access token. This repo's own TokenValidator/PrincipalDep validate any RS256 JWT the same way (algorithm, signature, kid, exp/nbf, iss, aud) and copy sub/groups from its claims, regardless of whether the JWT originated as an id_token or an access_token — so using the id_token means the client-side and server-side DEPTHFUSION_OIDC_AUDIENCE below are the same value (the OIDC client_id), not different things on each side. A Microsoft-Graph-audienced access token (the default result of the documented client scope, which has no resource scope) is cryptographically unverifiable by third parties by design — it carries a proprietary nonce in the JWT header — so no server-side configuration could ever have made an access-token export work.

Server-side setup — set these three variables so api/auth.py builds a real OIDC principal dependency instead of the legacy/unconfigured path:

Variable Description Example
DEPTHFUSION_JWKS_URI JWKS endpoint for the identity provider https://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keys
DEPTHFUSION_OIDC_ISSUER Expected token iss claim https://login.microsoftonline.com/<tenant-id>/v2.0
DEPTHFUSION_OIDC_AUDIENCE Expected token aud claim the same Entra app client ID used as DEPTHFUSION_OIDC_CLIENT_ID at login below

Client-side login — every documented client above (Cursor, Windsurf, Cline, curl, and cross-project callers like agent-ops) authenticates by sending a static Authorization: Bearer header sourced from a shell environment variable, not by calling into this package directly. So the device-code login's job is to hand you a real token you plug into whichever client you use, the same way you would with a static token:

export DEPTHFUSION_OIDC_CLIENT_ID="<entra-app-id>"
export DEPTHFUSION_OIDC_TENANT_ID="<entra-tenant-id>"

# DEPTHFUSION_JWKS_URI does not need to be set client-side — it is derived
# from DEPTHFUSION_OIDC_TENANT_ID automatically when absent.

# Prints status to stderr and, by default, NO token material at all
# (S-317 — the export lines used to print unconditionally and a live
# token leaked into a pasted transcript during verification). The cache
# file at $DEPTHFUSION_DATA_DIR/auth_token.json is already usable by any
# first-party caller; --show-token is the explicit opt-in for a client
# that needs the token in an env var instead (Cursor, Windsurf, Cline,
# curl — this page's examples):
eval "$(depthfusion auth login --show-token)"

This opens a device-code prompt (visit the printed URL, enter the code), caches the token at $DEPTHFUSION_DATA_DIR/auth_token.json (default ~/.depthfusion/auth_token.json, 0600 permissions) for callers that check the cache directly, and — only with --show-token — sets both DEPTHFUSION_MCP_TOKEN (used by Cursor, Windsurf, Cline, and the curl examples above) and DEPTHFUSION_API_TOKEN (used by the stdio/legacy-auth path) in your shell for this session. Without --show-token, the command prints only the cache path and signed-in principal — no token, on stdout or stderr. The token has a real expiry (the provider's exp claim); re-run depthfusion auth login --show-token once it lapses.

Prerequisite — groups must be added to the ID token specifically. Entra does not include a groups claim on any token by default. In the app registration, under Token configuration → Add groups claim, group membership must be enabled for ID tokens (not just access tokens) — since depthfusion auth login now exports the ID token (see above), a registration configured only for access-token group claims will produce a valid but groups-less login. Full walkthrough: docs/runbooks/entra-app-registration.md § Group Claims.

Group-naming requirement (cloud-only Entra tenants): capability resolution reads the identity provider's groups and roles claims, matching each against the literal role names owner, admin, member, or viewer (union of both — T-963). Group-display-name matching via groups works out of the box only for on-prem-synced Entra groups, or via an Entra claims-mapping policy for cloud-only tenants — cloud-only Entra tenants emit group object IDs, not display names, in the groups claim, which will not match any role name. For cloud-only tenants, register Entra app roles named to match owner/admin/member/viewer and assign them to users/groups in the app registration — authz/capability_check.py reads the resulting roles claim (in addition to groups) since T-963, so no claims-mapping workaround is required for this path anymore. Group claims still need to be enabled for ID tokens specifically either way, per the prerequisite above.

Renewing without an interactive prompt (depthfusion auth token) — S-321 AC-2

depthfusion auth login above is interactive by design: it always opens the device-code browser flow, every time. For a script, cron job, or headless hook that just needs a usable token — renewing an expired-but-refreshable cache when possible — use depthfusion auth token instead. Unlike auth login, it never opens a prompt:

exports="$(depthfusion auth token)" || { echo "depthfusion auth failed" >&2; exit 1; }
eval "$exports"

Capture the output before eval-ing it — do not write eval "$(depthfusion auth token)" directly. On failure the command exits 1 with empty stdout, and eval "" itself returns 0 — even under set -e — so a direct eval "$(...)" silently absorbs the failure and execution continues as if authentication had succeeded (PR #81 gate round 3, codex NEW-2). The capture-then-check form above fails loudly instead.

Precedence (identical to resolve_token_with_renewal(), the function behind this command): a still-valid cached token is printed as-is; an expired-but-refreshable one is silently renewed against the provider first (rotating the refresh token, per the provider's contract); if renewal isn't possible or fails, DEPTHFUSION_API_TOKEN is used as a last-resort fallback when set. If none of those yield a token, the command exits 1 with the same actionable "re-run depthfusion auth login" message auth login itself uses on failure — it is never swallowed.

External clients that currently export a token once (Cursor, Windsurf, Cline, curl, cross-project callers such as agent-ops — the clients documented above under "Client-side login") do not automatically benefit from this: they read DEPTHFUSION_MCP_TOKEN/DEPTHFUSION_API_TOKEN from an env var set at editor-launch or shell-session time, and nothing re-runs eval "$(depthfusion auth login --show-token)" for them once that token expires. Adopting auth token for these means either:

  • re-running the capture-then-check form above (instead of auth login --show-token) whenever the session needs a fresh token — it renews silently rather than opening a browser, so it's safe to re-run unattended; or
  • wiring it into whatever process supplies that env var in the first place (a shell profile, a CI job, a supervisor script) so the export happens on every invocation rather than once at setup time.

Neither is done automatically by this repo for any external client — adopting it is a per-client integration choice, tracked as a follow-up (see BACKLOG.md T-1058's note). One in-repo consumer already does this: scripts/mcp-smoke-test.sh calls depthfusion auth token itself (when the CLI is on PATH and no token env var is already set) before deciding whether to test against the live server.

For a hook that wants the renewal side effect (refreshing the on-disk cache) without a live credential ever appearing in its own output — avoiding the transcript-leak risk auth login's S-317 warning exists for — pass --renew-only: it renews the cached token if expired (a no-op when it's still valid) and exits 0/1 accordingly, printing nothing. A failure to save a successfully-renewed token to disk also exits 1 — --renew-only's entire contract is "the cache is now fresh," so a renewal whose result never made it to disk is a failure, not a silent success:

depthfusion auth token --renew-only || echo "renewal failed — check auth"

Troubleshooting

Symptom Likely cause Fix
401 Unauthorized Token not set or wrong value echo $DEPTHFUSION_MCP_TOKEN; re-export the correct value
Connection refused Editor launched before env var was set Set the env var, then restart the editor
curl: (6) Could not resolve host Network/DNS issue curl -sf https://mcp.tonracein.com/health to confirm reachability
Tools list empty Editor using legacy SSE transport Confirm type is http / streamableHttp (not sse) and URL ends in /mcp
tool_count=0 in curl test Using the SSE endpoint by mistake The SSE endpoint is /sse; Streamable HTTP is /mcp
Write calls denied with capability_denied despite a valid token Using the legacy static-token path, which is capability-limited by design Switch to depthfusion auth login (see above) for a real, capability-bearing identity