tcmts.cli.mcp

Types

Ctx type
Job type
A command run in the background, for `tcm_job` to report on: its record, `<id>.json` in its session's jobs directory (`jobsDirFor`), beside its output (`<id>.log`) and how it ended (`<id>.exit`, a `JobEnd`). A process of its own (`runJob`), it outlives the server that started it, and a server started later follows it.
JobEnd type
How a job ended: its exit code (a tool's run: 1 if it answered with an error), when, whether `stopJob` stopped it, and what a tool's run answered.
JobState type
A job as it is now (`readJob`): its record, what it has printed (`out`, from `log`), how it ended, or `lost` if its process is gone without saying how (stopped from outside, or with the machine).
McpServer type
An MCP server running on a codebase, as its record (`serversDir`) says: its process (the supervisor, `tcm mcp`), its session's branch, and who it's for (`sessionClient`).
Result type
Tool type

Server

TOOLS Tool[]
callLive (bin: string[], root: string, name: string, args: Record<string, unknown>, env?: Record<string, str…
A tool call answered by the codebase's own tcm, as it is now (`live`): a server of its own (`tcm run --bin tcm -- mcp`, every command of it run the same way) is asked it, so the whole tool is the new one, what it says included, not only the commands it runs. `bin` runs this tcm; `env` adds to the server's environment (its session's branch); `onLine` hears the output of what it runs as it comes (its progress notifications).
endSession (root: string, branch: string, stopping?: number | undefined) => boolean
When the server of a session on a branch made for it stops: the branch goes, with its directory, if everything on it is on the codebase's own branch already (nothing was changed, or it landed), no scratch file holds unsaved work, and no running server works on it (but the one stopping, `stopping`: its process). Otherwise it stays, for the user to look at and land (`tcm merge`), or for the agent to go on with (a server started again from the same directory does: `startSession`; or `tcm mcp --branch <it>`). Decided in a write transaction, as servers claim their branches. Whether it went.
jobIds (dir: string) => number[]
The jobs a directory holds, by id, in order.
jobWhen (dir: string, id: number, ready: (j: JobState) => boolean, ms?: number) => Promise<JobState>
A job in `dir` once `ready` says it is (its output, or its end), waited for up to `ms`.
jobsDirFor (root: string, branch?: string | undefined) => string
Where a session's background jobs keep their files: in its own directory (`sessionDirFor`), or `.tcm/jobs` for a server on the codebase's own branch (`--direct`).
lastJobId (dir: string) => number
The last job id a directory has given (`last` says it, as a job's files are cleared away): the next is one more.
leaseFile (root: string, branch: string) => string
Where a session's branch says whose it is (the client its server was for, `sessionClient`) and since when: in the session's directory, which goes with the branch.
liveMcpServers (root: string) => McpServer[]
The MCP servers running on a codebase now (the records of ones that stopped are cleared away). A process that's running but can't be signalled (another user's, or outside a sandbox) is running all the same, and a record that can't be read is a server whose branch isn't known: only a stopped one's goes.
mcpWorker (bin: string[], root: string, recycle?: number) => { run(argv: string[], env?: Record<string, strin…
The MCP server's worker: one tcm (`bin internal-worker`, in `root`) kept running, so a tool's command doesn't start one. It runs one command at a time; `run` gives undefined if it's busy (run that one in a tcm of its own), and a command whose worker exits under it is undefined too. After `recycle` commands it's started afresh, so nothing a command leaves behind piles up.
moveSession (root: string, server: McpServer, to: string) => { moved: boolean; text: string; }
Move a session's server (`server`, its record) onto `to` without a restart (`tcm_cli ["switch", <branch>]` in a session): a branch it owns, such as an earlier session's that holds its work, as long as no other server works on it, and not the codebase's own (work lands there with tcm_merge). Claimed as `startSession` claims one; the branch it leaves, one a server made for itself, goes if main has all of it (`endSession`). What it did, or why not.
plain (s: string) => string
processAlive (pid: number) => boolean
Whether a process is running (one that can't be signalled, another user's, is).
readJob (dir: string, id: number) => JobState | undefined
A job in `dir` as it is now, or undefined if it has no record there.
readLease (root: string, branch: string) => { client: string; at: number; } | undefined
A session's lease (`leaseFile`), if it has one.
registerMcpServer (root: string, branch: string | undefined, client?: string | undefined) => void
Say this process is an MCP server on the codebase (on `branch`, for `client`); undone when it exits.
restoreSessionBranch (root: string, branch: string) => boolean
A session's branch made again from the codebase's own if it's gone while its server runs (deleted by a tcm from before branches were claimed in a transaction): the session goes on, with its scratch files. Whether it was gone.
runJob (spec: { cmd: string[]; cwd: string; log: string; exit: string; call?: { name: string; arguments: R…
A background job's own process, run from its source (`node -e`, by `startJob`), so it uses only `deps`. It runs `spec.cmd`, its output going to `spec.log` as it comes, and says how it ended in `spec.exit` (a `JobEnd`; `stopped` if a SIGTERM, `stopJob`'s, stopped it). With `call`, `cmd` is an MCP server (`tcm mcp`) asked that tool call: the tool's progress goes to the log, and what it answers to `text`. (Run from its source, it isn't seen running by the coverage that caches end-to-end tests: its own tests, `testJobs…`, run when it changes.)
serveMcp (root: string, bin: string[], version: string, branch?: string | undefined, build?: string | undefi…
Serve MCP on stdin/stdout. Each tool runs a `tcm` command (this same program: `bin`) in `root`, so a tool does exactly what the command does at a terminal, without its output reaching the protocol channel. One that finishes soon (reading and editing the codebase: `WORKER_COMMANDS`) runs in a worker that stays up, with no tcm to start; the rest, and any while the worker is busy, in a tcm of their own. They report briefly (`TCM_BRIEF=1`): an agent reads every line, so what it can ask for (dependents by name, lint warnings) is a count. A call with an option its tool doesn't have is refused, naming the build (`build`, the hash of tcm's code) and the options it has.
serveWorker (run: (argv: string[]) => Promise<number>) => Promise<void>
The MCP server's worker (`tcm internal-worker`): tcm kept running, so a tool doesn't start one. It reads commands from stdin, a JSON line each (`{ id, argv, env }`), and runs them one at a time in this process, as `run` does; each one's output goes to stdout and stderr as a command's would (what it starts too), and ends with a mark on each: `\0tcm-done <id> <exit code>` on stdout, `\0tcm-done <id>` on stderr. What a command changes of the process (its environment, where it is, the exit code) is put back after it.
serverRecord (root: string, pid: number) => string
The record of the MCP server whose process is `pid`.
serversDir (root: string) => string
Where the MCP servers running on a codebase say so: a file each, by process id, with its branch.
sessionClient (cwd: string) => string
Who a session's server is for: the directory its agent runs from (as the shell that started it says, `PWD`, when that's this directory: one reached through a link, as a worktree's codebase is, is told apart from another), so a server started again for the same agent goes on with its branch.
stampOf (file: string) => string | undefined
When a file was last replaced or written, to notice a new version: its inode and time, or undefined if it's gone.
startJob (dir: string, job: { argv: string[]; tool?: string | undefined; }, run: { cmd: string[]; cwd: strin…
Start a background job in `dir` (a session's jobs directory, `jobsDirFor`): `runJob`, detached (a process group of its own, with nothing tying it to this process), running `run.cmd` in `run.cwd` with `run.env`, or asking it `run.call`. Its record, output and end are files there, so it runs on when the server that started it stops (tcm replaced), and the next one follows it. Ids go on from the directory's last, whatever's been cleared away since; finished jobs' files a day old are cleared away first (`sweepJobs`).
startSession (root: string, client: string, named?: string | undefined, wait?: number) => Promise<{ branch: stri…
Claim the branch a session's server (this process) works on, and say so in its record: `named` (`tcm mcp --branch`, made from the codebase's own if it's new), or else the branch of this client's latest session (one it made, or moved onto: its lease, `leaseFile`), if it holds work main doesn't have (or a scratch file holding unsaved work) and no other server works on it (so a server started again, after a crash or a client reconnecting, goes on with it), or else a new one (`session/<date>-<id>`). It's claimed in a write transaction, as `endSession` and `moveSession` decide in one, so two servers never share a branch and none is deleted under its server. A branch another server of the same client holds (one being replaced that hasn't stopped yet) is waited for, `wait` milliseconds at most; a named one another client's server holds is refused.
stopJob (dir: string, id: number, grace?: number) => Promise<JobState | undefined>
Stop a running job, and what it runs (its process group): asked to (SIGTERM), then made to (SIGKILL) if it hasn't ended in `grace` ms. Its end says it was stopped. The job as it is then.
superviseMcp (argv: string[], env: ProcessEnv | (() => ProcessEnv), cwd: string, watch: string, poll?: number) =…
Run the MCP server `argv` as a child and relay the protocol, starting it again when `watch` (this tcm's own program) is replaced: a new version installed, or a new known-good tcm verified. The new server starts at once: the client's handshake is replayed to it, what the client sends from then on goes to it, and the client is told the tools changed. The old one answers what it was asked before (nothing is waited on: a request never answered can't hold the new tcm back), then is stopped, or after ten minutes all the same. So an agent picks up a new tcm, tools and all, without restarting. A server that stops while the client is still there (it crashed) is started again the same way, three times a minute at most, and what it hadn't answered is answered with an error, to ask again. `env` is each server's environment, as it is when it starts (a session's branch, which it can move onto another). Resolves with the server's exit code.
sweepJobs (dir: string, age?: number, now?: number) => number[]
Clear away the files of jobs in `dir` that ended (or whose process is gone) more than `age` ms ago. The ids they had aren't given again (`lastJobId`).
sweepSessions (root: string) => string[]
What sessions that ended without their server stopping (it was killed) left, cleared away when a session's server starts: a branch a server made for itself (`session/<date>-<id>`) that no running server works on goes as `endSession` would take it (main has all of it, and no scratch file holds unsaved work), and so does a session directory whose branch is gone, unless a scratch file in it holds unsaved work (`tcm status` lists those). Each is decided in a write transaction, as servers claim their branches (`startSession`): one claimed meanwhile stays. What went: branches, and directories relative to the codebase.
withCodebase <T>(root: string, fn: (cb: Codebase) => T) => T
`fn` on the codebase at `root` as its own branch is, not this process's session's (`TCM_BRANCH`).
writeLease (root: string, branch: string, client: string) => void
Say `branch` is `client`'s session's, as of now (`leaseFile`).
writeServerRecord (root: string, server: McpServer) => void
Write a server's record whole (to a file beside it, then renamed over it): a server reading the records as it's written never sees half of one.

Shared

BRANCH_TOOLS ReadonlySet<string>
The tools that take `branch`: those that only read, each a command that takes `--branch` (`readBranch`). One reads another branch (another session's, say) as it is, from inside a session, without moving it.
GUIDE Record<string, string>
INSTRUCTIONS "This project's code is a tcm codebase (.tcm/codebase.sqlite): Unison's codebase model for TypeScri…
What the model is told when it connects: the way of working, in brief. tcm_guide has the rest.
LANG Record<string, string>
The language a client highlights a file in, by its extension (or a file definition's format).
LIVE_TOOLS ReadonlySet<string>
The tools that take `live`: those that change code, tcm_test, and those that read the codebase (what they print is the tcm's that prints it). Not tcm_job (a job is the session's, whichever tcm runs it), tcm_guide, or tcm_cli (its `live` runs the command with the codebase's own tcm, rather than asking another server).
MAX 60000
REINIT "tcm-supervisor-reinit"
The id the supervisor gives the handshake it replays to a restarted server.
WORKER_COMMANDS Set<string>
Commands the MCP server's worker runs: ones that finish soon (reading the codebase, editing it). The rest (tests, running code, servers) get a tcm of their own, as at a terminal.
background { type: string; description: string; }
branchOption { readonly type: "string"; readonly description: "read this branch instead of yours (another sessio…
The `branch` option of the tools that only read (BRANCH_TOOLS).
briefOutput (argv: string[], out: string) => string
A command's output as a tool says it, briefly: a test run's passing tests as a list of names rather than a line each (hundreds of them), so what failed, what ran, and the counts, are what an agent reads. The tests that ran are named; the cached ones too when tests were named or something failed (else the summary counts them). A merge's or revert's changes by name and count (`mergeSummary`: `tcm diff` has the code). Waiting for the machine's suite slots, a section's last line of it, above its summary, unless the summary says how long it waited. Other commands' as they are.
fence (text: string, lang: string) => string
`text` as a Markdown code block in `lang`: its fence longer than any run of backticks inside it.
isSessionBranch (branch: string) => boolean
A branch a session's server made for itself: `session/<date>-<id>`.
liveArgv (argv: string[]) => string[]
A command as the codebase's own tcm runs it.
liveOption { readonly type: "boolean"; readonly description: "run the codebase's own tcm (a package of it whos…
The `live` option of the tools that change code, of tcm_test, and of the ones that read the codebase (what each says is shaped by the tcm that says it): they run the codebase's own tcm (a package of it whose command is `tcm`), as it is now, rather than this server's, the last one released or verified. For developing tcm in its own codebase: a change to how tcm edits, reports or shows code, used before it's verified.
mergeSummary (out: string) => string
`tcm merge`'s report, briefly, for an agent that made the change it lands: what it said (conflicts, what was merged as text, what no longer typechecks), the definitions edited or conflicted by name (without their code), and how many of each other kind (re-hashed dependents, added, …).
message { type: string; description: string; }
A tool's `message`: what the history entry its change makes is for (the command's `-m`).
messageArgs (a: Record<string, unknown>) => string[]
`-m <message>`, for a tool given `message`.
names { type: string; items: { type: string; }; description: string; }
next (argv: string[], out: string, code: number) => string | undefined
A short note on what to do after a command, from what it printed.
onBranch (argv: string[], branch: string | undefined) => string[]
A read command's arguments with `--branch <branch>` (after the command's name: before a `--`), if it's given.
optionsProblem (name: string, inputSchema: Record<string, unknown>, args: Record<string, unknown>, build?: string …
What's wrong with a tool call's arguments, if anything: options the tool doesn't have (`unknownOptions`), each named with the ones it does, and the build that said so (a call written for a newer tcm, or a misspelt option, would otherwise be ignored, and the call do something else: run in another directory, say).
patchImports { type: string; description: string; items: { type: string; properties: Record<string, unknown>; re…
tcm_patch's `imports`: what to import that importing what the code uses can't find (`tcm patch --imports`).
schema (props: Record<string, unknown>, required?: string[]) => { type: string; properties: Record<string,…
str (v: unknown) => string
strs (v: unknown) => string[]
unknownOptions (inputSchema: Record<string, unknown>, value: unknown, at?: string) => { at: string; known: string[…
The arguments a call gives that its input schema doesn't have, where the schema allows nothing else (`additionalProperties: false`), in objects and arrays of them too: each by where it is (`patches[0].imports`), with the options known there.
viewMarkdown (out: string) => string
`tcm view`'s output as Markdown, for clients that render it: what it said first (the names it didn't find), then for each definition a line with its names, hash and where it runs, and its code (or a file's content) as a highlighted block.

Tests

testBriefOutputKeepsFailuresAndCounts () => void
testBriefOutputKeepsOneWaitingLineOrTheTotal () => void
testJobsMakeAToolCall () => Promise<void>
A tool's job asks an MCP server the call: what it hears of the tool's progress is its output, and what the tool answered (an error: exit 1) its end.
testJobsRunOnTheirOwn () => Promise<void>
A background job is a process of its own, writing its output and how it ended to files: what's read from them is what it printed, its exit code, and its record (this process started it). Ids go on.
testLease () => void
A lease says whose a session's branch is; one that's missing or isn't one says nothing.
testLiveMcpServers () => void
A running server is seen as one; a stopped one's record is cleared. One running that can't be signalled (pid 1, here) is running all the same, and so is one whose record can't be read (named by its process).
testLostJobsAndSweeping () => void
A job whose process is gone without saying how it ended is lost; a day after a job ends (or is lost), its files are cleared away, and its id isn't given again.
testMergeSummary () => void
testOptionsProblem () => void
testSessionClient () => void
A server's client is the directory it runs from, as the shell that started it named it when that's the same one (a link to it, say), and as it is otherwise.
testStopJobStopsWhatItRuns () => Promise<void>
Stopping a job stops what it runs too, and its end says it was stopped.
testTestToolRunsSuitesAgainstAnotherBuild () => void
testToolsRunAnOlderBuild () => void
tcm_cli's `build` runs the command with a kept build (`tcm --build`), on a repro's codebase with `cwd`; tcm_test's `bin` takes a build's name as it does a tcm.js.
testWriteServerRecord () => void
A record is written whole, as the server it names reads it back.