Terminator MCP server|Typed from the first tool call

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.

M
Matthew Diakonov
10 min read
4.9from 1.2k GitHub stars
Rust MCP agent in production
Windows UIA + macOS AX + Linux AT-SPI
TypeScript SDK with strict: true
Works under Claude Code, Cursor, and raw stdio
0async handlers in server.rs
0regex capture groups
0lines of context per error
0tsc fallbacks (bun → npx → global)

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

Claude Code
Cursor
Raw stdio client
typecheck_workflow
tsc --noEmit
src/**/*.ts
TypecheckResult

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.

install.sh

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.

crates/terminator-mcp-agent/src/tools/typecheck.rs
crates/terminator-mcp-agent/src/tools/typecheck.rs
TS2322

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.

typecheck_workflow response

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.

close-month-end ~ two runs, one fixed

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

1

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.

2

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.

3

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.

4

`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.

5

--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

Claude / LLMMCP stdioMCP servertsc --noEmitsrc/**/*.tscall_tool typecheck_workflow { workflow_path: "./close-month-end" }JSON-RPC over stdiospawn bun tsc --noEmit (falls back to npx, then global tsc)read tsconfig.json + src/**/*.tstype checkstderr lines in TSC formatparse_tsc_output + enrich_errors_with_contextTypecheckResult { success, errors[], error_count }structured JSON payload, LLM edits the offending file

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.

FeatureTypical MCP automation surfaceTerminator MCP
Workflow authoring surfaceYAML or JSON only; typos in selectors surface at runtimeTypeScript project with strict: true, scaffolded by `terminator init`
Pre-execution validationWorkflow starts clicking; errors appear mid-run`terminator mcp run` runs tsc --noEmit first; no clicks until it passes
Error feedback to the LLMRaw stderr blob pasted back into the conversationStructured TypeError[] with file, line, column, code, message, and 5-line code context
Bypass path for iterationDelete the workflow file and rewrite it--skip-type-check exists and is documented, but flagged as "not recommended"
Tool count the LLM can seeVariable; depends on the server15 async handlers in server.rs, all introspectable via `claude mcp get terminator`
LLM self-repair loopLLM guesses from a text dumpLLM calls typecheck_workflow, reads TS2304/TS2345 with context, edits the failing file, retries
CI integrationRequires separate CI step to lint/validateSame tsc --noEmit runs locally and in CI; no divergence between LLM path and pipeline path
Cross-platform reachOften Windows-onlyWindows UIA, macOS AX, and Linux AT-SPI under the same TypeScript SDK
typecheck_workflowtsc --noEmitTS2345TS2304bun tsc firstnpx tsc fallbackparse_tsc_outputenrich_errors_with_context--skip-type-check (not recommended)terminator initrole:Button && name:Save@mediar-ai/workflowz.object({ ... })createWorkflow

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.

terminatorDesktop automation SDK
© 2026 terminator. All rights reserved.