Skip to content

gabrielmaialva33/winx-code-agent

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

626 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Winx fairy mascot

✨ Winx - MCP Server for Shell & Coding Agents ✨

πŸ¦€ Native Rust implementation inspired by WCGW, built for local code-agent workflows

Language License MCP stdio transport

A local MCP server you can hand to a coding agent and stop worrying about the shell.

Winx is the MCP server I wanted while running Claude, Codex, and friends against real repos: one process that handles the shell, file IO, and PTY-backed interactive sessions, written in Rust so it doesn't fight you on stdio.

It started as a Rust port of WCGW but isn't a Python wrapper. Everything runs on a real PTY (via portable-pty), cd actually sticks, Ctrl+C actually interrupts, and background shells survive long-running TUIs without leaking output buffers into your token budget.

What you get

  • A stateful bash session per thread with proper PTY semantics - foreground, background, status checks, text input, Enter/Ctrl-C/Ctrl-D, raw ASCII. Multiline scripts and top-level command shorthand both work; NUL bytes are rejected before they reach the shell.
  • Workspaces with three modes: wcgw (full access), architect (read-only), code_writer (allowlist of commands and write globs). The command allowlist is parsed with tree-sitter, so it checks every command on the line - pipelines, &&/||/;, command substitution, subshells - not just the first word, and can't be bypassed with ls && curl … | sh or ls $(rm …).
  • A resilient PTY: a shell that won't return to a prompt (even after Ctrl-C) is auto-reset at the same cwd/mode, child processes are reaped on drop, and prompt detection is robust to a custom PS1. Opt into zsh with WINX_SHELL=zsh.
  • File reads with WCGW-style line ranges (file.rs:10-40, file.rs:10-, file.rs:-40). Active files are tracked and prioritized in the repository context across calls.
  • File writes and SEARCH/REPLACE edits that survive ambiguous matches, indentation drift, and the usual unicode quote-mismatches from LLMs. Writes are blocked when the file hasn't been read or the cached content is stale, the success message shows a compact diff of what changed, and recent edits are reversible with UndoEdit. MultiFileEdit applies a change across several files all-or-nothing (validated in memory first, so a failure on the last file leaves the earlier ones untouched).
  • Tree-sitter code navigation via CodeMap: a token-budgeted symbol map of a file or the whole repo, or a definition/reference lookup for a symbol name - the semantic view that plain grep can't give you, across 11 languages.
  • ContextSave for handing a task summary plus its files to the next session - including workspace context, active files, git status/diff, and terminal sharing for proper resumption. Resuming reopens the saved project root and token-caps the restored memory so it never overflows the context window.
  • ReadImage so multimodal clients can pull screenshots, mockups, error PNGs, etc.
  • Clean, token-aware shell output: cursor/ANSI noise from interactive programs (REPLs, progress bars) is rendered away through a terminal emulator, and mechanical repetition is collapsed losslessly (line [winx: Γ—N]) so build/install logs don't blow your context budget. Toggle the collapsing with WINX_NO_COMPRESS. When output still overflows the cap, the dropped head is streamed to a scratch file under .winx/scratch/ the agent can re-read, instead of being lost.
  • Secret redaction on by default: provider API keys, JWTs, PEM private-key blocks and user:pass@ URLs are scrubbed from all tool output and saved memory before they reach the model (disable with WINX_NO_REDACT=1). An opt-in Landlock sandbox (WINX_SANDBOX=1, Linux) adds a kernel-enforced second layer that confines writes to the workspace and hides the home directory.
  • Two transports: stdio for local clients, plus an optional token-gated Streamable HTTP server (winx serve --http) for remote MCP clients like ChatGPT - see Remote access.

MCP Tools

