{ "cells": [ { "cell_type": "markdown", "id": "eaf53978", "metadata": {}, "source": [ "![Redis](https://redis.io/wp-content/uploads/2024/04/Logotype.svg?auto=webp&quality=85,75&width=120)\n", "\n", "# Building Agents with Claude Agent SDK and RedisVL MCP\n", "\n", "In this notebook, we'll demonstrate how to build an agent using Claude Agent SDK and give it access to the RedisVL MCP Toolkit.\n", "\n", "## Before we get started\n", "To understand more about the frameworks used in this notebook, refer to the following resources:\n", "- [RedisVL MCP](https://github.com/redis/redis-vl-python#mcp-server) - GitHub repository to the RedisVL Project. We are using the MCP functionality in this notebook.\n", "- [Claude Agent SDK](https://code.claude.com/docs/en/agent-sdk/) - Documentation for Claude Agent SDK. We will be using it as the base framework to build our agent.\n", "\n", "## Let's Begin!\n", "\"Open" ] }, { "cell_type": "markdown", "id": "ba910c6c", "metadata": {}, "source": [ "## Setup" ] }, { "cell_type": "markdown", "id": "b248ec84", "metadata": {}, "source": [ "First, let's download the required packages and set our API keys:" ] }, { "cell_type": "code", "execution_count": 1, "id": "14001301", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m26.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m26.1\u001b[0m\n", "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n", "Note: you may need to restart the kernel to use updated packages.\n" ] } ], "source": [ "%pip install -q \"redisvl[mcp,sentence-transformers,nltk]>=0.17.1\" claude-agent-sdk " ] }, { "cell_type": "markdown", "id": "c72a7a0b", "metadata": {}, "source": [ "### Install Redis Stack\n", "\n", "In this notebook, Redis is used to store, index, and query vector\n", "embeddings created from hypothetical texts. **We need to make sure we have a Redis\n", "instance available.**" ] }, { "cell_type": "markdown", "id": "cf78c517", "metadata": {}, "source": [ "#### For Colab\n", "Use the shell script below to download, extract, and install Redis with the Search module." ] }, { "cell_type": "code", "execution_count": null, "id": "1d401726", "metadata": {}, "outputs": [], "source": [ "# NBVAL_SKIP\n", "# Colab only: install and start Redis with Search module.\n", "%%sh\n", "sudo apt-get install -y -qq lsb-release curl gpg > /dev/null\n", "curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg\n", "sudo chmod 644 /usr/share/keyrings/redis-archive-keyring.gpg\n", "echo \"deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main\" | sudo tee /etc/apt/sources.list.d/redis.list\n", "sudo apt-get update -qq > /dev/null\n", "sudo apt-get install -y -qq redis > /dev/null\n", "\n", "redis-server --version\n", "redis-server --daemonize yes --loadmodule /usr/lib/redis/modules/redisearch.so" ] }, { "cell_type": "markdown", "id": "3ae6a6cc", "metadata": {}, "source": [ "#### For Alternative Environments\n", "There are many ways to get the necessary redis-stack instance running\n", "1. On cloud, deploy a [FREE instance of Redis in the cloud](https://redis.com/try-free/). Or, if you have your\n", "own version of Redis Enterprise running, that works too!\n", "2. Per OS, [see the docs](https://redis.io/docs/latest/operate/oss_and_stack/install/install-stack/)\n", "3. With docker: `docker run -d --name redis-stack-server -p 6379:6379 redis/redis-stack-server:latest`" ] }, { "cell_type": "markdown", "id": "b84aee70", "metadata": {}, "source": [ "### Define the Redis Connection URL\n", "\n", "By default this notebook connects to the local instance of Redis Stack. **If you have your own Redis Enterprise instance** - replace REDIS_PASSWORD, REDIS_HOST and REDIS_PORT values with your own." ] }, { "cell_type": "code", "execution_count": 2, "id": "41ee6439", "metadata": {}, "outputs": [], "source": [ "import os\n", "\n", "# Replace values below with your own if using Redis Cloud instance\n", "REDIS_HOST = os.getenv(\"REDIS_HOST\", \"localhost\") # ex: \"redis-18374.c253.us-central1-1.gce.cloud.redislabs.com\"\n", "REDIS_PORT = os.getenv(\"REDIS_PORT\", \"6379\") # ex: 18374\n", "REDIS_PASSWORD = os.getenv(\"REDIS_PASSWORD\", \"\") # ex: \"1TNxTEdYRDgIDKM2gDfasupCADXXXX\"\n", "\n", "# If SSL is enabled on the endpoint, use rediss:// as the URL prefix\n", "REDIS_URL = f\"redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}\"\n", "os.environ[\"REDIS_URL\"] = REDIS_URL" ] }, { "cell_type": "markdown", "id": "fe5e56b6", "metadata": {}, "source": [ "## Imports" ] }, { "cell_type": "code", "execution_count": 3, "id": "9fde183d", "metadata": {}, "outputs": [], "source": [ "import json\n", "import os\n", "from pprint import pprint\n", "import getpass\n", "import warnings\n", "import yaml\n", "\n", "import numpy as np\n", "\n", "warnings.filterwarnings(\"ignore\")" ] }, { "cell_type": "markdown", "id": "6985b643", "metadata": {}, "source": [ "## API keys" ] }, { "cell_type": "code", "execution_count": 4, "id": "9af95463", "metadata": {}, "outputs": [], "source": [ "# NBVAL_SKIP\n", "# Anthropic API key is required for agent calls\n", "os.environ['ANTHROPIC_API_KEY'] = os.getenv(\"ANTHROPIC_API_KEY\") or getpass.getpass(\"Anthropic API key: \")" ] }, { "cell_type": "markdown", "id": "0222c590", "metadata": {}, "source": [ "## Setting up Redis" ] }, { "cell_type": "markdown", "id": "19844931", "metadata": {}, "source": [ "### Sample Data" ] }, { "cell_type": "markdown", "id": "9a0df4d7", "metadata": {}, "source": [ "The sample data contains a fictional knowledge base that contains support knowledge records stored as structured documents for retrieval and update. It includes runbooks, incident summaries, KB articles, and release notes, along with metadata and text content that can be indexed for search.\n", "\n", "We provide our agent with the `search-records` and `upsert-records` tools. Using these tools, the agent should be able to perform the following:\n", "- Retrieval (using `search-records`) - Answer support style questions such as \"What should I do after a `eu-central` failover?\" or \"which KB article covers stale reads?\"\n", "\n", "- Knowledge Management (using `upsert-records`) - Add a new incident summary or replace an outdated support article after the issue is understood." ] }, { "cell_type": "code", "execution_count": null, "id": "739c110f", "metadata": {}, "outputs": [], "source": [ "# NBVAL_SKIP\n", "# Colab only: download the resources directory.\n", "if not os.path.exists(\"resources/knowledge_records.json\"):\n", " !curl -sSL -o knowledge_records.json https://raw.githubusercontent.com/redis-developer/redis-ai-resources/main/python-recipes/MCP/resources/knowledge_records.json\n", " !mkdir -p resources && mv knowledge_records.json resources/" ] }, { "cell_type": "code", "execution_count": 5, "id": "a5d2e4ae", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[{'content': 'After a regional failover in eu-central, elevated cache miss '\n", " 'rate usually means replicas are warm but invalidation workers '\n", " 'are behind. Restart the cache warming job, replay invalidation '\n", " 'events, and pre-warm the top keys for the developer platform '\n", " 'before reopening traffic.',\n", " 'last_reviewed_at': '2026-03-18',\n", " 'product': 'developer-platform',\n", " 'record_id': 'runbook_cache_failover_eu_central',\n", " 'region': 'eu-central',\n", " 'release_version': 'na',\n", " 'severity': 'sev1',\n", " 'source_type': 'runbook',\n", " 'team': 'platform',\n", " 'title': 'Runbook: mitigate elevated cache miss rate after eu-central '\n", " 'failover'},\n", " {'content': 'A March 2026 failover in eu-central caused elevated cache miss '\n", " 'rate and stale responses for the developer portal. The fix was '\n", " 'to drain the legacy invalidation backlog, force a cache warmup '\n", " 'pass, and keep the platform severity at sev1 until hit rate '\n", " 'recovered.',\n", " 'last_reviewed_at': '2026-03-12',\n", " 'product': 'developer-platform',\n", " 'record_id': 'incident_cache_failover_summary',\n", " 'region': 'eu-central',\n", " 'release_version': 'na',\n", " 'severity': 'sev1',\n", " 'source_type': 'incident_summary',\n", " 'team': 'platform',\n", " 'title': 'Incident summary: cache miss spike during regional failover'},\n", " {'content': 'Support engineers should check whether a service is still '\n", " 'publishing to the legacy cache invalidation flow. Mixed old and '\n", " 'new invalidation events can leave stale objects in Redis until '\n", " 'the developer-platform consumers catch up.',\n", " 'last_reviewed_at': '2026-02-26',\n", " 'product': 'developer-platform',\n", " 'record_id': 'kb_legacy_invalidation_troubleshooting',\n", " 'region': 'global',\n", " 'release_version': 'na',\n", " 'severity': 'sev2',\n", " 'source_type': 'kb_article',\n", " 'team': 'support',\n", " 'title': 'KB: troubleshoot stale reads from legacy cache invalidation flow'},\n", " {'content': 'Release 2026.03 deprecates the legacy cache invalidation flow '\n", " 'for the developer platform. Teams should migrate to the '\n", " 'event-driven invalidation endpoint before the next quarterly '\n", " 'cutoff.',\n", " 'last_reviewed_at': '2026-03-01',\n", " 'product': 'developer-platform',\n", " 'record_id': 'release_2026_03_legacy_invalidation_deprecation',\n", " 'region': 'global',\n", " 'release_version': '2026.03',\n", " 'severity': 'info',\n", " 'source_type': 'release_note',\n", " 'team': 'platform',\n", " 'title': 'Release notes: deprecation of legacy cache invalidation flow'},\n", " {'content': 'To retire the legacy cache invalidation flow, deploy the new '\n", " 'invalidation endpoint, verify consumer lag stays below '\n", " 'threshold, and compare cache key eviction counts between the old '\n", " 'and new pipelines for one full release.',\n", " 'last_reviewed_at': '2026-03-22',\n", " 'product': 'developer-platform',\n", " 'record_id': 'runbook_new_invalidation_cutover',\n", " 'region': 'us-east',\n", " 'release_version': 'na',\n", " 'severity': 'sev2',\n", " 'source_type': 'runbook',\n", " 'team': 'platform',\n", " 'title': 'Runbook: cut over services to the new invalidation endpoint'},\n", " {'content': 'If users report stale content after a deploy, compare cache key '\n", " 'versions between the API and the worker fleet. A version '\n", " 'mismatch often looks like a partial invalidation failure even '\n", " 'when Redis itself is healthy.',\n", " 'last_reviewed_at': '2026-03-04',\n", " 'product': 'developer-platform',\n", " 'record_id': 'kb_cache_key_version_mismatch',\n", " 'region': 'global',\n", " 'release_version': 'na',\n", " 'severity': 'sev2',\n", " 'source_type': 'kb_article',\n", " 'team': 'support',\n", " 'title': 'KB: cache key version mismatch after application deploy'},\n", " {'content': 'A rollout in us-east introduced invalidation lag after the '\n", " 'release workers fell behind. Hybrid search over this summary is '\n", " 'useful when engineers ask about cache regressions without '\n", " 'knowing the exact incident title.',\n", " 'last_reviewed_at': '2026-02-17',\n", " 'product': 'developer-platform',\n", " 'record_id': 'incident_release_rollout_cache_regression',\n", " 'region': 'us-east',\n", " 'release_version': 'na',\n", " 'severity': 'sev2',\n", " 'source_type': 'incident_summary',\n", " 'team': 'platform',\n", " 'title': 'Incident summary: release rollout caused invalidation lag'},\n", " {'content': 'Release 2026.04 makes the new invalidation endpoint the default '\n", " 'path for developer-platform services and announces final removal '\n", " 'dates for the legacy cache invalidation flow.',\n", " 'last_reviewed_at': '2026-04-02',\n", " 'product': 'developer-platform',\n", " 'record_id': 'release_2026_04_new_invalidation_endpoint',\n", " 'region': 'global',\n", " 'release_version': '2026.04',\n", " 'severity': 'info',\n", " 'source_type': 'release_note',\n", " 'team': 'platform',\n", " 'title': 'Release notes: new invalidation endpoint is now the default'},\n", " {'content': 'This payments runbook covers queue draining and circuit breaker '\n", " 'resets after a processor brownout. It is intentionally unrelated '\n", " 'to developer-platform cache incidents so product filters have '\n", " 'something to exclude.',\n", " 'last_reviewed_at': '2026-03-03',\n", " 'product': 'checkout',\n", " 'record_id': 'runbook_checkout_processor_brownout',\n", " 'region': 'eu-west',\n", " 'release_version': 'na',\n", " 'severity': 'sev1',\n", " 'source_type': 'runbook',\n", " 'team': 'payments',\n", " 'title': 'Runbook: recover checkout queue after payment processor brownout'},\n", " {'content': 'Identity support uses this article to diagnose expired tokens '\n", " 'after long mobile app suspends. It gives the index one more '\n", " 'non-cache knowledge slice for metadata-filtered search examples.',\n", " 'last_reviewed_at': '2026-03-05',\n", " 'product': 'identity-service',\n", " 'record_id': 'kb_identity_token_expiry',\n", " 'region': 'global',\n", " 'release_version': 'na',\n", " 'severity': 'sev2',\n", " 'source_type': 'kb_article',\n", " 'team': 'identity',\n", " 'title': 'KB: identity token expiry after mobile app resume'}]\n" ] } ], "source": [ "with open(\"resources/knowledge_records.json\", \"r\") as f:\n", " knowledge_records = json.load(f)\n", "\n", "pprint(knowledge_records)" ] }, { "cell_type": "markdown", "id": "ea61401e", "metadata": {}, "source": [ "### Creating the Redis index" ] }, { "cell_type": "markdown", "id": "9f373d84", "metadata": {}, "source": [ "A Redis index defines how Redis should store and search our data, including the vector embeddings used for similarity search.\n", "\n", "We populate it with sample data that we will retrieve using our agents and the tools exposed by the MCP server.\n" ] }, { "cell_type": "code", "execution_count": 6, "id": "57b5c9e4", "metadata": {}, "outputs": [], "source": [ "# Vectorizer for creating document embeddings\n", "from redisvl.utils.vectorize import HFTextVectorizer\n", "\n", "EMBEDDING_MODEL = \"sentence-transformers/all-MiniLM-L6-v2\"\n", "vectorizer = HFTextVectorizer(model=EMBEDDING_MODEL)" ] }, { "cell_type": "markdown", "id": "b18f8d22", "metadata": {}, "source": [ "#### Define the index" ] }, { "cell_type": "markdown", "id": "8b9f8a02", "metadata": {}, "source": [ "Using a schema, we define an index called `knowledge`, the collection of fields to be indexed and how they are indexed." ] }, { "cell_type": "code", "execution_count": 7, "id": "5ffea67c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'fields': [{'name': 'record_id', 'type': 'tag'},\n", " {'name': 'title', 'type': 'text'},\n", " {'name': 'content', 'type': 'text'},\n", " {'name': 'source_type', 'type': 'tag'},\n", " {'name': 'team', 'type': 'tag'},\n", " {'name': 'region', 'type': 'tag'},\n", " {'name': 'product', 'type': 'tag'},\n", " {'name': 'severity', 'type': 'tag'},\n", " {'name': 'release_version', 'type': 'tag'},\n", " {'name': 'last_reviewed_at', 'type': 'text'},\n", " {'attrs': {'algorithm': 'hnsw',\n", " 'datatype': 'float32',\n", " 'dims': 384,\n", " 'distance_metric': 'cosine'},\n", " 'name': 'embedding',\n", " 'type': 'vector'}],\n", " 'index': {'name': 'knowledge', 'prefix': 'knowledge', 'storage_type': 'hash'}}\n" ] } ], "source": [ "INDEX_NAME = \"knowledge\"\n", "\n", "knowledge_schema = {\n", " \"index\": {\n", " \"name\": INDEX_NAME,\n", " \"prefix\": \"knowledge\",\n", " \"storage_type\": \"hash\",\n", " },\n", " \"fields\": [\n", " {\"name\": \"record_id\", \"type\": \"tag\"},\n", " {\"name\": \"title\", \"type\": \"text\"},\n", " {\"name\": \"content\", \"type\": \"text\"},\n", " {\"name\": \"source_type\", \"type\": \"tag\"},\n", " {\"name\": \"team\", \"type\": \"tag\"},\n", " {\"name\": \"region\", \"type\": \"tag\"},\n", " {\"name\": \"product\", \"type\": \"tag\"},\n", " {\"name\": \"severity\", \"type\": \"tag\"},\n", " {\"name\": \"release_version\", \"type\": \"tag\"},\n", " {\"name\": \"last_reviewed_at\", \"type\": \"text\"},\n", " {\n", " \"name\": \"embedding\",\n", " \"type\": \"vector\",\n", " \"attrs\": {\n", " \"dims\": vectorizer.dims,\n", " \"distance_metric\": \"cosine\",\n", " \"algorithm\": \"hnsw\",\n", " \"datatype\": \"float32\",\n", " },\n", " },\n", " ],\n", "}\n", "pprint(knowledge_schema)" ] }, { "cell_type": "markdown", "id": "9ce20a83", "metadata": {}, "source": [ "#### Create the search index" ] }, { "cell_type": "markdown", "id": "dea5fd69", "metadata": {}, "source": [ "Create the knowledge index within Redis. It will be queried by the agent via the MCP tools" ] }, { "cell_type": "code", "execution_count": 8, "id": "f05ead90", "metadata": {}, "outputs": [], "source": [ "from redisvl.index import SearchIndex\n", "\n", "index = SearchIndex.from_dict(knowledge_schema, redis_url=REDIS_URL)\n", "index.create(overwrite=True, drop=True)" ] }, { "cell_type": "code", "execution_count": 9, "id": "953d8de0", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "Index Information:\n", "╭───────────────┬───────────────┬───────────────┬───────────────┬───────────────╮\n", "│ Index Name │ Storage Type │ Prefixes │ Index Options │ Indexing │\n", "├───────────────┼───────────────┼───────────────┼───────────────┼───────────────┤\n", "| knowledge | HASH | ['knowledge'] | [] | 0 |\n", "╰───────────────┴───────────────┴───────────────┴───────────────┴───────────────╯\n", "Index Fields:\n", "╭──────────────────┬──────────────────┬──────────────────┬──────────────────┬──────────────────┬──────────────────┬──────────────────┬──────────────────┬──────────────────┬──────────────────┬──────────────────┬──────────────────┬──────────────────┬──────────────────┬──────────────────╮\n", "│ Name │ Attribute │ Type │ Field Option │ Option Value │ Field Option │ Option Value │ Field Option │ Option Value │ Field Option │ Option Value │ Field Option │ Option Value │ Field Option │ Option Value │\n", "├──────────────────┼──────────────────┼──────────────────┼──────────────────┼──────────────────┼──────────────────┼──────────────────┼──────────────────┼──────────────────┼──────────────────┼──────────────────┼──────────────────┼──────────────────┼──────────────────┼──────────────────┤\n", "│ record_id │ record_id │ TAG │ SEPARATOR │ , │ │ │ │ │ │ │ │ │ │ │\n", "│ title │ title │ TEXT │ WEIGHT │ 1 │ │ │ │ │ │ │ │ │ │ │\n", "│ content │ content │ TEXT │ WEIGHT │ 1 │ │ │ │ │ │ │ │ │ │ │\n", "│ source_type │ source_type │ TAG │ SEPARATOR │ , │ │ │ │ │ │ │ │ │ │ │\n", "│ team │ team │ TAG │ SEPARATOR │ , │ │ │ │ │ │ │ │ │ │ │\n", "│ region │ region │ TAG │ SEPARATOR │ , │ │ │ │ │ │ │ │ │ │ │\n", "│ product │ product │ TAG │ SEPARATOR │ , │ │ │ │ │ │ │ │ │ │ │\n", "│ severity │ severity │ TAG │ SEPARATOR │ , │ │ │ │ │ │ │ │ │ │ │\n", "│ release_version │ release_version │ TAG │ SEPARATOR │ , │ │ │ │ │ │ │ │ │ │ │\n", "│ last_reviewed_at │ last_reviewed_at │ TEXT │ WEIGHT │ 1 │ │ │ │ │ │ │ │ │ │ │\n", "│ embedding │ embedding │ VECTOR │ algorithm │ HNSW │ data_type │ FLOAT32 │ dim │ 384 │ distance_metric │ COSINE │ M │ 16 │ ef_construction │ 200 │\n", "╰──────────────────┴──────────────────┴──────────────────┴──────────────────┴──────────────────┴──────────────────┴──────────────────┴──────────────────┴──────────────────┴──────────────────┴──────────────────┴──────────────────┴──────────────────┴──────────────────┴──────────────────╯\n" ] } ], "source": [ "# Get info about the knowledge index\n", "!rvl index info -i knowledge" ] }, { "cell_type": "markdown", "id": "c415225e", "metadata": {}, "source": [ "### Populating the Redis index" ] }, { "cell_type": "markdown", "id": "7c41b668", "metadata": {}, "source": [ "At this stage, we have defined the index containing the documents we need for the agent. Next, we want to populate the index with records from our knowledge base." ] }, { "cell_type": "code", "execution_count": 10, "id": "3fb6a76d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Created 'knowledge' with 10 records.\n", "['knowledge:runbook_cache_failover_eu_central', 'knowledge:incident_cache_failover_summary', 'knowledge:kb_legacy_invalidation_troubleshooting', 'knowledge:release_2026_03_legacy_invalidation_deprecation', 'knowledge:runbook_new_invalidation_cutover', 'knowledge:kb_cache_key_version_mismatch', 'knowledge:incident_release_rollout_cache_regression', 'knowledge:release_2026_04_new_invalidation_endpoint', 'knowledge:runbook_checkout_processor_brownout', 'knowledge:kb_identity_token_expiry']\n" ] } ], "source": [ "# Embed the knowledge records and load them into the index\n", "embeddings = vectorizer.embed_many(\n", " contents=[record[\"content\"] for record in knowledge_records],\n", " batch_size=8,\n", ")\n", "\n", "records_with_embeddings = [\n", " {**record, \"embedding\": np.array(embedding, dtype=np.float32).tobytes()}\n", " for record, embedding in zip(knowledge_records, embeddings)\n", "]\n", "\n", "keys = index.load(records_with_embeddings, id_field=\"record_id\")\n", "\n", "print(f\"Created '{INDEX_NAME}' with {len(keys)} records.\")\n", "print(keys)" ] }, { "cell_type": "markdown", "id": "43b0a21f", "metadata": {}, "source": [ "## Setting up the MCP Server" ] }, { "cell_type": "markdown", "id": "1a202ef3", "metadata": {}, "source": [ "### Why The MCP Layer Matters\n" ] }, { "cell_type": "markdown", "id": "55d3295d", "metadata": {}, "source": [ "The MCP server is the bridge between the agent and the Redis-backed knowledge base. In this notebook, the important tools are `search-records` for retrieval and `upsert-records` for writes." ] }, { "cell_type": "markdown", "id": "50591a8f", "metadata": {}, "source": [ "### MCP Config" ] }, { "cell_type": "markdown", "id": "3d880806", "metadata": {}, "source": [ "The MCP config is a yaml file that defines how an MCP server is run. In this case, it also specifies how the MCP server interacts with the Redis index." ] }, { "cell_type": "code", "execution_count": 11, "id": "ee7043fa", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "('server:\\n'\n", " ' redis_url: redis://:@localhost:6379\\n'\n", " 'indexes:\\n'\n", " ' knowledge:\\n'\n", " ' redis_name: knowledge\\n'\n", " ' vectorizer:\\n'\n", " ' class: HFTextVectorizer\\n'\n", " ' model: sentence-transformers/all-MiniLM-L6-v2\\n'\n", " ' search:\\n'\n", " ' type: vector\\n'\n", " ' runtime:\\n'\n", " ' text_field_name: content\\n'\n", " ' vector_field_name: embedding\\n'\n", " ' default_embed_text_field: content\\n'\n", " ' default_limit: 10\\n'\n", " ' max_limit: 25\\n'\n", " ' max_upsert_records: 64\\n'\n", " ' skip_embedding_if_present: true\\n'\n", " ' max_result_window: 100\\n'\n", " ' startup_timeout_seconds: 120\\n'\n", " ' request_timeout_seconds: 120\\n'\n", " ' max_concurrency: 16\\n')\n" ] } ], "source": [ "# For readability, we define the config as a dictionary first. But it should be in yaml format.\n", "\n", "mcp_config = {\n", " \"server\": {\n", " \"redis_url\": REDIS_URL,\n", " },\n", " \"indexes\": {\n", " INDEX_NAME: {\n", " \"redis_name\": INDEX_NAME,\n", " \"vectorizer\": {\n", " \"class\": \"HFTextVectorizer\",\n", " \"model\": EMBEDDING_MODEL,\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", " \"max_upsert_records\": 64,\n", " \"skip_embedding_if_present\": True,\n", " \"max_result_window\": 100,\n", " # The first search-records call loads the embedding model into\n", " # memory, which can take 30+ seconds. Give it plenty of room.\n", " \"startup_timeout_seconds\": 120,\n", " \"request_timeout_seconds\": 120,\n", " \"max_concurrency\": 16\n", " },\n", " }\n", " },\n", "}\n", "\n", "pprint(yaml.safe_dump(mcp_config, sort_keys=False))" ] }, { "cell_type": "code", "execution_count": 12, "id": "632c88ee", "metadata": {}, "outputs": [], "source": [ "# Write the config into a file called \"mcp-config.yaml\"\n", "MCP_CONFIG_FILEPATH = os.path.join(\"config\", \"redisvl-mcp-config.yaml\")\n", "os.makedirs(os.path.dirname(MCP_CONFIG_FILEPATH), exist_ok=True)\n", "\n", "with open(MCP_CONFIG_FILEPATH, \"w\", encoding=\"utf-8\") as f:\n", " f.write(yaml.safe_dump(mcp_config, sort_keys=False))\n" ] }, { "cell_type": "markdown", "id": "9fb3adff", "metadata": {}, "source": [ "### Overriding tool descriptions" ] }, { "cell_type": "markdown", "id": "f99112aa", "metadata": {}, "source": [ "We override the tool descriptions for `search-records` and `upsert-records` via the `REDISVL_MCP_TOOL_SEARCH_DESCRIPTION` and `REDISVL_MCP_TOOL_UPSERT_DESCRIPTION` environment variables respectively so the model knows exactly which fields and filter operators are available, to prevent hallucinations." ] }, { "cell_type": "code", "execution_count": 13, "id": "53208d4b", "metadata": {}, "outputs": [], "source": [ "# search-records\n", "AVAILABLE_FIELDS = \"record_id (tag), title (text), content (text), source_type (tag), team (tag), region (tag), product (tag), severity (tag), release_version (tag), last_reviewed_at (tag).\"\n", "SEARCH_TOOL_DESCRIPTION = f\"Search the knowledge base in Redis. Available fields: {AVAILABLE_FIELDS} Only use these field names in return_fields and filters.\"\n", "os.environ[\"REDISVL_MCP_TOOL_SEARCH_DESCRIPTION\"] = SEARCH_TOOL_DESCRIPTION" ] }, { "cell_type": "code", "execution_count": 14, "id": "1b58a02a", "metadata": {}, "outputs": [], "source": [ "# upsert-records\n", "UPSERT_TOOL_DESCRIPTION = f\"Upsert records in the knowledge base in Redis. Available fields: {AVAILABLE_FIELDS} Ensure the incoming record contains these fields.\"\n", "os.environ[\"REDISVL_MCP_TOOL_UPSERT_DESCRIPTION\"] = UPSERT_TOOL_DESCRIPTION" ] }, { "cell_type": "markdown", "id": "2c3285f9", "metadata": {}, "source": [ "### Running the MCP server" ] }, { "cell_type": "markdown", "id": "05e3dc56", "metadata": {}, "source": [ "There are two options for running the MCP server: either from the terminal (when running on a local development environment) or natively from within the notebook (when running on Colab). " ] }, { "cell_type": "markdown", "id": "0642645c", "metadata": {}, "source": [ "#### Running on local development environments" ] }, { "cell_type": "markdown", "id": "18cc49c9", "metadata": {}, "source": [ "If you are running the MCP server for local development, you can run it from your terminal. Open a separate terminal, cd to this notebook's directory, and run:\n", "\n", "```\n", "export REDISVL_MCP_TOOL_SEARCH_DESCRIPTION=\"Search the knowledge base in Redis. Available fields: record_id (tag), title (text), content (text), source_type (tag), team (tag), region (tag), product (tag), severity (tag), release_version (tag), last_reviewed_at (tag). Only use these field names in return_fields and filters.\"\n", "\n", "export REDISVL_MCP_TOOL_UPSERT_DESCRIPTION=\"Upsert records in the knowledge base in Redis. Available fields: record_id (tag), title (text), content (text), source_type (tag), team (tag), region (tag), product (tag), severity (tag), release_version (tag), last_reviewed_at (tag). Ensure the incoming record contains these fields.\"\n", "\n", "uvx --from \"redisvl[mcp,sentence-transformers,nltk]\" rvl mcp \\\n", " --config tmp/google_adk_movies_mcp.yaml \\\n", " --read-only \\\n", " --transport streamable-http \\\n", " --host 127.0.0.1 \\\n", " --port 16740\n", "```\n", "\n", "You should see output indicating the server is running (e.g., `Uvicorn running on http://127.0.0.1:16740`). Leave the terminal open, the MCP server is up and running." ] }, { "cell_type": "markdown", "id": "77dc20e0", "metadata": {}, "source": [ "#### Running on Colab" ] }, { "cell_type": "markdown", "id": "6fdb8e43", "metadata": {}, "source": [ "If instead running on colab, run the MCP server from the notebook.\n", "\n", "To simulate the MCP server running in a separate process and independent of the client, we use `subprocess.Popen`. The MCP server should be running locally (`127.0.0.1`) on port `16740`." ] }, { "cell_type": "code", "execution_count": 15, "id": "42608655", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " 9703 ?? S 0:00.18 uv tool uvx --from redisvl[mcp,sentence-transformers,nltk] rvl mcp --config config/redisvl-mcp-config.yaml --transport streamable-http --host 127.0.0.1 --port 16740\n", " 9711 ?? R 0:00.83 python rvl mcp --config config/redisvl-mcp-config.yaml --transport streamable-http --host 127.0.0.1 --port 16740\n", " 9704 s013 Ss+ 0:00.63 zsh -c ps ax | grep \"mcp\" | sed 's|/[^ ]*/||g'\n", " 9744 s013 R+ 0:00.00 grep mcp\n" ] } ], "source": [ "# NBVAL_SKIP\n", "# Start MCP Server\n", "import subprocess\n", "\n", "# Start the process\n", "MCP_SERVER_HOSTNAME = \"127.0.0.1\"\n", "MCP_SERVER_PORT = \"16740\"\n", "MCP_TRANSPORT = \"streamable-http\"\n", "\n", "# Pass the custom tool description to the subprocess environment.\n", "mcp_env = os.environ.copy()\n", "mcp_env[\"REDISVL_MCP_TOOL_SEARCH_DESCRIPTION\"] = SEARCH_TOOL_DESCRIPTION\n", "mcp_env[\"REDISVL_MCP_TOOL_UPSERT_DESCRIPTION\"] = UPSERT_TOOL_DESCRIPTION\n", "mcp_env[\"FASTMCP_JSON_RESPONSE\"] = \"true\"\n", "\n", "# Run a redisvl mcp server on localhost:16740 on streamable-http\n", "mcp_server = subprocess.Popen([\"uvx\", \"--from\", \"redisvl[mcp,sentence-transformers,nltk]\", \"rvl\", \"mcp\", \n", " \"--config\", MCP_CONFIG_FILEPATH, \n", " \"--transport\", MCP_TRANSPORT, \n", " \"--host\", MCP_SERVER_HOSTNAME, \n", " \"--port\", MCP_SERVER_PORT], \n", " stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, \n", " text=True, env=mcp_env)\n", "\n", "# Verify that the mcp server is running in another process\n", "! ps ax | grep \"mcp\" | sed 's|/[^ ]*/||g'" ] }, { "cell_type": "markdown", "id": "7cbfa01f", "metadata": {}, "source": [ "After a few seconds, the MCP server should be up and running." ] }, { "cell_type": "code", "execution_count": null, "id": "8390fd76", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "MCP server is ready (PID 9703).\n", "Tool information: \n", "{'id': 1,\n", " 'jsonrpc': '2.0',\n", " 'result': {'tools': [{'_meta': {'fastmcp': {'tags': []}},\n", " 'description': 'Search the knowledge base in Redis. '\n", " 'Available fields: record_id (tag), '\n", " 'title (text), content (text), '\n", " 'source_type (tag), team (tag), region '\n", " '(tag), product (tag), severity (tag), '\n", " 'release_version (tag), last_reviewed_at '\n", " '(tag). Only use these field names in '\n", " 'return_fields and filters.',\n", " 'inputSchema': {'additionalProperties': False,\n", " 'properties': {'filter': {'anyOf': [{'type': 'string'},\n", " {'additionalProperties': True,\n", " 'type': 'object'},\n", " {'type': 'null'}],\n", " 'default': None},\n", " 'limit': {'anyOf': [{'type': 'integer'},\n", " {'type': 'null'}],\n", " 'default': None},\n", " 'offset': {'default': 0,\n", " 'type': 'integer'},\n", " 'query': {'type': 'string'},\n", " 'return_fields': {'anyOf': [{'items': {'type': 'string'},\n", " 'type': 'array'},\n", " {'type': 'null'}],\n", " 'default': None}},\n", " 'required': ['query'],\n", " 'type': 'object'},\n", " 'name': 'search-records'},\n", " {'_meta': {'fastmcp': {'tags': []}},\n", " 'description': 'Upsert records in the knowledge base in '\n", " 'Redis. Available fields: record_id '\n", " '(tag), title (text), content (text), '\n", " 'source_type (tag), team (tag), region '\n", " '(tag), product (tag), severity (tag), '\n", " 'release_version (tag), last_reviewed_at '\n", " '(tag). Ensure the incoming record '\n", " 'contains these fields.',\n", " 'inputSchema': {'additionalProperties': False,\n", " 'properties': {'id_field': {'anyOf': [{'type': 'string'},\n", " {'type': 'null'}],\n", " 'default': None},\n", " 'records': {'items': {'additionalProperties': True,\n", " 'type': 'object'},\n", " 'type': 'array'},\n", " 'skip_embedding_if_present': {'anyOf': [{'type': 'boolean'},\n", " {'type': 'null'}],\n", " 'default': None}},\n", " 'required': ['records'],\n", " 'type': 'object'},\n", " 'name': 'upsert-records'}]}}\n" ] } ], "source": [ "# NBVAL_SKIP\n", "# Ping and test initialization with the MCP server\n", "import requests\n", "import time\n", "\n", "MCP_SERVER_URL = f\"http://{MCP_SERVER_HOSTNAME}:{MCP_SERVER_PORT}/mcp\"\n", "\n", "# We need to use a session to connect to the server.\n", "session = requests.Session()\n", "session.headers.update({\n", " \"Accept\": \"application/json\",\n", "})\n", "\n", "for attempt in range(90):\n", " if mcp_server.poll() is not None:\n", " raise RuntimeError(\n", " f\"MCP server exited before becoming ready with code {mcp_server.returncode}.\"\n", " )\n", "\n", " try:\n", " resp = session.post(\n", " MCP_SERVER_URL,\n", " json={\n", " \"jsonrpc\": \"2.0\",\n", " \"id\": 0,\n", " \"method\": \"initialize\",\n", " \"params\": {\n", " \"protocolVersion\": \"2025-11-25\",\n", " \"capabilities\": {},\n", " \"clientInfo\": {\n", " \"name\": \"notebook-client\",\n", " \"version\": \"0.1.0\",\n", " }\n", " },\n", " },\n", " timeout=2,\n", " )\n", "\n", " resp.raise_for_status()\n", " payload = resp.json()\n", " if payload.get(\"jsonrpc\") == \"2.0\" and payload.get(\"id\") == 0 and \"result\" in payload:\n", " print(f\"MCP server is ready (PID {mcp_server.pid}).\")\n", " # We need to use the session ID from the initialization for future requests via HTTP\n", " mcp_session_id = resp.headers.get(\"mcp-session-id\")\n", " session.headers.update({\n", " \"mcp-session-id\": mcp_session_id,\n", " })\n", "\n", " # List the tools in the MCP server\n", " resp = session.post(\n", " MCP_SERVER_URL,\n", " json={\n", " \"jsonrpc\": \"2.0\",\n", " \"id\": 1,\n", " \"method\": \"tools/list\",\n", " \"params\": {},\n", " },\n", " timeout=2,\n", " )\n", " print(\"Tool information: \")\n", " pprint(resp.json())\n", "\n", " break\n", " except (requests.RequestException, ValueError):\n", " pass\n", "\n", " time.sleep(1)\n", "else:\n", " raise RuntimeError(\n", " \"MCP server did not start within 90 s or did not return a valid initialize response. \"\n", " \"Check that `uvx` is installed and `redisvl[mcp]` is available.\"\n", " )\n", "\n" ] }, { "cell_type": "markdown", "id": "c77812f0", "metadata": {}, "source": [ "At this stage, we have successfully set up our RedisVL MCP server and tested our connection to it." ] }, { "cell_type": "markdown", "id": "f460aa12", "metadata": {}, "source": [ "## Building our agent" ] }, { "cell_type": "code", "execution_count": 18, "id": "a9487a4c", "metadata": {}, "outputs": [], "source": [ "# NBVAL_SKIP\n", "# Build an agent using Claude Agent SDK\n", "from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions\n", "\n", "REDISVL_ALLOWED_TOOLS = [\"mcp__redisvl__*\"] # Allow all tools by redisvl mcp server\n", "CLAUDE_MODEL = \"claude-haiku-4-5\"\n", "\n", "# Configure the Claude Agent\n", "agent_options = ClaudeAgentOptions(\n", " model=CLAUDE_MODEL,\n", " cwd=os.getcwd(),\n", " system_prompt=(\n", " \"You are an internal support copilot for the developer-platform team. \"\n", " \"When a user asks about incidents, runbooks, release notes, or support \"\n", " \"KB content, use the RedisVL MCP tools first before answering. Prefer \"\n", " \"search-records for retrieval. Only use upsert-records when the user \"\n", " \"explicitly asks to add or update knowledge records. Cite the record \"\n", " \"titles you used in the final answer.\"\n", " ),\n", " mcp_servers={\n", " \"redisvl\": {\n", " \"type\": \"http\",\n", " \"url\": MCP_SERVER_URL,\n", " \"env\": {\n", " \"REDIS_URL\": os.environ[\"REDIS_URL\"] or REDIS_URL,\n", " },\n", " }\n", " },\n", " allowed_tools=REDISVL_ALLOWED_TOOLS,\n", " max_turns=6,\n", ")\n", "\n", "agent = ClaudeSDKClient(options=agent_options)" ] }, { "cell_type": "markdown", "id": "f8b7b515", "metadata": {}, "source": [ "## Using our Agent" ] }, { "cell_type": "markdown", "id": "94c3a770", "metadata": {}, "source": [ "Now that we have set up the following:\n", "- Redis Index: `knowledge`\n", "- RedisVL MCP Server\n", "- Claude Agent\n", "\n", "We can use our agent to perform retrieval and addition/update operations to our `knowledge` index." ] }, { "cell_type": "code", "execution_count": 19, "id": "829ca9c8", "metadata": {}, "outputs": [], "source": [ "# Helper functions\n", "from claude_agent_sdk.types import (\n", " AssistantMessage,\n", " ResultMessage,\n", " SystemMessage,\n", " TextBlock,\n", " ToolResultBlock,\n", " ToolUseBlock,\n", ")\n", "\n", "def print_agent_message(message):\n", " \"\"\"\n", " Formats the agent output in a more human-readable way.\n", " \"\"\"\n", " if isinstance(message, AssistantMessage):\n", " for block in message.content:\n", " if isinstance(block, TextBlock):\n", " print(f\"Claude:\\n{block.text}\\n\")\n", " elif isinstance(block, ToolUseBlock):\n", " print(f\"Tool use -> {block.name}\")\n", " pprint(block.input)\n", " print()\n", " elif isinstance(block, ToolResultBlock):\n", " print(f\"Tool result <- {block.tool_use_id}\")\n", " pprint(block.content)\n", " print()\n", " elif isinstance(message, ResultMessage):\n", " print(\"Result summary:\")\n", " print(message.result)\n", " # if message.total_cost_usd is not None:\n", " # print(f\"Cost: ${message.total_cost_usd:.4f}\")" ] }, { "cell_type": "markdown", "id": "b8f3cdd3", "metadata": {}, "source": [ "### Retrieval with `search-records`" ] }, { "cell_type": "markdown", "id": "9b3f0515", "metadata": {}, "source": [ "We use a few prompts to guide the agent to perform retrieval on our knowledge base." ] }, { "cell_type": "code", "execution_count": 20, "id": "3fd2e6a1", "metadata": {}, "outputs": [], "source": [ "SEARCH_PROMPTS = [\n", " \"Search the internal knowledge base for guidance on mitigating elevated cache miss rate after a eu-central failover. Focus on developer-platform material owned by the platform team, and summarize the recommended actions with the record titles you used.\",\n", " \"Find release-note and runbook content about the deprecation of the legacy cache invalidation flow for developer-platform. Summarize what changed and cite the relevant titles.\",\n", " \"Look up support knowledge for stale reads or invalidation troubleshooting in developer-platform. Prefer KB-style guidance over incident summaries and include the source type in the answer.\",\n", " \"Search for cache regression guidance, but exclude unrelated checkout and identity-service material. I only want developer-platform results with titles and source types.\",\n", "]" ] }, { "cell_type": "code", "execution_count": 21, "id": "c7ccb236", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "================================================================================\n", "Task 1: Search the internal knowledge base for guidance on mitigating elevated cache miss rate after a eu-central failover. Focus on developer-platform material owned by the platform team, and summarize the recommended actions with the record titles you used.\n", "\n", "Tool use -> mcp__redisvl__search-records\n", "{'filter': {'product': {'$eq': 'developer-platform'},\n", " 'region': {'$eq': 'eu-central'}},\n", " 'limit': 10,\n", " 'query': 'elevated cache miss rate eu-central failover mitigation',\n", " 'return_fields': ['record_id', 'title', 'content', 'source_type', 'team']}\n", "\n", "Tool use -> mcp__redisvl__search-records\n", "{'limit': 10,\n", " 'query': 'cache miss rate eu-central failover developer-platform',\n", " 'return_fields': ['record_id',\n", " 'title',\n", " 'content',\n", " 'source_type',\n", " 'team',\n", " 'region',\n", " 'product']}\n", "\n", "Claude:\n", "Based on the knowledge base search, I found two directly relevant records from the platform team for the developer-platform product:\n", "\n", "## Recommended Actions for Elevated Cache Miss Rate After EU-Central Failover\n", "\n", "**From the record** ***\"Runbook: mitigate elevated cache miss rate after eu-central failover\"***, the platform team recommends:\n", "\n", "1. **Restart the cache warming job** – After regional failover, replicas are usually warm but invalidation workers fall behind\n", "2. **Replay invalidation events** – Catch up the backlog of cache invalidation events that accumulated during failover\n", "3. **Pre-warm the top keys** – For the developer platform, proactively load frequently accessed keys into cache\n", "4. **Pre-validate before reopening traffic** – Verify cache health metrics before fully reopening the region to production traffic\n", "\n", "**Supporting context from** ***\"Incident summary: cache miss spike during regional failover\"*** – A March 2026 incident in eu-central demonstrated that the root cause was a legacy invalidation backlog. The successful resolution involved:\n", "- Draining the legacy invalidation backlog\n", "- Forcing a cache warmup pass\n", "- Keeping the platform at sev1 severity level until cache hit rates recovered\n", "\n", "**Additional note:** The platform team is actively deprecating the legacy cache invalidation flow (as documented in the 2026.03 and 2026.04 release notes). Once your services migrate to the event-driven invalidation endpoint, future failovers should see less invalidation lag.\n", "\n", "Result summary:\n", "Based on the knowledge base search, I found two directly relevant records from the platform team for the developer-platform product:\n", "\n", "## Recommended Actions for Elevated Cache Miss Rate After EU-Central Failover\n", "\n", "**From the record** ***\"Runbook: mitigate elevated cache miss rate after eu-central failover\"***, the platform team recommends:\n", "\n", "1. **Restart the cache warming job** – After regional failover, replicas are usually warm but invalidation workers fall behind\n", "2. **Replay invalidation events** – Catch up the backlog of cache invalidation events that accumulated during failover\n", "3. **Pre-warm the top keys** – For the developer platform, proactively load frequently accessed keys into cache\n", "4. **Pre-validate before reopening traffic** – Verify cache health metrics before fully reopening the region to production traffic\n", "\n", "**Supporting context from** ***\"Incident summary: cache miss spike during regional failover\"*** – A March 2026 incident in eu-central demonstrated that the root cause was a legacy invalidation backlog. The successful resolution involved:\n", "- Draining the legacy invalidation backlog\n", "- Forcing a cache warmup pass\n", "- Keeping the platform at sev1 severity level until cache hit rates recovered\n", "\n", "**Additional note:** The platform team is actively deprecating the legacy cache invalidation flow (as documented in the 2026.03 and 2026.04 release notes). Once your services migrate to the event-driven invalidation endpoint, future failovers should see less invalidation lag.\n", "================================================================================\n", "================================================================================\n", "Task 2: Find release-note and runbook content about the deprecation of the legacy cache invalidation flow for developer-platform. Summarize what changed and cite the relevant titles.\n", "\n", "Tool use -> mcp__redisvl__search-records\n", "{'limit': 10,\n", " 'query': 'legacy cache invalidation flow deprecation developer-platform '\n", " 'release runbook',\n", " 'return_fields': ['record_id',\n", " 'title',\n", " 'content',\n", " 'source_type',\n", " 'team',\n", " 'release_version']}\n", "\n", "Claude:\n", "## Legacy Cache Invalidation Flow Deprecation Summary\n", "\n", "Based on the platform team's release notes and runbook, here's the deprecation timeline and what changed:\n", "\n", "### **Release 2026.03** – Deprecation Announced\n", "***\"Release notes: deprecation of legacy cache invalidation flow\"***\n", "- The legacy cache invalidation flow was officially deprecated for the developer platform\n", "- Teams were given a migration window until the next quarterly cutoff\n", "- Services needed to migrate to the new event-driven invalidation endpoint\n", "\n", "### **Release 2026.04** – New Endpoint Becomes Default\n", "***\"Release notes: new invalidation endpoint is now the default\"***\n", "- The new invalidation endpoint became the default path for all developer-platform services\n", "- Final removal dates for the legacy cache invalidation flow were announced\n", "- This marked the transition point where new services automatically use the modern approach\n", "\n", "### **Cutover Runbook**\n", "***\"Runbook: cut over services to the new invalidation endpoint\"***\n", "- **Deployment step:** Deploy the new invalidation endpoint\n", "- **Validation step:** Verify consumer lag stays below threshold\n", "- **Comparison step:** Compare cache key eviction counts between old and new pipelines for one full release cycle to ensure parity before fully retiring the legacy system\n", "\n", "**Key takeaway:** The deprecation spans two quarters (2026.03 to 2026.04), with the new event-driven invalidation endpoint as the replacement. Services must migrate to avoid the eventual removal of the legacy flow, which will help prevent the invalidation lag issues that caused the March 2026 failover incident in eu-central.\n", "\n", "Result summary:\n", "## Legacy Cache Invalidation Flow Deprecation Summary\n", "\n", "Based on the platform team's release notes and runbook, here's the deprecation timeline and what changed:\n", "\n", "### **Release 2026.03** – Deprecation Announced\n", "***\"Release notes: deprecation of legacy cache invalidation flow\"***\n", "- The legacy cache invalidation flow was officially deprecated for the developer platform\n", "- Teams were given a migration window until the next quarterly cutoff\n", "- Services needed to migrate to the new event-driven invalidation endpoint\n", "\n", "### **Release 2026.04** – New Endpoint Becomes Default\n", "***\"Release notes: new invalidation endpoint is now the default\"***\n", "- The new invalidation endpoint became the default path for all developer-platform services\n", "- Final removal dates for the legacy cache invalidation flow were announced\n", "- This marked the transition point where new services automatically use the modern approach\n", "\n", "### **Cutover Runbook**\n", "***\"Runbook: cut over services to the new invalidation endpoint\"***\n", "- **Deployment step:** Deploy the new invalidation endpoint\n", "- **Validation step:** Verify consumer lag stays below threshold\n", "- **Comparison step:** Compare cache key eviction counts between old and new pipelines for one full release cycle to ensure parity before fully retiring the legacy system\n", "\n", "**Key takeaway:** The deprecation spans two quarters (2026.03 to 2026.04), with the new event-driven invalidation endpoint as the replacement. Services must migrate to avoid the eventual removal of the legacy flow, which will help prevent the invalidation lag issues that caused the March 2026 failover incident in eu-central.\n", "================================================================================\n", "================================================================================\n", "Task 3: Look up support knowledge for stale reads or invalidation troubleshooting in developer-platform. Prefer KB-style guidance over incident summaries and include the source type in the answer.\n", "\n", "Tool use -> mcp__redisvl__search-records\n", "{'limit': 15,\n", " 'query': 'stale reads invalidation troubleshooting developer-platform',\n", " 'return_fields': ['record_id',\n", " 'title',\n", " 'content',\n", " 'source_type',\n", " 'team',\n", " 'product']}\n", "\n", "Claude:\n", "## Support Knowledge Base for Stale Reads & Invalidation Troubleshooting\n", "\n", "I found two KB articles from the support team for developer-platform:\n", "\n", "### 1. **KB: troubleshoot stale reads from legacy cache invalidation flow**\n", "- **Source Type:** kb_article\n", "- **Team:** Support\n", "- **Guidance:** Support engineers should check whether a service is still publishing to the legacy cache invalidation flow. Mixed old and new invalidation events can leave stale objects in Redis until the developer-platform consumers catch up.\n", "\n", "### 2. **KB: cache key version mismatch after application deploy**\n", "- **Source Type:** kb_article\n", "- **Team:** Support\n", "- **Guidance:** If users report stale content after a deploy, compare cache key versions between the API and the worker fleet. A version mismatch often looks like a partial invalidation failure even when Redis itself is healthy.\n", "\n", "---\n", "\n", "**Key troubleshooting takeaways:**\n", "- For legacy flow issues: Verify that all services have migrated away from the old invalidation endpoint (especially critical given the 2026.03/2026.04 deprecation timeline)\n", "- For post-deploy stale content: Check cache key version alignment between API and workers before assuming Redis problems—this is a common misdiagnosis\n", "\n", "Result summary:\n", "## Support Knowledge Base for Stale Reads & Invalidation Troubleshooting\n", "\n", "I found two KB articles from the support team for developer-platform:\n", "\n", "### 1. **KB: troubleshoot stale reads from legacy cache invalidation flow**\n", "- **Source Type:** kb_article\n", "- **Team:** Support\n", "- **Guidance:** Support engineers should check whether a service is still publishing to the legacy cache invalidation flow. Mixed old and new invalidation events can leave stale objects in Redis until the developer-platform consumers catch up.\n", "\n", "### 2. **KB: cache key version mismatch after application deploy**\n", "- **Source Type:** kb_article\n", "- **Team:** Support\n", "- **Guidance:** If users report stale content after a deploy, compare cache key versions between the API and the worker fleet. A version mismatch often looks like a partial invalidation failure even when Redis itself is healthy.\n", "\n", "---\n", "\n", "**Key troubleshooting takeaways:**\n", "- For legacy flow issues: Verify that all services have migrated away from the old invalidation endpoint (especially critical given the 2026.03/2026.04 deprecation timeline)\n", "- For post-deploy stale content: Check cache key version alignment between API and workers before assuming Redis problems—this is a common misdiagnosis\n", "================================================================================\n", "================================================================================\n", "Task 4: Search for cache regression guidance, but exclude unrelated checkout and identity-service material. I only want developer-platform results with titles and source types.\n", "\n", "Tool use -> mcp__redisvl__search-records\n", "{'limit': 20,\n", " 'query': 'cache regression developer-platform',\n", " 'return_fields': ['title', 'source_type', 'product']}\n", "\n", "Claude:\n", "## Developer-Platform Cache Regression Guidance\n", "\n", "Here are the developer-platform results (excluding checkout and identity-service):\n", "\n", "| Title | Source Type |\n", "|-------|-------------|\n", "| Incident summary: release rollout caused invalidation lag | incident_summary |\n", "| Release notes: deprecation of legacy cache invalidation flow | release_note |\n", "| Runbook: cut over services to the new invalidation endpoint | runbook |\n", "| Incident summary: cache miss spike during regional failover | incident_summary |\n", "| Runbook: mitigate elevated cache miss rate after eu-central failover | runbook |\n", "| Release notes: new invalidation endpoint is now the default | release_note |\n", "| KB: troubleshoot stale reads from legacy cache invalidation flow | kb_article |\n", "| KB: cache key version mismatch after application deploy | kb_article |\n", "\n", "All 8 results are developer-platform material covering cache regressions through multiple content types: 2 incident summaries, 3 runbooks, 2 release notes, and 1 KB article.\n", "\n", "Result summary:\n", "## Developer-Platform Cache Regression Guidance\n", "\n", "Here are the developer-platform results (excluding checkout and identity-service):\n", "\n", "| Title | Source Type |\n", "|-------|-------------|\n", "| Incident summary: release rollout caused invalidation lag | incident_summary |\n", "| Release notes: deprecation of legacy cache invalidation flow | release_note |\n", "| Runbook: cut over services to the new invalidation endpoint | runbook |\n", "| Incident summary: cache miss spike during regional failover | incident_summary |\n", "| Runbook: mitigate elevated cache miss rate after eu-central failover | runbook |\n", "| Release notes: new invalidation endpoint is now the default | release_note |\n", "| KB: troubleshoot stale reads from legacy cache invalidation flow | kb_article |\n", "| KB: cache key version mismatch after application deploy | kb_article |\n", "\n", "All 8 results are developer-platform material covering cache regressions through multiple content types: 2 incident summaries, 3 runbooks, 2 release notes, and 1 KB article.\n", "================================================================================\n" ] } ], "source": [ "# NBVAL_SKIP\n", "async with agent as client:\n", " for i, prompt in enumerate(SEARCH_PROMPTS, start=1):\n", " print(\"=\" * 80)\n", " print(f\"Task {i}: {prompt}\\n\")\n", " await client.query(prompt)\n", " async for msg in client.receive_response():\n", " print_agent_message(msg)\n", " print(\"=\" * 80)\n" ] }, { "cell_type": "markdown", "id": "34293c1e", "metadata": {}, "source": [ "### Insert/update with `upsert-records`" ] }, { "cell_type": "markdown", "id": "bd3e2810", "metadata": {}, "source": [ "Similar to `search-records`, we use a few prompts to guide the agent to perform upsert operations on the knowledge base." ] }, { "cell_type": "code", "execution_count": 22, "id": "680a6468", "metadata": {}, "outputs": [], "source": [ "UPSERT_PROMPTS = [\n", " \"Create a new knowledge record with `record_id` `incident_ap_southeast_cache_failover_2026_04`, `source_type` `incident_summary`, `team` `platform`, `region` `ap-southeast`, `product` `developer-platform`, `severity` `sev2`, `release_version` `na`, `last_reviewed_at` `2026-04-09`, title `Incident summary: cache warmup lag after ap-southeast failover`, and content `After failover to ap-southeast, cache hit rate dropped because warmup workers started late. The mitigation was to replay invalidation events, pre-warm the highest-traffic keys, and hold rollout traffic until hit rate stabilized.`\",\n", " \"Create a new knowledge record with `record_id` `runbook_cache_warmup_recovery_ap_southeast`, `source_type` `runbook`, `team` `platform`, `region` `ap-southeast`, `product` `developer-platform`, `severity` `sev2`, `release_version` `na`, `last_reviewed_at` `2026-04-09`, title `Runbook: recover cache warmup after regional failover`, and content `If cache warmup lags after failover, restart the warmup workers, verify invalidation backlog drains, pre-warm hot keys, and confirm hit rate recovery before restoring full traffic.`\",\n", " \"Replace the existing knowledge record `kb_legacy_invalidation_troubleshooting` with this full record: `record_id` `kb_legacy_invalidation_troubleshooting`, `source_type` `kb_article`, `team` `support`, `region` `global`, `product` `developer-platform`, `severity` `sev2`, `release_version` `na`, `last_reviewed_at` `2026-04-10`, title `KB: troubleshoot stale reads from legacy cache invalidation flow`, content `Support engineers should first confirm whether a service is still publishing to the legacy invalidation path. If so, verify the new event-driven invalidation endpoint is enabled, compare consumer lag, and check for mixed old and new events causing stale objects.`\",\n", " \"Replace the existing knowledge record `release_2026_04_new_invalidation_endpoint` with this full record: `record_id` `release_2026_04_new_invalidation_endpoint`, `source_type` `release_note`, `team` `platform`, `region` `global`, `product` `developer-platform`, `severity` `info`, `release_version` `2026.04`, `last_reviewed_at` `2026-04-10`, title `Release notes: new invalidation endpoint is now the default`, content `Release 2026.04 makes the new invalidation endpoint the default for developer-platform services. The legacy cache invalidation flow enters final deprecation on 2026-06-30, and teams should complete migration before that date.`\", \n", "]" ] }, { "cell_type": "code", "execution_count": 23, "id": "0e5a9e45", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "================================================================================\n", "Task 1: Create a new knowledge record with `record_id` `incident_ap_southeast_cache_failover_2026_04`, `source_type` `incident_summary`, `team` `platform`, `region` `ap-southeast`, `product` `developer-platform`, `severity` `sev2`, `release_version` `na`, `last_reviewed_at` `2026-04-09`, title `Incident summary: cache warmup lag after ap-southeast failover`, and content `After failover to ap-southeast, cache hit rate dropped because warmup workers started late. The mitigation was to replay invalidation events, pre-warm the highest-traffic keys, and hold rollout traffic until hit rate stabilized.`\n", "\n", "Tool use -> mcp__redisvl__upsert-records\n", "{'id_field': 'record_id',\n", " 'records': [{'content': 'After failover to ap-southeast, cache hit rate '\n", " 'dropped because warmup workers started late. The '\n", " 'mitigation was to replay invalidation events, '\n", " 'pre-warm the highest-traffic keys, and hold rollout '\n", " 'traffic until hit rate stabilized.',\n", " 'last_reviewed_at': '2026-04-09',\n", " 'product': 'developer-platform',\n", " 'record_id': 'incident_ap_southeast_cache_failover_2026_04',\n", " 'region': 'ap-southeast',\n", " 'release_version': 'na',\n", " 'severity': 'sev2',\n", " 'source_type': 'incident_summary',\n", " 'team': 'platform',\n", " 'title': 'Incident summary: cache warmup lag after ap-southeast '\n", " 'failover'}]}\n", "\n", "Claude:\n", "Perfect! ✅ I've successfully created the knowledge record. Here's a summary of what was added:\n", "\n", "**Record ID:** `incident_ap_southeast_cache_failover_2026_04`\n", "\n", "**Key Details:**\n", "- **Title:** Incident summary: cache warmup lag after ap-southeast failover\n", "- **Source Type:** Incident Summary\n", "- **Team:** Platform\n", "- **Product:** Developer Platform\n", "- **Region:** AP-Southeast\n", "- **Severity:** SEV2\n", "- **Last Reviewed:** 2026-04-09\n", "\n", "**Content:** Documents the cache hit rate drop after failover to ap-southeast due to late warmup worker startup, and the mitigation steps taken (replay invalidation events, pre-warm high-traffic keys, and hold rollout traffic).\n", "\n", "The record is now stored in the knowledge base and can be retrieved for future reference.\n", "\n", "Result summary:\n", "Perfect! ✅ I've successfully created the knowledge record. Here's a summary of what was added:\n", "\n", "**Record ID:** `incident_ap_southeast_cache_failover_2026_04`\n", "\n", "**Key Details:**\n", "- **Title:** Incident summary: cache warmup lag after ap-southeast failover\n", "- **Source Type:** Incident Summary\n", "- **Team:** Platform\n", "- **Product:** Developer Platform\n", "- **Region:** AP-Southeast\n", "- **Severity:** SEV2\n", "- **Last Reviewed:** 2026-04-09\n", "\n", "**Content:** Documents the cache hit rate drop after failover to ap-southeast due to late warmup worker startup, and the mitigation steps taken (replay invalidation events, pre-warm high-traffic keys, and hold rollout traffic).\n", "\n", "The record is now stored in the knowledge base and can be retrieved for future reference.\n", "================================================================================\n", "================================================================================\n", "Task 2: Create a new knowledge record with `record_id` `runbook_cache_warmup_recovery_ap_southeast`, `source_type` `runbook`, `team` `platform`, `region` `ap-southeast`, `product` `developer-platform`, `severity` `sev2`, `release_version` `na`, `last_reviewed_at` `2026-04-09`, title `Runbook: recover cache warmup after regional failover`, and content `If cache warmup lags after failover, restart the warmup workers, verify invalidation backlog drains, pre-warm hot keys, and confirm hit rate recovery before restoring full traffic.`\n", "\n", "Tool use -> mcp__redisvl__upsert-records\n", "{'id_field': 'record_id',\n", " 'records': [{'content': 'If cache warmup lags after failover, restart the '\n", " 'warmup workers, verify invalidation backlog drains, '\n", " 'pre-warm hot keys, and confirm hit rate recovery '\n", " 'before restoring full traffic.',\n", " 'last_reviewed_at': '2026-04-09',\n", " 'product': 'developer-platform',\n", " 'record_id': 'runbook_cache_warmup_recovery_ap_southeast',\n", " 'region': 'ap-southeast',\n", " 'release_version': 'na',\n", " 'severity': 'sev2',\n", " 'source_type': 'runbook',\n", " 'team': 'platform',\n", " 'title': 'Runbook: recover cache warmup after regional '\n", " 'failover'}]}\n", "\n", "Claude:\n", "Perfect! ✅ I've successfully created the runbook knowledge record. Here's a summary of what was added:\n", "\n", "**Record ID:** `runbook_cache_warmup_recovery_ap_southeast`\n", "\n", "**Key Details:**\n", "- **Title:** Runbook: recover cache warmup after regional failover\n", "- **Source Type:** Runbook\n", "- **Team:** Platform\n", "- **Product:** Developer Platform\n", "- **Region:** AP-Southeast\n", "- **Severity:** SEV2\n", "- **Last Reviewed:** 2026-04-09\n", "\n", "**Content:** Step-by-step recovery procedure for cache warmup lag following failover, including restarting warmup workers, verifying invalidation backlog drain, pre-warming hot keys, and confirming hit rate recovery before full traffic restoration.\n", "\n", "The runbook is now available in the knowledge base for on-call engineers to reference during cache warmup recovery situations.\n", "\n", "Result summary:\n", "Perfect! ✅ I've successfully created the runbook knowledge record. Here's a summary of what was added:\n", "\n", "**Record ID:** `runbook_cache_warmup_recovery_ap_southeast`\n", "\n", "**Key Details:**\n", "- **Title:** Runbook: recover cache warmup after regional failover\n", "- **Source Type:** Runbook\n", "- **Team:** Platform\n", "- **Product:** Developer Platform\n", "- **Region:** AP-Southeast\n", "- **Severity:** SEV2\n", "- **Last Reviewed:** 2026-04-09\n", "\n", "**Content:** Step-by-step recovery procedure for cache warmup lag following failover, including restarting warmup workers, verifying invalidation backlog drain, pre-warming hot keys, and confirming hit rate recovery before full traffic restoration.\n", "\n", "The runbook is now available in the knowledge base for on-call engineers to reference during cache warmup recovery situations.\n", "================================================================================\n", "================================================================================\n", "Task 3: Replace the existing knowledge record `kb_legacy_invalidation_troubleshooting` with this full record: `record_id` `kb_legacy_invalidation_troubleshooting`, `source_type` `kb_article`, `team` `support`, `region` `global`, `product` `developer-platform`, `severity` `sev2`, `release_version` `na`, `last_reviewed_at` `2026-04-10`, title `KB: troubleshoot stale reads from legacy cache invalidation flow`, content `Support engineers should first confirm whether a service is still publishing to the legacy invalidation path. If so, verify the new event-driven invalidation endpoint is enabled, compare consumer lag, and check for mixed old and new events causing stale objects.`\n", "\n", "Tool use -> mcp__redisvl__upsert-records\n", "{'id_field': 'record_id',\n", " 'records': [{'content': 'Support engineers should first confirm whether a '\n", " 'service is still publishing to the legacy '\n", " 'invalidation path. If so, verify the new '\n", " 'event-driven invalidation endpoint is enabled, '\n", " 'compare consumer lag, and check for mixed old and '\n", " 'new events causing stale objects.',\n", " 'last_reviewed_at': '2026-04-10',\n", " 'product': 'developer-platform',\n", " 'record_id': 'kb_legacy_invalidation_troubleshooting',\n", " 'region': 'global',\n", " 'release_version': 'na',\n", " 'severity': 'sev2',\n", " 'source_type': 'kb_article',\n", " 'team': 'support',\n", " 'title': 'KB: troubleshoot stale reads from legacy cache '\n", " 'invalidation flow'}]}\n", "\n", "Claude:\n", "Perfect! ✅ I've successfully replaced the existing knowledge record. Here's a summary of the updated record:\n", "\n", "**Record ID:** `kb_legacy_invalidation_troubleshooting`\n", "\n", "**Key Details:**\n", "- **Title:** KB: troubleshoot stale reads from legacy cache invalidation flow\n", "- **Source Type:** KB Article\n", "- **Team:** Support\n", "- **Region:** Global\n", "- **Product:** Developer Platform\n", "- **Severity:** SEV2\n", "- **Last Reviewed:** 2026-04-10\n", "\n", "**Content:** Troubleshooting guide for support engineers to diagnose stale read issues related to legacy cache invalidation, including verifying publisher status, checking new event-driven invalidation endpoint activation, comparing consumer lag, and identifying mixed old/new event conflicts.\n", "\n", "The record has been updated in the knowledge base and is ready for use.\n", "\n", "Result summary:\n", "Perfect! ✅ I've successfully replaced the existing knowledge record. Here's a summary of the updated record:\n", "\n", "**Record ID:** `kb_legacy_invalidation_troubleshooting`\n", "\n", "**Key Details:**\n", "- **Title:** KB: troubleshoot stale reads from legacy cache invalidation flow\n", "- **Source Type:** KB Article\n", "- **Team:** Support\n", "- **Region:** Global\n", "- **Product:** Developer Platform\n", "- **Severity:** SEV2\n", "- **Last Reviewed:** 2026-04-10\n", "\n", "**Content:** Troubleshooting guide for support engineers to diagnose stale read issues related to legacy cache invalidation, including verifying publisher status, checking new event-driven invalidation endpoint activation, comparing consumer lag, and identifying mixed old/new event conflicts.\n", "\n", "The record has been updated in the knowledge base and is ready for use.\n", "================================================================================\n", "================================================================================\n", "Task 4: Replace the existing knowledge record `release_2026_04_new_invalidation_endpoint` with this full record: `record_id` `release_2026_04_new_invalidation_endpoint`, `source_type` `release_note`, `team` `platform`, `region` `global`, `product` `developer-platform`, `severity` `info`, `release_version` `2026.04`, `last_reviewed_at` `2026-04-10`, title `Release notes: new invalidation endpoint is now the default`, content `Release 2026.04 makes the new invalidation endpoint the default for developer-platform services. The legacy cache invalidation flow enters final deprecation on 2026-06-30, and teams should complete migration before that date.`\n", "\n", "Tool use -> mcp__redisvl__upsert-records\n", "{'id_field': 'record_id',\n", " 'records': [{'content': 'Release 2026.04 makes the new invalidation endpoint '\n", " 'the default for developer-platform services. The '\n", " 'legacy cache invalidation flow enters final '\n", " 'deprecation on 2026-06-30, and teams should complete '\n", " 'migration before that date.',\n", " 'last_reviewed_at': '2026-04-10',\n", " 'product': 'developer-platform',\n", " 'record_id': 'release_2026_04_new_invalidation_endpoint',\n", " 'region': 'global',\n", " 'release_version': '2026.04',\n", " 'severity': 'info',\n", " 'source_type': 'release_note',\n", " 'team': 'platform',\n", " 'title': 'Release notes: new invalidation endpoint is now the '\n", " 'default'}]}\n", "\n", "Claude:\n", "Perfect! ✅ I've successfully replaced the existing knowledge record. Here's a summary of the updated release note:\n", "\n", "**Record ID:** `release_2026_04_new_invalidation_endpoint`\n", "\n", "**Key Details:**\n", "- **Title:** Release notes: new invalidation endpoint is now the default\n", "- **Source Type:** Release Note\n", "- **Team:** Platform\n", "- **Region:** Global\n", "- **Product:** Developer Platform\n", "- **Severity:** Info\n", "- **Release Version:** 2026.04\n", "- **Last Reviewed:** 2026-04-10\n", "\n", "**Content:** Documents the release of version 2026.04 which makes the new invalidation endpoint the default for developer-platform services, with the legacy cache invalidation flow entering final deprecation on 2026-06-30 and a migration deadline for all teams.\n", "\n", "The release note has been updated in the knowledge base.\n", "\n", "Result summary:\n", "Perfect! ✅ I've successfully replaced the existing knowledge record. Here's a summary of the updated release note:\n", "\n", "**Record ID:** `release_2026_04_new_invalidation_endpoint`\n", "\n", "**Key Details:**\n", "- **Title:** Release notes: new invalidation endpoint is now the default\n", "- **Source Type:** Release Note\n", "- **Team:** Platform\n", "- **Region:** Global\n", "- **Product:** Developer Platform\n", "- **Severity:** Info\n", "- **Release Version:** 2026.04\n", "- **Last Reviewed:** 2026-04-10\n", "\n", "**Content:** Documents the release of version 2026.04 which makes the new invalidation endpoint the default for developer-platform services, with the legacy cache invalidation flow entering final deprecation on 2026-06-30 and a migration deadline for all teams.\n", "\n", "The release note has been updated in the knowledge base.\n", "================================================================================\n" ] } ], "source": [ "# NBVAL_SKIP\n", "async with agent as client:\n", " for i, prompt in enumerate(UPSERT_PROMPTS, start=1):\n", " print(\"=\" * 80)\n", " print(f\"Task {i}: {prompt}\\n\")\n", " await client.query(prompt)\n", " async for msg in client.receive_response():\n", " print_agent_message(msg)\n", " print(\"=\" * 80)" ] }, { "cell_type": "markdown", "id": "cdcbc1f4", "metadata": {}, "source": [ "## Conclusion" ] }, { "cell_type": "markdown", "id": "7805c56e", "metadata": {}, "source": [ "The RedisVL MCP server allows MCP-compatible clients to search or upsert data in an existing Redis index, without the need for explicit coding of these operations. In this notebook, we have built a support agent using Claude Agent SDK and successfully integrated it with RedisVL MCP, using the `search-records` and `upsert-records` tools to perform retrieval and insertion/update." ] }, { "cell_type": "markdown", "id": "48c041b4", "metadata": {}, "source": [ "## Cleanup" ] }, { "cell_type": "code", "execution_count": 24, "id": "cdb1276d", "metadata": {}, "outputs": [], "source": [ "# NBVAL_SKIP\n", "# kill the mcp server process\n", "mcp_server.terminate()" ] }, { "cell_type": "code", "execution_count": 25, "id": "dc03be46", "metadata": {}, "outputs": [], "source": [ "# delete any temp files (the MCP config)\n", "os.remove(MCP_CONFIG_FILEPATH)\n", "os.rmdir(\"config\")" ] } ], "metadata": { "kernelspec": { "display_name": ".venv (3.12.5)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.5" } }, "nbformat": 4, "nbformat_minor": 5 }