Capture And Slack
БесплатноНе проверенRelays a result screenshot to Slack so you can review a coding agent's output without opening the app.
Описание
Relays a result screenshot to Slack so you can review a coding agent's output without opening the app.
README
An MCP server that relays a result screenshot to Slack so you can review a coding agent's output without opening the app.
You hand an agent a task on your desktop app (e.g. Clonify, a Qt/VTK CAD tool), and end it with "…and send me the result via the capture-and-slack MCP." The agent renders the result to an image and this server posts it to your Slack DM or a channel. You glance at Slack instead of launching the app.
This server does not drive any GUI — it only relays images.
How it fits the loop
agent finishes a task
├─ (A, preferred) runs a repro/test that offscreen-renders the result → /tmp/result.png
│ → send_to_slack(["/tmp/result.png"], "3mm gaussian emboss done")
└─ (B, fallback) the app window is on screen (X11)
→ capture_window_and_post("result", window="Clonify")
→ Slack files_upload_v2 → you see the image in your Slack DM/channel
Path A is the robust one: the app's own headless render (VTK SetOffScreenRendering(1) +
vtkWindowToImageFilter, optionally under QT_QPA_PLATFORM=offscreen) writes a PNG, and this
server just posts it. Path B screenshots the live window and is for when no offscreen render exists.
Tools
| Tool | What it does |
|---|---|
send_to_slack(images, message="", channel=None, title=None, status=None, checks=None, links=None, thread_ts=None, echo_image=False) |
Post image(s) to Slack as a review: a root anchor message (a Block Kit task card when title/status/checks/links are given) with the image(s) threaded beneath it, and 👍/👎 seeded for one-tap review. Returns a JSON status with review_id and the permalink(s). Primary tool. |
capture_window_and_post(message="", window=None, channel=None, title=None, trim=True, allow_fullscreen=False, thread_ts=None, echo_image=False) |
Screenshot the running window (X11/KDE) and post it as a review. window defaults to CAPTURE_WINDOW_TITLE; use "active" or "full". |
capture_window(window=None, trim=True, allow_fullscreen=False) |
Screenshot without posting and return it for inspection. Compose with send_to_slack. No Slack token needed. |
Safety: when a specific
windowtitle can't be matched to an on-screen window, capture returnsNO_WINDOW_MATCHrather than silently grabbing the focused window or the whole desktop (which would leak unrelated apps/secrets to Slack). Passallow_fullscreen=True(orwindow="full") to opt into the whole-screen fallback deliberately. |wait_for_review(review_id, channel=None, timeout_seconds=50, poll_seconds=5)| Block until the human reacts (👍/👎/👀/✏️) or replies in the review thread. Returnsapproved/rejected/changes/seen, orstill_awaitingon timeout — call again to keep waiting. | |check_review(review_id, channel=None)| Non-blocking single read of the current verdict. Use in your own polling loop. | |ask_review(review_id, question, channel=None, timeout_seconds=50, poll_seconds=5)| Ask the reviewer a follow-up question in Slack (e.g. why they rejected) and wait for their typed answer. Posts + broadcasts the question so an away reviewer sees it. Returnsanswered+reply, orstill_awaiting+ask_id. | |wait_for_reply(review_id, since_ts, channel=None, timeout_seconds=50, poll_seconds=5)| Block until a thread reply appears aftersince_ts(e.g. theask_idfromask_review). Returnsanswered+replyorstill_awaiting. |
Image paths must be absolute (the server's working directory differs from the agent's).
Errors come back as a JSON envelope {"code","message","hint"} in an isError result.
Closing the review loop
send_to_slack returns a review_id (the thread's root message ts). The agent seeds a task card,
the human taps 👍/👎 on their phone (or replies "make it 5mm"), and wait_for_review(review_id)
returns the verdict to the agent:
send_to_slack(["/tmp/result.png"], title="3mm emboss", status="pass",
checks={"volume Δ": "+2.1%", "watertight": "yes"}) → {"review_id": "1712…", …}
wait_for_review("1712…", channel="C…") → {"status": "approved", …}
Re-render into the same thread by passing thread_ts=review_id to send_to_slack; add
notify=True to broadcast the re-render back to the channel (🔁) so a watching reviewer is re-pinged.
If the agent needs to ask something (e.g. the reviewer rejected and the agent wants to know
why), it must ask in Slack, not in the terminal — the reviewer is watching Slack. ask_review
posts the question into the thread, pings the reviewer, and returns their typed answer:
wait_for_review("1712…", "C…") → {"status": "rejected"}
ask_review("1712…", "Why — emboss too sharp, or wrong face?") → {"status": "answered", "reply": "too sharp"}
Requirements
- uv and Python ≥ 3.12.
- A Slack bot token (see Slack setup — image uploads need
files_upload_v2; webhooks can't attach files). - For live capture only: an X11 session with ImageMagick
importor KDEspectacleinstalled. Precise per-window targeting usesxdotoolif present, elsexwininfo(already on most X11 desktops) — no install needed. Match by window title or WM_CLASS substring; a class likeQClonifyAppdisambiguates from an editor tab that merely has "Clonify" in its title.
Slack setup (one time)
- Go to https://api.slack.com/apps → Create New App → From an app manifest, pick your
workspace, and paste:
Scopes:{ "display_information": { "name": "Capture and Slack Bot" }, "features": { "bot_user": { "display_name": "capture-and-slack", "always_online": true } }, "oauth_config": { "scopes": { "bot": [ "files:write", "chat:write", "im:write", "reactions:write", "reactions:read", "channels:history", "groups:history", "im:history" ] } }, "settings": { "org_deploy_enabled": false, "socket_mode_enabled": false } }files:write(upload),chat:write(the caption/card),im:write(open a DM when the destination is a user ID). For the review loop:reactions:write(seed 👍/👎),reactions:read(read the verdict), and history (channels:historyfor public channels,groups:historyfor private,im:historyfor DMs) to read threaded replies. Drop the loop scopes if you only ever post one-way. If you add scopes to an existing app, reinstall it so the new grants take effect. - Install to Workspace → authorize → copy the Bot User OAuth Token (
xoxb-…) from OAuth & Permissions. - Pick a destination ID:
- DM to yourself: your member ID
U…(Slack profile → ⋮ → Copy member ID). The bot can DM you without an invite. - A channel: the channel ID
C…(channel → View details), and run/invite @capture-and-slackin it.
- DM to yourself: your member ID
- Provide the token to the server (do not commit it):
cp .env.example .envand fill inSLACK_BOT_TOKENandSLACK_DEFAULT_CHANNEL, or export them in your shell. - (Optional, for precise window targeting)
sudo pacman -S xdotool.
Install & test
uv sync
uv run pytest
Register with Claude Code
Project-scoped (.mcp.json is already in this repo):
{
"mcpServers": {
"capture-and-slack": {
"command": "uv",
"args": ["run", "--directory", "/home/baho/Desktop/capture-and-slack-mcp", "capture-and-slack-mcp"]
}
}
}
The server reads SLACK_BOT_TOKEN / SLACK_DEFAULT_CHANNEL from the environment or a git-ignored
.env. (Keep the token out of .mcp.json, which is committed.)
Configuration
| Env var | Default | Purpose |
|---|---|---|
SLACK_BOT_TOKEN |
— | Bot token (xoxb-…). Required to post. |
SLACK_DEFAULT_CHANNEL |
— | Default destination: U… (DM), C… (channel), or D… (DM channel). |
CAPTURE_WINDOW_TITLE |
Clonify |
Default window title substring for live captures. |
CAPTURE_SLACK_MAX_FILE_MB |
25 |
Soft cap: larger images are auto-shrunk (downscale → JPEG) to fit before uploading. |
CAPTURE_SLACK_HARD_MAX_MB |
200 |
Hard ceiling: files bigger than this are rejected (FILE_TOO_LARGE) instead of shrunk. |
CAPTURE_SLACK_SCRATCH_DIR |
<tmp>/capture_and_slack_mcp |
Where captured/shrunk PNGs are written (created 0700, files 0600). |
CAPTURE_SLACK_SCRATCH_TTL_MIN |
60 |
Reap scratch files older than this (minutes) on access; 0 disables. |
CAPTURE_SLACK_KEEP_SCRATCH |
(unset) | Set (1/true) to never delete/reap scratch files — for debugging. |
LOG_LEVEL |
INFO |
Server log level, written to stderr (DEBUG, INFO, WARNING, …). |
SLACK_MAX_RETRIES |
3 |
Auto-retries for rate-limited (429, honors Retry-After) / connection-error Slack calls. |
SLACK_HTTP_TIMEOUT |
30 |
Per-request Slack HTTP timeout (seconds). |
Development
Module map — only server.py imports the MCP SDK:
server.py— the 3 tools +main().slack_client.py—slack_sdkwrapper:files_upload_v2, DM resolution, error translation.capture.py— X11/KDE window capture. Layered fallback: resolve window id (xdotool → xwininfo) and grab it directly → active window (spectacle) → fullscreen.images.py— Pillow: path/content validation, auto-trim, PNG byte reads.config.py— env-driven settings (with a tiny.envloader).errors.py—ErrorCode+CaptureSlackError(JSON envelope).
License
MIT
Установка Capture And Slack
У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.
▸ github.com/baho0/capture-and-slack-mcpFAQ
Capture And Slack MCP бесплатный?
Да, Capture And Slack MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Capture And Slack?
Нет, Capture And Slack работает без API-ключей и переменных окружения.
Capture And Slack — hosted или self-hosted?
Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.
Как установить Capture And Slack в Claude Desktop, Claude Code или Cursor?
Открой Capture And Slack на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
Gmail
Read, send and search emails from Claude
автор: GoogleSlack
Send, search and summarize Slack messages
автор: SlackRunbear
No-code MCP client for team chat platforms, such as Slack, Microsoft Teams, and Discord.
Discord Server
A community discord server dedicated to MCP by [Frank Fiegel](https://github.com/punkpeye)
Compare Capture And Slack with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории communication
