Semlith

Search code and documents together over MCP

One local index over a repository and the PDF, Word and Excel files beside it, searched by Claude Code, Codex or Cursor over MCP, with commands you can run.

Checked on 29 October 2026 with semlith 0.49.0.

A lot of what a project depends on lives outside the code. The security standard you have to meet, the spec a partner sent, the spreadsheet of requirements. Coding agents mostly can’t read any of it. grep and ripgrep skip a PDF as binary, and a Word or Excel file is a zip archive to them, so a search over these files finds nothing even when the words are there.

One folder, three formats

We put three public standards next to Flask 3.1.3. They are NIST SP 800-63B on authentication (PDF), the OASIS SARIF 2.1.0 spec for static analysis results (Word) and the NIST SP 800-53 control catalog (Excel). Each one is the file the scorecard’s documents round fetches, checked against the same SHA-256.

ls standards
Output
NIST.SP.800-63b.pdf
sarif-v2.1.0-os.docx
sp800-53r5-control-catalog.xlsx

Ask ripgrep for a word that two of them use.

rg -i -c inactivity standards

It prints nothing and exits 1. The PDF and the spreadsheet both talk about inactivity, but ripgrep never sees their text.

Index once, then ask in your own words

semlith index .
semlith search -k 3 "which control covers ending a session after inactivity"
Output
1. 0.092 vf  standards/NIST.SP.800-63b.pdf:2354-2366
   continue the session until such time that it is terminated. The session MAY be terminated for any 
   number of reasons, including but not limited to an inactivity timeout, an explicit logout event, or 
   other means. The session MAY be continued through a reauthentication event — described 
   in Section 7.2 — wherein the user repeats some or all of the initial authentication event, thereby 
   re-establishing the session. 
   
   Session management is preferable over continual presentation of credentials as the poor usability 
   of continual presentation often creates incentives for workarounds such as cached unlocking 
   credentials, negating the freshness of the authentication event. 
   
     Session Bindings 
   
   A session occurs between the software that a subscriber is running — such as a browser, 

2. 0.088 vf  standards/sp800-53r5-control-catalog.xlsx:175-175
    to continue after the session is terminated. Conditions or trigger events that require automatic termination of the session include organization-defined periods of user inactivity, targeted responses to certain types of incidents, or time-of-day restrictions on system use.	MA-4, SC-10, SC-23.

3. 0.082 vf  standards/sp800-53r5-control-catalog.xlsx:43-43
   AC-2(5)	Account Management | Inactivity Logout	Require that users log out when [Assignment: organization-defined time period of expected inactivity or description of when to log out].	Inactivity logout is behavior- or policy-based and requires users to take physical action to log out when they are expecting inactivity longer than the defined period. Automatic enforcement of inactivity logout is addressed by AC-11.	AC-11.

3 hits in 501.33825ms
question-shaped · vector and keyword equal

One answer from a PDF and two rows of a spreadsheet, each with its file and its lines. Row 175 is control AC-12, Session Termination, and semlith read standards/sp800-53r5-control-catalog.xlsx:175 prints the whole row. A Word file answers the same way.

semlith search -k 1 "how do you tell that two findings from different scans are the same issue"
Output
1. 0.093 vf  standards/sarif-v2.1.0-os.docx:3608-3609
   An analysis tool that works together with such a result management system can use the fingerprint to determine whether two results are logically the same; two results with the same fingerprint are considered logically the same.
   NOTE 2: A result management system might respond to a “new” result by filing an issue in a bug tracking system. It might respond to an “updated” result by editing the details of an existing issue in the bug tracking system, or by attaching an updated SARIF log to the issue. It might respond to an “absent” result by resolving the issue. It might take no action at all for an “unchanged” issue, or it might simply update its internal information about the range of runs that contained the result.

1 hits in 711.137667ms

Code and documents sit in the same index, so one question can bring back both. Here are the first lines of a search that asks about the app and the standard at once.

semlith search -k 4 "set how long a session stays valid, and what the standard requires"
Output
1. 0.077 v   standards/NIST.SP.800-63b.pdf:2354-2366
2. 0.071 v   standards/NIST.SP.800-63b.pdf:2397-2408
3. 0.067 vf  flask/docs/api.rst:95-128
4. 0.067 v   standards/NIST.SP.800-63b.pdf:2407-2422

Over MCP, for any agent

Your agent calls the same search as a tool. semlith setup registers Semlith in Claude Code, Codex, Cursor and the other clients it finds, and the client pages show what it writes for each one. For Claude Code by hand it is one command.

claude mcp add --scope user semlith -- "$(which semlith)" mcp

The agent then asks semlith_search in its own words and gets the spans above, and calls semlith_read for the lines it wants in full. It never has to open the PDF or the spreadsheet itself.

How it does on public documents

The scorecard’s documents round asks 127 questions about passages inside 53 public documents in 11 formats. Within 4k tokens semlith_search found the passage for 116 of them. rga, which is ripgrep with document adapters, found 34, and plain rg found 7.

With headless Claude Code on Opus answering the same questions, the grep arm found 80 of 127 and the arm with Semlith installed found 124 of 127. In the grep arm 41 of the top quotes were not in the document they named, so the agent wrote them rather than read them. The benchmarks page has the full tables, and the scorecard the command to run them again.

What it reads, and what it doesn’t

Thirteen formats have their own reader, among them PDF, Word, PowerPoint, Excel, OpenDocument, EPUB and Jupyter notebooks. Everything else is read as text. Images are searched by what they show, through a separate image model.

Semlith reads the text a file holds and runs no OCR, so a scanned page with no text layer has nothing in it to find. The default embedding model is English only. A file that looks like it holds a secret is refused and listed by semlith refused. In this run that was a test .env file and two pages of Flask’s docs with example secret keys. Everything stays on your machine, and the security page says exactly what leaves it.