- Docs
- AI Agents
- MCP Server
Your queue, driven by agents.
Connect Claude, Cursor or any MCP client to bunqueue, then ask in plain language: add jobs, schedule crons, retry failures, check on your queues. No code to write.
MCP (Model Context Protocol) is the standard way for AI agents to call tools. bunqueue includes an MCP server, bunqueue-mcp, with 75 tools that cover the whole queue: jobs, queues, crons, retries, limits, flows, workers, webhooks and monitoring.
Start in two minutes
Section titled “Start in two minutes”-
Install.
bunqueue-mcpships inside thebunqueuepackage. The MCP SDK is a separate, optional dependency that only the MCP server needs.bun add -g bunqueue @modelcontextprotocol/sdk -
Connect your client.
claude mcp add bunqueue -- bunx --package=bunqueue bunqueue-mcpAdd this to
claude_desktop_config.json, then restart Claude Desktop. The file is in~/Library/Application Support/Claude/on macOS,%APPDATA%\Claude\on Windows and~/.config/Claude/on Linux.{"mcpServers": {"bunqueue": {"command": "bunx","args": ["--package=bunqueue", "bunqueue-mcp"]}}}Add the same server to your client’s MCP settings:
{"mcpServers": {"bunqueue": {"command": "bunx","args": ["--package=bunqueue", "bunqueue-mcp"]}}} -
Ask. For example: “Add a job to the emails queue, then show me the queue.”
That is all. The queue runs inside the MCP server, in memory, so jobs disappear when the session ends. To keep them, add "env": { "DATA_PATH": "/absolute/path/to/bunq.db" } to the server config. Use an absolute path: the client, not you, chooses the directory the MCP server starts in. To manage a bunqueue server you already run, see where the queue lives.
What you can ask
Section titled “What you can ask”| You say | The agent uses |
|---|---|
| ”Add 3 notification jobs: push, email, sms” | bunqueue_add_jobs_bulk |
| ”Schedule session cleanup every day at 3 AM Rome time” | bunqueue_add_cron with timezone |
| ”Limit notifications to 100 per minute” | bunqueue_set_rate_limit with duration |
| ”Which billing jobs failed today, and why?” | bunqueue_get_dlq_stats, then bunqueue_get_dlq |
| ”Retry everything in the dead letter queue” | bunqueue_retry_dlq |
| ”Validate the payment, then send the receipt, then update inventory” | bunqueue_add_flow_chain |
| ”Why are jobs stuck in the emails queue?” | The bunqueue_debug_queue prompt |
You never call tools yourself: the agent picks them. The full list is in the tool reference.
Where the queue lives
Section titled “Where the queue lives”- toolsets
- confirmations
- decision models
- Embedded default Inside the MCP server In memory, or a SQLite file with
DATA_PATH - TCP
BUNQUEUE_MODE=tcpYour bunqueue server, port 6789 SQLite or PostgreSQL, shared with your workers
The MCP server works in one of two modes. All 75 tools work in both, including HTTP handlers: in TCP mode their workers connect to your server and process its jobs, and the MCP process never opens a local database, even with DATA_PATH set. Two differences remain: bunqueue_get_stats reports push and pull rates in TCP mode, and prioritized, waiting-for-children, lifetime and cron totals in embedded mode; and workflow approvals need TCP.
| Embedded (default) | TCP | |
|---|---|---|
| The queue runs | Inside the MCP server | On your bunqueue server |
| Good for | Trying things, one machine | Managing the queues your workers use |
| Jobs are kept | In memory, or in a SQLite file with DATA_PATH | By your server (SQLite or PostgreSQL) |
| Setup | Nothing | Set BUNQUEUE_MODE=tcp and the address |
To connect to your server:
{ "mcpServers": { "bunqueue": { "command": "bunx", "args": ["--package=bunqueue", "bunqueue-mcp"], "env": { "BUNQUEUE_MODE": "tcp", "BUNQUEUE_HOST": "your-server.com", "BUNQUEUE_PORT": "6789", "BUNQUEUE_TOKEN": "your-auth-token" } } }}Process jobs with HTTP handlers
Section titled “Process jobs with HTTP handlers”An agent can add jobs, but it cannot run a worker. An HTTP handler fills that gap: the agent registers a URL on a queue, and the MCP server calls that URL for every job. The response becomes the job’s result. An error status or a timeout fails the job, which is then retried or moved to the dead letter queue like any other.
> Register a GET handler on "meteo" that calls the OpenWeather API✓ bunqueue_register_handler Jobs in "meteo" will be processed via GET.
> Add a cron that pushes a job every 10 seconds✓ bunqueue_add_cron name: "check-meteo", repeatEvery: 10000- waiting A job is added By the agent, by a cron or, in TCP mode, by any app on your server
- active The handler takes it A worker inside the MCP server; in TCP mode it pulls from your server
- active It calls your URL
GETPOSTPUTDELETE; POST and PUT send the job's data, or your fixed body. It waits up totimeoutMs -
- completed 2xx response The response is saved as the job's result
- failed Error status, network error or timeout Retried with backoff while attempts remain, then moved to the dead letter queue
bunqueue_register_handler takes the queue, the url, the method (GET, POST, PUT or DELETE), and optionally headers, a fixed body (POST and PUT send it, or the job’s data when it is not set; GET and DELETE send no body) and timeoutMs (default 30000, from 1000 to 120000). In TCP mode the handler processes your server’s queue.
Use handlers when the work is a single HTTP call: polling an API, forwarding a webhook, a health check. For anything else, run a normal Worker: the agent still adds and schedules the jobs.
Optional: lighter and safer agents
Section titled “Optional: lighter and safer agents”Three features, all off by default. You turn them on in the server config, and they work well together.
| Feature | Turn it on | What you get |
|---|---|---|
| Load only the tools you need | BUNQUEUE_MCP_TOOLSETS=dynamic | The agent loads groups of tools on demand: about 1,300 to 4,600 tokens per request instead of about 16,000 |
| Confirm destructive operations | BUNQUEUE_MCP_CONFIRM=destructive | Operations that delete data for good ask you first, with their exact impact |
| Decision models | BUNQUEUE_MCP_DECISION_PROVIDER | A small, fast model picks tools from plain language and double-checks destructive requests |
The first two, together:
claude mcp add bunqueue \ -e BUNQUEUE_MCP_TOOLSETS=dynamic \ -e BUNQUEUE_MCP_CONFIRM=destructive \ -- bunx --package=bunqueue bunqueue-mcpLoad only the tools the agent needs
Section titled “Load only the tools the agent needs”Every tool an agent can see costs context: the 75 descriptions add up to about 16,000 tokens on every request, before the agent reads your question. Toolsets cut that down.
BUNQUEUE_MCP_TOOLSETS | The agent sees |
|---|---|
unset, or all | All 75 tools, up to 78 with the workflow tools (the default) |
queues,dlq | Only those groups. Works with every client |
dynamic | A short catalog of the groups. The agent loads the ones it needs |
dynamic,monitoring | The catalog, with monitoring already loaded |
The groups are jobs, job-control, processing, queues, dlq, cron, limits, flows, workers, webhooks and monitoring (plus workflows when workflow tools are on).
In dynamic mode the agent starts with two tools: bunqueue_enable_toolsets, which loads groups, and bunqueue_call_tool, for clients that do not refresh their tool list after a group is loaded. The catalog alone costs about 850 tokens; with one group loaded, 1,300 to 4,600 depending on the group (about 1,800 for a typical group, close to 90% less than the full list; jobs and flows are the largest).
How well does the agent pick the right group?
We measured it with 72 requests, including paraphrases such as “let billing jobs run again” and requests in Italian. Given only the group descriptions, TypeSafe Jev picked the group with the right tool for 71 of 72 on the first run, and for all 72 after we reworded the one description involved in the miss (so the second score is not a blind result). Keyword search over the individual tools found the right one in its top 5 for only 53% of the held-out requests. The requests, the script and the results are in scripts/mcp-eval/.
Confirm destructive operations
Section titled “Confirm destructive operations”With BUNQUEUE_MCP_CONFIRM=destructive, nine tools no longer act on the first call:
obliterate_queue, drain_queue, clean_queue, purge_dlq, cancel_job, clear_job_logs, delete_cron, remove_webhook, and retry_completed without a jobId.
They delete data for good, or re-run work that already completed. Retrying failed jobs from the dead letter queue is not on the list, because that is the normal fix for a failure, and neither is discard_job, which moves a job to the dead letter queue where it can still be retried.
Before running one of them, the server works out the impact, for example: Permanently delete queue “billing” and every job in it (1204 waiting, 0 prioritized, 0 delayed, 3 active, 18330 completed, 2 failed), including results and logs. Then:
- If your client can ask you (it supports MCP elicitation), you see the impact and choose. A decline always wins, even if the agent tried to confirm on its own.
- If it cannot, the call is refused with the impact. The agent must show it to you and, once you agree, call again with
confirmset to the exact target: the queue name, job id, cron name, webhook id or execution id.
The confirmation covers the target. The other arguments are the ones the impact describes, so read it before you say yes. Every other tool works as before; with this setting, every tool is also labeled as read-only, destructive, idempotent or open-world for clients that use those hints.
Decision models: Jev, Clef, Clef-flash, Kev 9B, Laya
Section titled “Decision models: Jev, Clef, Clef-flash, Kev 9B, Laya”A decision model is a small model that answers a typed question (pick one option, or say how likely something is) in milliseconds, instead of writing text. bunqueue can use one for two things:
- Picking tools. With
BUNQUEUE_MCP_TOOLSETS=dynamic, the agent getsbunqueue_find_tools: it describes what it needs and the model loads the right group. Helpful with smaller agent models. - A second check on destructive calls. With
BUNQUEUE_MCP_CONFIRM=destructiveand a client that cannot ask you, the agent must also pass your words (userRequest), and the model must agree that you asked for exactly that operation.
The model can only block a call, never approve one for you. It is not asked when you already confirmed in the client, and if it is unreachable or unsure, the call is refused.
| Provider | Models | You set |
|---|---|---|
typesafe | Jev (jev-latest, jev-1.13.0) | BUNQUEUE_MCP_DECISION_API_KEY |
cloudflare | Clef, Clef-flash on Workers AI | BUNQUEUE_MCP_DECISION_API_KEY, BUNQUEUE_MCP_DECISION_ACCOUNT_ID |
systemone | Any compatible endpoint you host: Clef and Clef-flash (open weights), Kev 9B, Laya, DiffusionGemma Jev | BUNQUEUE_MCP_DECISION_URL, BUNQUEUE_MCP_DECISION_MODEL |
{ "mcpServers": { "bunqueue": { "command": "bunx", "args": ["--package=bunqueue", "bunqueue-mcp"], "env": { "BUNQUEUE_MCP_TOOLSETS": "dynamic", "BUNQUEUE_MCP_CONFIRM": "destructive", "BUNQUEUE_MCP_DECISION_PROVIDER": "cloudflare", "BUNQUEUE_MCP_DECISION_MODEL": "clef-flash", "BUNQUEUE_MCP_DECISION_ACCOUNT_ID": "your-account-id", "BUNQUEUE_MCP_DECISION_API_KEY": "your-api-token" } } }}Which one? On public benchmarks, Jev is best at knowing when not to call a tool (When2Call 80.97), the skill behind the destructive-call check; Clef is best at finding the right tool (ToolRet 69.19), the skill behind bunqueue_find_tools; Clef-flash is the fastest, about 39 ms at the median, roughly 13 times faster than Jev. A weaker model only makes the check stricter, never less safe. For a self-hosted model, set BUNQUEUE_MCP_DECISION_URL (for example http://127.0.0.1:8000/v1/systemone) and BUNQUEUE_MCP_DECISION_MODEL (for example kev-9b); Clef’s model card includes a systemone() helper that serves exactly this request format.
Serve over HTTP
Section titled “Serve over HTTP”By default every client starts its own bunqueue-mcp process. To run one server that several clients share, start it in HTTP mode:
BUNQUEUE_MCP_TRANSPORT=http \BUNQUEUE_MCP_HTTP_TOKEN=change-me \bunx --package=bunqueue bunqueue-mcp# bunqueue MCP server started (mode: embedded, transport: http, url: http://127.0.0.1:6791/mcp, ...)Then connect each client to the URL with the token:
claude mcp add --transport http bunqueue http://127.0.0.1:6791/mcp \ --header "Authorization: Bearer change-me"Each client gets its own session, and all sessions share one queue connection and one set of HTTP handlers. Add BUNQUEUE_MODE=tcp and the server address, as above, to manage your bunqueue server.
Good to know:
- It listens on
127.0.0.1:6791. On any other address it refuses to start withoutBUNQUEUE_MCP_HTTP_TOKEN(a comma-separated list, so you can rotate tokens). A missing or wrong token gets 401. - There is no built-in TLS. To reach it from another machine, keep it on
127.0.0.1behind a reverse proxy that handles TLS, and keep the token. - Sessions live in memory. After a restart, or 30 minutes of inactivity, a client gets
Session not foundand has to reconnect.
Reverse proxy, Host and Origin checks, limits
To block DNS-rebinding attacks from web pages, the server checks the Host header (and the Origin header when a browser sends one) and answers 403 to names it does not know. Behind a reverse proxy, list the public name in BUNQUEUE_MCP_HTTP_ALLOWED_HOSTS, and browser origins in BUNQUEUE_MCP_HTTP_ALLOWED_ORIGINS:
# Caddyfile (start the server with BUNQUEUE_MCP_HTTP_ALLOWED_HOSTS=mcp.example.com)mcp.example.com { reverse_proxy 127.0.0.1:6791}Behind nginx, set proxy_buffering off so streamed responses are not held back.
On loopback or 0.0.0.0 the server accepts localhost, 127.0.0.1 and [::1] on its port; an allowed host can be name (any port) or name:port. At most 100 sessions are open at once (BUNQUEUE_MCP_HTTP_MAX_SESSIONS; beyond that, 503), an idle session closes after BUNQUEUE_MCP_HTTP_SESSION_TTL_MS (30 minutes) but never during a running tool call, an unknown session gets 404, and request bodies are limited to 4 MiB.
Workflow approvals
Section titled “Workflow approvals”If your application uses the workflow engine, an agent can see its runs and approve the ones waiting for a human signal: “approve expense run 4f1c…”. Point BUNQUEUE_MCP_WORKFLOW_DB at the Engine’s dataPath file and three tools appear:
| Tool | What it does |
|---|---|
bunqueue_list_workflow_executions | Lists runs, newest first. Filter by workflowName and state, page with limit and offset |
bunqueue_get_workflow_execution | Shows one run: input, every step with its status and result, signals received, failure reason |
bunqueue_signal_workflow | Does what engine.signal(executionId, event, payload) does in your app. The first signal for an event wins and cannot be undone |
The MCP server never runs your steps: their code lives in your application. To resume a run, it records the signal and queues the next step for your Engine. That needs this setup:
- Your Engine connects to a bunqueue server over TCP, with its own
dataPathfile:new Engine({ embedded: false, connection: { host, port }, dataPath: '/srv/app/data/workflows.db' }). - The MCP server runs in TCP mode against the same server, on the same machine, and can write that file.
{ "mcpServers": { "bunqueue": { "command": "bunx", "args": ["--package=bunqueue", "bunqueue-mcp"], "env": { "BUNQUEUE_MODE": "tcp", "BUNQUEUE_HOST": "your-server.com", "BUNQUEUE_PORT": "6789", "BUNQUEUE_TOKEN": "your-auth-token", "BUNQUEUE_MCP_WORKFLOW_DB": "/srv/app/data/workflows.db" } } }}Use an absolute path. The file must already exist: the MCP server never creates it.
Embedded apps signal from the app. An embedded Engine keeps its step queue inside your application, out of reach of any other process. The MCP server detects this: it still lists and shows runs, but leaves out bunqueue_signal_workflow and prints why on startup. Call engine.signal() in your app instead.
Limits and details
- If you changed the Engine’s
queueName, setBUNQUEUE_MCP_WORKFLOW_QUEUEto the same value (default__wf:steps). - A signal for a different event than the one a timed gate waits for is refused, so a typo cannot open a later gate. For gates without a timeout the awaited event is not recorded, so the run shows
waitingFor: null. - The MCP server cannot check that it talks to the same bunqueue server and queue as your Engine. If it does not, the run shows
runninguntil your app callsengine.recover(). - Your app’s
signal:receivedlisteners do not fire for signals sent through MCP; the step and workflow events that follow do. - SQLite over a network filesystem is not safe: keep the file local.
- With
BUNQUEUE_MCP_CONFIRM=destructive, every signal asks for confirmation first, with a preview of the payload.
Reference
Section titled “Reference”The agent discovers the tools on its own; this table shows what is available. Every name starts with bunqueue_.
| Group | Tools | Examples |
|---|---|---|
jobs | 12 | add_job, add_jobs_bulk, get_job, get_job_result, wait_for_job, get_job_logs |
job-control | 7 | cancel_job, promote_job, change_job_priority, move_to_delayed, discard_job |
processing | 8 | pull_job, ack_job, fail_job, job_heartbeat, extend_lock |
queues | 11 | list_queues, get_job_counts, pause_queue, drain_queue, clean_queue |
dlq | 5 | get_dlq, get_dlq_stats, retry_dlq, purge_dlq, retry_completed |
cron | 4 | add_cron, list_crons, get_cron, delete_cron |
limits | 5 | set_rate_limit, set_concurrency, get_queue_limits, clear_rate_limit |
flows | 5 | add_flow, add_flow_chain, add_flow_bulk_then, get_flow, get_children_values |
workers | 7 | register_worker, list_workers, register_handler, list_handlers |
webhooks | 4 | add_webhook, list_webhooks, set_webhook_enabled, remove_webhook |
monitoring | 7 | get_stats, get_queue_stats, get_memory_stats, get_prometheus_metrics |
workflows (optional) | 3 | list_workflow_executions, get_workflow_execution, signal_workflow |
Errors come back as plain messages, never stack traces. Inputs are checked by each tool’s schema before anything reaches the queue (for example: names cannot be empty, priorities are whole numbers within ±1,000,000, attempts go from 1 to 1000; these tool limits are stricter than the broker, which accepts any finite priority), and a rejected call changes nothing.
The server also offers 3 prompts, bunqueue_health_report, bunqueue_debug_queue and bunqueue_incident_response, and 5 read-only resources: bunqueue://stats, bunqueue://queues, bunqueue://crons, bunqueue://workers and bunqueue://webhooks.
Useful parameters
Section titled “Useful parameters”Job options: retries, timeouts, custom ids, deduplication
add_job and each item of add_jobs_bulk accept priority, delay, attempts, backoff (a number, used as an exponential base, or { type: 'fixed' | 'exponential', delay, maxDelay }), timeout, jobId, deduplication ({ id, ttl, extend, replace }, per queue), removeOnComplete, removeOnFail, durable, lifo, tags and stallTimeout. Adding a jobId again while that job is unfinished returns the existing job. The flow tools take the same options, except deduplication, tags and durable. get_job shows the options a job carries.
Processing jobs by hand: lock tokens
Pass owner (and optionally lockTtl, from 1 second to 24 hours, default 30 seconds) to pull_job or pull_job_batch, and each job comes back locked with a token. Pass that token to ack_job, fail_job and job_heartbeat (tokens, one per job, for the batch tools; "" for a job pulled without an owner). A wrong or missing token is an error and the job stays active. A heartbeat with the token renews the lock; a lock that is not renewed expires and the job is retried. fail_job with unrecoverable: true sends the job straight to the dead letter queue.
Why jobs failed: the dead letter queue
get_dlq returns entries, oldest first, each with the job and why it failed: reason (explicit_fail, max_attempts_exceeded, timeout, stalled, ttl_expired, worker_lost or unknown), the last error, the last 10 attempts (attemptCount is the total) and its retry and expiry times. Filter by reason, and page with limit (1 to 100, default 20) and offset while hasMore is true. get_dlq_stats gives the total, the count per reason, the entries due for an automatic retry, the expired entries and the oldest and newest entry.
Limits and cron schedules
set_rate_limit takes duration, the window in milliseconds (default 1000), so “100 per minute” is limit: 100, duration: 60000. get_queue_limits reads back the rate limit, the concurrency limit, the active jobs, whether a limit is holding jobs back, and whether the queue is paused.
add_cron takes timezone (an IANA name such as Europe/Rome, together with schedule), jobName, priority, maxLimit (remove the schedule after that many runs), immediately, skipIfNoWorker, preventOverlap (on by default: at most one pending job per schedule), skipMissedOnRestart, deduplication, and options for the jobs it adds: attempts, backoff, timeout, delay, stallTimeout, removeOnComplete and removeOnFail. list_crons and get_cron show the time zone, job name, priority and run limit; the other settings are applied but not shown, because the server does not return them.
Environment variables
Section titled “Environment variables”Set them in the env block of the server config. An invalid BUNQUEUE_MCP_* value stops the server at startup with a message that says what is accepted.
Connection
| Variable | Default | Description |
|---|---|---|
BUNQUEUE_MODE | embedded | embedded or tcp |
BUNQUEUE_HOST | localhost | Your bunqueue server (TCP mode); empty means localhost |
BUNQUEUE_PORT | 6789 | Its TCP port, from 1 to 65535; any other value stops the server at startup |
BUNQUEUE_TOKEN | none | Its auth token |
DATA_PATH | none (in memory) | SQLite file for embedded mode (BUNQUEUE_DATA_PATH and BQ_DATA_PATH take priority) |
HTTP server
| Variable | Default | Description |
|---|---|---|
BUNQUEUE_MCP_TRANSPORT | stdio | stdio or http |
BUNQUEUE_MCP_HTTP_HOST | 127.0.0.1 | Address to listen on; any other than loopback needs a token |
BUNQUEUE_MCP_HTTP_PORT | 6791 | Port; 0 picks a free one |
BUNQUEUE_MCP_HTTP_PATH | /mcp | Endpoint path |
BUNQUEUE_MCP_HTTP_TOKEN | none | Comma-separated bearer tokens |
BUNQUEUE_MCP_HTTP_MAX_SESSIONS | 100 | Sessions open at once |
BUNQUEUE_MCP_HTTP_SESSION_TTL_MS | 1800000 | Idle time before a session closes |
BUNQUEUE_MCP_HTTP_ALLOWED_HOSTS | none | Extra accepted Host names: name or name:port |
BUNQUEUE_MCP_HTTP_ALLOWED_ORIGINS | none | Extra accepted browser origins, such as https://app.example.com |
Agent features
| Variable | Default | Description |
|---|---|---|
BUNQUEUE_MCP_TOOLSETS | all tools | all, a list of groups, or dynamic (optionally followed by groups to preload) |
BUNQUEUE_MCP_CONFIRM | off | destructive to require confirmation for the nine destructive tools (ten with workflow tools) |
BUNQUEUE_MCP_DECISION_PROVIDER | none | typesafe, cloudflare or systemone |
BUNQUEUE_MCP_DECISION_MODEL | jev-latest / clef-flash | Model id; required for systemone |
BUNQUEUE_MCP_DECISION_API_KEY | none | API key; required for typesafe and cloudflare |
BUNQUEUE_MCP_DECISION_ACCOUNT_ID | none | Cloudflare account id |
BUNQUEUE_MCP_DECISION_URL | provider default | Endpoint; required for systemone |
BUNQUEUE_MCP_DECISION_TIMEOUT_MS | 10000 | Request timeout (retried once on network errors, 429 and overload) |
BUNQUEUE_MCP_DECISION_THRESHOLD | 0.8 | How sure the model must be that you asked for a destructive operation |
Workflow approvals
| Variable | Default | Description |
|---|---|---|
BUNQUEUE_MCP_WORKFLOW_DB | none | The Engine’s existing dataPath file; turns the workflow tools on |
BUNQUEUE_MCP_WORKFLOW_QUEUE | __wf:steps | The Engine’s queueName, if you changed it |
Troubleshooting
Section titled “Troubleshooting”bunx bunqueue-mcp returns a 404. bunqueue-mcp is not its own npm package: it lives inside bunqueue. Use bunx --package=bunqueue bunqueue-mcp.
requires "@modelcontextprotocol/sdk". Since v2.8.1 the MCP SDK is an optional peer dependency, so queue-only users get a 94% smaller install and it is not installed with bunqueue. Run bun add -g @modelcontextprotocol/sdk (or bun add @modelcontextprotocol/sdk in your project).
The agent does not see the tools. Restart the client and check that the config JSON is valid. Then run bunx --package=bunqueue bunqueue-mcp in a terminal to see the actual error.
Jobs stay in waiting. Nothing is processing the queue. Register an HTTP handler or run a Worker.
HTTP mode: 401, 403 or 404. 401: the client sends no Authorization: Bearer <token> header, or a token that is not in BUNQUEUE_MCP_HTTP_TOKEN. 403 (invalid Host header or Origin): the request came through a name the server does not know, so add it to BUNQUEUE_MCP_HTTP_ALLOWED_HOSTS or _ALLOWED_ORIGINS. 404 Session not found: the server restarted or the session expired, so reconnect the client.
Refusing to serve MCP over HTTP on non-loopback host. You set BUNQUEUE_MCP_HTTP_HOST to a public address without BUNQUEUE_MCP_HTTP_TOKEN. Add a token, or listen on 127.0.0.1 behind a reverse proxy.
Lock token required for job <id>. The job was pulled with an owner, so it is locked. Pass the token from the pull to ack_job, fail_job or job_heartbeat. move_to_delayed, change_delay and discard_job cannot unlock it: finish it with fail_job or wait for the lock to expire.
Unknown time zone. Use an IANA name such as Europe/Rome or America/New_York, not an offset like +02:00. A timezone also needs a schedule.
wait_for_job returns Job not found. No job has that id.
BUNQUEUE_MCP_WORKFLOW_DB ... does not exist or is not a bunqueue workflow database. Use the absolute path of the dataPath file of an Engine that has started at least once. The same variable is missing if you see BUNQUEUE_MCP_TOOLSETS selects toolsets with no tool in this configuration: workflows.
bunqueue_signal_workflow is missing. The server printed the reason at startup: it runs in embedded mode, or the workflow file belongs to an embedded Engine. See workflow approvals.