codesearch
OfficialProvides a VS Code extension and MCP server for index-based code search within GitHub Copilot.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@codesearchfind all references to the Config class"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
codesearch
Full-text and structural code search for a large monorepo. Runs an in-process Tantivy index (via tantivy-py) and exposes results as MCP tools so Claude can query the codebase directly without copy-pasting.
Early alpha. Expect rough edges.
Installation
git clone https://github.com/microsoft/tscodesearch
cd tscodesearch
setup.cmd
ts startsetup.cmd creates a Python venv (via uv), registers the MCP server with Claude Code and VS Code (GitHub Copilot), prompts for a source directory to index, creates config.json, and installs the VS Code extension.
To uninstall: setup.cmd --uninstall
Related MCP server: grepsense
Prerequisites
Windows 11 (or Linux/macOS for the daemon, with caveats)
Python 3.10+
Node.js 20+
uvis installed automatically bysetup.mjsif missing
There is no Docker, WSL, or Typesense dependency. The whole index is in-process.
One-time setup
From a Windows command prompt or PowerShell:
setup.cmdsetup.cmd checks for Node.js then calls node setup.mjs, which:
Registers the MCP server with Claude Code and VS Code (GitHub Copilot
mcp.servers)Creates
.client-venvand installs Python dependenciesCreates
config.json-- prompts for a source directory to index (can be added later)Installs the VS Code extension
After setup, start the daemon:
ts startTo uninstall: setup.cmd --uninstall
Adding roots
ts root --add NAME C:\path\to\source
ts restartOr use the VS Code extension command TsCodeSearch: Add Root.
Each root gets its own on-disk Tantivy index at <repo>/.tantivy/codesearch_NAME/. Multi-root config in config.json:
{
"api_key": "...",
"port": 8108,
"roots": {
"default": { "path": "C:/myproject/src" },
"other": { "path": "D:/other/src" }
}
}Each root entry can be either an object ({"path": "...", "extensions": [".cs", ".py"]}) or a bare string ("C:/myproject/src"). The object form is what setup.mjs writes and what ts root --add produces; the string form is accepted for backwards compatibility.
Use the MCP root= parameter to search a specific collection:
query_codebase("implements", "IRepository", root="other")
query_single_file("methods", file="path/to/Widget.cs", root="other")Daemon management
All daemon commands go through ts.cmd (Windows CMD/PowerShell):
ts status show daemon health, doc count, watcher state
ts start start the daemon (auto-indexes on first run)
ts stop stop the daemon
ts restart stop then start
ts verify scan FS + repair index: add missing, re-index stale, remove orphans
ts verify --root <name> verify a specific named root
ts verify --no-delete-orphans repair without removing deleted-file entries
ts recreate stop daemon, wipe the on-disk index, restart (full reindex)
ts recreate --root <name> recreate a specific named root's index
ts log [-n N] tail the daemon log (default: last 40 lines)Keeping the index up to date
The watcher picks up changes automatically within a couple of seconds (~1 s ReadDirectoryChangesW latency + 2 s debounce). For large repos, or after bulk operations like a git pull or branch switch, use the MCP tools or ts verify to confirm everything is in sync.
From Claude (MCP tools)
ready() # check index readiness (calls /check-ready)
verify_index(action="start") # launch background sync/repair scan
verify_index(action="status") # monitor sync progress (reads from GET /status)
verify_index(action="stop") # cancel a running scan
wait_for_sync(timeout_s=30) # poll until queue drained; pass 0 for instant statusready() returns a summary with poll_ok (FS walk completed), index_ok (zero missing/stale/orphaned), and timing. If not ready, verify_index(action="start") triggers the syncer to repair the index without resetting it.
wait_for_sync sleeps up to 1 s (watcher warm-up) then polls /status every 0.5 s until the queue is empty. Reports "Index synced in {N}s" with a "was: queue={N}" note if work was observed, or a timeout message with recovery hints.
From the command line
ts verify # foreground repair scan (missing + stale + orphans)
ts verify --no-delete-orphans # repair without removing deleted-file entries
ts verify --root other # verify a specific named rootRunning tests
.client-venv\Scripts\python.exe -m pytest tests/ query/tests/ -v # full Python suite
node run_tests.mjs # VS Code extension testsThe integration tests open a fresh Tantivy index in <repo>/.tantivy/test_* for each class and clean up afterwards. No external service needs to be running.
File / dir | What it tests |
| All language AST query modes against synthetic fixtures |
| Indexer, queue, watcher, verifier, MCP server helpers -- all use |
| Indexer, verifier, watcher, search modes, sample E2E -- open real Tantivy indexes |
Direct CLI usage
Management API via curl
# Read key/port from config.json -- never hard-code
API_KEY=$(node -e "const c=require('./config.json'); process.stdout.write(c.api_key)")
API_PORT=$(node -e "const c=require('./config.json'); process.stdout.write(String(c.port??8108))")
curl -s -X POST http://localhost:$API_PORT/query-codebase \
-H "Content-Type: application/json" -H "X-API-KEY: $API_KEY" \
-d '{"mode":"declarations","pattern":"SaveChanges","root":""}' | python -m json.toolThe daemon authenticates every request by matching the X-API-KEY header against config.json's api_key. The HTTP server binds localhost only, but the key still matters: any process on the same machine -- a browser background page, another dev tool, a malicious dependency -- can reach localhost:PORT. Requiring a shared secret means a random local process can't query or mutate the index without first reading config.json.
Standalone search CLI (scripts/search.py)
.client-venv\Scripts\python.exe scripts\search.py "BlobStore" --ext cs --limit 5
.client-venv\Scripts\python.exe scripts\search.py "IRepository" --implementsThis opens the on-disk Tantivy index in read-only mode, so it works whether or not the daemon is running.
AST queries without a daemon (python -m query)
.client-venv\Scripts\python.exe -m query --mode methods --file C:/myproject/src/Widget.cs
.client-venv\Scripts\python.exe -m query --mode calls --file C:/myproject/src/Widget.cs --pattern SaveChangesAST query modes
One canonical mode name per concept across every language. Listing modes take no pattern; pattern modes expect a single identifier (or LINE:COL for at). Unknown modes raise ValueError with the supported-mode list -- use capabilities to introspect which modes a given file's language actually supports.
Mode | Arg | Concept | Languages |
| -- | List the modes supported for this file's language | all |
| -- | Type declarations (class/interface/struct/enum/record/...) | all |
| -- | Method/ctor/property/field/event declarations | all |
| -- | Field / property / column declarations | C#, SQL |
| -- |
| all except SQL |
| METHOD | Parameter list for METHOD | C#, Python, JS, Rust, C++ |
| NAME | Declaration(s) of NAME (narrow with | all |
| NAME | Full source of NAME's declaration | C# only |
| LINE:COL | Deepest AST node at position + enclosing scope chain | C# only |
| METHOD | Call sites of METHOD ( | all |
| TYPE | Types that inherit/implement TYPE | all except SQL |
| TYPE | Type references; narrow with | C# only |
| TYPE |
| C# only |
| NAME? |
| C#, Python, JS |
| MEMBER | Access sites of property/field by name ( | C# only |
| TYPE |
| C# only |
| NAME | Every identifier occurrence (broadest -- AST-only, skips strings/comments). For SQL this is a plain substring scan over lines. | all |
Architecture
Two-layer search
Tantivy -- fast keyword/semantic search over pre-indexed metadata (class names, method names, base types, call sites, signatures, attributes, etc.). Data stored at
<repo>/.tantivy/<collection>/.tree-sitter -- precise AST queries on the file set returned by Tantivy. Skips comments and string literals, understands syntax.
Typical flow: Tantivy narrows the haystack to ~50 candidate files -> tree-sitter parses each one and applies the structural query.
Process topology
,----------------------------------------------------------------,
| MCP CLIENT (Claude <-> tools) |
| mcp_server.py (.client-venv -- runs on Windows) |
| Claude Code -> mcp.cmd -> .client-venv\python.exe |
`------------------------------T-----------------------------------'
| HTTP localhost:PORT
,-----------------------------v----------------------------------,
| DAEMON indexserver/daemon.py (.client-venv) |
| * HTTP server (management API on PORT) |
| * watcher (ReadDirectoryChangesW) |
| * IndexQueue (batch Tantivy writes) |
| * syncer (on-demand, via POST /verify/start) |
| * Tantivy indexes (one per root, on disk in .tantivy/) |
| * system-tray icon (Windows -- shows Stop menu item) |
`----------------------------------------------------------------'There is no longer a separate Typesense / Docker / WSL service -- the index lives in-process via tantivy-py. On Windows the daemon runs without a console window; right-click the magnifying-glass tray icon to stop it.
File map
File | Purpose |
| Python MCP server (FastMCP). Tools: |
| Management daemon. Owns the HTTP API, watcher, IndexQueue, syncer, system-tray icon, and one Tantivy |
| Windows launcher: |
| Daemon CLI: start/stop/restart/status/index/verify/log/root |
| One-time setup: |
| VS Code extension test runner |
AST query layer (query/)
File | Purpose |
| Per-language tree-sitter AST functions |
| Shared dataclasses + |
| Pure query dispatcher. |
| CLI: |
TreeIndex walks the AST once with tree-sitter's TreeCursor, buckets nodes by type, and (optionally) collects literal-aware identifier refs in the same pass. describe_*_file covers the union of types every extractor needs in one walk; per-query wrappers (q_classes, q_methods, ...) pass a narrow type set so they pay the cost of a single targeted walk.
Indexer (indexserver/)
File | Purpose |
| Tantivy schema + |
| Typesense-shaped |
|
|
|
|
|
|
| Deduplicated batch queue. Writes go through a |
| Management daemon: HTTP server, watcher thread, IndexQueue worker, syncer, tray icon. |
| Structural query CLI ( |
Config
File | Purpose |
|
|
Scripts / infra
File | Purpose |
| Standalone read-only search CLI. |
Backend schema
Every text field uses Tantivy's raw tokenizer: each entry is one verbatim term (case-sensitive, no underscore splitting, no length cap). All domain-aware splitting happens in the indexer before storage -- long identifiers stay whole, add_text_field is one token, Acme.Billing.Service is three namespace entries.
Indexed search fields -- populated by the AST extractors and indexed for query_by matching; stored=False, so values are not retrievable from a search hit:
Field | Populated from | Used by MCP mode |
| base classes + interface lists |
|
| every call expression's method name |
|
| declared field / property / event types |
|
| method / ctor / delegate parameter types |
|
| method / delegate return types |
|
| declared local variable types |
|
|
|
|
| union of |
|
| RHS of |
|
| every identifier in any member signature -- attribute names, parameter names, generic args, default-value identifiers | -- (auxiliary; covers signature content) |
|
|
|
|
|
|
| per-component split of the file's primary namespace (e.g. | -- (auxiliary) |
| type and method/property/field declarations |
|
| deduped bag of every identifier in the file (code only -- no strings or comments) |
|
| per-directory + filename components -- | every mode (path/filename fallback) |
Stored fields -- retrievable from the index at search time:
Field | Purpose |
| Document identity, returned with every hit. |
| Basename, used for display. |
| Exact-match filters ( |
| Cumulative ancestor folders for the |
| Verifier diff between filesystem and index. |
Nothing else is stored. The daemon pre-filters with Tantivy then runs tree-sitter on the candidate files; the AST output is what carries line-level results to the caller. Display-only stored payload would just bloat the index.
The daemon resolves query_by/weights server-side from the mode (and uses_kind / symbol_kind when relevant); callers don't pass these directly through /query-codebase. See _resolve_query_params in tsquery_server.py for the exact mapping.
config.json
{
"api_key": "codesearch-local",
"port": 8108,
"roots": {
"default": { "path": "C:/myproject/src" }
}
}This file is not checked in (listed in .gitignore). It is created by setup.mjs with an auto-generated API key. Roots use Windows-style paths (C:/...) and are added via ts root --add or the VS Code extension. Root entries may also be bare strings -- see Adding roots above.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/microsoft/tscodesearch'
If you have feedback or need assistance with the MCP directory API, please join our Discord server