1. Conduid
  2. Data
  3. srijanshukla18/xray
MCP server · Data

srijanshukla18/xray

XRAY MCP provides progressive code intelligence and navigation capabilities for AI assistants through structural code analysis using ast-grep

58Fair

Scored 5 days ago · breakdown

About srijanshukla18/xray

srijanshukla18/xray is an MCP server published by srijanshukla18 in the Data category: xRAY MCP provides progressive code intelligence and navigation capabilities for AI assistants through structural code analysis using ast-grep.

The repository has 52 stars and 10 forks, with the last commit 5 days ago. Recent commit activity is the strongest signal that reported issues will get attention.

Install

Claude Code
claude mcp add xray -- npx -y xray-mcp
npx
npx -y xray-mcp

This server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.

Ask AI

✦

Ask AI about srijanshukla18/xray

Powered by Claude · Grounded in docs

I know everything about srijanshukla18/xray. Ask me about installation, configuration, usage, or troubleshooting.

Security checks

Static checks derived from the repository 4 days ago. Heuristics on the source, not an audit.

  • ✓README presentThe repository ships documentation.
  • ✓License declaredUsable under a stated license.
  • ✓Tests presentA test suite exists in the repository.
  • ✓Dependencies pinnedA lockfile pins dependency versions.
  • ✓No dynamic code executionSource scan found no eval, dynamic Function, or shell execution.
  • !Scoped permissionsDoesn't declare a permission scope. Assume it can do anything its process can.

README

XRAY

An MCP server that lets coding agents see a codebase's shape — layout, signatures, definitions, references — without reading whole files.

Python MCP ast-grep

Why

Coding agents already have grep, glob and file reads. Where they burn context is:

  • Reading a 1,500-line file to learn three function signatures. read_interface returns the file's API — every class, method, function and type with its signature, one-line doc and line number — so the agent reads only the lines it needs.
  • Grepping a name and wading through comments, strings and partial matches. find_symbol returns definitions only, ranked, fuzzy. what_breaks returns identifier references with comments and string literals excluded.
grep / rg XRAY LSP
Setup none uv tool install a server per language
Finds definitions only ✗ ✓ ✓
Skips comments & strings ✗ ✓ ✓
File API without the bodies ✗ ✓ partial (document symbols)
Type-aware references ✗ ✗ (name-based) ✓

XRAY sits between the two: syntax-aware via tree-sitter, zero per-language setup, and honest about not resolving types.

Install

uv tool install git+https://github.com/srijanshukla18/xray

Then register the xray-mcp command with your client.

Claude Code

claude mcp add xray -s user -- xray-mcp

Claude Desktop, Cursor, VS Code, others — add to the client's MCP config:

{ "mcpServers": { "xray": { "command": "xray-mcp" } } }

To run without installing: uvx --from git+https://github.com/srijanshukla18/xray xray-mcp.

There is also install.sh, which installs uv if needed and then runs the command above.

Tools

All paths in results are absolute. root_path is the project directory.

explore_repo(root_path, max_depth=None, include_symbols=False, focus_dirs=None, max_symbols_per_file=5)

Directory tree honouring .gitignore (via git ls-files, or a .gitignore parser outside git). Directories cut off by max_depth show their file count. Output is capped at 1,000 lines.

explore_repo("/home/me/xray", max_depth=2)

/home/me/xray/
├── src/
│   └── xray/ (6 files)
├── tests/
│   ├── __init__.py
│   └── test_xray.py
├── pyproject.toml
└── README.md

focus_dirs takes paths relative to the root; with include_symbols=True each file lists its signatures:

explore_repo("/home/me/xray", focus_dirs=["src/xray/core"], include_symbols=True, max_symbols_per_file=3)

/home/me/xray/
└── src/
    └── xray/
        └── core/
            ├── files.py
            │   ├── def list_files(root: Path) -> List[str]  # Return POSIX paths, relative to root, of every non-ignored…
            │   ├── def _in_excluded_dir(rel: str) -> bool
            │   ├── def _git_files(root: Path)  # Tracked + untracked-but-not-ignored files, or None if root isn't in…
            │   └── ... and 1 more
            ...

find_symbol(root_path, query, limit=10)

Fuzzy search over definitions. Case and separators are ignored ("user service" finds UserService), a Class.method query matches methods by their class, and an exact spelling wins ties.

find_symbol("/home/me/xray", "symbols for")

[{
  "name": "symbols_for", "type": "method", "parent": "SymbolIndex",
  "path": "/home/me/xray/src/xray/core/symbols.py", "start_line": 357, "end_line": 385,
  "signature": "def symbols_for(self, rel_paths: List[str], prune: bool = False) -> Dict[str, List[dict]]"
}]

