Skip to main content
Glama
maiteuxzanela

Cirurgic Search MCP

πŸ”¬ Cirurgic Search MCP

npm version npm downloads License: MIT

A token-bounded, read-only code search engine exposed as a Model Context Protocol server.

It combines exact lexical recall (ripgrep) with relational AST symbols (Tree-Sitter, with a memory-safe Python ast path) and fuses both rankings with Reciprocal Rank Fusion (RRF). The result is a small, high-signal payload: whole functions and classes with line ranges, call metadata and the exact literal matches that led there β€” never a raw directory dump.

# Run universally via NPX (Node/Bun):
npx -y cirurgic-search-mcp

# Or via UVX / Pip (Python):
uvx cirurgic-search-mcp
pip install cirurgic-search-mcp

The problem: context dumping

An agent asked to "find where billing is calculated" usually reaches for cat, grep -r, or a full-file reader. That approach breaks down quickly:

  • Context flooding. A single grep -r over a monorepo can return tens of thousands of lines, exhausting the context window before reasoning starts.

  • Lost structure. A matching line without its enclosing function, class, decorators and call graph is hard to act on safely.

  • Truncation roulette. When the payload is naively truncated, the useful block is often the one that gets cut.

  • Stale indexes. Semantic indexes are expensive to build and drift as soon as a file changes; agents need answers about the current working tree.

  • No budget. Most tools cannot promise "use at most N tokens".

Surgical search flips the default: instead of maximizing recall per call, it maximizes useful signal per token.


Related MCP server: semantic-code-intelligence

Why not plain ripgrep or BM25?

Capability

ripgrep alone

BM25 / chunk index

Cirurgic Search

Exact literal recall

Yes, excellent

Tokenizer-dependent

Yes (ripgrep lane)

Whole symbol boundaries

No

No (fixed chunks)

Yes (ast / Tree-Sitter)

Call graph / base classes

No

No

Yes (calls:, bases:)

Works on uncommitted edits

Yes

Only after reindex

Yes (live filesystem walk)

Hard token ceiling

No

Usually no

Yes (strict guard)

Cold start on a large repo

Fast

Index build required

Bounded walk + candidate ceiling

Background daemon

No

Often

No

Pure lexical search has perfect precision on literals but no notion of a function. Pure semantic search handles paraphrases but loses exact identifiers, line numbers and freshness. Fusing both covers the failure mode of each.


Architecture

                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   query ──────────▢│        HybridSearchEngine    β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                   β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚                                         β”‚
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚   Lexical lane      β”‚                  β”‚    Symbol lane        β”‚
   β”‚  ripgrep / grep     β”‚                  β”‚  Python ast           β”‚
   β”‚  literal unions     β”‚                  β”‚  Tree-Sitter grammars β”‚
   β”‚  match counts       β”‚                  β”‚  relation extraction  β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              β”‚  file ranking                           β”‚  file ranking
              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                               β–Ό
                    Reciprocal Rank Fusion (RRF)
                               β”‚
                               β–Ό
                 Macro/micro packaging + Token Guard
                               β”‚
                               β–Ό
             snippets + line ranges + relations + exact matches

1. Lexical lane

ripgrep runs with literal-union patterns (the full query plus its significant words), skips vendor/build directories and groups matches by file. A POSIX grep fallback keeps the server usable when rg is not installed. This lane is what catches config files, lockfiles and strings that no parser understands.

2. Symbol lane

Python files are parsed with the standard-library ast module, which is memory-safe and gives exact ranges including decorators. Other languages are parsed with Tree-Sitter when a grammar bundle is available:

JavaScript, TypeScript, TSX, Go, Rust, Dart, Java, Kotlin, C#, Ruby, PHP, Swift, C, C++ and Bash.

Every extracted block carries relational metadata:

type:function | name:calculate_total
calls:parse_amount,apply_tax
type:class | name:BillingService | bases:BaseService

The extractor degrades gracefully: Tree-Sitter β†’ regex (JS/TS) β†’ lexical chunks. A missing grammar never aborts a search.

3. Reciprocal Rank Fusion

Each lane ranks files independently. Fusion uses the standard RRF formula:

score(file) = Ξ£  weight_lane / (k + rank_lane(file))

with k = 60 by default, weight_lexical = 1.0 and weight_symbol = 1.2. RRF needs no score calibration between lanes: it only consumes ranks, so a strong signal from either side promotes the file.

4. Macro/micro packaging and the Token Guard

After fusion, the engine iterates files in RRF order. For each file it emits the highest-scoring AST blocks (micro) and attaches any exact lexical matches that fall inside the block's line range (macro). If a file has no parsed symbol, a small literal preview is returned instead.

Before anything is appended, its estimated size is checked against the remaining budget. The budget is a hard ceiling, not a suggestion: the first oversized snippet is trimmed to fit and subsequent snippets that do not fit are dropped. Token counts use a conservative chars / 3.8 estimator.


