@esankhan3/code-search-mcp 1.1.4 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,495 +1,384 @@
1
- # @esankhan3/code-search-mcp
1
+ # code-search-mcp
2
2
 
3
- **Your whole codebase, in any MCP client.**
3
+ [![npm](https://img.shields.io/npm/v/@esankhan3/code-search-mcp)](https://www.npmjs.com/package/@esankhan3/code-search-mcp)
4
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)
4
5
 
5
- A Model Context Protocol server that wraps `@anvil/knowledge-core`
6
- behind an MCP-compliant tool surface. Point Claude Code, Claude
7
- Desktop, or Cursor at it and your agent gains hybrid search,
8
- caller tracing, and impact analysis across every repo you've
9
- indexed — local or remote.
6
+ **Code search for AI agents, across every repository you own.**
10
7
 
11
- ---
12
-
13
- ## Why "code search" needs more than grep
8
+ `code-search-mcp` is a Model Context Protocol server that indexes many
9
+ repositories at once and gives any MCP client (Claude Code, Claude Desktop,
10
+ Cursor, or your own agent) three things that `grep` and plain vector search
11
+ can't:
14
12
 
15
- MCP gave us a clean way to expose tools to LLM agents. What's
16
- missing is a *good* tool to expose. `grep` over your repo answers
17
- "where is this string." Vector search over your repo answers
18
- "what's semantically near this." Neither answers "who calls this
19
- function and which other repos depend on it."
13
+ - **Hybrid search that finds the right code.** It handles exact identifiers,
14
+ partial names, error strings and plain-English questions in one query path.
15
+ - **A code graph.** Agents can see callers, callees, call chains, dead code,
16
+ and the blast radius of a diff.
17
+ - **A system graph across repositories.** It links the HTTP routes, gRPC
18
+ services, Kafka topics, database tables, env vars and shared types that
19
+ connect your services, so "who consumes this topic?" has an answer.
20
20
 
21
- **code-search-mcp gives agents the answer.** Same hybrid retriever
22
- that powers Anvil's pipelines, exposed through MCP. Agents get
23
- AST-aware chunks, cross-repo graph traversal, and a whole-project
24
- view — without you writing a single tool.
21
+ It runs on a laptop over stdio, or as a shared HTTP server for a whole team.
22
+ It is tested at over a million indexed code chunks.
25
23
 
26
- ```jsonc
27
- // claude_desktop_config.json
28
- {
29
- "mcpServers": {
30
- "code-search": {
31
- "command": "code-search-mcp",
32
- "args": ["--local", "/path/to/your/projects"]
33
- }
34
- }
35
- }
24
+ ```sh
25
+ npm install -g @esankhan3/code-search-mcp
26
+ ollama pull bge-m3 # or use an OpenAI / Voyage / Mistral key
27
+ claude mcp add code-search -- code-search-mcp --local ~/code
36
28
  ```
37
29
 
38
- That's the whole setup. Claude Desktop now knows your codebase.
30
+ That's it. Your agent now knows your codebase. The **[step-by-step guide](./RUNNING.md)**
31
+ covers every way to run it.
39
32
 
40
33
  ---
41
34
 
42
- ## Three bin entries (one install)
35
+ ## Contents
36
+
37
+ - [What your agent can ask](#what-your-agent-can-ask)
38
+ - [Quick start](#quick-start)
39
+ - [Tools](#tools)
40
+ - [How it works](#how-it-works)
41
+ - [Performance at scale](#performance-at-scale)
42
+ - [Ways to run it](#ways-to-run-it)
43
+ - [Configuration](#configuration)
44
+ - [Security and operations](#security-and-operations)
45
+ - [Supported languages](#supported-languages)
46
+ - [Design principles](#design-principles)
47
+ - [Development](#development)
43
48
 
44
- The npm package ships three binaries. Pick the one that matches the
45
- job — none of them need a separate install.
49
+ ---
46
50
 
47
- | Bin | Use case |
51
+ ## What your agent can ask
52
+
53
+ | Question | Tool the agent reaches for |
48
54
  |---|---|
49
- | `code-search-mcp` | The MCP-server entry. Three operating modes below. |
50
- | `code-search` | Standalone CLI: `index` / `query` / `status` / `reset` / `daemon` / `serve` / `mcp`. |
51
- | `code-search-daemon` | Long-running indexer with file-watcher and JSON-RPC socket. |
55
+ | "Where do we verify JWTs?" | `search_code`: semantic + keyword + symbol search, fused |
56
+ | "Find `CompanySearchResponse`." / "Anything named `*SearchResponse`?" | `search_exact`: exact and partial identifier match, no embedder needed |
57
+ | "Who calls `createInvoice`, and from which repos?" | `find_callers` |
58
+ | "If I change `billing/tax.ts`, what breaks?" | `impact_analysis` (follows dependents across repos) |
59
+ | "What does this diff touch?" | `detect_changes`: diff hunks → changed entities → their dependents |
60
+ | "How does a request get from `checkout` to `ledgerWrite`?" | `trace_path`: shortest call chain |
61
+ | "Which services read the `orders.created` topic?" | `get_cross_repo_edges` |
62
+ | "What does this repo do and how is it laid out?" | `get_repo_profile` / `get_architecture` |
63
+
64
+ ---
65
+
66
+ ## Quick start
67
+
68
+ **1. Install.** Requires Node.js 20 or newer and `git`.
52
69
 
53
- ### Standalone CLI
54
- ```sh
55
- code-search index . # index current dir
56
- code-search query "where do we verify JWTs" # hybrid by default
57
- code-search query "createPet" --mode bm25 --top-k 5 # exact-identifier mode
58
- code-search query --project petshop --format json "adopt pet" # flags in any order
59
- code-search status --project petshop # JSON report
60
- code-search reset --project petshop # drop the index
61
- code-search --print-config # resolved config (redacted)
62
- ```
63
- Every `CodeSearchConfig` leaf is settable as `--<dotted-path>` —
64
- e.g. `--embedding.provider codestral`, `--retrieval.max-chunks 12`,
65
- `--no-indexing.auto-index`. An unknown flag (or an invalid `--mode` /
66
- `--format` / `--top-k`) exits with status 2 instead of being ignored.
67
- `query`, `status` and `--print-config` read the cwd's
68
- `.code-search.yaml`; `index <path>` reads the one in `<path>`.
69
- `status` reports where the KB lives (`dataDir`, `kbPath`), whether the
70
- project is indexed, and whether a daemon answers on its socket.
71
- `reset` only removes `<dataDir>/<project>` and rejects project names that
72
- aren't a single path segment (or are the reserved `daemon` / `projects`).
73
-
74
- ### Daemon mode (long-running indexer)
75
70
  ```sh
76
- code-search-daemon --workspace /path/to/repos
77
- code-search daemon --workspace . # same, via the CLI wrapper
71
+ npm install -g @esankhan3/code-search-mcp
78
72
  ```
79
- Builds the index once, then watches the workspace and debounce-
80
- reindexes on file events (one index run per batch; results are fresh as
81
- soon as a run finishes). The project is named after the workspace
82
- directory. Listens on a UDS socket at `<dataDir>/daemon/<project>.sock`
83
- (named pipe on Windows) and writes `<project>.pid` next to it; a second
84
- daemon for the same project refuses to start, and a stopped daemon
85
- removes only its own socket + pid file. `code-search query` prefers the
86
- daemon when it answers a ping (`--no-daemon` to skip it), falling back
87
- to in-process when it doesn't. The MCP server (`--local` / `--serve`)
88
- does **not** use the daemon — it indexes and searches in-process, so
89
- don't run both on the same project.
90
-
91
- ### MCP — three modes (`code-search-mcp` argv)
92
-
93
- **Remote proxy (default)**
73
+
74
+ This installs three commands: `code-search` (CLI), `code-search-mcp`
75
+ (MCP server) and `code-search-daemon` (watch-and-reindex).
76
+
77
+ **2. Pick an embedder.** The default (`auto`) uses the first one it finds:
78
+
79
+ | You have | It uses |
80
+ |---|---|
81
+ | `CODE_SEARCH_EMBEDDING_BASE_URL` set | any OpenAI-compatible endpoint |
82
+ | Ollama running locally | Ollama, model `bge-m3` (fully offline) |
83
+ | `MISTRAL_API_KEY` | Codestral Embed |
84
+ | `OPENAI_API_KEY` | OpenAI |
85
+ | `VOYAGE_API_KEY` | Voyage |
86
+ | `GEMINI_API_KEY` / `GOOGLE_API_KEY` or a logged-in Gemini CLI | Gemini |
87
+
88
+ **3. Index and query from the terminal.**
89
+
94
90
  ```sh
95
- code-search-mcp # default — proxies to remote
96
- code-search-mcp --remote URL
91
+ code-search index ~/code --project acme # one repo, or a folder full of repos
92
+ code-search query --project acme "where do we retry failed webhooks"
93
+ code-search query --project acme "WebhookRetryPolicy" --mode bm25
97
94
  ```
98
- Spawns a stdio MCP server that forwards to a remote HTTP server.
99
- Zero local setup, zero local index — perfect for hosted
100
- deployments where the index lives in the cloud and dev machines
101
- stay light. Auth via `CODE_SEARCH_API_KEY`.
102
95
 
103
- **Local**
96
+ **4. Connect your MCP client.**
97
+
104
98
  ```sh
105
- code-search-mcp --local /path/to/repos
106
- code-search-mcp --local github:my-org/my-pattern
99
+ # Claude Code
100
+ claude mcp add code-search -- code-search-mcp --local ~/code --project acme
107
101
  ```
108
- Discovers every repo under a path (or clones a GitHub org), builds
109
- the knowledge base, and serves over stdio. Works fully offline if
110
- your embedder + reranker are local (Ollama).
111
102
 
112
- **Serve**
113
- ```sh
114
- code-search-mcp --serve /path/to/repos --port 4000 --auth api-key
103
+ ```jsonc
104
+ // Claude Desktop (claude_desktop_config.json) or Cursor (~/.cursor/mcp.json)
105
+ {
106
+ "mcpServers": {
107
+ "code-search": {
108
+ "command": "code-search-mcp",
109
+ "args": ["--local", "/Users/me/code", "--project", "acme"]
110
+ }
111
+ }
112
+ }
115
113
  ```
116
- Boots an HTTP server (Streamable HTTP on `/mcp`; there is no SSE
117
- transport) with `/health`, `/ready`, `/version`, `/status`,
118
- `/metrics` (Prometheus), `/admin/api/status`, and an admin
119
- `POST /index`. The port is bound before the startup index runs, so
120
- probes answer from the start (`/ready` is 503 until the index is
121
- servable). `--serve <path>`, `--serve --local <path>` and
122
- `--local <path> --serve` are equivalent; the project is `default`
123
- unless `--project` says otherwise. Use this to host one index for a
124
- whole team — every dev points their client at the same URL.
125
-
126
- `code-search mcp [path]` is the stdio local mode over the cwd (or
127
- `path`); `code-search mcp --remote URL` is the remote proxy.
114
+
115
+ The guide covers team servers, Docker, GitHub orgs, object storage and
116
+ troubleshooting: **[RUNNING.md](./RUNNING.md)**.
128
117
 
129
118
  ---
130
119
 
131
- ## Tool surface
120
+ ## Tools
132
121
 
133
- Seventeen tools across four categories. Every one of them maps to a
134
- function in `@anvil/knowledge-core` — you're getting the same
135
- retrieval pipeline that powers the Anvil dashboard.
122
+ Seventeen tools in four groups. Every tool validates its arguments against
123
+ its schema and returns `isError: true` with a reason when they are wrong.
136
124
 
137
125
  ### Search
126
+
138
127
  | Tool | What it does |
139
128
  |---|---|
140
- | `search_code` | Hybrid retrieval — vector + BM25 + graph + rerank |
141
- | `search_semantic` | Vector-only (paraphrases, intent) |
142
- | `search_exact` | BM25, led by exact symbol-name / phrase hits (identifiers, error codes); works without the embedder |
143
- | `get_code_snippet` | Fetch the source for one entity by qualified name (cheaper than reading the file) |
129
+ | `search_code` | Hybrid search: vector + keyword + symbol + phrase tiers, fused, then expanded through the code graph and optionally reranked |
130
+ | `search_semantic` | Vector-only: paraphrases and intent |
131
+ | `search_exact` | Literal search led by exact and partial symbol-name and exact-phrase hits; works without an embedder |
132
+ | `get_code_snippet` | The source of one entity by name, cheaper than reading the whole file |
144
133
 
145
134
  ### Graph
135
+
146
136
  | Tool | What it does |
147
137
  |---|---|
148
- | `get_repo_graph` | Single-repo AST graph — entities and edges (`limit` caps each list) |
149
- | `get_cross_repo_edges` | Inter-repo edges (HTTP, gRPC, topics, tables, shared types, …), each with its evidence; same-repo workspace-package deps in their own section |
150
- | `find_callers` | Who calls this symbol — `calls` edges only by default; `relations` adds imports / uses / type-ref / inherits, listed per relation |
151
- | `find_dependencies` | What this symbol calls (same `relations` option) |
152
- | `impact_analysis` | What breaks if a file or entity changes — dependents up to `depth` hops (default 2, max 5), cross-repo included |
153
- | `trace_path` | Multi-hop call chain — shortest path (unweighted BFS) between two symbols, or what is reachable; `calls` edges by default |
154
- | `search_graph` | Structural query — filter nodes by name (regex over symbol names), type, file, repo; ranked by connectivity |
155
- | `find_dead_code` | Entities with no incoming edge and no other mention in their file (heuristic) |
156
- | `detect_changes` | Map a git diff to the entities whose lines changed (whole file for added / deleted files) + their dependents |
157
- | `get_architecture` | Project overview — repo roles, key flows, connections |
158
-
159
- Symbol arguments take a name (exact, case-sensitive) or a full
160
- `repo::file::entity` id; `fuzzy: true` matches a case-insensitive
161
- substring of symbol names. Every tool validates its arguments against
162
- its input schema — a bad argument returns `isError: true` and the reason.
163
-
164
- ### Profiles
165
- | Tool | What it does |
166
- |---|---|
167
- | `list_repos` | All indexed repos with profiles |
168
- | `get_repo_profile` | Single repo's tech stack, structure, key entry points |
138
+ | `find_callers` / `find_dependencies` | Who calls a symbol, and what it calls. `calls` edges by default; `relations` adds imports, uses, type refs and inheritance |
139
+ | `trace_path` | Shortest call chain between two symbols, or everything reachable from one |
140
+ | `impact_analysis` | Dependents of a file or entity, up to 5 hops, across repositories |
141
+ | `detect_changes` | Maps a git diff to the entities whose lines changed, plus their dependents |
142
+ | `search_graph` | Structural query: nodes by name (regex), type, file or repo, ranked by connectivity |
143
+ | `find_dead_code` | Entities nothing references (heuristic) |
144
+ | `get_repo_graph` | One repository's entities and edges |
145
+ | `get_cross_repo_edges` | Links between repositories, each with the evidence that produced it |
146
+ | `get_architecture` | Project overview: repository roles, key flows, connections |
147
+
148
+ Symbols are matched by exact name or full `repo::file::entity` id.
149
+ `fuzzy: true` matches a case-insensitive substring. Multi-hop traversals are
150
+ bounded (50k nodes, 1 s), so a hub symbol in a large org gets a partial
151
+ answer that says so, never a hang.
152
+
153
+ ### Profiles and index
169
154
 
170
- ### Index
171
155
  | Tool | What it does |
172
156
  |---|---|
173
- | `index_status` | Chunk count, embedding provider, last indexed time, vector-index health, chunks per repo |
174
-
175
- Plus four MCP resources via `code-search://`:
176
- `repos`, `system-graph`, `repo/{name}/profile`, `repo/{name}/graph`.
177
-
178
- ---
157
+ | `list_repos` / `get_repo_profile` | Each repository's tech stack, structure and entry points |
158
+ | `index_status` | Chunks per repo, embedder, last index time, vector-index health |
179
159
 
180
- ## What makes the search good
181
-
182
- This isn't a thin wrapper. The retrieval pipeline runs:
183
-
184
- 1. **Vector ⫽ BM25 in parallel** — semantic + lexical recall.
185
- 2. **Reciprocal Rank Fusion** — combine without one dominating.
186
- 3. **AST tripartite expansion** — pull in callers, callees, type
187
- refs via the project graph.
188
- 4. **Cross-encoder rerank** — Qwen3-Reranker by default; Cohere /
189
- Voyage / OpenAI-compatible swappable.
190
-
191
- A query classifier picks adaptive weights — identifier queries
192
- lean BM25, natural-language leans vector, error codes lean both.
193
- You don't tune anything; the retriever does it per query.
160
+ Plus MCP resources: `code-search://repos`, `code-search://system-graph`,
161
+ `code-search://repo/{name}/profile`, `code-search://repo/{name}/graph`.
194
162
 
195
163
  ---
196
164
 
197
- ## Multi-repo by design
198
-
199
- Unlike single-repo code-search MCPs, this one is built for the
200
- real world: a team has *many* repos, and the interesting
201
- questions cross them. Where does this Kafka topic get consumed?
202
- Which services depend on this proto? What service-mesh edges
203
- exist between web and api?
165
+ ## How it works
166
+
167
+ ```mermaid
168
+ flowchart LR
169
+ subgraph Index["Indexing (incremental)"]
170
+ direction TB
171
+ A[Discover repos] --> B[Change check per repo<br/>git SHA + working-tree snapshot]
172
+ B --> C[AST chunking<br/>tree-sitter, 8 languages]
173
+ C --> D[Per-repo code graph<br/>calls, imports, types, inheritance]
174
+ C --> E[Cross-repo signals<br/>routes, topics, tables, env vars, ...]
175
+ E --> F[Hash-join correlation<br/>14 strategies]
176
+ D & F --> G[(System graph<br/>SQLite)]
177
+ C --> H[Embed only changed chunks<br/>reuse vectors by content hash]
178
+ H --> I[(LanceDB<br/>vectors + full-text + trigram indexes)]
179
+ end
180
+ subgraph Query["Query"]
181
+ direction TB
182
+ Q[Query] --> K[Classify:<br/>identifier, path, error, natural language]
183
+ K --> V[Vector] & BM[Keyword BM25<br/>tokens + identifier parts] & S[Symbol names<br/>exact + trigram substring] & P[Exact phrase]
184
+ V & BM & S & P --> R[Weighted rank fusion<br/>+ exact-symbol pin + dedupe]
185
+ R --> X[Graph expansion<br/>callers, callees, type refs]
186
+ X --> RR[Optional cross-encoder rerank]
187
+ end
188
+ I --> V & BM & S & P
189
+ G --> X
190
+ ```
204
191
 
205
- Fourteen cross-repo edge strategies cover shared types, Kafka,
206
- HTTP, gRPC, databases, env vars, npm/workspace deps, k8s,
207
- docker-compose, proto, Redis, S3, and shared constants. Plus an
208
- LLM-inferred semantic edge layer. `get_cross_repo_edges`,
209
- `impact_analysis` and `detect_changes` work *across repos* (shared
210
- topics, tables and the like count as dependencies both ways);
211
- `find_callers` and `trace_path` do too with `relations: ["all"]`.
192
+ **Chunks follow the code's structure.** Files split at definition boundaries
193
+ (functions, classes, methods, top-level constants), not every N lines. Each
194
+ chunk carries a context prefix (file path, module and imports) that is
195
+ embedded with it.
196
+
197
+ **Four retrieval tiers run in parallel.** Vector search catches intent.
198
+ BM25 runs over the raw tokens and over the parts of compound identifiers, so
199
+ "expected backlog count" finds `verifyExpectedBacklogCount`. A symbol tier
200
+ matches entity names exactly and as substrings through a trigram index, so
201
+ `SearchResponse` finds `CompanySearchResponse`. A phrase tier matches error
202
+ strings word for word. A query classifier weights the tiers per query:
203
+ identifiers lean on keywords, questions lean on vectors.
204
+
205
+ **The graph does more than draw diagrams.** It feeds retrieval (seed hits pull in
206
+ their callers and callees) and answers structural questions directly.
207
+ Cross-repository edges come from 14 detectors: npm and workspace
208
+ dependencies, shared types, env vars, Kafka and other topics, HTTP routes
209
+ (including template-literal client URLs), gRPC services, database schemas,
210
+ Redis keys, S3 buckets, OpenAPI contracts, docker-compose and Kubernetes
211
+ service references, and shared constants.
212
+
213
+ **The index maintains itself.**
214
+ - Each cycle touches only what changed:
215
+ - repositories are checked concurrently;
216
+ - only changed files are re-chunked;
217
+ - only chunks whose text changed are re-embedded;
218
+ - the system graph is updated in place.
219
+ - The vector index retrains itself as the corpus grows.
220
+ - The number of IVF partitions probed per query is auto-calibrated to hit
221
+ 0.95 recall against an exact scan.
222
+ - The embedder is pinned per index: querying with a different model or
223
+ dimension size is an error, never silently wrong results.
212
224
 
213
225
  ---
214
226
 
215
- ## Observability
227
+ ## Performance at scale
216
228
 
217
- `code-search-mcp --serve` exposes these endpoints out of the box —
218
- no scrape config or APM agent required.
229
+ Measured on one 12-core laptop, over 1.15 million chunks across 360
230
+ repositories, with 600 mixed queries (exact identifiers, partial names,
231
+ natural language and phrases). The corpus was built by replicating real
232
+ repositories. A stub embedder was used, so the timings exclude
233
+ embedding-provider latency.
219
234
 
220
- | Endpoint | Returns |
235
+ | | |
221
236
  |---|---|
222
- | `GET /health` | Liveness ping with project + uptime |
223
- | `GET /ready` | `200` once the index is servable, `503` before (incl. during the startup index) |
224
- | `GET /version` | Package version + Node + platform |
225
- | `GET /status` | Current indexing phase + last-success history |
226
- | `GET /metrics` | Prometheus text format: queries, latency histogram, errors, seconds since the last successful index, chunks per repo, vector-index health |
227
- | `GET /admin/api/status` | JSON status dashboard for the admin UI (requires auth when auth is on) |
228
-
229
- Set `CODE_SEARCH_STRUCTURED_LOGS=1` for one-JSON-line-per-event
230
- stderr logs from the server, daemon and CLI (lines knowledge-core
231
- prints itself stay plain), and `CODE_SEARCH_TELEMETRY_RECORD_QUERIES=1`
232
- to opt into recording query text in spans (off by default — queries
233
- are PII-shaped).
237
+ | `search_code` latency (p50, 1 client) | **36 ms** |
238
+ | `search_exact` latency (p50, 1 client) | **17 ms** |
239
+ | Throughput, 8 concurrent clients | **26 q/s** (`search_code`), **39 q/s** (`search_exact`) |
240
+ | Vector recall@10 vs exact scan (auto-calibrated) | **0.977** |
241
+ | Re-index, nothing changed | **~15 s** |
242
+ | Re-index after a one-file edit (chunks, graph, embeddings) | **~30–70 s** |
243
+ | Cross-repository edges in the system graph | **454k** |
244
+
245
+ On the same queries, the expected file ranked in the top 10 for 100% of
246
+ exact-identifier queries in both search tools. For natural-language
247
+ questions it was 91% with `search_code` and 80% with `search_exact`.
234
248
 
235
249
  ---
236
250
 
237
- ## Multi-project / multi-tenant
251
+ ## Ways to run it
238
252
 
239
- Drop project entries under `<dataDir>/projects/<name>/project.yaml`:
253
+ | Mode | Command | Use it for |
254
+ |---|---|---|
255
+ | **Local, stdio** | `code-search-mcp --local ~/code` | One developer; the index lives on your machine |
256
+ | **GitHub org** | `code-search-mcp --local "github:my-org/api-*"` | Clone or refresh matching repos, then serve them |
257
+ | **Team server** | `code-search-mcp --serve ~/code --auth api-key` | One index for everyone, over Streamable HTTP at `/mcp` |
258
+ | **Remote proxy** | `code-search-mcp --remote https://search.internal` | A thin local stdio bridge to a team server; nothing indexed locally |
259
+ | **Daemon** | `code-search-daemon --workspace ~/code` | Watch files and re-index on change; the CLI queries it over a local socket |
260
+ | **Docker** | `docker build -f Dockerfile.all-in-one …` | One image with Ollama, the embedder and reranker models baked in |
240
261
 
241
- ```yaml
242
- workspace: /Users/me/repos/petshop
243
- scopes: [team-shop]
244
- quotas:
245
- max_queries_per_minute: 60
246
- max_embedding_cost_usd: 5
247
- ```
248
-
249
- The HTTP server reads the entry of the project it serves once at
250
- boot and enforces it on every `/mcp` request: the caller's scopes
251
- must match `scopes` (`*` is admin; API keys are admin; a JWT's
252
- scopes come from its `scope` claim) or it gets 403, and
253
- `max_queries_per_minute` caps requests per `(identity, project)` in
254
- a sliding 1-minute window (429 + `Retry-After`). Not enforced:
255
- `repos`, `max_embedding_cost_usd` / `max_llm_cost_usd`, and any
256
- `config:` block (per-project config comes from the workspace's
257
- `.code-search.yaml`).
258
-
259
- ---
262
+ The team server binds its port before the first index runs, so health
263
+ probes answer immediately (`/ready` is 503 until the index can serve).
264
+ It serves every `/mcp` request statelessly, which makes restarts free and
265
+ lets it scale across cores with `CODE_SEARCH_WORKERS=N`. In that mode one
266
+ primary does all writes and N workers serve.
260
267
 
261
- ## Server features
262
-
263
- ### Auth, three flavors
264
- `none` (local-only, default 127.0.0.1 binding), `api-key`
265
- (`Authorization: Bearer ...`, timing-safe compare), `jwt` (HS256;
266
- signature, `iss`, a required `exp` and, when present, `nbf`
267
- validated; the server refuses to start in `jwt` mode without
268
- `CODE_SEARCH_AUTH_JWT_SECRET`). Public binding without auth logs a
269
- warning, because that's what should happen.
270
-
271
- ### Rate limiting
272
- `CODE_SEARCH_RATE_LIMIT_PER_MINUTE` (default 100) requests per
273
- identity in a sliding 1-minute window (each API key counts on its
274
- own); 429 responses carry `Retry-After`. In cluster mode the limit
275
- applies to the whole server, not per worker.
276
-
277
- ### Auto-reindex
278
- `CODE_SEARCH_REINDEX_INTERVAL=30m` (or `1h`, `6h`, `0` to disable;
279
- also `indexing.reindex_interval_ms` in YAML or
280
- `--indexing.reindex-interval-ms`) runs an incremental re-index in
281
- the background. Anything else (e.g. `90s`) is reported as invalid and
282
- disables it. Skips itself if a manual index is already running. The
283
- timer is `unref`'d so it doesn't keep the process alive.
284
-
285
- ### Status tracking
286
- Every index call lands in a 50-entry FIFO history with start
287
- timestamp, success / error, last duration. Available at
288
- `GET /status`.
289
-
290
- ### Stateless Streamable HTTP
291
- Every `/mcp` request is served statelessly by a fresh Server
292
- instance: modern (2026-07-28) clients negotiate via
293
- `server/discover`; 2025-era clients are answered by the SDK's
294
- per-request legacy fallback. No session map, no TTL.
295
-
296
- ### Admin index endpoint
297
- `POST /index` with `{ "path": "/repos", "force"?: true }` (re)indexes
298
- the served project from `path`, which then becomes its directory for
299
- scheduled reindexes. `project`, if given, must be the served project
300
- (a server never switches projects — 400 otherwise). Invalid JSON or a
301
- missing `path` → 400, a body over 64 KiB → 413, an index already
302
- running → 409. Auth-required when auth is on. The response reports
303
- chunks, repos and cross-repo edges.
304
-
305
- ### Cluster mode
306
- `CODE_SEARCH_WORKERS=N` (2–16) forks N HTTP workers sharing the port.
307
- The primary is the only writer (startup index, `POST /index` forwarded
308
- over IPC, scheduled reindex) and pushes its indexing state to the
309
- workers, so `/status`, `/ready` and `/admin/api/status` look the same
310
- on every worker; rate limits and quotas are counted once for the whole
311
- server.
268
+ For stateless pods, vectors can live in S3 or MinIO (`s3://` LanceDB URIs) and
269
+ index artifacts in MongoDB. See [RUNNING.md § Object storage](./RUNNING.md#9-stateless-deployments-object-storage).
312
270
 
313
271
  ---
314
272
 
315
- ## How it fits with the rest of Anvil
273
+ ## Configuration
316
274
 
317
- ```
318
- ┌─────────────────────────┐
319
- │ MCP client │
320
- │ (Claude Code, │
321
- │ Claude Desktop, │
322
- │ Cursor, …) │
323
- └────────────┬────────────┘
324
- │ stdio / HTTP
325
- ▼
326
- ┌─────────────────────────┐
327
- │ code-search-mcp │
328
- │ (this package) │
329
- │ tools + resources │
330
- └────────────┬────────────┘
331
- │ wraps
332
- ▼
333
- ┌─────────────────────────┐
334
- │ @anvil/knowledge-core │
335
- │ AST chunks + │
336
- │ project graph + │
337
- │ hybrid retriever │
338
- └────────────┬────────────┘
339
- │ on disk
340
- ▼
341
- <dataDir>/<project>/
342
- (default ~/.anvil/knowledge-base/)
343
- ```
344
-
345
- Three different fronts, one knowledge stack:
275
+ Five layers, lowest to highest precedence:
346
276
 
347
- - **The CLI** indexes via `anvil index` and retrieves during
348
- pipelines.
349
- - **The dashboard** browses the project graph and surfaces
350
- retrieval results in pipeline UI.
351
- - **code-search-mcp** exposes the same retriever to any MCP
352
- client.
277
+ 1. built-in defaults
278
+ 2. `~/.code-search/config.yaml` (per user)
279
+ 3. `<workspace>/.code-search.yaml` (per workspace)
280
+ 4. `CODE_SEARCH_*` environment variables
281
+ 5. CLI flags: any setting as `--<dotted.path> <value>`, e.g. `--retrieval.max-chunks 12`
353
282
 
354
- If you've already indexed a project with `anvil index`, this
355
- server picks it up — same `~/.anvil/knowledge-base/` path, same
356
- LanceDB store, same graph files.
357
-
358
- ---
359
-
360
- ## Configuration — defaults → file → env → CLI flags
361
-
362
- Single resolver in `src/core/config.ts`. Five precedence layers
363
- (lowest → highest):
364
-
365
- 1. compiled-in `DEFAULTS`
366
- 2. `~/.code-search/config.yaml` (user-global)
367
- 3. `<workspaceDir>/.code-search.yaml` (per-workspace overlay)
368
- 4. `CODE_SEARCH_*` env vars
369
- 5. CLI flags via `--<dotted-path>` (e.g. `--embedding.provider codestral`)
370
-
371
- Run `code-search --print-config` or `code-search-mcp --print-config`
372
- any time to see the resolved struct (secrets redacted — including
373
- passwords in URIs and OTEL header values). `--port`, `--host`,
374
- `--transport` and `--auth` are shorthands for `server.port`,
375
- `server.host`, `server.transport` and `auth.mode`.
376
-
377
- | Var (`CODE_SEARCH_*`) | Effect |
378
- |---|---|
379
- | `SERVER` | Remote URL (proxy mode) |
380
- | `API_KEY` | API key for proxy or serve modes |
381
- | `DATA_DIR` | Index storage root (default `~/.anvil/knowledge-base`; also `storage.data_dir` / `--storage.data-dir`). Daemon sockets live under `<dataDir>/daemon/` |
382
- | `PORT` / `HOST` / `TRANSPORT` | HTTP server bind |
383
- | `AUTH_MODE` / `AUTH_API_KEYS` / `AUTH_JWT_SECRET` | Server auth |
384
- | `REINDEX_INTERVAL` | `30m` / `1h` / `6h` / `0`; invalid → disabled |
385
- | `RATE_LIMIT_PER_MINUTE` | Per-identity request limit (sliding window) |
386
- | `WORKERS` | `>1` → cluster mode |
387
- | `EMBEDDING_PROVIDER` | `auto` / `codestral` / `voyage` / `openai` / `ollama` / `openai-compatible` / `custom` / `gemini-oauth` |
388
- | `EMBEDDING_MODEL` / `EMBEDDING_DIMENSIONS` | Model + dims |
389
- | `EMBEDDING_API_KEY` / `EMBEDDING_BASE_URL` | Credentials |
390
- | `OLLAMA_HOST` | Default `http://localhost:11434` |
391
- | `RERANKER_PROVIDER` | `ollama` (default) / `cohere` / `voyage` / `openai-compatible` / `custom` / `none` |
392
- | `RERANKER_MODEL` / `RERANKER_API_KEY` / `RERANKER_BASE_URL` | Reranker config |
393
- | `RETRIEVAL_MAX_CHUNKS` / `RETRIEVAL_MAX_TOKENS` | Retrieval tuning |
394
- | `AUTO_INDEX` | `false` to disable startup auto-index |
395
- | `DAEMON_DISABLED` | `code-search query` never uses a daemon |
396
- | `LLM_MODE` / `LLM_PROVIDER` / `LLM_MODEL` / `LLM_API_KEY` / `LLM_BASE_URL` | Profiling + service-mesh LLM (`LLM_MODE` defaults to `none`). Pre-promoted to `ANVIL_LLM_*` so the agent-core integration is silent. |
397
- | `INDEXING_DEBOUNCE_MS` / `CHUNKING_MAX_TOKENS` | Indexer knobs |
398
- | `METRICS_ENABLED` / `STRUCTURED_LOGS` / `TELEMETRY_RECORD_QUERIES` | Observability |
399
-
400
- ### Per-workspace config
401
-
402
- Drop a `.code-search.yaml` at the root of your workspace:
283
+ `code-search --print-config` prints the resolved result with secrets
284
+ redacted. An unknown flag exits with status 2 instead of being ignored.
403
285
 
404
286
  ```yaml
287
+ # .code-search.yaml
405
288
  embedding:
406
- provider: codestral
407
- model: codestral-embed-2505
408
- dimensions: 1024
289
+ provider: openai # auto | ollama | openai | voyage | codestral | openai-compatible | gemini-oauth | custom
290
+ model: text-embedding-3-small
291
+ dimensions: 1536
409
292
  reranker:
410
- provider: ollama
411
- model: qwen2.5-coder:7b # the default that actually answers
293
+ provider: none # ollama (default, qwen2.5-coder:7b) | cohere | voyage | openai-compatible | none
412
294
  retrieval:
413
295
  max_chunks: 12
414
- hybrid_weights: { vector: 0.55, bm25: 0.3, graph: 0.15 }
415
296
  indexing:
416
- auto_index: true
417
- debounce_ms: 500
297
+ reindex_interval_ms: 1800000 # team server: re-index every 30 min
418
298
  ```
419
299
 
420
- CLI flags override env, env overrides this file, this file overrides
421
- the user-global `~/.code-search/config.yaml`, that overrides
422
- `DEFAULTS`.
300
+ | Variable (`CODE_SEARCH_…`) | Purpose |
301
+ |---|---|
302
+ | `EMBEDDING_PROVIDER` / `_MODEL` / `_DIMENSIONS` / `_API_KEY` / `_BASE_URL` | Embedder |
303
+ | `RERANKER_PROVIDER` / `_MODEL` / `_API_KEY` / `_BASE_URL` | Reranker (`none` to disable) |
304
+ | `DATA_DIR` | Where indexes live (default `~/.anvil/knowledge-base`) |
305
+ | `PORT` / `HOST` | HTTP bind (default `127.0.0.1:3100`) |
306
+ | `AUTH_MODE` / `AUTH_API_KEYS` / `AUTH_JWT_SECRET` | `none` / `api-key` / `jwt` |
307
+ | `REINDEX_INTERVAL` | `30m`, `1h`, `6h` or `0` |
308
+ | `WORKERS` | HTTP worker processes (2–16) |
309
+ | `SERVER` / `API_KEY` | Remote-proxy target and its key |
310
+ | `VECTOR_NPROBES` | Fix IVF probes per query (overrides auto-calibration; lower = faster, less recall) |
311
+ | `STORAGE_VECTOR_URI`, `STORAGE_BLOB_BACKEND`, `STORAGE_MONGO_*`, `STORAGE_S3_*` | Object-storage deployment |
312
+
313
+ The full list is in [RUNNING.md § Configuration reference](./RUNNING.md#configuration-reference).
423
314
 
424
315
  ---
425
316
 
426
- ## Philosophy
427
-
428
- **Multi-repo or nothing.** Real codebases span repos. Code search
429
- that doesn't is a toy.
317
+ ## Security and operations
318
+
319
+ - **Auth.**
320
+ - `none`: binds to `127.0.0.1` only.
321
+ - `api-key`: `Authorization: Bearer …`, compared in constant time.
322
+ - `jwt`: HS256; signature, issuer and a required `exp` are verified, plus `nbf` when present. The server refuses to start without a secret.
323
+ - A public bind without auth logs a warning.
324
+ - **Limits.** A per-identity sliding-window rate limit (default 100/min).
325
+ Per-project scopes and query quotas come from
326
+ `<dataDir>/projects/<name>/project.yaml`.
327
+ - **Endpoints.**
328
+ - `GET /health`, `/ready`, `/version`, `/status`.
329
+ - `GET /metrics`: Prometheus metrics for query counts, latency histogram, errors, index age, chunks per repo and vector-index health.
330
+ - `GET /admin/api/status`.
331
+ - `POST /index`: re-index on demand.
332
+ - **Logs and tracing.** `CODE_SEARCH_STRUCTURED_LOGS=1` emits JSON log lines.
333
+ Query text is kept out of traces unless you opt in with
334
+ `CODE_SEARCH_TELEMETRY_RECORD_QUERIES=1`.
430
335
 
431
- **No vendor LLM SDK.** Anything LLM-driven (repo profiling,
432
- semantic edges) routes through `@anvil/agent-core`'s single-shot —
433
- the same router, retries, and cost ledger as the rest of Anvil.
336
+ ---
434
337
 
435
- **Three modes, one binary.** Remote proxy for hosted, local for
436
- solo, serve for teams. The dispatcher is `src/index.ts:argv`.
338
+ ## Supported languages
437
339
 
438
- **Stateless serving.** Every `/mcp` request is an independent
439
- exchange — no session state at all, so restarts and per-request
440
- proxies (cluster workers) are free.
340
+ | Support | Languages / files |
341
+ |---|---|
342
+ | AST chunking, code graph, cross-repo signals | TypeScript, TSX, JavaScript, Python, Go, Java, Rust, PHP |
343
+ | Indexed for search | the above, plus shell, YAML, Terraform/HCL, TOML, `Dockerfile`, `Makefile`, `Jenkinsfile` |
441
344
 
442
- **Security defaults that don't bite.** Default bind is `127.0.0.1`
443
- when auth is `none`. API keys compared timing-safe. JWT
444
- signature + exp (required) + nbf + iss validated. Public binding
445
- without auth logs a warning instead of pretending it's fine.
345
+ `.gitignore` is respected, and `node_modules`, `dist`, `build`, `vendor`,
346
+ `target` and similar directories are skipped.
446
347
 
447
348
  ---
448
349
 
449
- ## End-to-end smoke test (T1–T8)
350
+ ## Design principles
450
351
 
451
- The eight-case smoke that pins the standalone product contract lives
452
- at `scripts/smoke/run-smoke.sh`. It spins up a tiny `pet-shop` git
453
- fixture under `/tmp/cs-smoke/`, then runs:
352
+ - **Multi-repo first.** The interesting questions cross repository
353
+ boundaries, so the index, the graph and the tools all do too.
354
+ - **Scale by doing less.** Every index cycle costs in proportion to what
355
+ changed, not to the size of the corpus. Every query tier is index-backed.
356
+ Every graph traversal is bounded.
357
+ - **Never silently wrong.** An embedder mismatch is a hard error. Truncated
358
+ traversals say they were truncated. Bad tool arguments return the reason.
359
+ - **Local by default, shared when you want.** The same binary serves one
360
+ laptop over stdio or a whole team over HTTP.
361
+ - **Few moving parts.** Embedded LanceDB and SQLite, no database server to
362
+ run, a hand-rolled HTTP layer and zero-dependency metrics.
454
363
 
455
- | # | Verifies |
456
- |---|---|
457
- | **T1** | `--print-config` env + CLI flag layering, redaction |
458
- | **T2** | `code-search index` produces a real Ollama-backed index; no stale legacy-env warning |
459
- | **T3** | `code-search query --mode vector` and `--mode bm25` both return the right file |
460
- | **T4** | `code-search status` reports provider + chunk count |
461
- | **T5** | Issue-#6 reproduction: `CODE_SEARCH_EMBEDDING_PROVIDER=openai` actually constructs the OpenAI embedder (skipped when `OPENAI_API_KEY` is unset) |
462
- | **T6** | Vector-space mismatch guard hard-errors on cross-provider query |
463
- | **T7** | Daemon round-trip: spawn, UDS socket, query through RPC, file-watcher debounce-reindex |
464
- | **T8** | HTTP serve: `/health` `/ready` `/version` `/metrics` `/admin/api/status` all 200; Prom text format |
364
+ ---
365
+
366
+ ## Development
465
367
 
466
- Run it locally:
368
+ This package lives in a monorepo alongside its retrieval engine,
369
+ `@esankhan3/anvil-knowledge-core`.
467
370
 
468
371
  ```sh
469
- npm -w @esankhan3/code-search-mcp run build # required first
470
- npm -w @esankhan3/code-search-mcp run smoke # T1–T8
372
+ npm install # at the repo root
373
+ npm -w @esankhan3/anvil-knowledge-core run build
374
+ npm -w @esankhan3/code-search-mcp run build # esbuild bundle → dist/
375
+ npm -w @esankhan3/code-search-mcp test
376
+ npm -w @esankhan3/code-search-mcp run smoke # end-to-end, needs Ollama with bge-m3
471
377
  ```
472
378
 
473
- Requirements: Ollama on `$OLLAMA_HOST` (default `localhost:11434`)
474
- with `bge-m3` pulled. Pass `OPENAI_API_KEY=...` to exercise the T5
475
- provider-switch path against the real OpenAI endpoint.
379
+ Internals: [ARCHITECTURE.md](./ARCHITECTURE.md). Retrieval engine:
380
+ [knowledge-core](../knowledge-core/ARCHITECTURE.md).
476
381
 
477
- The smoke runs **outside CI** — it requires Ollama + real models —
478
- so it's a hand-rolled local check, not a release gate. The release
479
- workflow does enforce a static guard that every bin entry
480
- (`code-search-mcp` / `code-search` / `code-search-daemon`) is in the
481
- built tarball before publishing.
482
-
483
- ---
484
-
485
- ## Status
486
-
487
- Stable. The retrieval and graph layers move with
488
- `@anvil/knowledge-core`; the MCP surface is locked. New tools
489
- land additively. P0–P8 of the standalone-product plan
490
- (`docs/CODE-SEARCH-MCP-STANDALONE-PLAN.md`) have landed; F1–F4
491
- follow-up fixes verified by `scripts/smoke/run-smoke.sh`.
492
-
493
- ---
382
+ ## License
494
383
 
495
- ## Part of [Anvil](../../) — the AI development pipeline.
384
+ [MIT](./LICENSE)
@@ -20,7 +20,10 @@ const ENV_KEYS = [
20
20
  "CODE_SEARCH_AUTH_MODE",
21
21
  "CODE_SEARCH_AUTH_API_KEYS",
22
22
  "CODE_SEARCH_DATA_DIR",
23
- "CODE_SEARCH_REINDEX_INTERVAL"
23
+ "CODE_SEARCH_REINDEX_INTERVAL",
24
+ "CODE_SEARCH_EMBEDDING_HEADERS",
25
+ "CODE_SEARCH_LLM_HEADERS",
26
+ "ANVIL_LLM_HEADERS"
24
27
  ];
25
28
  let tmp = "";
26
29
  const STASH = {};
@@ -105,6 +108,35 @@ describe("toKnowledgeConfig (P3 adapter)", () => {
105
108
  assert.equal(r.provider, cfg.reranker.provider);
106
109
  });
107
110
  });
111
+ describe("gateway request headers", () => {
112
+ const RAW = "CF-Access-Client-Id=id.access,CF-Access-Client-Secret=s3cret";
113
+ const PARSED = { "CF-Access-Client-Id": "id.access", "CF-Access-Client-Secret": "s3cret" };
114
+ it("CODE_SEARCH_EMBEDDING_HEADERS reach the embedder config", () => {
115
+ process.env.CODE_SEARCH_EMBEDDING_HEADERS = RAW;
116
+ const cfg = resolveCodeSearchConfig({ configPath: join(tmp, "missing.yaml") });
117
+ assert.deepEqual(cfg.embedding.headers, PARSED);
118
+ assert.deepEqual(toKnowledgeConfig(cfg).embedding.headers, PARSED);
119
+ });
120
+ it("CODE_SEARCH_LLM_HEADERS reach agent-core as ANVIL_LLM_HEADERS", () => {
121
+ process.env.CODE_SEARCH_LLM_HEADERS = RAW;
122
+ const cfg = resolveCodeSearchConfig({ configPath: join(tmp, "missing.yaml") });
123
+ assert.deepEqual(cfg.llm.headers, PARSED);
124
+ assert.equal(process.env.ANVIL_LLM_HEADERS, RAW);
125
+ });
126
+ it("llm.headers from YAML reach agent-core too", () => {
127
+ const file = join(tmp, "config.yaml");
128
+ writeFileSync(file, "llm:\n headers:\n CF-Access-Client-Id: id.access\n CF-Access-Client-Secret: s3cret\n");
129
+ resolveCodeSearchConfig({ configPath: file });
130
+ assert.equal(process.env.ANVIL_LLM_HEADERS, RAW);
131
+ });
132
+ it("--print-config redacts every header value", () => {
133
+ const out = redactSecrets({ embedding: { headers: PARSED }, llm: { headers: PARSED } });
134
+ for (const h of [out.embedding.headers, out.llm.headers]) {
135
+ assert.match(h["CF-Access-Client-Id"], /redacted/);
136
+ assert.match(h["CF-Access-Client-Secret"], /redacted/);
137
+ }
138
+ });
139
+ });
108
140
  describe("redactSecrets (P3 --print-config)", () => {
109
141
  it("redacts apiKey/jwtSecret/token fields, preserves shape", () => {
110
142
  const input = {
@@ -241,6 +241,7 @@ function envLayer() {
241
241
  dimensions: embedDims ? parseInt(embedDims, 10) : void 0,
242
242
  apiKey: env("EMBEDDING_API_KEY"),
243
243
  baseUrl: env("EMBEDDING_BASE_URL"),
244
+ headers: parseHeaders(env("EMBEDDING_HEADERS")),
244
245
  ollamaHost: env("OLLAMA_HOST") ?? process.env.OLLAMA_HOST
245
246
  },
246
247
  reranker: {
@@ -268,6 +269,7 @@ function envLayer() {
268
269
  model: env("LLM_MODEL"),
269
270
  apiKey: env("LLM_API_KEY") ?? process.env.ANTHROPIC_API_KEY ?? process.env.OPENAI_API_KEY,
270
271
  baseUrl: env("LLM_BASE_URL"),
272
+ headers: parseHeaders(env("LLM_HEADERS")),
271
273
  claudeBin: env("CLAUDE_BIN") ?? process.env.CLAUDE_BIN
272
274
  },
273
275
  github: {
@@ -420,6 +422,9 @@ function bridgeLegacyEnvVars(cfg) {
420
422
  process.env.ANVIL_LLM_MODEL ??= process.env.CODE_SEARCH_LLM_MODEL ?? cfg.llm.model;
421
423
  if (cfg.llm.apiKey) process.env.ANVIL_LLM_API_KEY ??= cfg.llm.apiKey;
422
424
  if (cfg.llm.baseUrl) process.env.ANVIL_LLM_BASE_URL ??= cfg.llm.baseUrl;
425
+ if (cfg.llm.headers) {
426
+ process.env.ANVIL_LLM_HEADERS ??= Object.entries(cfg.llm.headers).map(([k, v]) => `${k}=${v}`).join(",");
427
+ }
423
428
  if (cfg.llm.claudeBin) process.env.ANVIL_CLAUDE_BIN ??= cfg.llm.claudeBin;
424
429
  }
425
430
  function toKnowledgeConfig(c) {
@@ -454,7 +459,7 @@ function redactSecrets(value, parentKey = "") {
454
459
  if (isPlainObject(value)) {
455
460
  const out = {};
456
461
  for (const [k, v] of Object.entries(value)) {
457
- const secret = SECRET_KEY.test(k) || parentKey === "otelHeaders";
462
+ const secret = SECRET_KEY.test(k) || parentKey === "otelHeaders" || parentKey === "headers";
458
463
  if (secret && typeof v === "string" && v.length > 0) {
459
464
  out[k] = redactString(v);
460
465
  } else if (secret && Array.isArray(v)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@esankhan3/code-search-mcp",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "description": "MCP server for multi-repo code search \u2014 GitHub org or local path, zero config",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -23,8 +23,8 @@
23
23
  "smoke": "bash scripts/smoke/run-smoke.sh"
24
24
  },
25
25
  "dependencies": {
26
- "@esankhan3/anvil-agent-core": "^0.5.0",
27
- "@esankhan3/anvil-knowledge-core": "^0.4.10",
26
+ "@esankhan3/anvil-agent-core": "^0.5.4",
27
+ "@esankhan3/anvil-knowledge-core": "^0.4.12",
28
28
  "@modelcontextprotocol/node": "^2.0.0",
29
29
  "@modelcontextprotocol/server": "^2.0.0"
30
30
  },