
Vent
from bnomei
Have your agents file their own bug reports
vent-mcp
Allow your agent to complain before the same paper cut becomes tomorrow's bug.
vent-mcp is a small STDIO MCP server that gives agents a non-destructive
place to send actionable feedback while they work. Agents can report blocked
work, repeated failures, missing capabilities, confusing workflows, or
operational friction without interrupting the task flow.
The crate is named vent-mcp; the installed binary is named vent. The Rust
library surface supports that binary and is not a stable embedding API.
The idea pairs well with Benjamin Verbeek's talk, The agent that files its own bug reports and the official Lovable blog post.
CLI
With no arguments, vent starts the STDIO MCP server:
ventUse the same binary from a shell when the cli feature is enabled:
vent list
vent "The queue changed mid-run."
vent --channel automation "The failing check output was hard to correlate."
vent --mcpSuccessful CLI delivery prints the event id and channel:
vented aZ8pQ2xK to feedbackMessage text is trimmed before delivery. Empty messages and unknown channels are rejected before any sink receives an event.
MCP tools
vent-mcp exposes a small tool surface:
| Tool | Purpose |
|---|---|
vent | Send actionable feedback to the configured default channel or to a named channel. |
list_channels | List configured channel names and descriptions when multiple channels are available. |
When the config contains only the default channel, list_channels is hidden and
the vent input schema only contains message. When multiple channels exist,
list_channels is exposed and vent accepts an optional channel.
Example multi-channel vent input:
{
"message": "The failing check output was hard to correlate with the changed file.",
"channel": "automation"
}The vent response is an acknowledgement:
{
"ok": true,
"eventId": "aZ8pQ2xK",
"channel": "automation"
}If delivery fails, ok is false and error contains the first sink failure.
The eventId is a short trace id, not a deduplication key. Agents should not
send repeated vents for the same issue unless they have new root-cause evidence.
Channels, sinks, and providers
vent-mcp keeps routing deliberately simple:
- A channel is the route the agent can choose, or omit to use
default_channel. - A sink is a concrete destination, such as local JSONL logging or a webhook.
- A provider is a webhook payload shape.
Sink names and channel names do not have to match. For example, an automation
channel can write to the default log and post to Discord:
default_channel = "feedback"
[[channels]]
name = "feedback"
description = "General feedback."
sinks = ["log"]
[[channels]]
name = "automation"
description = "Build, test, CI/CD, deployment, scheduler, or pipeline failures that blocked progress."
sinks = ["log", "discord-automation"]
[[sinks]]
type = "jsonl"
name = "log"
[[sinks]]
type = "webhook"
name = "discord-automation"
provider = "discord"
url = "https://discord.com/api/webhooks/..."
timeout_ms = 10000With this config, channel = "automation" vents are written to vents.jsonl
and posted to Discord. Other channels go only to the sinks they list.
Webhook providers
Webhook sinks POST JSON. With no provider, or with provider = "raw", the raw
vent event is sent unchanged.
Built-in provider maps include:
| Provider | Shape |
|---|---|
zapier, make, n8n, pipedream, workato | Raw canonical event fields. |
ifttt | message, channel, and project mapped to value1, value2, and value3. |
slack, mattermost | Text plus attachment-style project field. |
discord | content plus an embed field for project. |
microsoft_teams, google_chat, webex | Text-only message field. |
Custom provider maps live in the same TOML config file. The left side is a
canonical event field and the value is a dotted JSON output path. Numeric path
segments create arrays. If field_label_key is set, paths ending in .value
also get a generated label such as Project.
[providers.discord]
field_label_key = "name"
message = "content"
project = "embeds.0.fields.0.value"
[[sinks]]
type = "webhook"
name = "discord-automation"
provider = "discord"
url = "https://discord.com/api/webhooks/..."
timeout_ms = 10000Webhook headers read values from environment variables:
[[sinks]]
type = "webhook"
name = "private-endpoint"
url = "https://example.test/vent"
[[sinks.headers]]
name = "Authorization"
env = "VENT_WEBHOOK_AUTH"If a webhook returns a non-2xx response, the error preview is shortened and known URL or header secrets are redacted before the caller sees it.
Development
Run the test suite:
cargo testBuild a release binary:
cargo build --releaseSource anchors:
- src/main.rs: binary mode selection, config loading, CLI output, and MCP server startup.
- src/config.rs: config path resolution, defaults, validation, and built-in provider maps.
- src/server.rs: MCP tool definitions and dynamic tool-surface shaping.
- src/delivery.rs: message trimming, channel selection, event construction, and acknowledgement output.
- src/sinks.rs: JSONL writing, webhook delivery, env-backed headers, timeout handling, and error redaction.
- src/provider.rs: provider path validation and webhook JSON rendering.
- tests/cli.rs: process-level CLI behavior and config bootstrapping coverage.
cargo install vent-mcpQuickstart
Use this path when you want vent available to a local MCP client and a default
JSONL feedback log.
Prerequisites
- Rust 1.88 or newer and Cargo, when installing from crates.io or source.
- An MCP client that can run a local STDIO server, such as Codex or Claude.
Install
cargo install vent-mcpThis installs the vent binary with the default cli and webhook features.
Create the default config
Run:
vent listOn first run, vent creates a default config at
$XDG_CONFIG_HOME/vent-mcp/config.toml or
~/.config/vent-mcp/config.toml. The default config contains one feedback
channel and one local JSONL sink.
Expected output:
feedback (default) - Blocked work, repeated failures, or confusing workflows. Avoid routine progress updates.JSONL events are written to vents.jsonl beside the config file unless you set
[logging].jsonl_dir.
Register the MCP server
If vent is on your PATH, add it as a local STDIO MCP server:
codex mcp add vent -- vent
claude mcp add --transport stdio vent -- ventUse an absolute path to vent if your MCP client does not inherit your shell
PATH.
Installation
Cargo
cargo install vent-mcpGitHub Releases
Download a prebuilt archive from the
GitHub Releases page, extract it,
and place vent on your PATH.
From source
git clone https://github.com/bnomei/vent-mcp.git
cd vent-mcp
cargo build --releaseThe binary is written to target/release/vent.
Feature builds
Build without webhook and HTTP dependencies:
cargo build --release --no-default-featuresBuild JSONL-only delivery while keeping the shell CLI:
cargo build --release --no-default-features --features cliWhen the cli feature is disabled, the binary only accepts a bare MCP server
invocation. Any CLI arguments exit with an error.
Configuration
vent resolves config in this order:
VENT_MCP_CONFIG$XDG_CONFIG_HOME/vent-mcp/config.toml~/.config/vent-mcp/config.toml
Implicit XDG or home-directory configs are created when missing. If
VENT_MCP_CONFIG points to a missing file, startup fails instead of creating it.
Start from configs/config.sample.toml or the generated default config.
Minimal config
default_channel = "feedback"
[[channels]]
name = "feedback"
description = "Blocked work, repeated failures, or confusing workflows. Avoid routine progress updates."
sinks = ["log"]
[[sinks]]
type = "jsonl"
name = "log"Config reference
| Setting | Required | Description |
|---|---|---|
default_channel | Yes | Channel used when callers omit channel. It must match one [[channels]] entry. |
[logging].jsonl_dir | No | Directory for vents.jsonl. Empty or omitted values use the config directory. ~ and ~/... expand from HOME. |
[[channels]].name | Yes | Channel name agents may choose. Names must be lowercase ASCII letters, digits, underscores, or dashes, up to 64 characters. |
[[channels]].description | Yes | Short description exposed to MCP clients and vent list. |
[[channels]].sinks | Yes | One or more sink names. Every referenced sink must exist. A channel may reference at most one JSONL sink. |
[[sinks]].type | Yes | jsonl or, with the webhook feature, webhook. |
[[sinks]].name | Yes | Unique sink name referenced by channels. |
[[sinks]].url | Webhook only | HTTP or HTTPS endpoint. |
[[sinks]].provider | No | Built-in or custom provider map. Omit it or use raw to send the canonical event JSON. |
[[sinks]].headers | No | Environment-backed webhook headers. Header values are read when the event is sent. |
[[sinks]].timeout_ms | Webhook only | Positive timeout in milliseconds. Defaults to 10000. |
[providers.<name>] | No | Maps canonical event fields onto webhook JSON output paths. |
Every vent event contains:
{
"id": "aZ8pQ2xK",
"timestamp": "2026-06-03T12:34:56Z",
"channel": "automation",
"message": "The failing check output was hard to correlate with the changed file.",
"project": "my-repo"
}The project value is only the current directory name. vent-mcp does not
record the full local workspace path.
Troubleshooting
config file not found
Cause: VENT_MCP_CONFIG points to a path that does not exist.
Fix: Create the file at that path, unset VENT_MCP_CONFIG, or point it at an
existing TOML config.
unknown channel: <name>
Cause: The CLI or MCP caller requested a channel that is not declared in
[[channels]].
Fix: Run vent list, choose one of the configured names, or add the channel and
its sink route to the config.
message must not be empty
Cause: The message was empty after trimming whitespace.
Fix: Send a specific, actionable message that says what failed and what would unblock the work.
missing environment variable <NAME>
Cause: A webhook header references an environment variable that is not set in
the vent process environment.
Fix: Export the variable before starting the MCP client or remove the header from the sink.
CLI mode is disabled
Cause: The binary was built without the cli feature and received CLI
arguments.
Fix: Use the binary only as an MCP server or rebuild with --features cli.
Licensed under MITโ you can use, modify, and redistribute it under that license's terms.
View the full license file on GitHub โ