| name | add-claude-context |
| description | [DEPRECATED] Replaced by scripts/code_search.py (native sqlite-vec + Ollama). Do not use. |
| disable-model-invocation | true |
Add Claude Context (Semantic Code Search) -- DEPRECATED
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'
set -euo pipefail
CONTAINER=deus-milvus
INTERNAL_NET=deus-milvus-net
BRIDGE_NET=deus-milvus-bridge
docker network create --internal "$INTERNAL_NET" 2>/dev/null || true
docker network create "$BRIDGE_NET" 2>/dev/null || true
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
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
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
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; }
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:
- Index a codebase: the agent will use
index_codebase with a directory path
- Search: ask something like "find the function that handles message routing"
- 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:
| Layer | Mechanism |
|---|
| Embeddings | EMBEDDING_PROVIDER=Ollama — computed locally, never sent to cloud |
| Vector DB address | MILVUS_ADDRESS=127.0.0.1:19530 — always set explicitly, prevents Zilliz Cloud auto-provisioning |
| Cloud token | No MILVUS_TOKEN — hard gate enforced by skill (abort, not warn) |
| Storage | COMMON_STORAGETYPE=local — vectors on local filesystem, no S3/MinIO |
| Port binding | 127.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
- Check if port 19530 is already in use:
lsof -i :19530
- Check Docker is running:
docker info
- Check container logs:
docker logs deus-milvus --tail 50
- 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
- Check Milvus is running:
docker ps --filter "name=deus-milvus"
- Check health:
docker exec deus-milvus curl -sf http://localhost:9091/healthz
- 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()"
- 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
- 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:
-
Remove from MCP config:
-
Stop and remove Milvus:
docker stop deus-milvus && docker rm deus-milvus
-
Remove the Docker volume (warning: destroys all indexed codebase data — re-indexing required after reinstall):
docker volume rm deus-milvus-data
-
Remove Docker networks:
docker network rm deus-milvus-net deus-milvus-bridge 2>/dev/null
-
Remove config files:
rm -rf ~/.config/deus/milvus/
-
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.