Tool What it does
Initialize Boots the workspace, picks the mode, hands you a thread_id. Call this first or everything else errors out. With no workspace path it spins up a scratch playground; resuming a task (task_id_to_resume) reopens its saved project root.
BashCommand Runs commands, polls long-running ones, sends Enter/Ctrl-C, drives TUIs. Supports is_background, status_check, send_text, send_specials, send_ascii, allow_multi, plus screen (a stable point-in-time frame of an interactive TUI with the cursor position; pass diff:true for only the lines that changed since your last look) and wait_for_turn (block until the TUI is ready for input, via per-app or configurable recognizers). When a foreground command finishes, the status line reports its real exit code (parsed from the prompt marker), so failures surface without grepping stderr.
ReadFiles One or many files, with line numbers. Append :10-40 to a path for a range. When the token budget is hit it tells you the exact line + file:N-M syntax to resume from instead of silently dropping the tail.
FileWriteOrEdit Full overwrites or SEARCH/REPLACE blocks (with optional @start-end line anchors to pin a repeated block). Validates file read coverage and freshness before writing, reports any fuzzy tolerances it had to apply, then runs a tree-sitter syntax check (18+ languages) and points at the offending line with a snippet. The success message includes a compact diff of what changed.
MultiFileEdit Edits several files all-or-nothing: every file's edit is validated and computed in memory first, and only if all succeed is anything written - so a SEARCH that fails to match in the last file leaves the earlier ones untouched. For a single file use FileWriteOrEdit.
UndoEdit Reverts a file to its content before the last FileWriteOrEdit/MultiFileEdit this session (per-file, last ~10 edits kept in memory). Refused if the file changed on disk since your edit; a brand-new file's creation isn't undoable.
ContextSave Dumps task description + file globs into a single text file with workspace context, active files, and git status/diff for clean handoff and task resumption.
ReadImage Returns a native MCP image content block (not base64 as text), so multimodal models actually see the image. Confined to the workspace (like ReadFiles) and size-capped.
CodeMap Tree-sitter code navigation, in one tool with two operations. outline: a symbol map (functions, types, methods, ...) - a file returns its definitions, a directory (or empty) a relevance-ranked, token-budgeted repo symbol map, in 11 languages. references: where a name is defined and used (called) across the repo, counting only real identifier occurrences (never inside strings/comments, unlike grep), definitions first. For plain-text/regex search and file discovery, just use rg/fd/grep via BashCommand.

Search/Replace editing

Standard block syntax:

<<<<<<< SEARCH
old content
=======
new content
>>>>>>> REPLACE

Things the matcher forgives so you don't have to babysit the model:

  • atomic: ambiguous or missing matches abort without touching the file
  • adjusts replacement indentation when the LLM gets the leading whitespace wrong
  • strips ReadFiles line numbers if they leak into a SEARCH block
  • normalizes the usual "smart quote" / em-dash / ellipsis substitutions
  • uses neighboring blocks to disambiguate when the same snippet appears twice
  • single-line substring edits work - you don't need the whole line in SEARCH
  • retries once with \" unescaped when the model over-escapes quotes in SEARCH
  • refuses edits that only matched after too much fuzzy fixup, and rejects blocks that match in too many places - so you re-read instead of corrupting the file
  • anchor a block to a line number to pin one of several identical snippets - <<<<<<< SEARCH @42 (or a range @42-50); a stale anchor falls back to the normal search, so it never fails an otherwise-valid edit
  • tells you on success which tolerances it had to apply (so you learn your SEARCH drifted), and on a miss how close the nearest match was, with ~ marking the lines that diverged

Install

cargo install winx-code-agent

Binary lands in ~/.cargo/bin - every config snippet below assumes that's on $PATH. If your MCP client launches with a sterile env, swap winx-code-agent for the absolute path (which winx-code-agent).

Needs Rust 1.75+, Linux/macOS/WSL2, and a real terminal (any modern one - Winx spawns its own PTY).

Claude Code (CLI)

One-liner via the CLI (stdio is the default transport):

claude mcp add winx -- winx-code-agent

Or drop a .mcp.json in your project root:

{
  "mcpServers": {
    "winx": {
      "command": "winx-code-agent",
      "env": { "RUST_LOG": "winx_code_agent=info" }
    }
  }
}
Claude Desktop

