kurou 烏

烏 crow on the wire / 苦労 toil (we wrote it in rust). a small, deliberately
dim window into one discord server.
a tiny rust MCP server that lets an assistant peek at a discord server and, when it
has something to say, say it. seven tools, no more. by default it talks to discord
over the REST api only - no gateway, no websocket - so it can read, it can post,
but it can never be pinged, mentioned, or summoned by anyone in the server. if you
opt into gateway mode, it can appear online and collect a tiny mention inbox without
ever auto-replying.
this is a personal tool. it started as a ~5-tool rewrite of the ~50-tool
SaseQ/discord-mcp (java/spring/JDA), trimmed
down to just the read-window-plus-voice that one assistant actually needed. if you
stumbled in here, hi - it works, but it was built for an audience of one. use at your
own risk.
what you get
- streamable MCP over stdio (local) or HTTP (hosted)
- seven tools: read server info, list channels, read messages, check mentions, mark mentions seen, send a message, find a user id
- compact message blocks built for an LLM to read, author ids inline
- bearer auth + a tiny PKCE OAuth shim for hosted HTTP
- structurally un-summonable by default: REST only, the gateway is never opened unless you ask for it
- optional gateway presence/mention mode for online status and a pull-based mention inbox
- one small static binary, rustls (no openssl), bundled sqlite when mention mode is enabled
it can post messages. that's a real action in a real server. send_message exists
because the whole point was letting the crow drop the occasional remark, but it's an
LLM with a discord account and no sense of social consequence, so: indoor voice.
quick start
you need a discord bot token and the guild (server) id you want the window
pointed at. the bot has to actually be a member of that server. then:
$env:DISCORD_TOKEN = "your_bot_token_here"
$env:DISCORD_GUILD_ID = "your_server_id_here"
.\kurou.exe --transport stdio
linux/macos:
DISCORD_TOKEN="your_bot_token_here" DISCORD_GUILD_ID="your_server_id_here" ./kurou --transport stdio
stdio is the easy path - your MCP client launches the process and talks over
stdin/stdout. minimal client shape:
{
"mcpServers": {
"kurou": {
"command": "/path/to/kurou",
"args": ["--transport", "stdio"],
"env": {
"DISCORD_TOKEN": "your_bot_token_here",
"DISCORD_GUILD_ID": "your_server_id_here"
}
}
}
}
note: kurou reads its config straight from the process environment (clap/env). it does
not load a .env file on its own. either export the vars first or put them in your
client's env block above.
the tools
guild_id defaults to DISCORD_GUILD_ID on every tool that takes it, so you usually
leave it out. snowflake ids come back as strings, because they outlive js number
precision and nobody wants that bug.
| tool | mutates? | what it does |
|---|
get_server_info | no | guild name, id, member count, description |
list_channels | no | every channel with id, name, kind (Text/Voice/Category/Forum/...), and topic |
read_messages | no | recent messages from a channel as compact blocks, newest first, author ids inline; reactions/attachments/stickers/embeds when present. limit clamps 1-100 (default 50) |
check_mentions | no | reads the collected mention inbox when GATEWAY_MODE=mentions. limit clamps 1-100 (default 20) |
mark_mentions_seen | yes-ish | marks mention inbox rows as seen. pass ids, or omit them to mark all unseen rows |
send_message | yes | posts to a channel. up to discord's 2000 chars, plus optional file attachments. this changes the server |
get_user_id_by_name | no | prefix-search guild members by username/nick, returns ids, display names, nicknames, and <@id> mention strings. limit 1-100 (default 10) |
list_channels returns all channels, not just text ones - a read window is more
useful seeing the whole layout, and the kind field tells you what each one is.
get_user_id_by_name is prefix search (that's what discord's REST endpoint gives
you), so it's good for finding people, not for fuzzy magic.
attachments
send_message carries files three ways, cheapest first:
attachment_urls - already-hosted http(s) links. the crow fetches them itself.
attachment_refs - refs from the upload endpoint (see below). the way to attach a
local file without the bytes passing through the assistant's context.
attachments_inline - { filename, data_base64 }. last resort; the bytes ride in
the tool call and cost tokens, so reach for a ref or url first.