Add Claude Context (Semantic Code Search) -- DEPRECATED

SkillDatabases & data

[DEPRECATED] Replaced by scripts/code_search.py (native sqlite-vec + Ollama). Do not use.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Add Claude Context (Semantic Code Search) -- DEPRECATED skill

What this skill tells your AI

The instructions your AI receives, as published by sliamh11/deus in .claude/skills/add-claude-context/SKILL.md and read by ahel’s review.

This skill is deprecated. claude-context (Milvus-based) was evaluated and found to have never worked due to 5 cascading bugs (wrong config path, corrupt npx cache, wrong protocol, broken network isolation, short timeout). Milvus is also massive overkill for code search at any realistic codebase scale.

Replacement: scripts/code_search.py -- native semantic code search using sqlite-vec + Ollama embeddings + RRF fusion, following the memory_tree.py pattern. No Docker, no Milvus, no external dependencies beyond Ollama.

The content below is preserved for historical reference only.


This skill adds semantic code search to Claude Code via the claude-context MCP server by Zilliz. Once installed, Claude can index any codebase and search it by meaning — not just keyword matching.

Tools added:

  • index_codebase — index a directory into the vector database
  • search_code — semantic search across indexed code
  • get_indexing_status — check indexing progress
  • clear_index — remove an index

Privacy: All data stays local. Embeddings via Ollama, vectors in a network-isolated Docker Milvus container. No cloud APIs, no telemetry. See Security for the full defense-in-depth breakdown.

Phase 1: Pre-flight

Check if already configured

grep -q "claude-context" ~/.claude/mcp.json 2>/dev/null && echo "INSTALLED" || echo "NOT_INSTALLED"

If already installed, skip to Phase 5 (Verify).

Check prerequisites

Docker:

docker info > /dev/null 2>&1 && echo "Docker OK" || echo "Docker not running"

If Docker is not running, tell the user to start Docker Desktop (macOS) or the Docker daemon (Linux).

Ollama:

ollama list > /dev/null 2>&1 && echo "Ollama OK" || echo "Ollama not running"

If Ollama is not installed, direct the user to https://ollama.com/download.

Node.js version:

node --version

Must be >= 20 and < 24. Node 24 is incompatible with claude-context. If the version is wrong, tell the user which versions are supported.

Phase 2: Embedding Model

Detect available embedding models

ollama list 2>/dev/null | grep -i embed

Choose a model

If embedding models are found, ask the user which one to use via AskUserQuestion.

If no embedding models are found, suggest pulling one:

No embedding models found in Ollama. You need one for code search. Options:

  • nomic-embed-text (768-dim, 274MB) — lightweight, recommended for most use cases
  • mxbai-embed-large (1024-dim, 669MB) — higher quality embeddings
  • snowflake-arctic-embed2 (1024-dim, 1.2GB) — multilingual support

Pull the chosen model:

ollama pull <model-name>

Detect embedding dimension

Get the dimension from a test embedding call — never hardcode this value:

curl -s http://127.0.0.1:11434/api/embed -d '{"model":"<model-name>","input":"test"}' | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d['embeddings'][0]))"

Store the model name and dimension for Phase 4.

Phase 3: Start Milvus

Check if Milvus is already running

docker ps --filter "name=deus-milvus" --format "{{.Status}}" 2>/dev/null

If deus-milvus is already running and healthy, skip to Phase 4:

docker exec deus-milvus curl -sf http://localhost:9091/healthz > /dev/null 2>&1 && echo "HEALTHY" || echo "NOT_HEALTHY"

Step 3a: Create config files

Create the Milvus config directory:

mkdir -p ~/.config/deus/milvus

Create ~/.config/deus/milvus/embedEtcd.yaml:

cat > ~/.config/deus/milvus/embedEtcd.yaml << 'EOF'
listen-client-urls: http://0.0.0.0:2379
advertise-client-urls: http://0.0.0.0:2379
quota-backend-bytes: 4294967296
auto-compaction-mode: revision
auto-compaction-retention: "1000"
EOF

Create ~/.config/deus/milvus/user.yaml (required mount, no overrides needed):

