# EULEX MCP — Authentication

Authentication reference for the EULEX legal-research MCP server. Used as
a submission artefact for connector reviews (Anthropic Claude for Legal,
ChatGPT, Cursor, etc.).

---

## Endpoint

* **Production URL:** `https://mcp.eulex.ai/mcp`
* **Transport:** Streamable HTTP (MCP 2025-06-18 spec compliant)
* **Implementation:** FastMCP (Python)
* **Region:** `europe-west1` (GCP Cloud Run)

```jsonc
// .mcp.json snippet (Claude Desktop, Cursor, VS Code, etc.)
{
  "mcpServers": {
    "eulex": {
      "url": "https://mcp.eulex.ai/mcp",
      "transport": "streamable-http"
    }
  }
}
```

The server publishes RFC 9728 / RFC 8414 well-known discovery routes
both at the path-scoped location (`/.well-known/oauth-protected-resource/mcp`)
and at the domain root (`/.well-known/oauth-protected-resource`,
`/.well-known/oauth-authorization-server`) for clients (Claude.ai,
ChatGPT, Copilot Studio, Mistral) that look at the root.

---

## Three authentication paths

All tokens are bearer JWTs sent as `Authorization: Bearer <token>` on
every MCP HTTP request. The server resolves the path automatically by
inspecting issuer / audience claims.

### 1. OAuth 2.1 marketplace (Claude.ai, ChatGPT, Copilot, Cursor)

* **Standard:** OAuth 2.1 with **Dynamic Client Registration** (RFC 7591)
  and **PKCE** (RFC 7636).
* **Authorisation server:** `https://eulex.ai`
* **Token TTL:** 90 days (refresh-token rotation).
* **Scopes:**
  * `eulex_free` (default) → 10 free tools
  * `eulex_plus` → all 12 tools (free + `eu_transposition`,
    `eurostat_query`)
* **User UX:** click the connector → browser hop to `eulex.ai`
  consent page → back to client. Tokens are stored in the client's
  credential vault.

### 2. Personal Access Tokens (PAT) — direct API / scripts

* **Self-service issuance is not currently exposed on production.** A
  `/eulex-ai/mcp-token` route existed in an earlier WordPress build but
  was retired alongside the social-login (`mcp_resume`) hardening of
  the OAuth flow. There is no public web UI to mint a PAT today.
* **How to obtain one:** email **[support@eulex.ai](mailto:support@eulex.ai)**
  with your EULEX account email and intended use (script, third-party
  client, internal tool). PATs are minted manually with the same
  90-day TTL and tier-matched scope (`eulex_free` or `eulex_plus`)
  that the OAuth flow would issue.
