The MCP server that compiles your desktop workflow
before it clicks a single pixel.
Every other guide on this stops at claude mcp add terminator and a tool list. None of them tell you that the server ships a first-class typecheck_workflow tool that runs tsc --noEmit against your TypeScript workflow directory and hands the LLM a structured list of TS2345 errors with five lines of code context around each failing line, before any part of your desktop starts moving.
The shape of a typed MCP round-trip
Every other MCP server treats the LLM as the type checker: you write a fuzzy intent, the model picks a tool, the tool runs, and you learn it was wrong when something on your screen moves to a place you did not intend. Terminator inverts that. The LLM writes code. The code is compiled. Only compiling code gets to talk to your desktop.
How one typecheck_workflow call flows end-to-end
Install the server, then scaffold a typed workflow
The server and the CLI ship together. One command registers the MCP, the next command creates a TypeScript project that is already compatible with the compile-before-you-click contract.
The anchor fact: five capture groups, seven lines of context
The part of the source that makes this uncopyable is short. It is a parser and an enricher. The parser maps a TSC line to a typed struct. The enricher pulls three lines above and three lines below the failing line so the LLM can see what it broke.
“src/steps/02-step-two.ts(18,9): error TS2322: Type 'number' is not assignable to type 'string'.”
parse_tsc_output on a real TS2322
What the LLM actually sees in its tool response
After the parser and the enricher finish, the MCP server returns a JSON object through Content::json(TypecheckResult). The response is not a blob of stderr. It is a structured array the model can walk, match on code, and turn into a specific edit against a specific file.
The same contract, enforced from the CLI
A typed MCP surface that only the LLM respects would be a gimmick. The same check runs when you invoke terminator mcp run from a shell, so your CI pipeline and your laptop see the exact same gate. The session below is a real two-pass run: first attempt fails the type check, second attempt passes and executes.
The single line worth remembering
"Fix type errors before running the workflow. Or use --skip-type-check to bypass (not recommended)."
Literal text from crates/terminator-cli/src/typescript_workflow.rs:247-248. The CLI prints it to stderr on every failed type check. The flag exists, but the help string in main.rs:146 names it "not recommended" on the tin.
How authoring actually flows
Five steps take you from a blank directory to a workflow that a Claude Code tab can run against a real QuickBooks window. The compile gate sits between steps three and four.
From init to first successful run
npx terminator init close-month-end
Scaffolds a package.json with @mediar-ai/workflow, a strict tsconfig.json, and a src/terminator.ts that uses `createWorkflow` with a zod input schema. The project's `build` script is literally `tsc --noEmit`, exactly what the MCP server will run.
Write steps as plain TypeScript modules
Each step exports a function that takes a context and returns a typed result. Selectors stay in one place, variables are zod-validated at the workflow boundary, and the TypeScript compiler catches every wrong prop before the runner ever touches your desktop.
The LLM calls typecheck_workflow mid-chat
Before the agent triggers execute_sequence, it can ask the server to type-check the workflow directory. The response is JSON: success flag, error count, and a structured errors array with line numbers and context. The agent edits the file, not the chat history.
`terminator mcp run ./close-month-end`
The CLI detects the TypeScript project, runs tsc --noEmit itself (bun → npx → global tsc), prints a bordered error block if anything fails, and refuses to call execute_sequence. A single code path for humans, agents, and CI.
--start-from-step and .mediar/workflows/<name>/state.json
When execution does start, every step with an id writes its result and env into a per-workflow state.json. Re-run with --start-from-step to pick up exactly where a failure happened, without re-typing anything.
The server is a compiler host too
Six things to keep in your head when you think about what this server actually exposes to the LLM. Three of them are ordinary MCP mechanics. Three of them exist because the authors wanted the LLM to fix its own mistakes without help.
typecheck_workflow is a first-class MCP tool
Sits right next to click_element, type_into_element, and execute_sequence in the same dispatch table. The LLM can ask the server "is this workflow type-correct?" mid-conversation and get a parseable TypecheckResult back.
15 async handlers in server.rs
get_window_tree, get_applications_and_windows_list, click_element, activate_element, validate_element, navigate_browser, open_application, execute_sequence, read_file, write_file, edit_file, copy_content, glob_files, grep_files, typecheck_workflow. Every one routes through the same dispatch path at server.rs line 9953.
The regex is the contract
Each tsc line is parsed with ^(.+?)\((\d+),(\d+)\):\s*error\s+(TS\d+):\s*(.+)$. Five capture groups map directly to file, line, column, code, message. No prose.
Errors ship with code context
enrich_errors_with_context reads the offending file, walks 3 lines up and 3 lines down, prefixes the failing line with " -> ", and returns the whole block so the LLM fixes the real code not a paraphrase.
bun, then npx, then global tsc
The server probes for bun first (faster cold start), falls back to npx, then a global tsc, before giving up. Whatever your dev box has, the check runs.
The CLI guards execution too
terminator mcp run ./workflow-dir refuses to execute a TypeScript workflow until tsc --noEmit passes. You can pass --skip-type-check, but the help text literally says "not recommended".
The full sequence, one frame at a time
What happens between the LLM calling the tool and the LLM reading the result. Notice that step seven, parse_tsc_output + enrich_errors_with_context, is the only step the tsc binary does not participate in. That piece of code is where a blob of stderr turns into something the model can fix.
typecheck_workflow, end to end
Versus the untyped default
The comparison below is not against a specific competitor. It is against the shape most desktop-automation MCP servers land on: YAML or JSON workflows, validated at runtime, reported as exceptions when something goes wrong.
| Feature | Typical MCP automation surface | Terminator MCP |
|---|---|---|
| Workflow authoring surface | YAML or JSON only; typos in selectors surface at runtime | TypeScript project with strict: true, scaffolded by `terminator init` |
| Pre-execution validation | Workflow starts clicking; errors appear mid-run | `terminator mcp run` runs tsc --noEmit first; no clicks until it passes |
| Error feedback to the LLM | Raw stderr blob pasted back into the conversation | Structured TypeError[] with file, line, column, code, message, and 5-line code context |
| Bypass path for iteration | Delete the workflow file and rewrite it | --skip-type-check exists and is documented, but flagged as "not recommended" |
| Tool count the LLM can see | Variable; depends on the server | 15 async handlers in server.rs, all introspectable via `claude mcp get terminator` |
| LLM self-repair loop | LLM guesses from a text dump | LLM calls typecheck_workflow, reads TS2304/TS2345 with context, edits the failing file, retries |
| CI integration | Requires separate CI step to lint/validate | Same tsc --noEmit runs locally and in CI; no divergence between LLM path and pipeline path |
| Cross-platform reach | Often Windows-only | Windows UIA, macOS AX, and Linux AT-SPI under the same TypeScript SDK |
The payoff, in one number
The MCP agent crate defines 0 async tool handlers in crates/terminator-mcp-agent/src/server.rs. Exactly one of them exists to stop the other fourteen from ever running on code that has not compiled. If you remember one thing about this server, let that be it.
The other fourteen are the obvious ones: window trees, click_element, type_into_element, activate, validate, navigate a browser, open an application, execute a whole sequence, read, write and edit files, copy content, glob, and grep. You can see the list in git grep "pub async fn" crates/terminator-mcp-agent/src/server.rs.
Want to see typecheck_workflow run against your own workflow?
Bring a TypeScript automation that almost works. We will wire it into a Claude Code tab, run tsc --noEmit through the MCP, and watch the LLM patch its own errors in real time. 20 minutes, live.
Frequently asked questions
What exactly does the typecheck_workflow tool do?
It takes a workflow_path argument, checks that the path exists and contains a tsconfig.json, then shells out to the TypeScript compiler with `tsc --noEmit` in that directory. It tries `bun tsc --noEmit` first because bun has a faster cold start; if bun is not on PATH it falls back to `npx tsc --noEmit`; if npx is not on PATH it falls back to a global `tsc`. The implementation is at crates/terminator-mcp-agent/src/tools/typecheck.rs lines 142-204. The tool is registered as an MCP tool in crates/terminator-mcp-agent/src/server.rs at lines 9521-9545 with the description "Type-check a TypeScript workflow using tsc --noEmit. Returns structured error information including file, line, column, error code, and message for each type error found."
How are the type errors parsed, and how do I know the parser is accurate?
Each line of tsc stdout/stderr is matched against the regex `^(.+?)\((\d+),(\d+)\):\s*error\s+(TS\d+):\s*(.+)$` in typecheck.rs line 54. The five capture groups map to file path, 1-indexed line, 1-indexed column, the TS error code, and the message. There are two unit tests in the same file at lines 210-228: `test_parse_tsc_output_single_error` verifies a single TS2345 error parses correctly, and `test_parse_tsc_output_multiple_errors` verifies two errors on two lines both parse. Both are the first things that run on every CI build of the MCP agent.
What does the code context attached to each error look like?
After the parser produces a list of TypeError structs, enrich_errors_with_context walks each one, reads the corresponding source file, and builds a block of 7 lines: the 3 lines before the failing line, the failing line itself, and the 3 lines after. The failing line is prefixed with the literal string " -> " and all other lines with 4 spaces. Each line includes its 1-indexed line number, right-aligned in a 4-character field. The logic is at typecheck.rs lines 90-115. The practical effect is that when the LLM reads the tool response, it sees the broken code in its original shape, not a paraphrase, and can edit the file directly.
Does `terminator mcp run` also type-check, or is that only available via MCP?
Both. The MCP tool typecheck_workflow exists so agents can call it explicitly. The CLI runs the same check automatically whenever you point `terminator mcp run` at a TypeScript workflow. The detection lives in crates/terminator-cli/src/typescript_workflow.rs `is_typescript_workflow` at lines 7-60: a `.ts` or `.js` file, or a directory containing package.json plus one of terminator.ts, src/terminator.ts, workflow.ts, or index.ts. If detected, `run_type_check` at lines 154-249 runs tsc and, on failure, prints a bordered error block with a literal hint: "Fix type errors before running the workflow. Or use --skip-type-check to bypass (not recommended)." You can bypass by passing `--skip-type-check` but the flag description in crates/terminator-cli/src/main.rs line 146 says exactly "Skip TypeScript type checking before execution (not recommended)."
Which TypeScript entry points does the project scaffold expect?
Four accepted locations, in priority order: `terminator.ts` at the project root, then `src/terminator.ts`, then `workflow.ts`, then `index.ts`. The logic is in typescript_workflow.rs at `path_to_file_url` (lines 63-90). `terminator init` scaffolds `src/terminator.ts`, which becomes `file:///absolute/path/src/terminator.ts` as the workflow URL. That URL is what the state persistence layer hashes to derive the per-workflow state directory under `mediar/workflows/<folder>/state.json`.
What does the scaffolded project look like?
`terminator init my-workflow` creates a directory with package.json (depends on `@mediar-ai/workflow` at latest, devDeps on typescript ^5 and @types/node ^20), a strict tsconfig.json (target ES2020, module commonjs, strict: true, esModuleInterop, rootDir ./src, outDir ./dist), a src/terminator.ts calling `createWorkflow` with a zod input schema and a list of step imports, and two example step files under src/steps/. The package.json's only script is `"build": "tsc --noEmit"`, which is the exact command the MCP server shells out to when typecheck_workflow runs. The implementation is in crates/terminator-cli/src/commands/init.rs lines 69-120.
How is this different from other desktop-automation MCP servers?
Most MCP automation servers expose click, type, and screenshot primitives and let the LLM stitch them together inside the context window. If the LLM gets a selector wrong, you find out when the click misfires on a real window. Terminator's MCP server moves validation left: you author the workflow as a strongly-typed TypeScript module, the selectors and step return types are part of the TS signature, and both the MCP surface and the CLI refuse to run until `tsc --noEmit` passes. The LLM gets a structured error payload it can act on directly instead of a generic runtime exception, and the same check runs in CI without any divergence.
Does the type check block execute_sequence as well, or only TypeScript workflows?
execute_sequence accepts YAML, JSON, and TypeScript workflows. The pre-execution type check only fires for TypeScript projects, because `is_typescript_workflow` in typescript_workflow.rs only returns true when it sees a TS entry file or a package.json + TS-entry combination. A YAML workflow takes the original path through the execute_sequence engine unchanged. If you want type-level safety on a YAML workflow, the documented pattern is to generate the YAML from a TypeScript file using `createWorkflow` and the zod schemas.
If this one was useful, these walk through the rest of the surface
Other angles on the same server
The Claude Code MCP server that treats your context window as a budget
The same server, seen from the other side: execute_sequence collapses a 20-step workflow into one MCP round-trip, and state.json outlives the Claude Code session.
Windows automation in Python with a real visual debugger
element.highlight() paints a layered Win32 overlay around any control your script touches. The visible cursor of intent that pywinauto never shipped.
Accessibility API desktop automation
Why Terminator reads UIA/AX trees instead of pixels: faster, cheaper, and resilient to theme changes that break vision models.