touch ~/.config/deus/milvus/user.yaml

Create ~/.config/deus/milvus/start-milvus.sh:

cat > ~/.config/deus/milvus/start-milvus.sh << 'SCRIPT'
#!/bin/bash
set -euo pipefail

CONTAINER=deus-milvus
INTERNAL_NET=deus-milvus-net
BRIDGE_NET=deus-milvus-bridge

# Create networks if missing
docker network create --internal "$INTERNAL_NET" 2>/dev/null || true
docker network create "$BRIDGE_NET" 2>/dev/null || true

# If container exists but stopped, remove it (clean restart with correct config)
if docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "^${CONTAINER}$"; then
  if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${CONTAINER}$"; then
    echo "deus-milvus already running"
    exit 0
  fi
  docker rm "$CONTAINER"
fi

# Start on bridge network (port publishing requires a non-internal network at creation time)
docker run -d --name "$CONTAINER" \
  --network "$BRIDGE_NET" \
  --restart no \
  --security-opt seccomp:unconfined \
  -e ETCD_USE_EMBED=true \
  -e ETCD_DATA_DIR=/var/lib/milvus/etcd \
  -e ETCD_CONFIG_PATH=/milvus/configs/embedEtcd.yaml \
  -e COMMON_STORAGETYPE=local \
  -e DEPLOY_MODE=STANDALONE \
  --memory=3g --memory-swap=3g --cpus=2 \
  -p 127.0.0.1:19530:19530 \
  -v deus-milvus-data:/var/lib/milvus \
  -v "$HOME/.config/deus/milvus/embedEtcd.yaml:/milvus/configs/embedEtcd.yaml:ro" \
  -v "$HOME/.config/deus/milvus/user.yaml:/milvus/configs/user.yaml:ro" \
  milvusdb/milvus:latest \
  milvus run standalone

# Wait for Milvus to be healthy before switching networks
HEALTHY=false
for i in $(seq 1 60); do
  docker exec "$CONTAINER" curl -sf http://localhost:9091/healthz >/dev/null 2>&1 && HEALTHY=true && break
  sleep 2
done

if [ "$HEALTHY" != "true" ]; then
  echo "ERROR: Milvus did not become healthy after 120s"
  echo "Run: docker logs $CONTAINER --tail 50"
  exit 1
fi

# Switch to internal network (blocks all outbound internet)
docker network connect "$INTERNAL_NET" "$CONTAINER" || { echo "ERROR: failed to connect internal network"; exit 1; }
docker network disconnect "$BRIDGE_NET" "$CONTAINER" || { echo "ERROR: failed to disconnect bridge network"; exit 1; }

# Verify isolation — hard abort if outbound is reachable
if docker exec "$CONTAINER" curl -sf --connect-timeout 3 http://1.1.1.1 >/dev/null 2>&1; then
  echo "ABORT: outbound internet still reachable after network switch"
  exit 1
fi
echo "Network isolation confirmed"
SCRIPT
chmod +x ~/.config/deus/milvus/start-milvus.sh

Why the network dance? Docker Desktop for Mac's --internal network blocks port publishing — the Docker proxy can't route traffic to containers on internal-only networks. The startup script creates the container on a regular bridge (port publishing works), waits for health, then switches to the internal network (blocks outbound). This survives restarts because the LaunchAgent/systemd unit replays the full sequence.

Why seccomp:unconfined? Milvus v2.6 uses clone3() and io_uring syscalls that the Docker default seccomp profile blocks. Without this flag, Milvus crashes on startup.

Why --restart no? The service manager (LaunchAgent/systemd) owns the lifecycle, not Docker. This ensures the network dance runs on every start.

Resource limits: --memory=3g --cpus=2 prevents Milvus from consuming all host RAM during indexing. Users with more RAM can increase these values in the script.

Step 3b: Run the startup script

bash ~/.config/deus/milvus/start-milvus.sh

Step 3c: Install service manager

macOS — LaunchAgent:

