{
  "schema_version": 2,
  "id": "develop/ai/redisvl/user_guide/how_to_guides/mcp_authentication",
  "title": "Authenticate RedisVL MCP",
  "url": "https://redis.io/docs/latest/develop/ai/redisvl/user_guide/how_to_guides/mcp_authentication/",
  "summary": "",
  "aliases": [
    "/integrate/redisvl/user_guide/how_to_guides/mcp_authentication"
  ],
  "tags": [],
  "last_updated": "2026-10-12T09:34:21+02:00",
  "page_type": "content",
  "content_hash": "ab33465189d27b0d68a04765f361d7671b22bf254c2476a411e3e0b2406fd746",
  "sections": [
    {
      "id": "overview",
      "title": "Overview",
      "role": "overview",
      "text": "This guide explains how the RedisVL MCP server authenticates clients on its HTTP\ntransports and how it gates read vs write access. It also draws the boundary\nbetween what RedisVL enforces and what belongs in a gateway or policy layer.\n\n> [!NOTE]\n> Authentication applies only to the HTTP transports (`streamable-http`, `sse`).\n> The `stdio` transport is a local subprocess with no network surface and is never\n> authenticated.\n\n> [!NOTE]\n> Authentication is a separate concern from **transport security** (Host/Origin\n> validation), which is always on for the HTTP transports and defends against DNS\n> rebinding independently of auth. See\n> [Transport Security](mcp.md#transport-security-host-origin-validation). Both\n> layers apply together: auth decides *who* may call; the Host/Origin guard\n> rejects requests whose claimed authority is not allowlisted."
    },
    {
      "id": "what-redisvl-enforces",
      "title": "What RedisVL Enforces",
      "role": "content",
      "text": "RedisVL validates a bearer **JWT** that an existing identity provider (IdP)\nissued. It does not run an OAuth authorization server and does not issue tokens.\n\nOn each request it checks:\n\n- **Signature**, against a JWKS endpoint or a static public key.\n- **Issuer** (`iss`), so only tokens from your IdP are accepted.\n- **Audience** (`aud`), so a token minted for a different service cannot be\n  replayed against this server (RFC 8707).\n- **Expiration**: a present `exp` in the past is rejected, and tokens are\n  required to carry `exp` and `iat` (configurable via `required_claims`), so a\n  token with no expiration, which would never expire, is rejected.\n- **Required scopes** to connect, and (optionally) a **read scope** to call\n  `search-records` and a **write scope** to call `upsert-records`.\n\n> [!NOTE]\n> This is **coarse** authorization: it decides whether a caller may connect and whether it may read or write. It does **not** map token claims to a Redis ACL user or a per-tenant index. A custom tool profile can additionally scope its queries to a tenant claim; see [The Authorization Boundary]()."
    },
    {
      "id": "oauth-which-part-redisvl-handles",
      "title": "OAuth: Which Part RedisVL Handles",
      "role": "content",
      "text": "In OAuth terms, RedisVL MCP is a **resource server**: it validates access\ntokens that your identity provider issued. It does not run an OAuth login flow\nand does not issue tokens.\n\n[code example]\n\nSteps 1 and 2 (obtaining the token) are handled by your client and IdP. RedisVL\nonly performs the validation in step 3. As a result:\n\n- It works with any OAuth 2.0 / OIDC provider (for example Auth0, Okta, Azure AD\n  / Entra, Cognito, Keycloak): point `jwks_uri` at the provider and validate its\n  tokens.\n- RedisVL does not broker interactive \"log in with…\" flows and does not mint\n  tokens.\n\nYou would only need more than token validation when you want the server itself\nto drive an interactive browser login (an OAuth proxy), or to act as its own\nauthorization server that issues tokens. Both are out of scope today. If\ninteractive login is ever needed, a single generic `oauth-proxy` option can be\nadded behind the `auth.type` switch. For enterprise and agent deployments where\nthe caller already holds a token, JWT validation is sufficient."
    },
    {
      "id": "request-flow",
      "title": "Request Flow",
      "role": "content",
      "text": "[code example]"
    },
    {
      "id": "configure-jwt-authentication",
      "title": "Configure JWT Authentication",
      "role": "configuration",
      "text": "Add a `server.auth` block to your MCP config. Secrets can be injected with\n`${ENV}` substitution.\n\n[code example]\n\nEvery field is also settable through `REDISVL_MCP_AUTH_*` environment variables,\nwhich take precedence over the YAML block."
    },
    {
      "id": "choosing-the-authorization-claim",
      "title": "Choosing the Authorization Claim",
      "role": "content",
      "text": "Different identity providers carry authorization in different claims. The\ndefault JWT scope claim is `scp` (or `scope`). Some enterprise providers carry\nauthorization in a **`roles`** claim instead, which does not appear in the\nstandard scope set.\n\n[code example]\n\nSet the claim that holds your authorization values so read and write gating\nreads the right place:\n\n[code example]\n\nA token like the following would then pass the read gate, because\n`kb.search.read` is present in `roles`:\n\n[code example]"
    },
    {
      "id": "the-authorization-boundary",
      "title": "The Authorization Boundary",
      "role": "content",
      "text": "RedisVL MCP authenticates the caller, gates read vs write, and can scope every query a custom tool profile runs to a tenant carried in the token. It does **not** translate token claims into a specific Redis ACL user or a per-tenant index: every caller shares the server’s Redis connection, established at startup.\n\nTo scope queries by tenant inside RedisVL, add `lock.inject` to a profile; see Tenant Scoping With Claim Injection in [Run RedisVL MCP](https://redis.io/docs/latest/mcp). That filter is then the only thing separating tenants, so read the threat model in [RedisVL MCP](https://redis.io/docs/latest/../../concepts/mcp) before relying on it.\n\nIsolation enforced by Redis itself, rather than by a query filter, belongs in a **gateway or policy layer** in front of the MCP server, which validates the token, looks up a binding of claim to Redis identity, and injects the matching credentials.\n\n[code example]\n\nUse RedisVL’s JWT validation for authentication, read/write authorization, and tenant-scoped queries. Layer a gateway on top when you need per-tenant Redis ACL enforcement.\n\nWhen such a gateway or reverse proxy terminates the connection and forwards a\nrewritten `Host` header, set `server.transport_security.enabled: false` (or\ndeclare the public host via `server.transport_security.allowed_hosts`) so the\nproxy’s rewritten `Host` is not rejected by the Host/Origin guard."
    },
    {
      "id": "see-also",
      "title": "See Also",
      "role": "related",
      "text": "- [Run RedisVL MCP](https://redis.io/docs/latest/mcp): run and configure the RedisVL MCP server.\n- [RedisVL MCP](https://redis.io/docs/latest/../../concepts/mcp): custom tool profiles, tenant scoping, and its threat model."
    }
  ],
  "examples": [
    {
      "id": "oauth-which-part-redisvl-handles-ex0",
      "language": "mermaid",
      "code": "flowchart LR\n    Client -->|1 - OAuth login flow| IdP[Identity Provider]\n    IdP -->|2 - issues JWT access token| Client\n    Client -->|3 - Bearer JWT| MCP[RedisVL MCP]\n    MCP -->|validates token| Redis[(Redis)]",
      "section_id": "oauth-which-part-redisvl-handles"
    },
    {
      "id": "request-flow-ex0",
      "language": "mermaid",
      "code": "sequenceDiagram\n    actor User\n    participant IdP as Identity Provider\n    participant MCP as RedisVL MCP Server\n    participant Redis\n\n    User->>IdP: Authenticate\n    IdP-->>User: Signed JWT (iss, aud, scopes/roles)\n    User->>MCP: MCP request + Bearer JWT\n    MCP->>MCP: Validate signature (JWKS / public key)\n    MCP->>MCP: Check issuer + audience\n    alt token invalid / wrong audience / missing connect scope\n        MCP-->>User: 401 Unauthorized\n    else token valid\n        MCP->>MCP: Gate tool by read / write scope\n        alt scope present\n            MCP->>Redis: Search or upsert (single configured ACL user)\n            Redis-->>MCP: Results\n            MCP-->>User: Tool result\n        else scope missing\n            MCP-->>User: Forbidden\n        end\n    end",
      "section_id": "request-flow"
    },
    {
      "id": "configure-jwt-authentication-ex0",
      "language": "yaml",
      "code": "server:\n  redis_url: ${REDIS_URL:-redis://localhost:6379}\n  auth:\n    type: jwt\n    jwks_uri: ${MCP_JWKS_URI}          # or set public_key for a static key\n    issuer: ${MCP_ISSUER}\n    audience: api://redisvl-mcp\n    required_scopes: [kb.read]         # required to connect\n    required_claims: [exp, iat]        # claims every token must carry (default)\n    read_scope: kb.search.read         # required for search-records\n    write_scope: kb.search.write       # required for upsert-records\n\nindexes:\n  knowledge:\n    redis_name: docs_index\n    search:\n      type: fulltext\n    runtime:\n      text_field_name: content",
      "section_id": "configure-jwt-authentication"
    },
    {
      "id": "choosing-the-authorization-claim-ex0",
      "language": "mermaid",
      "code": "flowchart TD\n    A[Validated JWT claims] --> B{authorization_claim}\n    B -->|scp / scope| C[\"access.scopes<br/>e.g. kb.read\"]\n    B -->|roles| D[\"access.claims.roles<br/>e.g. kb.search.read\"]\n    C --> E[Check read_scope / write_scope]\n    D --> E\n    E -->|present| F[Allow tool]\n    E -->|absent| G[Deny tool]",
      "section_id": "choosing-the-authorization-claim"
    },
    {
      "id": "choosing-the-authorization-claim-ex1",
      "language": "yaml",
      "code": "server:\n  auth:\n    type: jwt\n    # ...\n    authorization_claim: roles   # default: scp\n    read_scope: kb.search.read\n    write_scope: kb.search.write",
      "section_id": "choosing-the-authorization-claim"
    },
    {
      "id": "choosing-the-authorization-claim-ex2",
      "language": "json",
      "code": "{\n  \"iss\":   \"https://your-idp.example/{tenant}/v2.0\",\n  \"aud\":   \"api://redisvl-mcp\",\n  \"sub\":   \"nitin\",\n  \"roles\": [\"kb.search.read\"],\n  \"scp\":   \"kb.read\"\n}",
      "section_id": "choosing-the-authorization-claim"
    },
    {
      "id": "the-authorization-boundary-ex0",
      "language": "mermaid",
      "code": "flowchart LR\n    subgraph Gateway[\"Gateway / policy layer (out of scope for RedisVL)\"]\n        T[Validate token] --> M[\"Map claims to<br/>Redis user + index\"]\n    end\n    subgraph RedisVL[\"RedisVL MCP (this guide)\"]\n        A[Validate JWT] --> S[Gate read / write by scope] --> I[\"Inject tenant filter<br/>(profiles with lock.inject)\"]\n    end\n    Client --> Gateway --> RedisVL --> Redis[(Redis)]",
      "section_id": "the-authorization-boundary"
    }
  ]
}
