# Run RedisVL MCP

```json metadata
{
  "title": "Run RedisVL MCP",
  "description": "",
  "categories": null,
  "tableOfContents": {"sections":[{"id":"before-you-start","title":"Before You Start"},{"children":[{"id":"cli-flags","title":"CLI Flags"},{"id":"environment-variables","title":"Environment Variables"}],"id":"start-the-server","title":"Start the Server"},{"id":"connect-a-remote-mcp-client","title":"Connect a Remote MCP Client"},{"children":[{"id":"what-this-config-means","title":"What This Config Means"},{"id":"fulltext-only-config","title":"Fulltext-Only Config"}],"id":"example-config","title":"Example Config"},{"children":[{"id":"search-records","title":"search-records"},{"id":"upsert-records","title":"upsert-records"}],"id":"tool-contracts","title":"Tool Contracts"},{"children":[{"id":"read-only-vector-search","title":"Read-Only Vector Search"},{"id":"raw-string-filter","title":"Raw String Filter"},{"id":"json-dsl-filter","title":"JSON DSL Filter"},{"id":"pagination-and-field-projection","title":"Pagination and Field Projection"},{"id":"hybrid-search-with-schemaoverrides","title":"Hybrid Search With schema_overrides"}],"id":"search-examples","title":"Search Examples"},{"children":[{"id":"auto-embed-new-records","title":"Auto-Embed New Records"},{"id":"update-existing-records-with-idfield","title":"Update Existing Records With id_field"},{"id":"control-re-embedding-with-skipembeddingifpresent","title":"Control Re-Embedding With skipembeddingif_present"},{"id":"plain-writes-without-embedding","title":"Plain Writes Without Embedding"}],"id":"upsert-examples","title":"Upsert Examples"},{"children":[{"id":"missing-mcp-dependencies","title":"Missing MCP Dependencies"},{"id":"configured-redis-index-does-not-exist","title":"Configured Redis Index Does Not Exist"},{"id":"missing-required-environment-variables","title":"Missing Required Environment Variables"},{"id":"vectorizer-dimension-mismatch","title":"Vectorizer Dimension Mismatch"},{"id":"hybrid-config-requires-native-runtime-support","title":"Hybrid Config Requires Native Runtime Support"}],"id":"troubleshooting","title":"Troubleshooting"}]}

,
  "codeExamples": []
}
```

This guide shows how to run the RedisVL MCP server against an existing Redis index, configure its behavior, and use the MCP tools it exposes.

