{
  "id": "mcp_authentication",
  "title": "Authenticate RedisVL MCP",
  "url": "https://redis.io/docs/latest/develop/ai/redisvl/0.22.0/user_guide/how_to_guides/mcp_authentication/",
  "summary": "",
  "content": "\n\nThis 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\nAuthentication applies only to the HTTP transports (`streamable-http`, `sse`).\nThe `stdio` transport is a local subprocess with no network surface and is never\nauthenticated.\n\n\n## What RedisVL Enforces\n\nRedisVL 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\nThis is **coarse** authorization: it decides whether a caller may connect and\nwhether it may read or write. It does **not** map token claims to a Redis ACL\nuser, a per-tenant index, or query filters. See [The Authorization Boundary]().\n\n\n## OAuth: Which Part RedisVL Handles\n\nIn 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```mermaid\nflowchart LR\n    Client --\u003e|1 - OAuth login flow| IdP[Identity Provider]\n    IdP --\u003e|2 - issues JWT access token| Client\n    Client --\u003e|3 - Bearer JWT| MCP[RedisVL MCP]\n    MCP --\u003e|validates token| Redis[(Redis)]\n```\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.\n\n## Request Flow\n\n```mermaid\nsequenceDiagram\n    actor User\n    participant IdP as Identity Provider\n    participant MCP as RedisVL MCP Server\n    participant Redis\n\n    User-\u003e\u003eIdP: Authenticate\n    IdP--\u003e\u003eUser: Signed JWT (iss, aud, scopes/roles)\n    User-\u003e\u003eMCP: MCP request + Bearer JWT\n    MCP-\u003e\u003eMCP: Validate signature (JWKS / public key)\n    MCP-\u003e\u003eMCP: Check issuer + audience\n    alt token invalid / wrong audience / missing connect scope\n        MCP--\u003e\u003eUser: 401 Unauthorized\n    else token valid\n        MCP-\u003e\u003eMCP: Gate tool by read / write scope\n        alt scope present\n            MCP-\u003e\u003eRedis: Search or upsert (single configured ACL user)\n            Redis--\u003e\u003eMCP: Results\n            MCP--\u003e\u003eUser: Tool result\n        else scope missing\n            MCP--\u003e\u003eUser: Forbidden\n        end\n    end\n```\n\n## Configure JWT Authentication\n\nAdd a `server.auth` block to your MCP config. Secrets can be injected with\n`${ENV}` substitution.\n\n```yaml\nserver:\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\n```\n\nEvery field is also settable through `REDISVL_MCP_AUTH_*` environment variables,\nwhich take precedence over the YAML block.\n\n## Choosing the Authorization Claim\n\nDifferent 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```mermaid\nflowchart TD\n    A[Validated JWT claims] --\u003e B{authorization_claim}\n    B --\u003e|scp / scope| C[\"access.scopes\u003cbr/\u003ee.g. kb.read\"]\n    B --\u003e|roles| D[\"access.claims.roles\u003cbr/\u003ee.g. kb.search.read\"]\n    C --\u003e E[Check read_scope / write_scope]\n    D --\u003e E\n    E --\u003e|present| F[Allow tool]\n    E --\u003e|absent| G[Deny tool]\n```\n\nSet the claim that holds your authorization values so read and write gating\nreads the right place:\n\n```yaml\nserver:\n  auth:\n    type: jwt\n    # ...\n    authorization_claim: roles   # default: scp\n    read_scope: kb.search.read\n    write_scope: kb.search.write\n```\n\nA token like the following would then pass the read gate, because\n`kb.search.read` is present in `roles`:\n\n```json\n{\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}\n```\n\n## The Authorization Boundary\n\nRedisVL MCP authenticates the caller and gates read vs write. It does **not**\ntranslate token claims (such as a tenant id or role) into a specific Redis ACL\nuser, a per-tenant index, or injected query filters. The server holds one Redis\nconnection for one index, established at startup.\n\nFine-grained, per-tenant data isolation belongs in a **gateway or policy layer**\nin front of the MCP server, which validates the token, looks up a binding of\nclaim to Redis identity, and injects credentials and filters.\n\n```mermaid\nflowchart LR\n    subgraph Gateway[\"Gateway / policy layer (out of scope for RedisVL)\"]\n        T[Validate token] --\u003e M[\"Map claims to\u003cbr/\u003eRedis user + index + filters\"]\n    end\n    subgraph RedisVL[\"RedisVL MCP (this guide)\"]\n        A[Validate JWT] --\u003e S[Gate read / write by scope]\n    end\n    Client --\u003e Gateway --\u003e RedisVL --\u003e Redis[(Redis)]\n```\n\nUse RedisVL’s JWT validation for authentication and coarse read/write\nauthorization. Layer a gateway on top when you need per-tenant Redis ACL\nenforcement.\n\n## See Also\n\n- [Run RedisVL MCP](https://redis.io/docs/latest/mcp): run and configure the RedisVL MCP server.\n",
  "tags": [],
  "last_updated": "2026-07-06T15:51:40-04:00"
}