Installation

Requirements: Python 3.10+ on Linux or macOS. ripgrep is recommended for the fast lane; without it the server falls back to POSIX grep.

# Ephemeral execution, no install step
uvx cirurgic-search-mcp

# Regular installation
pip install cirurgic-search-mcp

# Recommended: maintained grammar bundle with wider language coverage
pip install "cirurgic-search-mcp[language-pack]"

The base install uses tree-sitter-languages on Python < 3.13 and tree-sitter-language-pack on newer interpreters. Installing the language-pack extra explicitly is recommended on every version.


MCP client configuration

With uvx:

{
  "mcpServers": {
    "cirurgic-search": {
      "command": "uvx",
      "args": ["cirurgic-search-mcp"]
    }
  }
}

With a regular installation:

{
  "mcpServers": {
    "cirurgic-search": {
      "command": "cirurgic-search-mcp"
    }
  }
}

The server speaks stdio by default. Other transports are available for local experimentation:

cirurgic-search-mcp --transport sse
cirurgic-search-mcp --transport streamable-http

Tool reference

Argument

Type

Default

Description

query

string

required

Identifier, technical term or natural-language question.

workspace

string

current cwd

Repository root. Must be an existing directory.

scope

string

""

Comma-separated directories, packages or files to restrict the walk.

max_tokens

integer

1200

Hard ceiling for the returned payload.

depth

integer

2

Relational depth reserved for symbol expansion.

Example response:

{
  "status": "success",
  "workspace": "/path/to/repo",
  "latency_ms": 18.4,
  "total_tokens": 742,
  "token_savings_pct": 97.8,
  "snippets_count": 2,
  "files_found": 4,
  "candidates_scanned": 312,
  "candidates_truncated": false,
  "snippets": [
    {
      "file": "billing/service.py",
      "lines": "42-67",
      "rrf_score": 0.0284,
      "relations": [
        "type:function | name:calculate_total",
        "calls:parse_amount,apply_tax"
      ],
      "exact_matches": [
        {"line": 51, "text": "# rule: daily rate for ICU"}
      ],
      "content": "def calculate_total(...):\n    ...",
      "source": "hybrid"
    }
  ]
}

source is hybrid when the file was found by both lanes, ast when it came from the symbol lane only, and lexical for literal-only files.

Backwards-compatible alias of cirurgic_search. It is registered with the exact same coroutine object and schema, so the two names cannot drift. New clients should call cirurgic_search; existing clients keep working unchanged.


Environment variables

Variable

Default

Purpose

CIRURGIC_RG_PATH

auto-detected

Explicit path to a ripgrep binary.

CIRURGIC_GREP_TIMEOUT

5.0

Subprocess timeout in seconds for the lexical lane.

CIRURGIC_MAX_CANDIDATE_FILES

800

Upper bound on files walked per search. Narrow scope when hit.

CIRURGIC_TS_EXTRACT

1

Set to 0 to disable Tree-Sitter and use regex/lexical fallbacks only.


Design principles

  • Read-only. The server never writes to the workspace, spawns no daemon and makes no network calls.

  • Fresh by construction. Every search walks the current working tree, so uncommitted edits are visible immediately.

  • Fail-soft. Missing binaries, missing grammars, unreadable files and parse errors degrade to the next lane instead of failing the request.

  • Memory-safe Python path. Python always uses ast, never a native binding.

  • Bounded work. Candidate files, AST nodes and returned tokens are all capped, protecting latency and memory on pathological repositories.

  • No hidden state. Results are deterministic for a given workspace, query and budget.


Development

python -m venv .venv
. .venv/bin/activate
pip install -e ".[dev]"
python -m pytest

The deterministic suite uses real temporary directories and real rg/grep subprocesses. Tests that require an optional grammar skip cleanly when that grammar is not installed.


Limitations

  • Ranking is lexical/AST-based, not embedding-based. It favors exact identifiers, symbol names and structural proximity over paraphrase.

  • Very large repositories should be searched with a narrow scope; the candidate ceiling exists to protect latency, not to index the world.

  • Tree-Sitter coverage depends on the installed grammar bundle. Python works everywhere; other languages fall back to regex or lexical chunks.

  • Symbolic links are not followed and binary files are ignored.

  • depth is reserved for relational expansion; the current release ranks symbols by name, relations and content and does not traverse the full call graph transitively.


License

MIT. See LICENSE.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables local semantic code search across repositories using natural language, with AST-aware chunking and hybrid vector/FTS5 retrieval.
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables agents to perform local hybrid code search and code intelligence across a workspace, including semantic and full-text search, symbol lookup, file outlines, and caller analysis.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables coding agents to find code by behavior across an authorized repository, returning exact source excerpts with paths and line numbers. It exposes a single semantic search tool over stdio so agents can locate relevant code without knowing file names or symbols.
    15 npm
    MIT