{
  "id": "mcp",
  "title": "Run RedisVL MCP",
  "url": "https://redis.io/docs/latest/develop/ai/redisvl/0.23.0/user_guide/how_to_guides/mcp/",
  "summary": "",
  "content": "\n\nThis guide shows how to run the RedisVL MCP server against an existing Redis index, configure its behavior, and use the MCP tools it exposes.\n\nFor the higher-level design, see [RedisVL MCP](https://redis.io/docs/latest/../../concepts/mcp).\n\n## Before You Start\n\nRedisVL MCP assumes all of the following are already true:\n\n- you have Python 3.10 or newer\n- you have Redis with Search capabilities available\n- the Redis index already exists\n- you know which text field and vector field the server should use\n- you have installed the vectorizer provider dependencies your config needs\n\nInstall the MCP extra:\n\n```bash\npip install redisvl[mcp]\n```\n\nIf your vectorizer needs a provider extra, install that too:\n\n```bash\npip install redisvl[mcp,openai]\n```\n\n## Start the Server\n\nRun the server over stdio (default):\n\n```bash\nuvx --from redisvl[mcp] rvl mcp --config /path/to/mcp.yaml\n```\n\nRun it over Streamable HTTP for remote MCP clients. Binding to a non-loopback host (`--host 0.0.0.0`) requires either JWT authentication (see [Authenticate RedisVL MCP](https://redis.io/docs/latest/mcp_authentication)) or the explicit `--allow-unauthenticated` flag; otherwise the server refuses to start:\n\n```bash\nuvx --from redisvl[mcp] rvl mcp --config /path/to/mcp.yaml --transport streamable-http --host 0.0.0.0 --port 8000 --allow-unauthenticated\n```\n\nRun it over SSE:\n\n```bash\nuvx --from redisvl[mcp] rvl mcp --config /path/to/mcp.yaml --transport sse --host 0.0.0.0 --port 9000 --allow-unauthenticated\n```\n\n\nStreamable HTTP and SSE endpoints are **unauthenticated by default**. Binding to a non-loopback host without auth fails closed unless you pass `--allow-unauthenticated`; binding to loopback without auth only warns. For real deployments, enable JWT authentication (see [Authenticate RedisVL MCP](https://redis.io/docs/latest/mcp_authentication)) rather than using `--allow-unauthenticated`. When not using `--read-only`, the `upsert-records` tool is also exposed to any client that can reach the server.\n\n\n### Transport Security (Host / Origin Validation)\n\nOn the HTTP transports the server validates the request `Host` and `Origin` headers against allowlists before any tool runs. This is **on by default** and defends against [DNS rebinding](https://en.wikipedia.org/wiki/DNS_rebinding): without it, a malicious web page could rebind its own hostname to `127.0.0.1` and use the victim’s browser to reach a local MCP server, even one bound to loopback.\n\n- **Host:** the allowlist is derived automatically from the bind address. Loopback binds accept `localhost`, `127.0.0.1`, and `[::1]` (with or without the port), so local MCP clients work with no configuration.\n- **Origin:** requests with no `Origin` header (typical of non-browser MCP clients) always pass. A request that carries a cross-site `Origin` is rejected unless that origin is explicitly allowlisted.\n\nYou only need to configure this for deployments where the client-visible `Host` differs from the bind address (a reverse proxy, or a `--host 0.0.0.0` bind reached via a public hostname). Use the `REDISVL_MCP_ALLOWED_HOSTS` / `REDISVL_MCP_ALLOWED_ORIGINS` env vars, or a `server.transport_security` block in the config:\n\n```yaml\nserver:\n  redis_url: ${REDIS_URL}\n  transport_security:\n    allowed_hosts: [mcp.example.com]          # extra Host values to accept\n    allowed_origins: [https://app.example.com]  # browser origins to accept\n    # allow_any_origin: true                   # trust upstream Origin validation\n    # enabled: false                           # disable when a trusted proxy validates\n```\n\n\nBehind a reverse proxy or gateway that already validates `Host`/`Origin`, set `server.transport_security.enabled: false` (or `allow_any_origin: true`) so the proxy’s rewritten headers are not rejected.\n\n\nRun it in read-only mode to expose search without upsert:\n\n```bash\nuvx --from redisvl[mcp] rvl mcp --config /path/to/mcp.yaml --read-only\n```\n\n### CLI Flags\n\n| Flag          | Default     | Purpose                                                   |\n|---------------|-------------|-----------------------------------------------------------|\n| `--config`    | —           | Path to the MCP YAML config (required)                    |\n| `--transport` | `stdio`     | Transport protocol: `stdio`, `sse`, or `streamable-http`  |\n| `--host`      | `127.0.0.1` | Bind address (only used with `sse` and `streamable-http`) |\n| `--port`      | `8000`      | Bind port (only used with `sse` and `streamable-http`)    |\n| `--read-only` | off         | Disable writes across every index (global read-only)      |\n\n### Environment Variables\n\nYou can also control boot settings through environment variables:\n\n| Variable                                 | Purpose                                                                                                                                                                                                                                                   |\n|------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `REDISVL_MCP_CONFIG`                     | Path to the MCP YAML config                                                                                                                                                                                                                               |\n| `REDISVL_MCP_READ_ONLY`                  | Disable `upsert-records` when set to `true`                                                                                                                                                                                                               |\n| `REDISVL_MCP_TOOL_SEARCH_DESCRIPTION`    | Set the base search tool description text. On a single-index server RedisVL appends schema-derived typed filter, `exists`, and `return_fields` hints; on a multi-index server it appends a note directing clients to call `list-indexes` and pass `index` |\n| `REDISVL_MCP_TOOL_UPSERT_DESCRIPTION`    | Override the upsert tool description                                                                                                                                                                                                                      |\n| `REDISVL_MCP_ALLOWED_HOSTS`              | Comma-separated extra `Host` header values to accept on HTTP transports (in addition to the bind-derived defaults)                                                                                                                                        |\n| `REDISVL_MCP_ALLOWED_ORIGINS`            | Comma-separated browser `Origin` values to accept on HTTP transports                                                                                                                                                                                      |\n| `REDISVL_MCP_ALLOW_ANY_ORIGIN`           | Accept any `Origin` (for trusted-proxy setups) when set to `true`                                                                                                                                                                                         |\n| `REDISVL_MCP_TRANSPORT_SECURITY_ENABLED` | Set to `false` to disable Host/Origin validation (e.g. behind a validating proxy)                                                                                                                                                                         |\n\n## Connect a Remote MCP Client\n\nWhen using Streamable HTTP or SSE transport, point your MCP client at the server URL:\n\n- **Streamable HTTP**: `http://\u003chost\u003e:\u003cport\u003e/mcp`\n- **SSE**: `http://\u003chost\u003e:\u003cport\u003e/sse`\n\n**Note:** `\u003chost\u003e` here is the bind address the server was started with. The default `127.0.0.1` only accepts connections from the same machine. To allow connections from other machines, start the server with `--host 0.0.0.0` and use the machine’s actual IP or hostname in the client URL. Because a `0.0.0.0` bind has no single canonical `Host`, add the client-visible hostname(s) to `REDISVL_MCP_ALLOWED_HOSTS` (or `server.transport_security.allowed_hosts`), or Host validation will reject those requests. See [Transport Security]().\n\nFor example, to configure a remote MCP client to connect to a Streamable HTTP server running on `192.168.1.10:8000`:\n\n```json\n{\n  \"mcpServers\": {\n    \"redisvl\": {\n      \"url\": \"http://192.168.1.10:8000/mcp\",\n      \"transport\": \"streamable-http\"\n    }\n  }\n}\n```\n\n## Example Config\n\nThis example binds one logical MCP server to one existing Redis index called `knowledge`. A single configured index is the simplest deployment, and callers never need to name it. See [Multiple Indexes]() below to expose several indexes from the same server.\n\nThe config uses `${REDIS_URL}` and `${OPENAI_API_KEY}` as environment-variable placeholders. These values are resolved when the server starts. You can also use `${VAR:-default}` to provide a fallback value.\n\n```yaml\nserver:\n  redis_url: ${REDIS_URL}\n\nindexes:\n  knowledge:\n    redis_name: knowledge\n\n    vectorizer:\n      class: OpenAITextVectorizer\n      model: text-embedding-3-small\n      api_config:\n        api_key: ${OPENAI_API_KEY}\n\n    schema_overrides:\n      fields:\n        - name: embedding\n          type: vector\n          attrs:\n            dims: 1536\n            datatype: float32\n\n    search:\n      type: hybrid\n      params:\n        text_scorer: BM25STD\n        stopwords: english\n        vector_search_method: KNN\n        combination_method: LINEAR\n        linear_text_weight: 0.3\n\n    runtime:\n      text_field_name: content\n      vector_field_name: embedding\n      default_embed_text_field: content\n      default_limit: 10\n      max_limit: 25\n      max_result_window: 1000\n      max_upsert_records: 64\n      skip_embedding_if_present: true\n      startup_timeout_seconds: 30\n      request_timeout_seconds: 60\n      max_concurrency: 16\n```\n\n### What This Config Means\n\n- `redis_name` must point to an index that already exists in Redis\n- `search.type` fixes retrieval behavior for every MCP caller\n- `runtime.text_field_name` is required for `fulltext` and `hybrid` search\n- `runtime.vector_field_name` is required for `vector` and `hybrid` search, and optional for plain full-text deployments\n- `runtime.default_embed_text_field` is only required when the server should generate embeddings during upsert\n- `vectorizer` is required for query embedding and server-side embedding, but optional for fulltext-only configs\n- `runtime.max_result_window` caps deep paging by limiting the maximum `offset + limit`\n- `schema_overrides` is only for patching incomplete field attrs discovered from Redis\n\n### Fulltext-Only Config\n\nFor a non-vector deployment, omit vector-only settings entirely:\n\n```yaml\nserver:\n  redis_url: ${REDIS_URL}\n\nindexes:\n  knowledge:\n    redis_name: knowledge\n\n    search:\n      type: fulltext\n      params:\n        text_scorer: BM25STD\n        stopwords: english\n\n    runtime:\n      text_field_name: content\n      default_limit: 10\n      max_limit: 25\n      max_result_window: 1000\n      max_upsert_records: 64\n      skip_embedding_if_present: true\n      startup_timeout_seconds: 30\n      request_timeout_seconds: 60\n      max_concurrency: 16\n```\n\n### Multiple Indexes\n\nThe `indexes` mapping can hold more than one binding. Each entry is keyed by a logical id, points at its own existing Redis index through `redis_name`, and carries its own `search`, `runtime`, and optional `vectorizer`. The example below exposes a writable vector index `knowledge` alongside a read-only fulltext index `tickets` from the same server:\n\n```yaml\nserver:\n  redis_url: ${REDIS_URL}\n\nindexes:\n  knowledge:\n    redis_name: knowledge\n    description: Internal runbooks and operational guidance.\n\n    vectorizer:\n      class: OpenAITextVectorizer\n      model: text-embedding-3-small\n      api_config:\n        api_key: ${OPENAI_API_KEY}\n\n    search:\n      type: vector\n\n    runtime:\n      text_field_name: content\n      vector_field_name: embedding\n      default_embed_text_field: content\n      default_limit: 10\n      max_limit: 25\n\n  tickets:\n    redis_name: support-tickets\n    description: Read-only mirror of resolved support tickets.\n    read_only: true\n\n    search:\n      type: fulltext\n      params:\n        text_scorer: BM25STD\n        stopwords: english\n\n    runtime:\n      text_field_name: body\n      default_limit: 10\n      max_limit: 50\n```\n\nNotes:\n\n- Each binding is inspected and validated independently at startup. Startup is all-or-nothing: if any binding fails, the server does not start.\n- The optional per-index `description` and `read_only` flags are surfaced through `list-indexes`.\n- `read_only: true` makes that binding reject writes even though the server as a whole is not in global read-only mode. Because `knowledge` is still writable, the `upsert-records` tool is registered; an upsert targeting `tickets` is rejected with `forbidden`.\n- A single-index config keeps working unchanged — adding bindings does not change how the sole-binding case behaves.\n\n### Index Selection\n\nOn a multi-index server, `search-records` and `upsert-records` take an optional `index` argument naming the logical id to target:\n\n- With exactly one index configured, `index` may be omitted and resolves to that binding.\n- With multiple indexes configured, omitting `index` returns `invalid_request`; an unknown id also returns `invalid_request`.\n- Clients should call [`list-indexes`]() first to discover the available ids and their filterable fields.\n\n## Tool Contracts\n\nRedisVL MCP exposes a small, implementation-owned contract.\n\n### `list-indexes`\n\n`list-indexes` is always available and takes no arguments. Call it first on a multi-index server to discover which logical ids exist and how to filter each one.\n\nExample response payload:\n\n```json\n{\n  \"indexes\": [\n    {\n      \"id\": \"knowledge\",\n      \"description\": \"Internal runbooks and operational guidance.\",\n      \"upsert_available\": true,\n      \"fields\": [\n        { \"name\": \"title\", \"type\": \"text\" },\n        { \"name\": \"category\", \"type\": \"tag\" },\n        { \"name\": \"rating\", \"type\": \"numeric\" }\n      ],\n      \"limits\": { \"max_limit\": 25 }\n    },\n    {\n      \"id\": \"tickets\",\n      \"description\": \"Read-only mirror of resolved support tickets.\",\n      \"upsert_available\": false,\n      \"fields\": [\n        { \"name\": \"category\", \"type\": \"tag\" }\n      ],\n      \"limits\": { \"max_limit\": 50 }\n    }\n  ]\n}\n```\n\nNotes:\n\n- `upsert_available` reflects the binding’s effective write availability (global read-only **or** the per-index `read_only` flag)\n- `fields` lists the filterable fields discovered from the index; the vector field and the configured embed-source text field are intentionally omitted\n- `limits` includes only runtime limits that were explicitly configured (such as `max_limit` or `max_upsert_records`); defaults are not echoed\n- the underlying Redis index name (`redis_name`) is never exposed\n- `description` appears only when configured for that binding\n\n### `search-records`\n\nArguments:\n\n- `index` (optional; required when multiple indexes are configured)\n- `query`\n- `limit`\n- `offset`\n- `filter`\n- `return_fields`\n\nExample request payload:\n\n```json\n{\n  \"index\": \"knowledge\",\n  \"query\": \"incident response runbook\",\n  \"limit\": 2,\n  \"offset\": 0,\n  \"filter\": {\n    \"and\": [\n      { \"field\": \"category\", \"op\": \"eq\", \"value\": \"operations\" },\n      { \"field\": \"rating\", \"op\": \"gte\", \"value\": 4 }\n    ]\n  },\n  \"return_fields\": [\"title\", \"content\", \"category\", \"rating\"]\n}\n```\n\nExample response payload:\n\n```json\n{\n  \"index\": \"knowledge\",\n  \"search_type\": \"hybrid\",\n  \"offset\": 0,\n  \"limit\": 2,\n  \"results\": [\n    {\n      \"id\": \"knowledge:runbook:eu-failover\",\n      \"score\": 0.82,\n      \"score_type\": \"hybrid_score\",\n      \"record\": {\n        \"title\": \"EU failover runbook\",\n        \"content\": \"Restore traffic after a regional failover.\",\n        \"category\": \"operations\",\n        \"rating\": 5\n      }\n    }\n  ]\n}\n```\n\nNotes:\n\n- `index` selects the logical binding; omit it only on a single-index server. The resolved id is echoed back in the response\n- `search_type` is response metadata, not a request argument\n- when `return_fields` is omitted, RedisVL MCP returns all non-vector fields\n- returning the configured vector field is rejected\n- `filter` accepts either a raw string or a JSON DSL object\n- the `search-records` tool description includes schema-derived hints for typed JSON DSL filter fields, object-filter `exists` support, and valid `return_fields`\n- `offset + limit` must stay within `runtime.max_result_window`\n- startup rejects schemas that use MCP-reserved score metadata field names:\n  `id`, `__key`, `key`, `score`, `vector_distance`, `__score`, `text_score`, `vector_similarity`, `hybrid_score`\n\n### `upsert-records`\n\nArguments:\n\n- `index` (optional; required when multiple indexes are configured)\n- `records`\n- `id_field`\n- `skip_embedding_if_present`\n\nExample request payload:\n\n```json\n{\n  \"index\": \"knowledge\",\n  \"records\": [\n    {\n      \"doc_id\": \"doc-42\",\n      \"content\": \"Updated operational guidance for failover handling.\",\n      \"category\": \"operations\",\n      \"rating\": 5\n    }\n  ],\n  \"id_field\": \"doc_id\"\n}\n```\n\nExample response payload:\n\n```json\n{\n  \"index\": \"knowledge\",\n  \"status\": \"success\",\n  \"keys_upserted\": 1,\n  \"keys\": [\"knowledge:doc-42\"]\n}\n```\n\nNotes:\n\n- `index` selects the logical binding; omit it only on a single-index server. The resolved id is echoed back in the response\n- this tool is not registered when every binding is read-only (global read-only mode or every binding setting `read_only: true`)\n- a write targeting a read-only binding is rejected with `forbidden` before any data is changed, even when the tool is registered because other bindings are writable\n- when server-side embedding is configured, records that need embedding must contain `runtime.default_embed_text_field`\n- when `skip_embedding_if_present` is `true`, records that already contain the configured vector field can skip re-embedding\n- when a vector field is configured but server-side embedding is disabled, callers must supply vectors explicitly\n\n## Search Examples\n\n### Discovery-First Multi-Index Flow\n\nOn a multi-index server, call `list-indexes` first, pick a logical id from the response, then pass it as `index`:\n\n```json\n{\n  \"index\": \"knowledge\",\n  \"query\": \"cache invalidation incident\",\n  \"limit\": 3,\n  \"return_fields\": [\"title\", \"content\", \"category\"]\n}\n```\n\nThe same `index` argument routes an `upsert-records` write to a specific binding:\n\n```json\n{\n  \"index\": \"knowledge\",\n  \"records\": [\n    { \"doc_id\": \"doc-7\", \"content\": \"New runbook entry\", \"category\": \"operations\" }\n  ],\n  \"id_field\": \"doc_id\"\n}\n```\n\nOn a single-index server you can omit `index` entirely; the examples below show that backward-compatible shape.\n\n### Read-Only Vector Search\n\nUse read-only mode when assistants should only retrieve data:\n\n```bash\nuvx --from redisvl[mcp] rvl mcp --config /path/to/mcp.yaml --read-only\n```\n\nWith a `search.type` of `vector`, callers send only the query, filters, pagination, and field projection:\n\n```json\n{\n  \"query\": \"cache invalidation incident\",\n  \"limit\": 3,\n  \"return_fields\": [\"title\", \"content\", \"category\"]\n}\n```\n\n### Raw String Filter\n\nPass a raw Redis filter string through unchanged:\n\n```json\n{\n  \"query\": \"science\",\n  \"filter\": \"@category:{science}\",\n  \"return_fields\": [\"content\", \"category\"]\n}\n```\n\n### JSON DSL Filter\n\nThe DSL supports logical operators and type-checked field operators:\n\n```json\n{\n  \"query\": \"science\",\n  \"filter\": {\n    \"and\": [\n      { \"field\": \"category\", \"op\": \"eq\", \"value\": \"science\" },\n      { \"field\": \"rating\", \"op\": \"gte\", \"value\": 4 }\n    ]\n  },\n  \"return_fields\": [\"content\", \"category\", \"rating\"]\n}\n```\n\n### Pagination and Field Projection\n\n```json\n{\n  \"query\": \"science\",\n  \"limit\": 1,\n  \"offset\": 1,\n  \"return_fields\": [\"content\", \"category\"]\n}\n```\n\n### Hybrid Search With `schema_overrides`\n\nUse `schema_overrides` when Redis inspection cannot recover complete vector attrs, then keep hybrid behavior in config:\n\n```yaml\nschema_overrides:\n  fields:\n    - name: embedding\n      type: vector\n      attrs:\n        algorithm: flat\n        dims: 1536\n        datatype: float32\n        distance_metric: cosine\n\nsearch:\n  type: hybrid\n  params:\n    text_scorer: BM25STD\n    stopwords: english\n    vector_search_method: KNN\n    combination_method: LINEAR\n    linear_text_weight: 0.3\n```\n\nThe MCP caller still sends the same request shape:\n\n```json\n{\n  \"query\": \"legacy cache invalidation flow\",\n  \"filter\": { \"field\": \"category\", \"op\": \"eq\", \"value\": \"release-notes\" },\n  \"return_fields\": [\"title\", \"content\", \"release_version\"]\n}\n```\n\n## Upsert Examples\n\n### Auto-Embed New Records\n\nIf a record does not include the configured vector field, RedisVL MCP embeds `runtime.default_embed_text_field` and writes the result:\n\n```json\n{\n  \"records\": [\n    {\n      \"content\": \"First upserted document\",\n      \"category\": \"science\",\n      \"rating\": 5\n    },\n    {\n      \"content\": \"Second upserted document\",\n      \"category\": \"health\",\n      \"rating\": 4\n    }\n  ]\n}\n```\n\n### Update Existing Records With `id_field`\n\n```json\n{\n  \"records\": [\n    {\n      \"doc_id\": \"doc-1\",\n      \"content\": \"Updated content\",\n      \"category\": \"engineering\",\n      \"rating\": 5\n    }\n  ],\n  \"id_field\": \"doc_id\"\n}\n```\n\n### Control Re-Embedding With `skip_embedding_if_present`\n\n```json\n{\n  \"records\": [\n    {\n      \"doc_id\": \"doc-2\",\n      \"content\": \"Existing content\",\n      \"category\": \"science\",\n      \"rating\": 4\n    }\n  ],\n  \"id_field\": \"doc_id\",\n  \"skip_embedding_if_present\": false\n}\n```\n\nSet `skip_embedding_if_present` to `false` when you want the server to regenerate embeddings during upsert. In most cases, the caller should omit the vector field and let the server manage embeddings from `runtime.default_embed_text_field`.\n\n### Plain Writes Without Embedding\n\nFor fulltext-only indexes, `upsert-records` can write records without any vectorizer or vector field configuration:\n\n```json\n{\n  \"records\": [\n    {\n      \"content\": \"Updated FAQ entry\",\n      \"category\": \"support\",\n      \"rating\": 5\n    }\n  ]\n}\n```\n\nIf you configure a vector field but omit server-side embedding support, the caller must send vectors in each record instead of relying on the server to generate them.\n\n## Troubleshooting\n\n### Missing MCP Dependencies\n\nIf `rvl mcp` reports missing optional dependencies, install the MCP extra:\n\n```bash\npip install redisvl[mcp]\n```\n\nIf the configured vectorizer needs a provider SDK, install that provider extra too. Fulltext-only configs can omit the vectorizer entirely.\n\n### Configured Redis Index Does Not Exist\n\nThe server only binds to an existing index. Create the index first, then point `indexes.\u003cid\u003e.redis_name` at that index name.\n\n### Missing Required Environment Variables\n\nYAML values support `${VAR}` and `${VAR:-default}` substitution. Missing required variables fail startup before the server registers tools.\n\n### Vectorizer Dimension Mismatch\n\nIf the vectorizer dims do not match the configured vector field dims, startup fails. Make sure the embedding model and the effective vector field dimensions are aligned.\n\n### Hybrid Config Requires Native Runtime Support\n\nSome hybrid params depend on native hybrid support in Redis and redis-py. If your environment does not support that path, remove native-only params such as `knn_ef_runtime` or upgrade Redis and redis-py.\n",
  "tags": [],
  "last_updated": "2026-07-27T11:50:23+02:00"
}
