Skip to content

MCP server

aven mcp lets AI assistants read and change your tasks through the Model Context Protocol. It runs as a local process, talks to the client over standard input and output, and works on your device’s local database. It can also serve clients on other machines or in other containers over HTTP.

For coding agents working in a repository, the aven skill is usually a better fit. It also covers project inference and handoff notes. Use MCP for assistants that combine your tasks with their other tools.

Once connected, ask for what you need in plain language:

  • “What’s due this week?”
  • “File a high priority bug: login fails after token refresh.”
  • “The token rotation docs are merged. Mark that done and note the PR.”
  • “Add a weekly task to review dependency updates every Monday.”
example
The token rotation docs are merged. Mark that done and note the PR.
Worked for 9s

Both changes completed in Aven.

Document token rotationMarked as doneNote added: "Merged in #482."APP-P8N3

You have 3 open tasks left.

The assistant can read, create, change, and delete tasks. There is no read-only mode. Tools are marked as read-only or destructive, but each client decides whether to ask you before running them. Some clients don’t ask.

If the client doesn’t inherit your shell’s PATH, use the full path to aven. Run which aven to find it. To use another database, add --db <path> before mcp.

Terminal window
claude mcp add aven -- aven mcp

Add this to ~/.codex/config.toml:

[mcp_servers.aven]
command = "aven"
args = ["mcp"]

Open Settings, then Developer, then Edit Config. Add the server to claude_desktop_config.json, using the full path to aven:

{
"mcpServers": {
"aven": {
"command": "/full/path/to/aven",
"args": ["mcp"]
}
}
}

Then restart Claude Desktop.

ChatGPT can’t start a local process. OpenAI’s tunnel-client runs aven mcp on your machine and connects it to ChatGPT over an outbound connection, so you don’t need to open a public port.

ChatGPT can reach your tasks only while the tunnel runs. To keep them available when your laptop is closed, use a VPS or home server. Give it its own Aven database, join your existing sync, and run the daemon there:

Terminal window
aven sync join
aven config set sync.enabled true
aven daemon install

Without the daemon, changes made through ChatGPT stay on that host. On Linux, run loginctl enable-linger so the daemon keeps running after you log out.

You need an OpenAI Platform organization, and your role needs the Tunnels Manage permission.

  1. Open Tunnels and choose Create tunnel.
  2. Enter a name and description, such as aven-mcp.
  3. Select your organization and your ChatGPT workspace.
  4. Leave Restrict client network access off.
  5. Save the tunnel and copy its ID, which starts with tunnel_.

tunnel-client finds the tunnel by ID, so you can rename it later without changing anything else.

tunnel-client uses this key to connect. It should be able to do nothing else.

  1. Open API keys and create a new secret key in any project.
  2. Set the expiry. The tunnel stops working when the key expires, so pick a date far enough out and note it.
  3. Choose Restricted permissions.
  4. Find Tunnels near the end of the list and allow Read and Use. Leave everything else off.
  5. Create the key and copy it. OpenAI shows it only once.

Install tunnel-client and create a profile that runs Aven:

Terminal window
brew install openai/tools/tunnel-client
read -rs CONTROL_PLANE_API_KEY && export CONTROL_PLANE_API_KEY
tunnel-client init --sample sample_mcp_stdio_local --profile aven \
--tunnel-id <tunnel id> --mcp-command "/full/path/to/aven mcp"
tunnel-client doctor --profile aven --explain
tunnel-client run --profile aven

read waits for you to paste the API key and doesn’t show it, which keeps it out of your shell history.

On Linux, skip the brew line. Download the zip for your architecture from the tunnel-client releases, check it against SHA256SUMS.txt, and put the tunnel-client binary on your PATH.

In ChatGPT, open Plugins, choose Add, then Add custom MCP server. Name it Aven, because ChatGPT shows this name in chats. Select Tunnel, choose your tunnel, and set authentication to none.

This setup has been tested on the Pro plan, including task changes. Other plans are untested, and OpenAI’s plan limits may change.

Run tunnel-client under a service manager such as systemd or launchd. A service doesn’t load your shell, so it can’t see CONTROL_PLANE_API_KEY. Save the API key in a private file instead:

Terminal window
install -m 600 /dev/null ~/.config/tunnel-client/aven.key
read -rs key && printf '%s\n' "$key" > ~/.config/tunnel-client/aven.key

Then edit ~/.config/tunnel-client/aven.yaml to read the key from that file, using its absolute path, and to exit if aven mcp stops so the service manager restarts both:

control_plane:
api_key: file:/home/you/.config/tunnel-client/aven.key
mcp:
stdio_failure_policy: exit

Don’t paste the key itself into the profile. Run tunnel-client doctor --profile aven to check the result, and have the service start tunnel-client run --profile aven. To replace an expiring key, overwrite the file and restart the service.

If the tunnel is down, ChatGPT may wait on a tool call for minutes without finishing or reporting an error. When a chat hangs on an Aven tool, check the tunnel first.

Run only one tunnel-client per tunnel at a time, including during restarts. Stop the old process before starting a new one, or requests can reach different server processes.

An agent that can’t start aven itself, such as one in its own container, can connect over HTTP to a separate Aven process that owns the database:

Terminal window
export AVEN_MCP_TOKEN="$(openssl rand -hex 32)"
aven mcp --http --bind 127.0.0.1:3747 --sync

The server speaks MCP Streamable HTTP at /mcp, so this example is reached at http://127.0.0.1:3747/mcp. Clients authenticate with the header Authorization: Bearer <token>.

OptionEffect
--httpServe HTTP instead of standard input and output
--bind <addr>Listen address, 127.0.0.1:3747 by default
--unsafe-public-bindAllow a public or wildcard address such as 0.0.0.0
--syncSync from the server process instead of the daemon
AVEN_MCP_TOKENRequired token, at least 32 visible ASCII characters

