MCP server
The log MCP server is how every agent works with log-cli: it carries matt's rules into the agent's context, serves the guides and the use cases, names on every tool the rules, guide and use cases it falls under, and the hooks keep agents on it. Rules 47, 95 and 106 (rule 95 replaces rule 47's long instructions with short ones that send the agent to the full guide; rule 76 puts rule one first).
What the server carries
REQ-MCP-01: The instructions carry the core rules, rule one first
The server's instructions, sent at initialize, list the core rules each with its rule number, and the first of them is rule one ("if we don't know, we do this", rules 73-76), followed by rule 30.
Checked by: code and SubQ
REQ-MCP-02: The instructions send the agent to the full guide first
The instructions say to load getGuide({ topic: "logMcpInstructions" }) before the other log tools, once per session. Rule 95.
Checked by: code (anchors)
REQ-MCP-03: AGENTS.md is the resource log://agents
The server lists one resource, log://agents, and reading it returns AGENTS.md as it is in the checkout.
Checked by: code and SubQ
REQ-MCP-04: Every tool names the rules it falls under
Every tool's description ends with a line naming AGENTS.md (log://agents) and the rules it falls under: its command's own rules from COMMAND_RULES and the rules every tool falls under (30, 38, 39, 40, 41).
Checked by: code and SubQ
REQ-MCP-05: Every tool names its guide
Every tool whose command belongs to a guide topic says in its description to load that guide with getGuide({ topic }) before first using it. A flow tool names the flows guide.
Checked by: code and SubQ
Guides and use cases
REQ-MCP-06: getGuide reads a guide or a use case
The getGuide tool takes a topic (one of the guide topics, read from docs/guides/<topic>.md, the core guide when none is given) or a useCase (<platform>/<page>, read from docs/useCases/<platform>/<page>.md), and an unknown one is answered with an error listing the ones there are.
Checked by: code and SubQ
REQ-MCP-07: Every tool names the use cases that call it
A use case page names its tools in backticks. Every tool named on a use case page carries a line in its description naming those use cases as getGuide({ useCase: "<id>" }), placed before its guide line.
Checked by: code and SubQ
REQ-MCP-08: Every tool declares Claude Code's result limit
Every tool the server lists carries anthropic/maxResultSizeChars in its _meta, so a guide or a big result arrives whole.
Checked by: code and SubQ
REQ-MCP-09: The pre-commit review asks SubQ whether the use cases are stale
On pre-commit, check:useCases reads the latest Claude Code session for the repo (matt's asks and the tools called) and asks SubQ, for every use case page, whether it is still current, stale or should move, and whether the session did repeatable work no page covers. A stale or misplaced page, or uncovered work, stops the commit unless that page, or a new page, is staged. A commit that stages only the landing site (site/) and the commit log (log.md, log/<date>.md) isn't reviewed (siteOnly): site work is never a use case (matt, 2026-11-06).
Checked by: code and SubQ
Through the MCP only
REQ-MCP-10: A log command run by hand from a shell is refused
The PreToolUse hook refuses a shell command that runs log … or bun bin/log.ts …, and names the MCP tool route instead. Rule 106.
Checked by: code and SubQ
REQ-MCP-11: The terminal-only prompts are let through
A hand-run command whose name is in TERMINAL_ONLY (launch, login, mcp, chat pin, cookies save, messenger pin) is not refused, since matt runs those prompts in a terminal.
Checked by: code and SubQ
REQ-MCP-12: A stale MCP server is said to matt
When the running MCP server started before the newest change to log-cli's code, the engines hook says so. When no reload proxy is running (rule 167), it tells matt to restart the app and never to work around it with a hand-run command. When the proxy is running, there is no line while the reload is due, a line naming what the reload waits on (a call or a job), or a line saying why the new code didn't start.
Checked by: code and SubQ
Local changes first
REQ-MCP-13: Uncommitted local changes are said loudly
The engines hook lists the uncommitted changes in log-cli's code (src, python, vendor, bin, scripts, package.json) under >>> UNCOMMITTED LOCAL CHANGES, and says to read each one's diff and never overwrite, revert, stage, publish or ship them without matt. Rule 106.
Checked by: code and SubQ
REQ-MCP-14: They are said at session start and whenever they change
The engines hook writes its text at every session start, and on a prompt only when the text differs from the last one it wrote.
Checked by: code and SubQ
REQ-MCP-18: Any command is found and run with logSearch, logGet and logCall
catalogTools in src/mcp/catalog/index.ts lists three tools, which the ListTools handler in src/mcp/server/index.ts puts right after getGuide. catalogOf (src/mcp/catalog/helpers/entries.ts) makes one entry per command tool from commandTools, its …Live twin folded in as liveTool, with the id catalogIdOf gives (<platform>.<command>.<form words>, no platform for any). Each terminal-only command (TERMINAL_ONLY) is an entry too, with terminalOnly set and no tool.
logSearch:searchCatalog(src/mcp/catalog/helpers/search.ts) scores the query's words against each entry's id, summary, input names and usage, keeps only the named platform (andany) when a platform is named, and answersstrongwhen the first result has every word,closestwhen results have at least half, andnonewith up to 3 near misses otherwise.logGet:renderGet(src/mcp/catalog/helpers/view.ts) answers the entry's inputs, required first, alogCallline fromexampleInputs, a live line when there is aliveTool, the CLI form fromcliExample, its guide andrulesLine. A terminal-only entry says matt runs it in a terminal. An unknown id getsdidYouMean.logCall:resolveCallrefuses an unknown id, a terminal-only entry,liveon an entry with noliveTool, and any input not among the entry's inputs (naming the ones it takes, and that nothing was run). OtherwiserunToolCallruns the entry'stool, or itsliveToolwhenliveis true, with the inputs andconfirmLive,confirmBypassandwaitMs, soguardCall,playRefusal, jobs and claims apply as for that tool, and the play footer names that tool.
Every answer ends with helpLines. src/mcp/catalog/catalog.test.ts fails when an entry's exampleInputs don't pass guardCall. matt, 2026-11-07. Rule 163.
Checked by: code and SubQ
REQ-MCP-17: One warm context layer routes matt's prompts and puts the play back after compaction
scripts/contextLayer/service.ts keeps the route index and its embedder loaded on 127.0.0.1 at CONTEXT_PORT (src/contextLayer/service/types.ts), and answers /hook with hookAnswer (src/contextLayer/hook/client.ts). The UserPromptSubmit hook for scripts/hooks/useCases.ts carries service: "/hook" in src/agentHooks/registry/index.ts, so hookCommand writes serviceHookCommand: a curl to the service, with the bun script after ||. routePrompt in src/useCases/router/client.ts:
- returns nothing for a text
isPromptrefuses (NOT_A_PROMPT); - corrects words with
correctTyposagainst the index's vocabulary; - embeds
windowsOfthe prompt; - scores each route with
denseScores(its utterances among theTOP_Kclosest) andsparseScores, atDENSE_WEIGHT; - names matches at or above the route's threshold (default
DEFAULT_THRESHOLD); - otherwise gives a
menuof theMATCHES_SHOWNclosest at or aboveDEFAULT_MENU_FLOOR.
playMatchText writes >>> PLAY MATCH for matches, >>> MAYBE A PLAY for a menu, and nothing for neither. On SessionStart, hookAnswer reads the session's transcript with sessionPlayOf and returns playReminder for the last play it loaded. scripts/useCases/benchRouter.ts scores the router 5-fold on ~/.logCli/contextLayer/labelledPrompts.json, kept on matt's machine and never committed because the repo is public and reports plays caught, wrong fires on "none", the right play in the top 3 and p50/p95/p99; with --fit it writes the threshold that catches the most plays under FALSE_FIRE_CAP. matt, 2026-11-07. Rule 159.
Checked by: code and SubQ
REQ-MCP-16: The prompt is matched to a play, and every play is a skill
matchPlays in src/useCases/router/client.ts scores a prompt against every use case from listUseCases by content words (contentWords, stop words and links left out) over each page's quoted "How the owner asks for it" phrases and its title (phrasesOf), keeps those at MATCH_FLOOR (0.6) or above, best first, at most MATCHES_SHOWN (2), and playMatchText turns them into a >>> PLAY MATCH line telling the agent to load the top one with getGuide({ useCase }) before anything else; with no match it says nothing. The UserPromptSubmit hook scripts/hooks/useCases.ts, registered in src/agentHooks/registry/index.ts, writes that text as additionalContext only when there is one. scripts/docs/syncSiteSkills.mjs writes every docs/useCases/<platform>/<page>.md as the skill log-<platform>-<page> (its description the title and lead, its when_to_use the page's own asks or else its title, its body saying to load the play with getGuide first) into .claude/skills/ and /.well-known/skills/, and --check refuses one out of step. matt, 2026-11-07: "how do the guides get CALLED when I say something". Rule 158.
Checked by: code and SubQ
REQ-MCP-15: The loaded use case is the session's play, and sends need one
When getGuide is called with a useCase, runToolCall in src/mcp/server/index.ts makes it the active play with setActivePlay (src/mcp/server/helpers/activePlay.ts), which reads the page's steps table (stepsOf: each row's number, step, tools named in backticks, and done when). Every other tool's answer then ends with playFooter: the play's id and title, the step this tool is and what counts as done, and the next step with its tools, or that the tool isn't one of the play's steps. A tool in PLAY_REQUIRED (the live sends as matt) is refused by playRefusal with playMissing while no play is loaded, naming the use cases whose pages name that tool. matt, 2026-11-07: the guides were not being called for actions. Rule 157.
Checked by: code and SubQ