Add to your config file (~/Library/Application Support/Claude/claude_desktop_config.json on macOS, %APPDATA%\Claude\claude_desktop_config.json on Windows):

{
  "mcpServers": {
    "winx": {
      "command": "winx-code-agent",
      "env": { "RUST_LOG": "winx_code_agent=info" }
    }
  }
}

Restart Claude Desktop after saving.

Codex (OpenAI CLI)

One-liner:

codex mcp add winx -- winx-code-agent

Or edit ~/.codex/config.toml:

[mcp_servers.winx]
command = "winx-code-agent"
env = { RUST_LOG = "winx_code_agent=info" }
Cursor

Add to ~/.cursor/mcp.json (or .cursor/mcp.json for project-local):

{
  "mcpServers": {
    "winx": {
      "command": "winx-code-agent",
      "env": { "RUST_LOG": "winx_code_agent=info" }
    }
  }
}
VS Code (Copilot Chat / MCP)

Add to .vscode/mcp.json:

{
  "servers": {
    "winx": {
      "type": "stdio",
      "command": "winx-code-agent"
    }
  }
}
Zed

Add to your Zed settings (~/.config/zed/settings.json):

{
  "context_servers": {
    "winx": {
      "source": "custom",
      "command": "winx-code-agent",
      "args": [],
      "env": { "RUST_LOG": "winx_code_agent=info" }
    }
  }
}
Windsurf

Add to ~/.codeium/windsurf/mcp_config.json:

{
  "mcpServers": {
    "winx": {
      "command": "winx-code-agent",
      "env": { "RUST_LOG": "winx_code_agent=info" }
    }
  }
}
OpenCode

Add to opencode.json:

{
  "mcp": {
    "winx": {
      "type": "local",
      "command": ["winx-code-agent"],
      "enabled": true,
      "environment": { "RUST_LOG": "winx_code_agent=info" }
    }
  }
}
Gemini CLI

Add to ~/.gemini/settings.json:

{
  "mcpServers": {
    "winx": {
      "command": "winx-code-agent",
      "args": [],
      "env": { "RUST_LOG": "winx_code_agent=info" }
    }
  }
}
agy (Google Antigravity CLI)

agy is Google's new Gemini-powered CLI (Go binary, usually at ~/.local/bin/agy). No mcp add subcommand yet - it reads MCP servers from JSON.

Edit ~/.gemini/config/mcp_config.json (also ~/.gemini/antigravity/mcp_config.json if you run the Antigravity IDE alongside):

{
  "mcpServers": {
    "winx": {
      "command": "winx-code-agent",
      "env": { "RUST_LOG": "winx_code_agent=info" }
    }
  }
}

If winx-code-agent is not on the agy process $PATH, swap command for the absolute path (~/.cargo/bin/winx-code-agent after cargo install winx-code-agent).

Continue.dev

Add to your ~/.continue/config.yaml:

mcpServers:
  - name: winx
    command: winx-code-agent
    env:
      RUST_LOG: winx_code_agent=info
Kiro

Add to ~/.kiro/settings/mcp.json:

{
  "mcpServers": {
    "winx": {
      "command": "winx-code-agent",
      "env": { "RUST_LOG": "winx_code_agent=info" }
    }
  }
}
Warp

Settings β†’ MCP Servers β†’ Add MCP Server:

{
  "winx": {
    "command": "winx-code-agent",
    "env": { "RUST_LOG": "winx_code_agent=info" }
  }
}
Roo Code

Add to your Roo Code MCP config:

{
  "mcpServers": {
    "winx": {
      "type": "stdio",
      "command": "winx-code-agent"
    }
  }
}
Other clients (generic stdio)

Any client that speaks stdio MCP works with this shape:

{
  "mcpServers": {
    "winx": {
      "command": "winx-code-agent",
      "args": [],
      "env": { "RUST_LOG": "winx_code_agent=info" }
    }
  }
}

If your client launches Winx with an empty $PATH, swap command for the absolute path ( ~/.cargo/bin/winx-code-agent).