cat > ~/Library/LaunchAgents/com.deus.milvus.plist << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.deus.milvus</string>
  <key>ProgramArguments</key>
  <array>
    <string>bash</string>
    <string>$HOME/.config/deus/milvus/start-milvus.sh</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
  <key>KeepAlive</key>
  <false/>
  <key>StandardOutPath</key>
  <string>$HOME/Library/Logs/deus-milvus.log</string>
  <key>StandardErrorPath</key>
  <string>$HOME/Library/Logs/deus-milvus.error.log</string>
</dict>
</plist>
EOF
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.deus.milvus.plist

Note: The heredoc uses << EOF (not << 'EOF') so $HOME expands to the absolute path. launchd does not expand ~ in plist values.

Linux — systemd user unit:

mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/deus-milvus.service << 'EOF'
[Unit]
Description=Deus Milvus (semantic code search)
After=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=%h/.config/deus/milvus/start-milvus.sh
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=default.target
EOF
systemctl --user enable --now deus-milvus.service

Phase 4: Configure MCP

Safety gate

Before writing MCP config, verify these invariants. Abort if any check fails — do not proceed with a warning.

Check 1 — No MILVUS_TOKEN:

[ -n "$MILVUS_TOKEN" ] && echo "ABORT: MILVUS_TOKEN is set. This causes data to be sent to Zilliz Cloud. Run: unset MILVUS_TOKEN" && exit 1 || echo "OK"

If MILVUS_TOKEN is set, tell the user to unset it and stop. Do NOT write the MCP config.

Check 2 — MILVUS_ADDRESS is localhost:

The config being written must set MILVUS_ADDRESS to exactly 127.0.0.1:19530. Never omit this field — without it, the SDK may auto-provision a Zilliz Cloud cluster if MILVUS_TOKEN is set in the future.

Read and merge MCP config

Read ~/.claude/mcp.json and merge the new claude-context entry. Never overwrite existing servers.

The final entry should be:

{
  "claude-context": {
    "command": "npx",
    "args": ["-y", "@zilliz/claude-context-mcp@0.1.13"],
    "env": {
      "EMBEDDING_PROVIDER": "Ollama",
      "OLLAMA_HOST": "http://127.0.0.1:11434",
      "OLLAMA_MODEL": "<detected-model>",
      "EMBEDDING_MODEL": "<detected-model>",
      "EMBEDDING_DIMENSION": "<detected-dimension>",
      "EMBEDDING_BATCH_SIZE": "5",
      "MILVUS_ADDRESS": "127.0.0.1:19530",
      "HYBRID_MODE": "false"
    }
  }
}

Replace <detected-model> and <detected-dimension> with the values from Phase 2.

Configuration notes:

  • EMBEDDING_BATCH_SIZE=5 is a conservative default to avoid OOM with local models. Users with more RAM can increase this.
  • HYBRID_MODE=false disables sparse vector (BM25) search which has reported instability with Milvus standalone. Dense search with the MCP's own reranking is sufficient. Set to true to experiment.
  • MILVUS_ADDRESS must always be set explicitly — omitting it while a MILVUS_TOKEN is present would trigger calls to Zilliz Cloud.
  • This is a user-scope config (~/.claude/mcp.json), making code search available across all projects.

Phase 5: Verify

Smoke test Milvus

Port 9091 is the Milvus management HTTP API (healthz). Port 19530 is the gRPC data port (used by the MCP server). Both must be working.

docker exec deus-milvus curl -sf http://localhost:9091/healthz && echo "Milvus healthy"

Confirm port accessibility

python3 -c "import socket; s=socket.socket(); s.settimeout(3); r=s.connect_ex(('127.0.0.1',19530)); print('Port 19530: OK' if r==0 else 'Port 19530: FAIL'); s.close()"

Confirm network isolation

docker exec deus-milvus curl -sf --connect-timeout 3 http://1.1.1.1 2>&1 && echo "WARNING: outbound internet reachable" || echo "Isolation confirmed — no outbound internet"

This command must fail. If it succeeds, the network switch in start-milvus.sh did not complete — re-run the script.

Test the MCP server

Tell the user:

