sun-tzu-mcp
# sun-tzu-mcp
An MCP server that finds the Sun Tzu quote for a situation. Built for workplace use: describe what is
happening ("the PM keeps adding scope two days before release") and get back verses from *The Art of
War* with their chapter citation, so the quote you paste is the quote Sun Tzu actually wrote.
## Tools
| Tool | What it does |
|---|---|
| `find_quote(situation, count?, theme?)` | Matches a plain-language situation to verses, best first |
| `list_themes()` | The theme vocabulary, with a gloss and verse count for each |
| `random_quote(theme?)` | One verse at random from the annotated set |
| `quote_card(situation? \| verse?, caption?, scale?)` | Renders a verse as a PNG card, 1200x630, and copies it to the clipboard |
There is also a `sun-tzu://corpus` resource holding all 383 verses.
The card is written to a temp file and put on the clipboard. Clipboard copying is macOS only; on other
platforms the PNG is still written and the tool says so instead of failing.
`caption` sets one italic line above the verse, for framing the quote (`On being told "good job"`). It is
taken verbatim, shrunk to fit, then clipped with an ellipsis. Omit it for a bare card.
## Install
Needs Node 20 or newer. Nothing else: no API key, no network, no database.
```
claude mcp add sun-tzu -- npx -y sun-tzu-mcp
```
Or, for any MCP client that takes a JSON config:
```json
{
"mcpServers": {
"sun-tzu": {
"command": "npx",
"args": ["-y", "sun-tzu-mcp"]
}
}
}
```
The server runs locally as a subprocess of the client and talks stdio. Nothing is hosted, nothing
phones home.
### From a clone
```
pnpm install
pnpm build
claude mcp add sun-tzu -- node /absolute/path/to/sun-tzu-mcp/dist/index.js
```
## How matching works
No embeddings, no network, no API key. The corpus ships with the server and matching is deterministic:
1. The situation is normalized, tokenized and stemmed.
2. A lexicon maps working-life vocabulary onto the theme vocabulary, so `reorg` reaches verses tagged
`adaptability` and `micromanage` reaches `delegation` without sharing a word with the text.
3. Verses score on three fields: matched themes (weight 3), curated situation hints (2), and verse
text (1). A theme that several query terms agree on counts for more, which keeps an incidental word
from steering the result.
The tradeoff: paraphrase far outside the lexicon matches less well than embeddings would. In exchange
it is instant, free, offline and debuggable, and the calling model is already good at rewriting a
situation into words the lexicon knows.
## Data
- `data/source/pg132.txt` — the Giles 1910 translation, Project Gutenberg #132, public domain.
- `data/verses.json` — **generated.** 383 verses parsed out of the source by `pnpm corpus`. Do not
edit by hand.
- `data/tags.json` — **hand-curated.** Themes and situation hints for the verses that carry over to
working life. Kept separate from `verses.json` so re-parsing never wipes it.
- `data/themes.json` — the theme vocabulary. `tags.json` is validated against it at server startup,
so an unknown theme fails loudly instead of silently never matching.
### Parsing notes
The Gutenberg plain text interleaves Giles' commentary with the verses and its brackets are not
reliable: blocks are missing opening brackets, missing closing brackets, and one verse (V.9) is missing
the period after its number. Character-level bracket counting silently swallows whole chapters. The
parser therefore works paragraph by paragraph, uses verse numbers as hard boundaries, and falls back on
markers of Giles' voice (commentator names, dates, "says:") where the brackets fail. `pnpm corpus`
prints verse coverage per chapter and flags any gap in the numbering, so a regression is visible.
## Tests
```
pnpm build && pnpm test
```
Covers the corpus (verse count, citation format, no commentary leakage) and the matcher (a table of
situations that must each reach a specific verse in the top 3). Add a row to `EXPECTED` in
`test/match.test.mjs` when you extend the lexicon.
## Licence
The translation is public domain. The code and the curation are MIT.
TDQS
Scored across 4 tools
Each tool has a clearly distinct role: list_themes for vocabulary browsing, find_quote for situation-based search, random_quote for random selection, and quote_card for rendering. There is no functional overlap between them.
list_themes and find_quote follow a verb_noun pattern, but random_quote and quote_card are noun phrases, mixing conventions. The names are still readable and consistently lowercase with underscores.
With only 4 tools, the server is tightly scoped for its purpose of delivering Art of War quotes. Each tool earns its place and the count is appropriate.
The core workflow of finding, randomizing, and sharing quotes is covered, but there is no way to list all quotes for a given theme or fetch a quote's text by citation. These are notable gaps for a quote-focused server.