Keep any .env or client configuration file that holds the token private. Clients that only support OAuth login can’t connect. Requests that carry a browser Origin header are refused, so web pages can’t reach the server.

--sync means a container doesn’t need aven daemon. It syncs even when sync.enabled is false, and sends reminders when reminders.enabled is true. One process runs sync and reminders for a database at a time. When aven daemon or Aven Desktop already does, the server still starts, logs a sync-client line naming that process, and wakes it after each change instead of syncing itself. Within 30 seconds of that process stopping, the server takes over sync and reminders and logs a sync-host line. A daemon started after the server waits until it stops.

Keep these limits in mind:

  • The token gives full access to every tool and every workspace in the database. --workspace only changes the default.
  • The server has no TLS. The token and your task text travel in plaintext, and sync’s end-to-end encryption doesn’t cover this connection. Use it on loopback, a private container network, or a VPN, or put an HTTPS proxy in front of it.

Here the Aven container keeps its own database and syncs with your existing sync server. The agent container needs only the URL and token, with no Aven executable and no Aven volume.

name: aven-agent
services:
aven-mcp:
image: ${AVEN_IMAGE:?Set AVEN_IMAGE to a versioned ghcr.io/raine/aven image}
command: ["mcp", "--http", "--bind", "0.0.0.0:3747", "--unsafe-public-bind", "--sync"]
environment:
AVEN_MCP_TOKEN: ${AVEN_MCP_TOKEN:?Set AVEN_MCP_TOKEN}
AVEN_DB: /data/db.sqlite
AVEN_CONFIG_DIR: /data/config
XDG_STATE_HOME: /data/state
volumes:
- aven-data:/data
restart: unless-stopped
stop_grace_period: 15s
agent:
image: your-agent-image
environment:
AVEN_MCP_URL: http://aven-mcp:3747/mcp
AVEN_MCP_TOKEN: ${AVEN_MCP_TOKEN:?Set AVEN_MCP_TOKEN}
volumes:
aven-data:

The container binds 0.0.0.0 because the agent connects through the Compose network, and --unsafe-public-bind acknowledges that. No port is published, so only containers on this network can connect. /data holds the database and also this device’s sync keys, which is why XDG_STATE_HOME points inside it.

Set it up in this order:

  1. Put a generated token and your image in an .env file beside compose.yaml:

    Terminal window
    printf 'AVEN_MCP_TOKEN=%s\nAVEN_IMAGE=%s\n' \
    "$(openssl rand -hex 32)" ghcr.io/raine/aven:VERSION > .env
  2. On a device that already syncs, run aven sync invite and leave it running.

  3. Join from a one-off container on the same volume, and paste the invitation:

    Terminal window
    docker compose run --rm -it aven-mcp sync join

    Pass a non-interactive invitation with docker compose run --rm -T aven-mcp sync join --yes < invitation.txt. The invitation carries the sync server’s address, so there’s nothing else to configure and no need to run aven server here.

  4. Start both services with docker compose up -d, then check the Aven container with docker compose logs aven-mcp. daemon-synced lines mean sync is working.

Join before the first up, because joining needs an empty database. If the agent has already created tasks, stop the services, change AVEN_DB to a new path under /data, and join again. Those tasks stay in the old database and don’t sync. To start over instead, docker compose down --volumes deletes the volume, including the tasks and this device’s sync keys.

Point the agent at the URL and token however its MCP client takes them. For Claude Code:

Terminal window
claude mcp add --transport http aven "$AVEN_MCP_URL" \
--header "Authorization: Bearer $AVEN_MCP_TOKEN"

For a file-based configuration:

{
"mcpServers": {
"aven": {
"type": "http",
"url": "http://aven-mcp:3747/mcp",
"headers": { "Authorization": "Bearer ${AVEN_MCP_TOKEN}" }
}
}
}

Give each deployment its own volume. Don’t mount it into another Aven process.

Tools pick the default workspace in this order:

  1. workspace.default from your configuration
  2. The built-in default workspace
  3. The only workspace

An assistant can pass workspace to use another one. To change the default for one client, start the server with aven mcp --workspace <name>. Clients can still reach other workspaces.

The working directory has no effect, so workspace.routes and project path mappings don’t apply. Searches and lists cover every project in the workspace unless the assistant names one. New tasks go to the default project unless the assistant names an existing one. The server doesn’t create other projects.

The server reads its configuration when it starts. Restart it after changing workspace.default or sync settings.

The server’s clock may differ from yours, especially on a remote host, so assistants work out phrases like “tomorrow” themselves. They pass concrete dates, the current date for overdue checks, and an IANA time zone for recurring tasks.

aven mcp doesn’t sync on its own. Each write asks the background daemon on the same device to sync, so set it up if you want changes to reach your other devices automatically. Check it with aven daemon status. Over HTTP, --sync replaces the daemon.

A tool call succeeds once the change is saved locally, and the change syncs in the background. If sync fails or Aven stops first, the change stays queued for a later sync. Aven checks for changes from other devices every sync.interval_seconds, 30 by default.

ToolPurpose
list_tasksList tasks as compact rows, with filters
search_tasksFind tasks by text across the workspace
count_tasksCount tasks with the same filters as list_tasks
show_taskRead one task in full, including notes
create_taskCreate a task, or a recurring series with repeat
update_taskChange fields or status, including completing a recurring occurrence
add_noteAppend a note
delete_taskSoft-delete a task; restore it with aven restore
list_projectsList projects in a workspace
list_workspacesList workspaces and show which is the default
list_recurringList recurring series
edit_recurringEdit a series, or skip, pause, resume, or stop it