Capture And Slack
FreeNot checkedRelays a result screenshot to Slack so you can review a coding agent's output without opening the app.
About
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
Installing Capture And Slack
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/baho0/capture-and-slack-mcpFAQ
Is Capture And Slack MCP free?
Yes, Capture And Slack MCP is free — one-click install via Unyly at no cost.
Does Capture And Slack need an API key?
No, Capture And Slack runs without API keys or environment variables.
Is Capture And Slack hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Capture And Slack in Claude Desktop, Claude Code or Cursor?
Open Capture And Slack on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
Gmail
Read, send and search emails from Claude
by GoogleSlack
Send, search and summarize Slack messages
by 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)
Klavis AI
Open Source MCP Infra. Hosted MCP servers and MCP clients on Slack and Discord.
Work90210/APIFold
Turn any REST API into a hosted MCP server. 18 free public servers (GitHub, Stripe, Slack, OpenAI, Notion, and more) — no setup required, bring your own API key
by Work90210arikusi/deepseek-mcp-server
MCP server for DeepSeek AI with chat, reasoning, multi-turn sessions, function calling, thinking mode, and cost tracking.
by arikusihashgraph-online/hashnet-mcp-js
MCP server for the Registry Broker. Discover, register, and chat with AI agents on the Hashgraph network.
by hashgraph-onlineprofullstack/mcp-server
A comprehensive MCP server aggregating 20+ tools including SEO optimization, document conversion, domain lookup, email validation, QR generation, weather data,
by profullstackWayStation-ai/mcp
Seamlessly and securely connect Claude Desktop and other MCP hosts to your favorite apps (Notion, Slack, Monday, Airtable, etc.). Takes less than 90 secs.
by waystation-aiCompare Capture And Slack with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All communication MCPs