Build from source

For unreleased changes or a custom build:

git clone https://github.com/gabrielmaialva33/winx-code-agent.git
cd winx-code-agent
cargo install --path .

Or run it without installing:

cargo run --release

Check it's wired up

List MCP tools in your client. You should see nine entries: Initialize, BashCommand, ReadFiles, FileWriteOrEdit, MultiFileEdit, UndoEdit, ContextSave, ReadImage, CodeMap. The first call always has to be Initialize; Winx tracks workspace + mode per thread.

Remote access (ChatGPT & other remote MCP clients)

By default Winx speaks MCP over stdio - the local transport every desktop client (Claude Desktop, Cursor, VS Code) uses. For clients that live in the cloud and can't reach your machine over stdio - like ChatGPT's developer-mode custom connectors - Winx can also serve MCP over Streamable HTTP:

winx serve --http --bind 127.0.0.1:8000 --token "$(openssl rand -hex 24)"

The MCP protocol is served at /mcp. Every request must carry the token in the Authorization: Bearer <token> header (header-only - a ?token= query parameter would leak the secret into proxy/tunnel access logs and browser history). Without a token the server refuses to start - serving a shell over the network without auth is remote code execution waiting to happen. The endpoint also caps request bodies (64 MB) and times out stuck requests (120 s).

Flag Purpose
--http Serve over Streamable HTTP instead of stdio.
--bind Listen address. Defaults to 127.0.0.1:8000. Keep it on loopback.
--token Shared secret required on every request. Falls back to the WINX_HTTP_TOKEN env var.
--allowed-host Extra Host authority to accept (your tunnel hostname). Repeatable. Loopback is always allowed.

Remote clients run in the cloud, so the endpoint has to be reachable over HTTPS - put a tunnel in front of the loopback listener and allow its hostname through the built-in DNS-rebinding guard:

# 1. tunnel first, to learn the public hostname
cloudflared tunnel --url http://localhost:8000
#    -> https://<random>.trycloudflare.com

# 2. start Winx, allowing that host
winx serve --http --bind 127.0.0.1:8000 \
     --token "$(openssl rand -hex 24)" \
     --allowed-host <random>.trycloudflare.com

In ChatGPT (Settings β†’ Apps β†’ Advanced β†’ Developer mode), add a connector with:

  • URL: https://<random>.trycloudflare.com/mcp
  • Authentication: bearer / API-key token set to <your-token>, so the connector sends it as Authorization: Bearer <your-token> (the token is no longer accepted in the URL)

Remote clients are effectively stateless - they don't reuse the MCP session between tool calls - so the HTTP transport shares one shell session across all requests: the shell Initialize creates stays alive for the lifetime of the server, and later BashCommand calls find it. Reuse the same thread_id across calls.

Warning

The HTTP transport puts arbitrary shell and file access on the network. Anyone with the token (and URL) gets a shell on your machine as your user - and not just inside the workspace, since BashCommand in wcgw mode isn't path-restricted. Bind to loopback, keep it behind an authenticated tunnel, prefer architect/code_writer mode or a container, and shut it down when you're done.

Environment variables

All optional - Winx works out of the box without any of these.

