- Rework delivery into pluggable destinations (file, slack) selected via VENT_DESTINATIONS; add a destination by implementing Destination + REGISTRY. - Load config from an env file on startup: $VENT_ENV_FILE, else ~/.cursor/.env (process env wins; empty values are overridden). Drop the mcp.json env passthrough so stale empty placeholders can't shadow the file. - Replace bunfig.toml with a `bun run --install=fallback` flag. - Remove the mood parameter and the canned acknowledgement responses; reorder inputs to complaint, intensity, project_path. - Collapse the changelog to a single unreleased 0.1.0. Co-authored-by: Cursor <cursoragent@cursor.com>
Agent Vent
An MCP server that gives the coding agent a vent tool to record friction it hits while working — confusing or contradictory instructions, flaky tooling, painful code, and the like.
Each grievance is dispatched to one or more pluggable destinations: a per-project JSONL log on disk, a Slack channel, or anything you add. Delivery is best-effort and independent — one destination failing never fails the others or the tool call.
The tool
vent(complaint, intensity?, project_path?)
| Argument | Required | Description |
|---|---|---|
complaint |
yes | Freeform prose. The grievance, in full. |
intensity |
no | Severity, 1 (minor) to 10 (severe). |
project_path |
no | Absolute workspace path; routes the file log to that project. |
Destinations
Choose destinations with the VENT_DESTINATIONS environment variable (comma-separated). When unset it defaults to file, plus slack if Slack credentials are present.
export VENT_DESTINATIONS="file,slack"
| Name | Description | Configuration |
|---|---|---|
file |
Appends the grievance as JSONL to the project's .cursor/complaints.jsonl. |
none |
slack |
Posts the grievance to a Slack channel. | see below |
Adding a destination is a few lines of TypeScript: implement the Destination interface in server.ts and register it in REGISTRY.
file
Each grievance is one JSON line:
{"ts": "2026-06-29T11:08:00-07:00", "complaint": "…", "intensity": 7, "project": "workbench", "project_path": "/Users/you/dev/workbench"}
Routed to <project>/.cursor/complaints.jsonl, falling back to ~/.cursor/complaints/unfiled.jsonl when no project root is detected. Read them back with:
jq . .cursor/complaints.jsonl
slack
Credentials are read from your environment or ~/.cursor/.env (see Configuration) — never stored in the plugin. Use an incoming webhook (simplest):
export VENT_SLACK_WEBHOOK_URL="https://hooks.slack.com/services/XXX/YYY/ZZZ"
Create one at https://api.slack.com/apps → Incoming Webhooks. Alternatively, use a bot token:
export VENT_SLACK_BOT_TOKEN="xoxb-…"
export VENT_SLACK_CHANNEL="#agent-grievances"
Configuration
Every variable above is read in this order; the first non-empty value wins:
- the process environment (e.g. a shell
export), $VENT_ENV_FILE, if set,~/.cursor/.env.
~/.cursor/.env is the recommended home for these — scoped to Cursor, shared across every project, and outside any repository, so secrets stay out of version control (the plugin never bundles it):
# ~/.cursor/.env
VENT_DESTINATIONS=file,slack
VENT_SLACK_WEBHOOK_URL=https://hooks.slack.com/services/XXX/YYY/ZZZ
Requirements
- Bun on your
PATH. The server (server.ts) is launched withbun run --install=fallback, which auto-installs its dependencies (@modelcontextprotocol/sdk,zod, pinned inpackage.json) from Bun's global cache on first use — no committednode_modules, no build step. The--install=fallbackflag keeps this working even if an unrelatednode_modulesexists higher up the filesystem. The first launch downloads the dependency tree (a few seconds to ~30s); every launch after that is instant.
License
MIT