read_interface(root_path, file_path)

A file's API, nested by scope, with line numbers. Functions defined inside other functions are left out — they're not part of the interface. Paths outside root_path are refused.

read_interface("/home/me/xray", "src/xray/core/indexer.py")

src/xray/core/indexer.py (13 symbols; left column is the line number)
 17  def _norm(text: str) -> str
 21  class XRayIndexer
 22    def __init__(self, root_path: str)
 28    def _rel(self, path: str) -> str  # Resolve a user-supplied path to a POSIX path relative to root, refusing…
105    def find_symbol(self, query: str, limit: int = 10) -> List[Dict[str, Any]]
134    def read_interface(self, file_path: str) -> str
...

what_breaks(root_path, name, path=None, line=None, limit=50)

Every identifier spelled exactly name, excluding comments, string literals and the definitions themselves (which are listed separately). Pass the path and line of the definition you mean, and XRAY warns when other definitions share the name.

what_breaks("/home/me/xray", "list_files", path="src/xray/core/files.py", line=18, limit=2)

{
  "symbol": "list_files",
  "total_references": 7,
  "files": 2,
  "references": [
    {"file": "/home/me/xray/src/xray/core/indexer.py", "line": 9,
     "text": "from xray.core.files import list_files\nfrom xray.core.symbols import ..."},
    {"file": "/home/me/xray/src/xray/core/indexer.py", "line": 50,
     "text": ") -> str:\n    files = list_files(self.root_path)\n    if focus_dirs:"}
  ],
  "definitions": [{"path": "/home/me/xray/src/xray/core/files.py", "line": 18}],
  "note": "Name-based: every identifier spelled exactly like this, excluding comments and strings. ...",
  "truncated": "showing first 2 of 7; raise limit to see more"
}

It is name-based, not type-aware. what_breaks("save") finds every .save in the repo, not just the one on your class. Use it as a precise grep, not as a compiler.

Languages

Language Extensions Extracted
Python .py .pyi functions, async functions, classes, methods; first line of docstrings
JavaScript .js .jsx .mjs .cjs functions, generators, classes, methods, const f = () => / function expressions
TypeScript .ts .tsx .mts .cts JavaScript's set plus interfaces, type aliases, enums, namespaces, abstract classes
Go .go functions, methods, structs, interfaces, other type declarations

For non-Python languages the doc is the first line of the // or /** */ comment directly above the definition.

How it works

  • Files: git ls-files --cached --others --exclude-standard, so nested .gitignores, negations and global excludes all behave exactly as in git. Dependency and build directories (node_modules, vendor, .venv, dist, …) are always skipped.
  • Parsing: ast-grep's Python binding (tree-sitter grammars). One pass per file extracts every definition; explore_repo, find_symbol and read_interface all read that same data.
  • Cache: ~/.cache/xray/<hash-of-root>.sqlite (honours XDG_CACHE_HOME), one row per file keyed on mtime and size. Only changed files are re-parsed; the cache survives server restarts.
  • Large repos: batches of 200+ files are spread over up to 8 worker processes.

Measured on a 21,000-file Go/TS repo (Apple M-series, 10 cores): first find_symbol 8 s (builds the cache), later calls ~1 s, what_breaks ~2 s.

Development

git clone https://github.com/srijanshukla18/xray && cd xray
uv sync
uv run pytest

test_samples/ holds one file per language; the tests assert exact names and line numbers against them, and drive the tools through a real MCP client.

Uninstall

uv tool uninstall xray && rm -rf ~/.cache/xray

or run uninstall.sh.

Design notes

Approaches tried before settling on this one:

  1. grep — can't tell a definition from a comment or a string.
  2. Raw tree-sitter bindings — grammar and binding build problems across platforms.
  3. LSP — precise, but asks users to install and run a language server per language.
  4. Comby — unstable for this workload.
  5. ast-grep CLI — correct, but its JSON repeats each matched node's full text; on a 20k-file repo that was ~1 GB of output per index. The in-process binding gives the same parser without the serialisation.

README fetched from the repository 4 days ago. The original is authoritative.

Questions

About srijanshukla18/xray

How do I install srijanshukla18/xray?

Run claude mcp add xray -- npx -y xray-mcp, then add the server to your MCP client's configuration.

Is srijanshukla18/xray safe to use with an AI agent?

Its trust score is 58 out of 100 (fair). It passes 5 of the 6 static security checks run so far; the failures are listed above. It has no ConduID identity yet, so agent calls to it are not receipted.

Is srijanshukla18/xray still maintained?

Yes — the last commit was 5 days ago. The repository has 52 stars and 10 forks.