Claude Context is now installed. To use it, start a new Claude Code session and try:

  1. Index a codebase: the agent will use index_codebase with a directory path
  2. Search: ask something like "find the function that handles message routing"
  3. The agent uses search_code to find semantically relevant code

First-time indexing may take a few minutes depending on codebase size.

To verify no data leaves your machine during indexing, run in a separate terminal:

lsof -i -P | grep node

You should only see connections to 127.0.0.1:19530 (Milvus) and 127.0.0.1:11434 (Ollama).

Security

Defense in depth — what prevents data leakage:

LayerMechanism
EmbeddingsEMBEDDING_PROVIDER=Ollama — computed locally, never sent to cloud
Vector DB addressMILVUS_ADDRESS=127.0.0.1:19530 — always set explicitly, prevents Zilliz Cloud auto-provisioning
Cloud tokenNo MILVUS_TOKEN — hard gate enforced by skill (abort, not warn)
StorageCOMMON_STORAGETYPE=local — vectors on local filesystem, no S3/MinIO
Port binding127.0.0.1 only — not accessible from LAN
Network isolation--internal Docker network — blocks all outbound internet from container
Resource limits--memory=3g --cpus=2 — prevents resource exhaustion on shared machines

MCP trust boundary: The claude-context MCP server runs on the host as the user — it has unrestricted filesystem read access. It is not sandboxed. Only index directories you trust; avoid indexing paths containing secrets (.env, .aws/, .ssh/, ~/.config/). Embeddings of cleartext secrets would persist in the unencrypted Milvus volume.

Supply chain: The top-level npm package is pinned to @0.1.13, but transitive dependencies are resolved at npx time without a lockfile. The --internal Docker network mitigates container-side supply chain risk; the MCP server's npm tree is the remaining surface.

Known limitation: Between container start and the network switch (a few seconds during startup), the container is on the bridge network and theoretically has outbound access. The bridge-first approach is required because Docker Desktop for Mac cannot publish ports on --internal or --network none networks. In practice, Milvus doesn't make outbound calls during this window — it's initializing etcd and internal services. The startup script applies isolation as soon as healthz returns OK.

Troubleshooting

Milvus won't start

  1. Check if port 19530 is already in use: lsof -i :19530
  2. Check Docker is running: docker info
  3. Check container logs: docker logs deus-milvus --tail 50
  4. If the container exists but won't start, remove and recreate:
    docker rm -f deus-milvus
    bash ~/.config/deus/milvus/start-milvus.sh
    

"embedded etcd can not be used under distributed mode"

DEPLOY_MODE=STANDALONE is missing. The startup script sets this automatically. If running Docker manually, add -e DEPLOY_MODE=STANDALONE to your docker run command.

"failed to create etcd client: connection refused"

ETCD_USE_EMBED=true and the config file mounts are missing. The startup script handles this. If running manually, add:

  • -e ETCD_USE_EMBED=true
  • -e ETCD_DATA_DIR=/var/lib/milvus/etcd
  • -e ETCD_CONFIG_PATH=/milvus/configs/embedEtcd.yaml
  • -v ~/.config/deus/milvus/embedEtcd.yaml:/milvus/configs/embedEtcd.yaml:ro

"Failed to connect to Milvus" / Port 19530 not accessible

  1. Check Milvus is running: docker ps --filter "name=deus-milvus"
  2. Check health: docker exec deus-milvus curl -sf http://localhost:9091/healthz
  3. Check port: python3 -c "import socket; s=socket.socket(); s.settimeout(3); r=s.connect_ex(('127.0.0.1',19530)); print('open' if r==0 else 'closed'); s.close()"
  4. If port is closed but container is running, the container may be on the internal network only (port publishing broken). Re-run: bash ~/.config/deus/milvus/start-milvus.sh
  5. Check MILVUS_ADDRESS in ~/.claude/mcp.json is 127.0.0.1:19530

Port not accessible after Docker restart

The LaunchAgent (macOS) or systemd unit (Linux) should handle restarts automatically. Check logs:

  • macOS: cat ~/Library/Logs/deus-milvus.error.log
  • Linux: journalctl --user -u deus-milvus.service