For the higher-level design, see [RedisVL MCP](https://redis.io/docs/latest/../../concepts/mcp).

## Before You Start

RedisVL MCP assumes all of the following are already true:

- you have Python 3.10 or newer
- you have Redis with Search capabilities available
- the Redis index already exists
- you know which text field and vector field the server should use
- you have installed the vectorizer provider dependencies your config needs

Install the MCP extra:

```bash
pip install redisvl[mcp]
```

If your vectorizer needs a provider extra, install that too:

```bash
pip install redisvl[mcp,openai]
```

## Start the Server

Run the server over stdio (default):

```bash
uvx --from redisvl[mcp] rvl mcp --config /path/to/mcp.yaml
```

Run it over Streamable HTTP for remote MCP clients:

```bash
uvx --from redisvl[mcp] rvl mcp --config /path/to/mcp.yaml --transport streamable-http --host 0.0.0.0 --port 8000
```

Run it over SSE:

```bash
uvx --from redisvl[mcp] rvl mcp --config /path/to/mcp.yaml --transport sse --host 0.0.0.0 --port 9000
```

#### WARNING
Streamable HTTP and SSE endpoints are **unauthenticated by default**. Only bind to public interfaces (`--host 0.0.0.0`) on trusted networks or behind an authenticating reverse proxy. When not using `--read-only`, the `upsert-records` tool is also exposed to any client that can reach the server.

Run it in read-only mode to expose search without upsert:

```bash
uvx --from redisvl[mcp] rvl mcp --config /path/to/mcp.yaml --read-only
```

### CLI Flags

| Flag          | Default     | Purpose                                                   |
|---------------|-------------|-----------------------------------------------------------|
| `--config`    | —           | Path to the MCP YAML config (required)                    |
| `--transport` | `stdio`     | Transport protocol: `stdio`, `sse`, or `streamable-http`  |
| `--host`      | `127.0.0.1` | Bind address (only used with `sse` and `streamable-http`) |
| `--port`      | `8000`      | Bind port (only used with `sse` and `streamable-http`)    |
| `--read-only` | off         | Disable the `upsert-records` tool                         |

### Environment Variables

You can also control boot settings through environment variables:

| Variable                              | Purpose                                                                                                                           |
|---------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|
| `REDISVL_MCP_CONFIG`                  | Path to the MCP YAML config                                                                                                       |
| `REDISVL_MCP_READ_ONLY`               | Disable `upsert-records` when set to `true`                                                                                       |
| `REDISVL_MCP_TOOL_SEARCH_DESCRIPTION` | Set the base search tool description text; RedisVL still appends schema-derived typed filter, `exists`, and `return_fields` hints |
| `REDISVL_MCP_TOOL_UPSERT_DESCRIPTION` | Override the upsert tool description                                                                                              |

## Connect a Remote MCP Client

When using Streamable HTTP or SSE transport, point your MCP client at the server URL:

- **Streamable HTTP**: `http://<host>:<port>/mcp`
- **SSE**: `http://<host>:<port>/sse`

**Note:** `<host>` 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.

For example, to configure a remote MCP client to connect to a Streamable HTTP server running on `192.168.1.10:8000`:

```json
{
  "mcpServers": {
    "redisvl": {
      "url": "http://192.168.1.10:8000/mcp",
      "transport": "streamable-http"
    }
  }
}
```

## Example Config

This example binds one logical MCP server to one existing Redis index called `knowledge`.

The 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.

```yaml
server:
  redis_url: ${REDIS_URL}

indexes:
  knowledge:
    redis_name: knowledge

    vectorizer:
      class: OpenAITextVectorizer
      model: text-embedding-3-small
      api_config:
        api_key: ${OPENAI_API_KEY}

    schema_overrides:
      fields:
        - name: embedding
          type: vector
          attrs:
            dims: 1536
            datatype: float32

    search:
      type: hybrid
      params:
        text_scorer: BM25STD
        stopwords: english
        vector_search_method: KNN
        combination_method: LINEAR
        linear_text_weight: 0.3

    runtime:
      text_field_name: content
      vector_field_name: embedding
      default_embed_text_field: content
      default_limit: 10
      max_limit: 25
      max_result_window: 1000
      max_upsert_records: 64
      skip_embedding_if_present: true
      startup_timeout_seconds: 30
      request_timeout_seconds: 60
      max_concurrency: 16
```

### What This Config Means

- `redis_name` must point to an index that already exists in Redis
- `search.type` fixes retrieval behavior for every MCP caller
- `runtime.text_field_name` is required for `fulltext` and `hybrid` search
- `runtime.vector_field_name` is required for `vector` and `hybrid` search, and optional for plain full-text deployments
- `runtime.default_embed_text_field` is only required when the server should generate embeddings during upsert
- `vectorizer` is required for query embedding and server-side embedding, but optional for fulltext-only configs
- `runtime.max_result_window` caps deep paging by limiting the maximum `offset + limit`
- `schema_overrides` is only for patching incomplete field attrs discovered from Redis

### Fulltext-Only Config

For a non-vector deployment, omit vector-only settings entirely:

```yaml
server:
  redis_url: ${REDIS_URL}

indexes:
  knowledge:
    redis_name: knowledge

    search:
      type: fulltext
      params:
        text_scorer: BM25STD
        stopwords: english

    runtime:
      text_field_name: content
      default_limit: 10
      max_limit: 25
      max_result_window: 1000
      max_upsert_records: 64
      skip_embedding_if_present: true
      startup_timeout_seconds: 30
      request_timeout_seconds: 60
      max_concurrency: 16
```

## Tool Contracts

RedisVL MCP exposes a small, implementation-owned contract.

### `search-records`

Arguments:

- `query`
- `limit`
- `offset`
- `filter`
- `return_fields`

Example request payload:

```json
{
  "query": "incident response runbook",
  "limit": 2,
  "offset": 0,
  "filter": {
    "and": [
      { "field": "category", "op": "eq", "value": "operations" },
      { "field": "rating", "op": "gte", "value": 4 }
    ]
  },
  "return_fields": ["title", "content", "category", "rating"]
}
```

Example response payload:

```json
{
  "search_type": "hybrid",
  "offset": 0,
  "limit": 2,
  "results": [
    {
      "id": "knowledge:runbook:eu-failover",
      "score": 0.82,
      "score_type": "hybrid_score",
      "record": {
        "title": "EU failover runbook",
        "content": "Restore traffic after a regional failover.",
        "category": "operations",
        "rating": 5
      }
    }
  ]
}
```

Notes:

- `search_type` is response metadata, not a request argument
- when `return_fields` is omitted, RedisVL MCP returns all non-vector fields
- returning the configured vector field is rejected
- `filter` accepts either a raw string or a JSON DSL object
- the `search-records` tool description includes schema-derived hints for typed JSON DSL filter fields, object-filter `exists` support, and valid `return_fields`
- `offset + limit` must stay within `runtime.max_result_window`
- startup rejects schemas that use MCP-reserved score metadata field names:
  `id`, `__key`, `key`, `score`, `vector_distance`, `__score`, `text_score`, `vector_similarity`, `hybrid_score`

### `upsert-records`

Arguments:

- `records`
- `id_field`
- `skip_embedding_if_present`

Example request payload:

```json
{
  "records": [
    {
      "doc_id": "doc-42",
      "content": "Updated operational guidance for failover handling.",
      "category": "operations",
      "rating": 5
    }
  ],
  "id_field": "doc_id"
}
```

Example response payload:

```json
{
  "status": "success",
  "keys_upserted": 1,
  "keys": ["knowledge:doc-42"]
}
```

Notes:

- this tool is not registered in read-only mode
- when server-side embedding is configured, records that need embedding must contain `runtime.default_embed_text_field`
- when `skip_embedding_if_present` is `true`, records that already contain the configured vector field can skip re-embedding
- when a vector field is configured but server-side embedding is disabled, callers must supply vectors explicitly

## Search Examples

### Read-Only Vector Search

Use read-only mode when assistants should only retrieve data:

```bash
uvx --from redisvl[mcp] rvl mcp --config /path/to/mcp.yaml --read-only
```

With a `search.type` of `vector`, callers send only the query, filters, pagination, and field projection:

```json
{
  "query": "cache invalidation incident",
  "limit": 3,
  "return_fields": ["title", "content", "category"]
}
```

### Raw String Filter

Pass a raw Redis filter string through unchanged:

```json
{
  "query": "science",
  "filter": "@category:{science}",
  "return_fields": ["content", "category"]
}
```

### JSON DSL Filter

The DSL supports logical operators and type-checked field operators:

```json
{
  "query": "science",
  "filter": {
    "and": [
      { "field": "category", "op": "eq", "value": "science" },
      { "field": "rating", "op": "gte", "value": 4 }
    ]
  },
  "return_fields": ["content", "category", "rating"]
}
```

### Pagination and Field Projection

```json
{
  "query": "science",
  "limit": 1,
  "offset": 1,
  "return_fields": ["content", "category"]
}
```

### Hybrid Search With `schema_overrides`

Use `schema_overrides` when Redis inspection cannot recover complete vector attrs, then keep hybrid behavior in config:

```yaml
schema_overrides:
  fields:
    - name: embedding
      type: vector
      attrs:
        algorithm: flat
        dims: 1536
        datatype: float32
        distance_metric: cosine

search:
  type: hybrid
  params:
    text_scorer: BM25STD
    stopwords: english
    vector_search_method: KNN
    combination_method: LINEAR
    linear_text_weight: 0.3
```

The MCP caller still sends the same request shape:

```json
{
  "query": "legacy cache invalidation flow",
  "filter": { "field": "category", "op": "eq", "value": "release-notes" },
  "return_fields": ["title", "content", "release_version"]
}
```

## Upsert Examples

### Auto-Embed New Records

If a record does not include the configured vector field, RedisVL MCP embeds `runtime.default_embed_text_field` and writes the result:

```json
{
  "records": [
    {
      "content": "First upserted document",
      "category": "science",
      "rating": 5
    },
    {
      "content": "Second upserted document",
      "category": "health",
      "rating": 4
    }
  ]
}
```

### Update Existing Records With `id_field`

```json
{
  "records": [
    {
      "doc_id": "doc-1",
      "content": "Updated content",
      "category": "engineering",
      "rating": 5
    }
  ],
  "id_field": "doc_id"
}
```

### Control Re-Embedding With `skip_embedding_if_present`

```json
{
  "records": [
    {
      "doc_id": "doc-2",
      "content": "Existing content",
      "category": "science",
      "rating": 4
    }
  ],
  "id_field": "doc_id",
  "skip_embedding_if_present": false
}
```

Set `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`.

### Plain Writes Without Embedding

For fulltext-only indexes, `upsert-records` can write records without any vectorizer or vector field configuration:

```json
{
  "records": [
    {
      "content": "Updated FAQ entry",
      "category": "support",
      "rating": 5
    }
  ]
}
```

If 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.

## Troubleshooting

### Missing MCP Dependencies

If `rvl mcp` reports missing optional dependencies, install the MCP extra:

```bash
pip install redisvl[mcp]
```

If the configured vectorizer needs a provider SDK, install that provider extra too. Fulltext-only configs can omit the vectorizer entirely.

### Configured Redis Index Does Not Exist

The server only binds to an existing index. Create the index first, then point `indexes.<id>.redis_name` at that index name.

### Missing Required Environment Variables

YAML values support `${VAR}` and `${VAR:-default}` substitution. Missing required variables fail startup before the server registers tools.

### Vectorizer Dimension Mismatch

If 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.

### Hybrid Config Requires Native Runtime Support

Some 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.