* **Recommended alternative:** use **OAuth 2.1** via any MCP-compatible
  client (Claude desktop, Cursor, ChatGPT, custom) — Dynamic Client
  Registration + PKCE handles the entire dance for you, no PAT
  required. Subscription / billing is at
  **[My account](https://eulex.ai/my-account)**.
* **TTL:** 90 days (refresh token rotation handled by OAuth flow; PATs
  are static — re-issue at expiry).
* **Use:** copy/paste into a script, an alternative MCP client, or a
  Cursor / VS Code config that supports static bearer tokens.
* **Revocation:** email support to revoke; revoked PATs propagate to
  every Cloud Run instance within the `EULEX_MCP_PAT_REVOCATION_TTL`
  window (default 60 s).

### 3. Service tokens (B2B partner flow)

* **Standard:** signed JWT (HMAC-SHA256), short-lived (15 min).
* **Issuer:** the partner's own backend; `partner_app` claim must be
  one of the configured partners (e.g. `max`).
* **Trust:** the partner's backend holds a long-lived
  `EULEX_MCP_PARTNER_<NAME>_SECRET` (Google Secret Manager) and mints
  per-user JWTs server-side. The browser never sees the partner
  secret.
* **Tier behaviour:** partner tokens auto-bypass the daily rate
  limiter (the partner self-throttles) and are auto-Plus.

---

## Token claims (canonical)

```json
{
  "iss": "https://eulex.ai",
  "sub": "user_or_partner_subject",
  "aud": "https://mcp.eulex.ai",
  "scope": "eulex_plus",   // or eulex_free
  "tier": "plus",          // mirrored from scope
  "partner_app": "max",    // only on service tokens
  "exp": 1781234567,
  "iat": 1773468167,
  "jti": "..."
}
```

The server validates: signature, `iss`, `aud`, `exp`, and tier on every
call. Tier is enforced in-function (not as a per-tool `auth=`
decorator) so MCP clients don't show a confirmation dialog for every
Plus call.

---

## Permission errors — user-facing UX

When a free-tier token calls a Plus tool, the server returns a friendly
`ValueError`:

```
🔒 eu_transposition requires an EULEX Plus subscription.
Your current plan is 'free'.
Upgrade at https://eulex.ai/landing/pricing to unlock graph
exploration, semantic search, compliance snapshots, and other advanced
legal tools.
```

Clients should **not** silently retry on permission errors — surface
the upgrade link directly. The server-side `instructions` block
explicitly tells the agent to relay the message and not retry.

---

## Examples

### Cursor (`~/.cursor/mcp.json`)

```json
{
  "mcpServers": {
    "eulex": {
      "url": "https://mcp.eulex.ai/mcp",
      "transport": "streamable-http"
    }
  }
}
```

For PAT users, add a `headers` field with the bearer token:

```json
{
  "mcpServers": {
    "eulex": {
      "url": "https://mcp.eulex.ai/mcp",
      "transport": "streamable-http",
      "headers": { "Authorization": "Bearer <PAT>" }
    }
  }
}
```

### Claude Code (`~/.config/claude-code/mcp_servers.json`)

```json
{
  "eulex": {
    "type": "http",
    "url": "https://mcp.eulex.ai/mcp"
  }
}
```

### VS Code (workspace `.vscode/mcp.json`)

```json
{
  "servers": {
    "eulex": {
      "type": "streamable-http",
      "url": "https://mcp.eulex.ai/mcp"
    }
  }
}
```

### Claude.ai marketplace

End users add EULEX from the marketplace; the OAuth dance is automatic.

---

## Storage

OAuth client registrations and refresh tokens are persisted in
**Memorystore Redis** (db=2), encrypted at rest with a Fernet key
(`EULEX_MCP_STORAGE_ENCRYPTION_KEY` from Google Secret Manager). PATs
are stored in WordPress with bcrypt-hashed identifiers; only revoked
IDs leave the WordPress boundary.

See [`EULEX_MCP_OAuth_Spec.md`](../../dokumentacija_vanjska/EULEX_MCP_OAuth_Spec.md)
for the full OAuth flow, JWT shape, error codes, and operations
guide.

---

## Security disclosure

EULEX publishes a [`security.txt`](https://mcp.eulex.ai/.well-known/security.txt)
file (RFC 9116) at both `https://mcp.eulex.ai/.well-known/security.txt`
and `https://mcp.eulex.ai/security.txt`. Researchers and automated
scanners can use either path.

* **Vulnerability reports:** `mailto:security@eulex.ai`
* **Operational issues:** `mailto:support@eulex.ai`
* **Preferred languages:** English, Croatian
* **Policy:** https://eulex.ai/privacy

We acknowledge reports within 72 hours and aim to land a fix or
mitigation within 30 days for high-severity findings, faster for
critical ones. Coordinated disclosure is appreciated.

---

## Privacy and data handling

The full privacy policy is published at
[https://eulex.ai/privacy](https://eulex.ai/privacy). Summary relevant
to MCP integrators:

* **Token storage:** OAuth refresh tokens and dynamically registered
  client credentials are encrypted at rest with Fernet
  (`EULEX_MCP_STORAGE_ENCRYPTION_KEY` from Google Secret Manager) in
  Memorystore Redis (`db=2`, region `europe-west1`).
* **Audit log:** the tier-gate writes structured events of the form
  `(user_id, tool_name, timestamp, success_bool, tier)` to Cloud
  Logging. **Query content is not persisted.** Logs are kept for 30 days
  for abuse prevention and rate-limit auditing.
* **Upstream processors:**
  * **CloudSQL PostgreSQL** (Google Cloud, `europe-west1`) — corpus
    storage of EUR-Lex documents and sections.
  * **Neo4j Aura** (Google Cloud, `europe-west1`) — citation /
    amendment / legal-basis graph.
  * **Pinecone** (managed, AWS `us-east-1`) — 3072-dim semantic
    vectors over public EU legal text. No user content is sent.
  * **OpenAI** (`gpt-5.4`) — query intent classification and result
    reranking on the consumer surface; opt-out of training enabled
    (Zero Data Retention where the API supports it).
  * **Anthropic Claude 4.5 Sonnet** — secondary synthesis on the
    consumer surface; same ZDR posture.
  * **EUR-Lex Cellar SPARQL** and **Eurostat REST** — public
    upstreams; we forward only the resolved query string, never user
    identifiers or token claims.
  * **Memorystore Redis** — OAuth + rate-limit state, region
    `europe-west1`, encrypted at rest by Google.
* **No third-party advertising or analytics processors** are wired
  into the MCP server.
