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_TOKENin 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 theDEPTHFUSION_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):
- Log in:
depthfusion auth login --show-token— opens an Entra device-code prompt and exports a real, capability-bearing token. - 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/rolesclaim and you'll be right back to zero capabilities. See the prerequisite note below. - Switch to
depthfusion auth tokenfor non-interactive renewal — once logged in, use this instead of re-runningauth loginin 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 32for 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:
- Open a terminal and confirm the env var is set:
echo $DEPTHFUSION_MCP_TOKEN - Create or edit
~/.cursor/mcp.jsonwith the snippet above. - Restart Cursor (or run Cursor: Restart MCP Servers from the command palette).
- Open the MCP panel (View → MCP) —
depthfusionshould 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:
- Confirm the env var is set in the shell you use to launch Windsurf:
echo $DEPTHFUSION_MCP_TOKEN - Create or edit
~/.codeium/windsurf/mcp_config.jsonwith the snippet above. - Restart Windsurf.
- Open Cascade → click the plug icon → verify
depthfusionis 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:
- Confirm the env var is set:
echo $DEPTHFUSION_MCP_TOKEN - In VSCode, open Settings (JSON) and add the block above (merge into any existing
cline.mcpServers). - Reload the VSCode window (Developer: Reload Window).
- Open the Cline sidebar → MCP Servers —
depthfusionshould 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
groupsclaim 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) — sincedepthfusion auth loginnow exports the ID token (see above), a registration configured only for access-token group claims will produce a valid butgroups-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
groupsandrolesclaims, matching each against the literal role namesowner,admin,member, orviewer(union of both — T-963). Group-display-name matching viagroupsworks 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 thegroupsclaim, which will not match any role name. For cloud-only tenants, register Entra app roles named to matchowner/admin/member/viewerand assign them to users/groups in the app registration —authz/capability_check.pyreads the resultingrolesclaim (in addition togroups) 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 |
Related docs
- docs/mcp-http-server.md — transport internals and server configuration
- docs/chatgpt-mcp-setup.md — ChatGPT Desktop setup (uses SSE transport)
- docs/cli.md — CLI usage for standalone token operations
- docs/oidc-adoption-checklist.md — per-client OIDC migration status (S-331 AC-3)
- docs/configuration.md — full environment variable reference including
DEPTHFUSION_OIDC_*