{"id":"72358df4-f73d-4ae9-a456-cefe8c72dfb9","slug":"citrixmcp","kind":"mcp","name":"CitrixMCP","summary":"MCP server that drives a Windows PowerShell session inside a Citrix/RDP window over the synced clipboard — run commands, control the desktop, transfer files, no SSH/WinRM needed.","owner":"FrancoisChastel","repoUrl":"https://github.com/FrancoisChastel/CitrixMCP","license":"MIT","licenseTier":"permissive","trust":"community","stars":1,"match":null,"fewStars":true,"format":"mcp","createdAt":"2026-09-03T02:53:32.000Z","addedAt":"2026-10-21T06:42:03.211Z","logoUrl":"https://avatars.githubusercontent.com/u/10299319?u=9391503a508c51a58996d09481bfa247b418c843&v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["cloud-infra","files-documents","memory-knowledge"],"tags":["anthropic","automation","citrix","claude","clipboard","powershell","rdp","remote-desktop"],"runtime":"binary","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-21T07:04:01.229Z","sample":false,"url":"https://conduits.dev/tools/citrixmcp","language":"TypeScript","transports":["stdio"],"install":{},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"<div align=\"center\">\n\n# 🖥️ CitrixMCP\n\n### Drive a Windows PowerShell session inside a Citrix / RDP window — from Claude, over the clipboard.\n\nRun commands, control the desktop, capture screenshots, and move files **in and\nout** of a locked-down remote session that has **no SSH and no WinRM** — using\nthe one channel that always crosses the gap: the **synced clipboard**.\n\n[![CI](https://github.com/FrancoisChastel/CitrixMCP/actions/workflows/ci.yml/badge.svg)](https://github.com/FrancoisChastel/CitrixMCP/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)\n[![Node](https://img.shields.io/badge/node-%E2%89%A520-brightgreen)](https://nodejs.org)\n[![MCP](https://img.shields.io/badge/Model_Context_Protocol-server-8A2BE2)](https://modelcontextprotocol.io)\n\n![CitrixMCP demo](docs/demo.gif)\n\n</div>\n\n---\n\n## Why this exists\n\nSome Windows machines are only reachable through a **double hop** — a Citrix\nWorkspace client that publishes an RDP window, which in turn logs into the box:\n\n```\n Your Mac  ──Citrix Workspace──▶  published mstsc.exe  ──RDP──▶  Windows box + PowerShell\n```\n\nThere is no direct network path (no SSH, no WinRM) to that final machine. The one\nthing that reliably travels across all three boundaries is the **clipboard**,\nwhich Citrix keeps synchronized in both directions. CitrixMCP turns that shared\nclipboard cell into a reliable, ordered command-and-file channel, and exposes it\nto Claude (or any MCP client) as a clean set of tools.\n\n## Architecture\n\n```mermaid\nflowchart LR\n    A[\"Claude session<br/>(MCP client)\"] -->|stdio| B[\"CitrixMCP server<br/>Node · macOS\"]\n    B <-->|pbcopy / pbpaste| CB1[\"macOS clipboard\"]\n    CB1 <-->|Citrix clipboard sync| CB2[\"Windows clipboard\"]\n    CB2 <-->|Get / Set-Clipboard| H[\"rdt-agent helper<br/>PowerShell or Python\"]\n    H -->|runs| PS[\"PowerShell · desktop · files\"]\n\n    classDef mac fill:#0b7285,stroke:#083f4d,color:#fff\n    classDef win fill:#5f3dc4,stroke:#3b2593,color:#fff\n    class A,B,CB1 mac\n    class CB2,H,PS win\n```\n\nThe link runs a classic **stop-and-wait protocol** over that single shared cell:\neach message is a full clipboard write of `RDT1|<base64(json)>`, with sequence\nnumbers for ordering and de-duplication, automatic retransmit if the user\nclobbers the clipboard mid-exchange, and chunking for large payloads.\n\n## Features\n\n- 🧑‍💻 **Full PowerShell control** — persistent working directory, real output.\n- 🪟 **Desktop & GUI control** — list/focus windows, move & click the mouse, type text, send key combos.\n- 📸 **Screenshots** — capture the remote desktop as PNG and see it inline.\n- 📦 **File transfer both ways** — binary-safe, chunked, hash-verified.\n- 🔌 **No SSH / WinRM / agents on the network** — clipboard only.\n- 🛡️ **Safe, auditable helper** — no network, no persistence, prints every command.\n- 🔁 **Reconnect-friendly** — survives Citrix disconnect/reconnect and Claude restarts.\n- 🐍 **Two interchangeable helpers** — PowerShell *or* stdlib-only Python (for SRP-locked boxes).\n\n## Quick start\n\n### 1. Register the MCP server (on the Mac)\n\n**Zero-install, straight from the repo** (npm clones and builds it for you):\n\n```bash\nclaude mcp add citrix -s user -- npx -y github:FrancoisChastel/CitrixMCP\n```\n\n<details>\n<summary>Or from a local clone / for Claude Desktop</summary>\n\n```bash\ngit clone git@github.com:FrancoisChastel/CitrixMCP.git && cd CitrixMCP\nnpm install          # installs deps and builds dist/ via the prepare script\nclaude mcp add citrix -s user -- node \"$PWD/dist/index.js\"\n```\n\nClaude Desktop (`claude_desktop_config.json`) or any MCP client:\n\n```json\n{\n  \"mcpServers\": {\n    \"citrix\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"github:FrancoisChastel/CitrixMCP\"]\n    }\n  }\n}\n```\n\n`-s user` registers it for **all** your projects/sessions. Add an `\"env\"` block\nfor any [configuration](#configuration) overrides.\n</details>\n\n### 2. Start the helper (inside the Citrix/RDP Windows session)\n\nPick whichever your locked-down box allows — both speak the same protocol, so the\nserver works with either.\n\n**PowerShell** (`windows/rdt-agent.ps1`):\n\n```powershell\n.\\rdt-agent.ps1 -DryRun   # safe first run: prints commands, executes nothing\n.\\rdt-agent.ps1           # go live\n```\n\n> **“cannot be loaded … blocked by software restriction policies”?** That blocks\n> the `.ps1` *file*, not PowerShell. Check `$ExecutionContext.SessionState.LanguageMode`:\n> - `FullLanguage` → run it interactively: `Get-Content .\\rdt-agent.ps1 -Raw | iex`\n> - `ConstrainedLanguage` → use the Python helper below.\n\n**Python** (`windows/rdt_agent.py`) — standard library only, any Python 3.6+:\n\n```powershell\npython rdt_agent.py --dry-run   # or:  py rdt_agent.py --dry-run\npython rdt_agent.py\n```\n\nLeave the helper running. Stop it anytime with **Ctrl+C**.\n\n### 3. Use it\n\nAsk Claude to `rdt_ping` (confirms the round-trip), then `rdt_run` a command.\nThat’s it. 🎉\n\n`rdt_ping` reports the server version, protocol version, and the helper's\nversion + gzip support, so you can confirm both ends match. The helpers also\nprint their version in the startup banner and support `--version` (Python) /\n`-Version` (PowerShell).\n\n## Tools\n\n| Tool | What it does |\n|------|--------------|\n| `rdt_ping` | Confirm the helper is alive; returns host / user / PowerShell version. |\n| `rdt_run` | Run a PowerShell command/script; working directory persists between calls. The universal tool. |\n| `rdt_launch` | `Start-Process` a program (`powershell.exe`, `notepad.exe`, a path, a document); returns PID. |\n| `rdt_list_windows` | List visible top-level windows as JSON `[{pid, process, title}]`. |\n| `rdt_focus` | Bring a window to the foreground by PID or title substring. |\n| `rdt_send_keys` | Send key combos (.NET SendKeys syntax, e.g. `^c`, `{F5}`), optionally activating a window first. |\n| `rdt_type` | Type arbitrary literal text (special chars auto-escaped; newlines press Enter). |\n| `rdt_mouse` | Move the cursor to `(x, y)` and optionally click (left/right/middle · click/double). |\n| `rdt_screenshot` | Capture the remote desktop as PNG and return it as an image. |\n| `rdt_processes` | List top processes by memory as JSON `[{pid, name, ws_mb, cpu}]`. |\n| `rdt_upload` | Send a local file or inline text into a file on the Windows box (binary-safe, chunked). |\n| `rdt_download` | Pull a file off the Windows box to a local path (binary-safe, chunked). |\n\n**A GUI-driving loop:** `rdt_screenshot` to see → `rdt_list_windows` / `rdt_focus`\nto target → `rdt_mouse` / `rdt_type` / `rdt_send_keys` to act → screenshot to confirm.\n\n## How it works\n\n```mermaid\nsequenceDiagram\n    participant S as CitrixMCP (Mac)\n    participant C as Clipboard (Citrix-synced)\n    participant H as Helper (Windows)\n    S->>C: frame n — req: run \"Get-Date\"\n    C-->>H: synced by Citrix\n    H->>H: execute in the PowerShell session\n    H->>C: frame n+1 — reply: output (chunked if large)\n    C-->>S: synced by Citrix\n    S->>S: reassemble → return to Claude\n```\n\nOne operation is in flight at a time (there is a single clipboard cell), each\nframe is acknowledged before the next, and a frame is retransmitted if the peer\ngoes quiet — so a stray copy/paste during a transfer is recovered, not fatal.\nThe `src/protocol.ts` file is the single source of truth for the frame format;\nboth helpers implement it identically (verified byte-for-byte in CI).\n\n## Configuration\n\nEnvironment variables on the **server** (Mac) side, all optional:\n\n| Variable | Default | Meaning |\n|----------|---------|---------|\n| `RDT_POLL_INTERVAL_MS` | `250` | Clipboard poll interval. |\n| `RDT_RETRANSMIT_MS` | `2500` | Re-assert a frame if the peer goes quiet this long. |\n| `RDT_FRAME_TIMEOUT_MS` | `60000` | Give up on one frame round-trip. |\n| `RDT_EXEC_TIMEOUT_MS` | `120000` | Default per-command wait. |\n| `RDT_CHUNK_BYTES` | `262144` | Payload bytes per frame before base64. |\n| `RDT_RESTORE_CLIPBOARD` | `true` | Restore the user’s clipboard after each op. |\n| `RDT_DEBUG` | `false` | Verbose frame logging to stderr. |\n\nHelper flags mirror these: `-PollMs`, `-ChunkBytes`, `-DefaultTimeoutMs`,\n`-DryRun`, `-DenyRegex`, `-Quiet` (PowerShell) / `--dry-run`, `--deny-regex`, … (Python).\n\n## Safety\n\nThe Windows helper runs on what may be a sensitive machine, so it is deliberately\nconservative:\n\n- **One readable file**, no obfuscation — read it top to bottom before running.\n- **No network.** It never opens a socket; the clipboard is the only channel.\n- **No persistence.** Runs in your console only; Ctrl+C stops it. No service,\n  scheduled task, registry key, or startup entry.\n- **Does nothing on its own.** It only runs the commands you send — the same\n  ones you could type yourself. Runs as you, no elevation.\n- **Transparent.** Every command is printed before it executes.\n- **Opt-in guardrails.** `-DryRun` / `--dry-run` echoes without executing;\n  `-DenyRegex` / `--deny-regex` refuses matching commands.\n\nOnly reacts to clipboard values beginning with the `RDT1|` marker — normal\ncopy/paste is ignored.\n\n## Testing\n\n**Offline** (no Windows, no Citrix):\n\n```bash\nnpm test                         # build + relay self-test + builder unit tests\npython3 scripts/selftest_py.py   # Python responder self-test\n```\n\n**Live** (helper running, clipboard synced) — drives every tool through the real\nMCP interface:\n\n```bash\nnode scripts/test-all.mjs                 # safe: never sends input to the desktop\nnode scripts/test-all.mjs --interactive   # also tests keyboard input via Notepad\n```\n\nFocused probes: `scripts/ping-client.mjs` (ping + one command) and\n`scripts/transfer-test.mjs` (700 KB binary round-trip with a Windows-side hash check).\n\n## Large files\n\nFile transfer (`rdt_upload` / `rdt_download`) is built for big files, both ways:\n\n- **Gzip in transit, on by default.** Compressible data (logs, dumps, text,\n  most office files) shrinks a lot before it hits the clipboard. The server only\n  compresses when the helper advertises support (via `ping`), so an older helper\n  never receives bytes it can't decompress — it transparently falls back to raw.\n- **Streamed to and from disk.** Neither side loads the whole file into memory,\n  so multi-GB files won't OOM the server or the helper.\n- **Integrity is easy to verify** — hash on the Windows side with\n  `rdt_run \"(Get-FileHash -Algorithm SHA256 -LiteralPath 'C:\\\\path').Hash\"` and\n  compare with a local `shasum -a 256`.\n\nThroughput is still bounded by clipboard round-trip latency (see below), so for\n**very** large or already-compressed payloads, prefer a faster channel (a\nnetwork share both machines can reach) when one is available. Pass\n`compress: false` for already-compressed files (zip, jpg, mp4).\n\n## Limitations\n\n- **Control channel, not a bulk pipe** — ops are serialized; throughput is bounded\n  by clipboard sync latency × round-trips. Compression widens the effective pipe\n  for compressible data, but incompressible multi-GB transfers are still slow.\n- **Don’t copy/paste while an op runs** — it transiently owns the clipboard.\n- **Manual bootstrap** — you start the helper once by hand (by design; no auto-start\n  on a sensitive box). After that, the agent can launch more windows/apps itself.\n\n## Contributing\n\nIssues and PRs welcome. Please run `npm test` (and, if you touched the protocol or\nthe Python helper, `python3 scripts/selftest_py.py`) before opening a PR — CI runs\nthe same checks. Keep the two helpers byte-compatible with `src/protocol.ts`.\n\n## License\n\n[MIT](./LICENSE) © Francois Chastel\n"}