Variable Effect
RUST_LOG Log verbosity, e.g. winx_code_agent=info. At info you get the per-call audit trail (tool name, arg summary, duration, ok/error).
WINX_HTTP_TOKEN Shared secret for the HTTP transport, used if --token isn't passed (see Remote access).
WINX_NO_COMPRESS Set to 1 to disable output compression and see raw, uncollapsed shell output (the [winx: Γ—N] collapsing is on by default).
WINX_NO_REDACT Set to 1 to disable secret redaction. By default winx scrubs high-confidence credentials (provider API keys, JWTs, PEM private keys, user:pass@ URLs) from all tool output and saved memory, replacing each with [REDACTED:<rule>]. Turn this off only when you knowingly need a raw value.
WINX_SANDBOX Set to 1 to enable an opt-in Landlock filesystem sandbox (Linux 5.13+, EXPERIMENTAL). Confines winx and its shell to write only the workspace (the cwd at startup) plus /tmp, and makes the home directory unreadable, so a manipulated agent can't read ~/.ssh/~/.aws or modify files outside the project. Coarse and best-effort: a command needing a path outside the allowlist fails. Degrades to a warning (unsandboxed) on older kernels.
WINX_SANDBOX_RO_PATHS / WINX_SANDBOX_RW_PATHS :-separated absolute paths to additionally allow read-only / read-write under WINX_SANDBOX (e.g. WINX_SANDBOX_RO_PATHS=$HOME/.cargo:$HOME/.rustup so cargo still works).
WINX_TURN_RECOGNIZER_CONFIG JSON {"busy":[…],"awaiting_input":[…],"awaiting_approval":[…]} of marker strings/regexes. With recognizer:"configurable", lets wait_for_turn drive an arbitrary TUI without bespoke code.
WINX_CODING_TOKEN_BUDGET / WINX_NONCODING_TOKEN_BUDGET Override the per-file token budget for ReadFiles (and saved memory) - raise it for large-context models. Defaults: 24000 / 8000.
WINX_KEEP_TAIL_PIPE Set to 1 to keep a trailing | tail … instead of stripping it. Winx truncates output server-side, so by default it drops a redundant trailing tail (wcgw parity).
WINX_USE_SCREEN / WINX_ATTACH_TERMINAL Run the shell inside screen/tmux so you can attach to the live session. Set to screen, tmux, or any truthy value; Winx prints an attach hint on Initialize.
WINX_OPEN_CONTEXT Set to 1 to open the saved context file in your default app after ContextSave.
WINX_SHELL Set to zsh to run the session under zsh instead of bash (opt-in; bash stays the default). Falls back to bash if zsh isn't on PATH or the mode is restricted.
WINX_SERVER_INSTRUCTIONS Extra operator instructions appended to every Initialize response (e.g. house rules for the agent).

Hacking on it

cargo fmt --all
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features

CI runs the same three. If you touch src/state/pty.rs or anything in src/tools/bash_command.rs, the regression suite at tests/bash_pty_regression_test.rs is what protects against the usual TUI/PTY foot-guns - run it first.

Robustness is also fuzzed and model-checked:

  • proptest feeds arbitrary/adversarial bytes into the live terminal emulator, the ANSI stripper, and the exit-code parser, asserting they never panic and stay within the viewport. (This is how we found - and worked around - a vt100 underflow on tiny grids and a reflow panic on column shrink that would otherwise crash the panic = "abort" release.)

  • loom exhaustively model-checks the session pin counter (the lock-free guard that keeps an in-flight session from being LRU-evicted) across every thread interleaving. It's behind a feature so it doesn't perturb the normal build:

    cargo test --features loom --lib loom_

A note on security

By default this is a local (stdio) MCP server. Anything connected to it can read files, edit files, and run shell commands inside the workspace - same blast radius as letting the model into your terminal. The optional HTTP transport (--http) extends that reach to the network; see Remote access for the extra precautions it demands.

Two things are on by default to reduce the blast radius: secret redaction scrubs high-confidence credentials from all tool output and saved memory (WINX_NO_REDACT=1 to disable), and the PTY shell's whole process group is killed on teardown so background jobs it spawned don't leak.

If you want a tighter leash:

  • architect mode disables writes and most commands;
  • code_writer mode lets you allowlist commands and write globs;
  • WINX_SANDBOX=1 enables an opt-in Landlock filesystem sandbox (Linux): writes are confined to the workspace plus /tmp, and the home directory is unreadable, so a manipulated agent can't read ~/.ssh/~/.aws or modify files outside the project.

SECURITY.md has the disclosure process and threat model.

License

MIT - Gabriel Maia (@gabrielmaialva33)

About

πŸ¦€ A high-performance code agent written in Rust, combining the best features of WCGW for maximum efficiency and semantic capabilities.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Contributors