Skip to main content
Glama

Ink Dev MCP

Local stdio-only MCP server for inspecting, exercising and debugging React/Ink terminal applications.

Install

Requires Node.js 24.11+ and a native build toolchain for node-pty (Python, make, C/C++ compiler).

npm install
npm run build

Configure your MCP client to spawn the server over stdio:

{
  "mcpServers": {
    "ink-dev": {
      "command": "node",
      "args": ["/absolute/path/to/ink-mcp/dist/index.js"],
      "env": {
        "INK_MCP_WORKSPACE_ROOT": "/absolute/path/to/workspace",
        "INK_MCP_ALLOW_EXECUTION": "1"
      }
    }
  }
}

This is not an HTTP service. It does not listen on a port and must not print diagnostics to stdout.

Related MCP server: ptywright

MCP tools

Five tools are advertised by default:

Tool

Description

ink_project_check

Inspect dependencies, scripts, versions, and compatibility findings

ink_run_start

Start an Ink CLI in a PTY, optionally wait, and return its screen/status

ink_run_interact

Send keys and/or resize, optionally wait, and return the resulting screen/status

ink_run_observe

Read screen/status, optionally wait, or request bounded raw PTY output

ink_run_stop

Terminate and clean up a run

ink_project_check returns { project, valid, findings }. The three active session tools return a screen snapshot and process status; when waitFor is given, they also return wait: { matched, elapsedMs }. A timed-out wait returns matched: false rather than an MCP error. Wait conditions are text_present, text_absent, screen_changed, screen_stable, and process_exit. Conditions requiring a text value enforce it at runtime.

The read-only ink_run_observe can optionally return output: { output, offset, nextOffset, truncatedBefore, runId } when passed output: { offset?, limit? }. Raw output is omitted by default.

Example development loop

  1. Call ink_project_check with projectRoot: ".".

  2. Call ink_run_start with projectRoot: ".", executable: "node", args: ["dist/cli.js"], and waitFor: {condition: "text_present", text: "Select an option"}.

  3. Call ink_run_interact with runId, keys: ["ArrowDown", "Enter"], and waitFor: {condition: "screen_stable", stableMs: 300}. Inspect screen.text and wait.matched.

  4. Use ink_run_observe to inspect the screen or await process_exit without sending input.

  5. Always stop the process with ink_run_stop.

Run handles persist across MCP calls; exited runs are automatically reaped after five minutes or earlier when the four-run capacity is needed. The screen snapshot is the current terminal viewport, not raw output with ANSI stripped.

Screen snapshots include a revision. Pass it as waitFor.sinceRevision to ink_run_observe to detect output completed since that snapshot. ink_run_interact automatically captures a revision before input for screen_changed unless an explicit since or sinceRevision is supplied. A revision indicates completed terminal output (or a resize), not necessarily a change in visible text. screen_stable requires a completed render and a quiet interval. After keyboard input it also requires a new render; input that produces no output times out with matched: false rather than returning the previous stable frame.

The runtime uses PTY read backpressure when the terminal emulator falls behind. ink_run_output offsets and limits count UTF-16 code units, not bytes, and will not split a surrogate pair. ink_run_stop attempts group termination on POSIX and uses Windows-compatible termination, escalating after a bounded grace period, even if the POSIX group leader exits before its descendants. It returns stopped: false and retains the run if the PTY leader's exit cannot be confirmed. Process-tree cleanup is best-effort; OS-level isolation remains necessary for untrusted child processes.

Legacy client compatibility and Ink guidance

The previous eleven-tool interface remains available with INK_MCP_TOOLSET=legacy, including ink_project_inspect, ink_project_validate, ink_docs_search, ink_run_screen, ink_run_keys, ink_run_resize, ink_run_wait, ink_run_output, and ink_run_status. This mode replaces, rather than augments, the compact tool list. The default is INK_MCP_TOOLSET=compact; other values are rejected at registration.

Curated Ink reference snippets now live in skills/ink-development/references/ink-guidance.json. Clients supporting MCP Skills or ordinary resources can load them from skill://ink-development/references/ink-guidance.json. Legacy ink_docs_search searches that same local resource; no network request is made. Omitting the bundled skill leaves the server and other tools available; ink_docs_search reports a tool error when its guidance is absent.

Local execution safety

PTY code execution is disabled by default. Set INK_MCP_ALLOW_EXECUTION=1 to enable it. INK_MCP_WORKSPACE_ROOT defaults to the server working directory. It limits which project directories can be selected and rejects path-style executables that resolve outside that directory at validation time. This is a path-selection convenience, not a security boundary: bare executable names (such as node or bash) resolve through PATH, and command arguments are unrestricted. Even an executable inside the workspace can read or modify files elsewhere.

To reduce accidental credential exposure, spawned processes inherit only common launch variables (for example PATH, HOME, temporary-directory settings, locale, and platform settings), not the entire MCP server environment. Additional variables can be provided through ink_run_start.env. Overrides of PATH, NODE_OPTIONS, NODE_PATH, LD_* and DYLD_* are rejected (case-insensitively); TERM is fixed to xterm-256color. This filtering does not prevent trusted code or unrestricted command arguments from accessing the host.

The server bounds retained processes, output history, terminal dimensions, user input and waits. Local code execution is not sandboxed. Child processes run with the server user's permissions, including access to files and credentials available to that user. Do not run untrusted projects. For untrusted execution, use OS-level isolation with restricted mounts, credentials and network access. Do not expose the stdio process through a network bridge without separately designing isolation and authentication.

ESM and CommonJS

The published @xterm/headless package has a CommonJS entry point that may fail when imported with ESM named exports on Node 24. The runtime uses createRequire(import.meta.url) to load it and node-pty, and Vite leaves both dependencies external.

Development

npm run lint
npm run format:check
npm run build
npm run test:ci
npm run test:smoke
npm run verify:skill-package

The smoke test launches the compiled dist/index.js over MCP stdio, checks tool discovery, and exercises a PTY through startup, screen reads, input, exit, and cleanup. It also repeats this workflow with optional skills omitted. This protects against type-check-passing but runtime-failing ESM import regressions.

Use MCP Inspector to exercise the server:

npx @modelcontextprotocol/inspector node ./dist/index.js

Documentation guidance is bundled and indicative. Verify API details against the exact Ink version installed in the target project.

Repo structure

  • src/index.ts — stdio entry point

  • src/tools.ts — MCP tool registration and validated schemas

  • src/runtime/registry.ts — PTY lifecycle and headless terminal screen

  • src/runtime/project.ts — project analysis and legacy guidance search

  • skills/ink-development/references/ — published Ink guidance resource

  • src/skills.ts and skills/ — agent skill publishing

  • scripts/smoke-stdio.mts — compiled entry-point integration test

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    Related MCP Connectors

    Related MCP Servers