If the service manager isn't running, start manually: bash ~/.config/deus/milvus/start-milvus.sh

Embedding dimension mismatch

If you see errors about dimension mismatch, the EMBEDDING_DIMENSION in ~/.claude/mcp.json doesn't match your model's output. Re-detect:

curl -s http://127.0.0.1:11434/api/embed -d '{"model":"<your-model>","input":"test"}' | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d['embeddings'][0]))"

Update EMBEDDING_DIMENSION in ~/.claude/mcp.json to match. This is a known upstream issue (claude-context #235) — auto-detection fails in the background indexer.

Node 24 incompatibility

claude-context requires Node 20 or 22. Node 24 is not supported. Check your version with node --version and switch if needed (e.g., via nvm use 22).

Upgrading from old installation

If you installed from an earlier version of this skill (before the Docker fix), your container may have --restart unless-stopped and be missing config mounts. Run:

docker stop deus-milvus && docker rm deus-milvus
bash ~/.config/deus/milvus/start-milvus.sh

The named volume deus-milvus-data is preserved — no indexed data is lost.

Re-indexing after volume removal

If you removed the Milvus volume (docker volume rm deus-milvus-data), all indexed data is gone. Re-index by asking Claude to run index_codebase again on your project directories.

Removal

To completely remove claude-context:

  1. Remove from MCP config:

    # Edit ~/.claude/mcp.json and remove the "claude-context" key from mcpServers
    
  2. Stop and remove Milvus:

    docker stop deus-milvus && docker rm deus-milvus
    
  3. Remove the Docker volume (warning: destroys all indexed codebase data — re-indexing required after reinstall):

    docker volume rm deus-milvus-data
    
  4. Remove Docker networks:

    docker network rm deus-milvus-net deus-milvus-bridge 2>/dev/null
    
  5. Remove config files:

    rm -rf ~/.config/deus/milvus/
    
  6. Remove service manager:

    macOS:

    launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.deus.milvus.plist
    rm ~/Library/LaunchAgents/com.deus.milvus.plist
    rm -f ~/Library/Logs/deus-milvus.log ~/Library/Logs/deus-milvus.error.log
    

    Linux:

    systemctl --user disable --now deus-milvus.service
    rm ~/.config/systemd/user/deus-milvus.service
    

Telemetry

Audited: 2026-05-23. Milvus 2.6 has no configurable telemetry opt-out - network isolation is sufficient.

Milvus 2.6 server (Go)

Searched the milvus-io/milvus repo for: telemetry, analytics, phoneHome, usageReport, pingHome. All GitHub code search queries returned 0 results.

The repo contains internal/rootcoord/telemetry/ but it is internal cluster telemetry only - client SDK metrics (request counts, error rates, SDK versions) stored in etcd for cluster-internal monitoring. No data is transmitted to external services:

  • manager.go: stores client metrics in-memory + etcd; pull-based (not push); no external HTTP calls
  • command_store.go: writes only to etcd (internal); no Zilliz/external endpoints
  • configs/milvus.yaml: no telemetry, analytics, or phone-home keys present
  • Dockerfile (build/docker/milvus/ubuntu22.04/Dockerfile): no telemetry ENV vars; entrypoint is /tini --
  • docker-compose env vars (v2.6 standalone): only MINIO_REGION, ETCD_ENDPOINTS, MINIO_ADDRESS

PyMilvus (Python client)

GitHub code search for telemetry in milvus-io/pymilvus: 0 results. No external reporting found.

milvus-sdk-node (Node.js client)

GitHub code search for telemetry in milvus-io/milvus-sdk-node: 0 results. No external reporting found.

Conclusion

No configurable telemetry opt-out keys exist because Milvus 2.6 does not phone home. The --internal Docker network isolation applied in Phase 3 is the complete mitigation. No user.yaml overrides are needed for telemetry suppression - ~/.config/deus/milvus/user.yaml is mounted empty (as a required mount point) and should remain so.

Signals

GitHub stars
51
Forks
4
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
add-claude-context
Source
github.com/sliamh11/deus