rovo
Provides tools for delegating coding tasks to a sandboxed agent that interacts with GitHub: creates a working branch, pushes commits, opens a Draft PR, updates the PR body, and revokes the installation token. Supports task creation, status polling, waiting, result retrieval, cancellation, answering agent questions, and steering running tasks via GitHub App user authorization.
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
Rovo
English | 日本語
Not affiliated with Atlassian Rovo. This project is unrelated to Atlassian's "Rovo" AI product.
Rovo lets Claude Code / Cowork hand coding tasks to workers over Remote MCP. Each task runs in an isolated Sandbox on your own Cloudflare account and comes back as a Draft PR, and you can watch it live in a dashboard.
The dashboard (read-only; it follows the system's light / dark setting).
https://github.com/user-attachments/assets/9e0d0914-1947-4c8f-89e2-79315b8d283d
How a task flows, from task_start to the merge. Other versions, in Japanese, light / dark and shorter or more detailed: docs/assets/videos.
Quick start
Deploy Rovo to your own Cloudflare account, connect it to Claude Code, and run your first task.
0. What you need
A Cloudflare account on the Workers Paid plan (Containers, which run the Sandbox, need it). Rovo uses Workers, Durable Objects, Containers, KV, R2, Workers AI and AI Gateway.
A Cloudflare API token for Terraform. It is the only token you create by hand; its permissions are listed in infra/README.md (Japanese).
A GitHub account to own the GitHub App (
mise run setupcreates the App; you only press "Create GitHub App" in the browser).An Anthropic API key (it is stored in AI Gateway as BYOK; an OpenAI key is optional).
mise, Docker (running; it builds the agent's container image), and Claude Code.
1. Get the code and the tools
git clone https://github.com/youyo/rovo.git
cd rovo
mise install # node / pnpm / terraform
mise run install # dependencies
(cd apps/gateway && pnpm exec wrangler login) # for deploying (OAuth)
export CLOUDFLARE_API_TOKEN=... # the Terraform token. Also used by `mise run byok`2. Write the config
cp rovo.config.example.json rovo.config.jsonFill in accountId (your Cloudflare account ID), publicBaseUrl (the https URL the gateway will be served from, for example https://rovo.example.com together with customDomain), and allowed.userIds (your numeric GitHub user ID: gh api user --jq .id). Everything else has a default. Details of every field: docs/deploy.md section 4.
3. Deploy (one command)
mise run setupIt checks the prerequisites, runs Terraform, deploys the three Workers (web, agent, gateway), puts in the secrets, and creates the GitHub App. Your browser opens: press "Create GitHub App" on GitHub, then install the App on your org / repositories on the page that opens next. It can be run again as many times as you need.
Then register your Anthropic key in AI Gateway (hidden input; add openai too if you want OpenAI models):
mise run byok -- anthropicCheck that it is up: curl -i https://<your-host>/mcp returns 401 without credentials. The full guide (values, custom domain, GitHub App, troubleshooting) is docs/deploy.md (Japanese).
4. Connect Claude Code
claude mcp add --transport http rovo https://<your-host>/mcpOn first use you see Rovo's consent screen and sign in with GitHub. In claude.ai and other clients that take a connector URL, enter https://<your-host>/mcp as the remote MCP server URL.
5. Install the mod (optional, recommended)
The bundled mod shows task progress in a pane beside Claude Code, and adds /rovo:create-task and /rovo:consult. In Claude Code:
/plugin marketplace add youyo/rovo
/plugin install rovo@rovo6. Try it
In Claude Code, run
/rovo:create-task Fix the flaky pagination test in the widgets API(the repository, branch and SHA come from the git repository you are in). The pane beside the chat shows the stages and the live log, and a Draft PR appears on GitHub.Open the dashboard at
https://<your-host>/dashboardand sign in with GitHub.Ask another model for a second opinion with
/rovo:consult Is this migration plan safe?
Related MCP server: claude-code-mcp
What you can do
Hand off coding tasks over MCP:
task_startcreates a working branch and a Draft PR, and a worker does the work in an isolated Sandbox. When the task completes, rovo makes the PR Ready for review.Review and merge on your terms: the client (the calling AI or you) runs the full tests and the review on the PR branch locally, then merges (
task_pr_merge, or directly withgh).review_policysays where the full tests and review happen (defaultclient_review), and GitHub branch protection always applies.Choose the model and the effort per task: Claude, OpenAI (BYOK) and free Workers AI models, with prices from
models_listand an automatic pick when you do not choose.Stay within budget: a monthly allowance, a per-task budget that asks before extending, and a limit on concurrent tasks.
Get a second opinion:
consultasks another model a single question, with prompt caching. Withtools, the model can look things up on the web first.Search and read the web safely:
web_search/web_fetchreturn third-party content as untrusted data with a safety verdict.Watch it all: a read-only dashboard with the timeline, stages, logs, cost and the state of the Draft PR, plus a Claude Code mod that shows the same progress beside the chat.
Architecture
Claude Code / Cowork
| Remote MCP (OAuth)
v
apps/gateway --- service binding ---> apps/web (Web Research Service: search / fetch / browse)
| Durable Object ^
v | service binding
apps/agent (AgentDO -> SandboxDO + Container)
packages/audit (shared audit-log library, used by all 3 Workers)Directory | Role |
| MCP server, OAuth, task state ( |
| Worker ( |
| Web Research Service (a separate, stateless Worker) |
|
|
| Allowlist of usable models, and prices and cost calculation taken from the pi-ai catalog (shared by gateway and agent) |
| How web_search / web_fetch results are shown to a model, wrapped as untrusted data (shared by the worker and |
The diagram below is generated from the code by Sketio Arch and updates on every analysis of main. Other views: infrastructure / application / deployment.
Reference
The rest of this page is reference material.
Licensed under MIT.
Design: docs/specs/design.md (Japanese). Deployment guide (for downstream users who use rovo as their upstream): docs/deploy.md (Japanese). Manual acceptance test: docs/specs/acceptance.md (Japanese).
Tools
Sign in with GitHub App user authorization, then use the following tools from /mcp.
Tool | Access | Description |
| write | Creates a task ( |
| read-only | Returns the status (with |
| read-only | A read-only tool for showing progress on screen (called periodically by the Claude Code mod). |
| read-only | Lists your own tasks (started with |
| read-only | Waits up to |
| read-only | Returns the outcome of a task in a terminal state (summary, verification result, patch, cost, |
| write | Cancels the task (idempotent, even in a terminal state). The Gateway resends until it confirms the worker has stopped |
| write | Answers a question from the worker ( |
| write | Sends additional instructions to a running worker |
| write | Turns the Draft PR of a COMPLETED task into Ready for review (idempotent). rovo already does this automatically when the task completes, so it is only needed when the automatic Ready failed ( |
| write | Merges the PR. |
| write | Asks rovo to review a pull request the client made (comments only). |
| read-only | Returns the usable models and their prices (no input). |
| read-only | Returns the available sizes and prices of the worker's Sandbox (no input). |
| read-only | Returns the state of your own budget (no input). |
| write | A second opinion (a design advisor). Answers |
| read-only | Receives the answer of a |
| read-only | Returns Web Search API results (url, title, description). See "Web Research Service" below |
| read-only | Fetches a public page and returns it as Markdown (does not run JS) |
| read-only | Disabled by default. Fetches a page after rendering its JS |
Constraints (also stated in the tool responses)
Task branch and Draft PR (Gateway side):
task_startcreates a working branchagent/task-<id>and a Draft PR. The URL is returned bytask_status,task_result(pr), and thedraft_pr_createdevent (draft_pr_urlin thetask_startresponse isnullbecause the PR is not created yet). At the terminal state, it updates the rovo-managed section of the PR body and revokes the Installation Token. The result is also returned as a patch intask_result.Reviewing, readying, and merging the PR: when the worker's task completes (
COMPLETED), rovo makes the PR Ready for review automatically (with the GitHub App's Installation Token;FAILED/BUDGET_EXCEEDED/CANCELLEDstay Draft, and if the automatic Ready fails the task staysCOMPLETEDandnext_stepssuggeststask_pr_ready). rovo never merges. Withclient_review(default), the client checks out the PR branch, runs the full tests (including ones that cannot run in the Sandbox, such as docker-based e2e) and the review locally, then merges withtask_pr_mergeor directly withgh. Keepverification_commandsto the tests related to the change; running the whole suite in the Sandbox costs time and money. The policy is a per-task input rather than a deployment-wide env, and organization-level enforcement is left to GitHub branch protection (operations use the user's own GitHub token, so a rejection by protection returnsmerge_blocked). The flow is also written in the MCPinstructionsand innext_stepsoftask_result.Model selection and budget: use
models_listto see the usable models and prices, and choosemodelfortask_startaccording to the difficulty of the task. The usable models are determined by the envAGENT_MODELS(the same value for gateway and agent; if unset, onlyAGENT_MODEL), and prices come from the pi-ai catalog (allowlist:packages/models). OpenAI models can be used by registering an OpenAI key as BYOK in the AI Gateway (the code of the target repository is sent to OpenAI too; set usage limits on the OpenAI side; seedocs/deploy.md, Japanese).budget_statusshows the remaining monthly amount. The default thinking level (effort) is set per model (initial values: haiku=low, sonnet=high, opus=medium, fable=low, gpt-6-luna=low, gpt-6-sol=medium, gpt-6.1-sol=medium, gpt-6-astra=low; for Workers AI, gpt-oss, qwen, nemotron, and glm-4.7-flash=low, gemma=high) and is shown inmodels_listandtask_status/task_result. Thinking tokens are billed as output, so a higher level costs more. The default can be overridden by the envMODEL_EFFORTS, and for a single task byeffortintask_start(off / minimal / low / medium / high / xhigh / max; a level the model does not support returnsinvalid_input, and the message lists the usable levels).consultdefaults tomediumthinking (envCONSULT_EFFORT; unset or invalid meansmedium; if the model does not accept it, the per-model default is used) and can be overridden per call with theeffortinput ofconsult; the level used is returned in the response (task defaults are unchanged). When the model rejects aconsultrequest (4xx),model_rejectedis returned, which does not encourage retrying.Provider keys and Workers AI: the gateway reads the AI Gateway BYOK keys (the Cloudflare API "List provider keys"; the secret
CLOUDFLARE_API_TOKENis read-only with only Account / AI Gateway / Read, and the vars areCLOUDFLARE_ACCOUNT_ID/AI_GATEWAY_ID), marks models of providers without a key whose alias isdefaultasavailable: false, andtask_start/consultreject them withmodel_unavailable(the result is cached for 5 minutes; if the API fails, the previous cache is used, and if there is none, the decision falls back toAGENT_MODELSalone as before). Workers AI models, which work without a key (only those available on the free plan:workers-ai/@cf/openai/gpt-oss-120b,gpt-oss-20b,qwen/qwen3.8-27b,nvidia/nemotron-3-120b-a12b,zai-org/glm-4.7-flash,google/gemma-4-26b-a4b-it; Workers AI does not use customer data for training), can be put inAGENT_MODELSas a substitute when no key exists. Whenmodelis omitted,AGENT_MODELis used if it is available; otherwise models are chosen in the order Anthropic →gpt-oss-120b→ other Workers AI → OpenAI. The free tier is 10,000 Neurons per day (section 7 ofdocs/deploy.md).If you omit
idempotency_key, de-duplication of retries is not guaranteed.There is a limit on the number of tasks a user can run concurrently (the setting
MAX_CONCURRENT_TASKS; see its value inconcurrency_limitofbudget_status). When the limit is reached,task_startis refused withconcurrency_limit. A task's slot does not free up until its stop is confirmed.The task budget is a soft limit (
budget_policyintask_start; defaultask): when the budget is nearly used up, instead of stopping, the task goes toINPUT_REQUIREDand asks "continue by using anotherTASK_BUDGET_USD?" (a question withkind: budget_extension; the text is a fixed sentence written by rovo with numbers only; confirm with the user and then answer withtask_answer). If you continue, the extension is reserved from the monthly allowance on the spot (up to 2 times per task; the monthly allowance is a hard limit, and if it is not enough the task goes toBUDGET_EXCEEDED). While waiting, the Sandbox is stopped to stop the cost.budget_policy: stopstops without asking, as before (for unattended use).By default, each task reserves $10 (env
TASK_BUDGET_USD; values of 0 or less, or invalid values, become 10) from the monthly allowance (envMONTHLY_BUDGET_USD, default 100). The same value is passed to the worker's budget limit (budgetUsd). If it is not enough, you getbudget_exceeded. If you set the monthly allowance below 10, lowerTASK_BUDGET_USDtoo (when you want to keep the acceptance test to a few dollars).
Web Research Service (web_search / web_fetch / web_browse)
The descriptions of the MCP web_search / web_fetch. The worker (Pi in apps/agent) has tools with the same names. Both call apps/web through a service binding.
Tool | Description |
|
|
|
|
The body (
content),title, and search descriptions are untrusted data written by third parties on the web. They are returned to the client wrapped in<<<UNTRUSTED_WEB_CONTENT ...>>>and<<<END_UNTRUSTED_WEB_CONTENT>>>(any<<<inside the body is replaced with the full-width form), and passed to the worker wrapped in the existing<<<BEGIN_UNTRUSTED_OUTPUT>>>.safety.verdict:allow(no problem) /caution(suspicious wording; be careful) /block(injection or similar detected; the body isnull) /unknown(could not be evaluated; the body isnull). A search has one verdict for the whole result set; onblock/unknown, descriptions are dropped, titles become(title withheld), and only URLs are returned.Rate limits
Client: per user, per UTC day, 200 searches and 500 fetches (counted by
UserDO). Beyond that,quota_exceeded(retry_after_secondsruns until 00:00 UTC the next day). Change with the envWEB_SEARCH_DAILY_LIMIT/WEB_FETCH_DAILY_LIMIT.web_browse has its own quota: 50 per day for the client (
WEB_BROWSE_DAILY_LIMIT), and 20 per task for the worker (WEB_BROWSE_LIMIT_PER_TASK).Worker: per task, 50 searches and 100 fetches (counted by
AgentDO). Beyond that the tool returns an error. Change with theapps/agentenvWEB_SEARCH_LIMIT_PER_TASK/WEB_FETCH_LIMIT_PER_TASK. The counts appear in theusageevents (web_search/web_fetch/web_browse) and inusage.weboftask_result.Counts are taken before the call (not given back on failure). web itself has no limit on the number of calls (it relies on the AI Gateway rate limit and the account limits).
web_browse(fetches a page as Markdown after rendering its JS; Browser Run's Kitesurf) is disabled by default. Only Workers whose envWEB_BROWSE_ENABLEDis"true"inapps/web,apps/agent, andapps/gatewayenable it (when disabled it appears neither in the client's tools/list nor in the worker Pi's tools). It accepts only{url}, andstatusisok/blocked(bot challenge, robots, redirect or destination that is private, not allowed, https→http, or a name that resolves to a private address) /http_error(4xx, 5xx; no body) /unknown(could not be determined; no body). What is guaranteed is only that "results from forbidden or undeterminable destinations are not returned"; it cannot prevent communication with a forbidden destination from happening. Before enabling it, please read the residual risks in section 11 ofdocs/specs/acceptance.md(Japanese), "web_browse (gate before enabling)", and decide whether you accept them. The gate on real systems is optional, and the author enabled it without performing it.
Dashboard
The gateway serves a read-only dashboard (/dashboard) where you can see the state of tasks in a browser. After signing in with GitHub, you can see a list of today's timeline, a table of tasks, your budget, everyone's results, cost by model, and Cloudflare usage (AI Gateway, Workers, containers, and more), as well as the details of each task (progress, the tail of the output, cost, and the state of the Draft PR).
Read-only: there are no operations that change state (except logging out).
You see only tasks in repositories where you have permission on GitHub. Tasks in repositories you lack permission for appear neither in the list nor in the details (only the counts and the cost totals are shown).
Opening the
dashboard_urlin the response oftask_startand others takes you to that task's details.Deployment steps are in section 18 of docs/deploy.md (Japanese); the design is in docs/specs/dashboard.md (Japanese).
Claude Code mod
A read-only mod that shows the progress of tasks in a pane beside Claude Code is bundled (plugins/rovo/README.md). It also adds /rovo:create-task, /rovo:consult, /rovo-console. To install it, see step 5 of the Quick start (/plugin marketplace add youyo/rovo and /plugin install rovo@rovo). From a shell: claude plugin install rovo --marketplace youyo/rovo.
Components
apps/web: a separate Worker for the Web Research Service. The RPC methodssearch/fetchUrlofWebResearch(aWorkerEntrypoint;fetchcannot be used because it is the name of the HTTP handler). The caller type is taken from the service binding'sprops.caller(parentfor the gateway,childfor the agent), and the call is rejected if props are missing or invalid. It holds no state, and its only binding isai(it holds no GitHub token or Anthropic key). It has no public route (workers_dev: false)apps/gateway: a Cloudflare Worker. Routing is done with Hono, and everything is wrapped by@cloudflare/workers-oauth-provider./mcp(OAuth required):createMcpHandlerfromagents/mcp/serverplus@modelcontextprotocol/server. A McpServer is created per request, and user information is not shared/authorize(consent screen) → GitHub App user authorization →/callback: the defaultHandler. For state (single-use, 10 minutes) and binding to the browser that started the flow via a cookie, it uses OAuthProvider'sbeginUpstream/finishUpstreamUserDO(per user): GitHub credentials (encrypted with AES-GCM), a cache of team membership (TTL 5 minutes), concurrency slots (one row per task; the limit isMAX_CONCURRENT_TASKS), and the monthly budget (per-task reservation, idempotent settlement of usage, refund at the end). The GitHub token is never returned outside the DOTaskDO(per task): state and events. Converges, via alarms, the delivery of start / cancel to the AgentDO, the ingestion of events, and the settlement of usage
apps/agent: a separate Worker for the worker.SandboxDO(startsctx.containerwithenableInternet:falseand hasOutboundGatewayintercept outbound traffic),OutboundGateway(the allow decision is a pure function insrc/outbound-policy.ts), andAgentDO(a minimal implementation withPiHarnessand a scripted fake model). The Gateway calls it through the DO bindingAGENT_DOwithscript_nameDependency cache: if the lockfile at the root (
pnpm-lock.yaml/package-lock.json/yarn.lock) is unchanged, it starts by restoring the installednode_modulesfrom a Containers snapshot (public beta).DepsCacheDO(makes creation happen once per key) and a separate instance of SandboxDO (which builds it) do this. If it cannot be used, the task does not fail and runs as before. The result isusage.deps_cacheintask_result(hit/miss/built/disabled), andcache_build_msfor the task that built it. It can be disabled withdepsCache.enabledinrovo.config.json(section 17 of docs/deploy.md)
Consent screen (protection against the confused deputy problem)
Because DCR (dynamic client registration) is enabled, /authorize always shows a consent screen before going to GitHub. Even if a malicious client makes a user start authorization, no MCP access token is handed over unless the user approves.
Shown: the client name (
client_name; self-declared and unverified), the host ofredirect_uri(displayed prominently) and the full URI, the requested scope, and "Allow this app to create and operate your rovo tasks?". If you do not recognize the destination host, reject.On approval, you proceed to the GitHub login. On rejection, you are sent back to the client's
redirect_uriwitherror=access_denied.CSRF protection: the form values are a single-use handle that is matched against a
__Host-cookie (Secure, HttpOnly, SameSite=Lax). A submission from another site or another browser, or a resubmission, gets a 400.As protection against clickjacking, it sets
X-Frame-Options: DENYand a CSP (default-src 'none',frame-ancestors 'none', and so on).form-actionis not set (Chrome applies it to the redirect target after a POST too, which would break the redirect after approval or rejection).Approval is not remembered. The consent screen appears at every login.
Development
Runtimes (node / pnpm) are pinned with mise. Run every command through mise run.
Docker is required: the E2E tests for apps/agent (mise run test:e2e:docker, and therefore mise run test) have wrangler dev build and start a container (apps/agent/Dockerfile) with Docker. Docker must be running locally (Docker Desktop, for example). The first run takes a few minutes to build the image (including pulling cloudflare/sandbox:1.0.1).
Do not start mise run dev:all while E2E tests are running: the E2E cleanup removes with docker rm -f the workerd-rovo-agent-* containers that were added during that run. E2E runs are serialized with each other by a lock ($TMPDIR/rovo-e2e-docker.lock), but dev:all does not take the lock, so running them in parallel can delete the containers of your development session. Also, E2E rewrites apps/*/.dev.vars only while it runs (the original file is moved aside to .dev.vars.rovo-e2e-backup, and it is restored at the start of the next run even after an abnormal exit).
mise install # install node / pnpm
mise run install # dependencies (pnpm install --frozen-lockfile)
mise run test # Unit + fake GitHub + E2E
mise run lint # Biome + type checkTask | Description |
| Unit tests for gateway, agent, and web. vitest + |
| Integration tests of the fake GitHub server and the GitHub client (Node) |
|
|
| Starts the real Worker and a fake GitHub with |
| Docker required. Starts a real container with |
| Docker required. Starts the three Workers gateway, agent, and web at the same time with |
| Docker required, real network. The real-network version of the E2E above. It only checks that GET / |
| Biome lint / formatting, and |
| Run after changing |
| Starts gateway, agent, and web locally at the same time (Docker required). |
| Checks that the Workers bundle ( |
| Tests for the Terraform in |
| Self-checks of |
| Format / validate |
| Actually deploys. The first-time end-to-end flow (check prerequisites → terraform → |
| Generates the wrangler config in |
| Actually deploy (using the downstream wrangler config in |
| Local start ( |
| Retakes the README screenshots in |
If an E2E test fails, E2E_DEBUG=1 mise run test:e2e also shows the output of wrangler dev. The E2E tests do not connect to GitHub (they use a fake GitHub).
E2E diagnostic logs: E2E tests (those that use Docker) always leave diagnostic logs outside the place where the tests are run. They are saved in $TMPDIR/rovo-e2e-logs/<timestamp>-xxxxxx/ (one per test file; the newest 30 are kept and older ones are removed; the root can be changed with ROVO_E2E_LOG_DIR).
wrangler-<config name>.log: the output (stdout / stderr) ofwrangler dev. At the end of the E2E run,[rovo-e2e] wrangler のログ: <path>is printed. The path is also included in the error when startup failstiming.log: the durations of the main stages (wrangler ready/wrangler stop, the Sandbox'sensure-ready/exec/stop(reaper) /terminate/destroy, and each Agent RPC). A failed stage is recorded asfailed. WithE2E_DEBUG=1it is also printed to stderr (timeout values are not changed)
Configuration
The complete list of values to prepare at deploy time is in section 5 of docs/deploy.md (Japanese; if they disagree, deploy.md is authoritative).
Variables (vars in wrangler.jsonc, or .dev.vars locally)
Variable | Description |
|
|
| The public URL. It becomes the OAuth issuer, the |
| The GitHub App's Client ID (not a secret) |
|
|
| Numeric GitHub user IDs allowed to log in and call all tools (comma-separated; you can find yours with |
| The GitHub App's App ID (not a secret). Used to issue Installation Access Tokens (the JWT's |
| What to do for repositories where Draft PRs cannot be created (such as private repositories on the Free plan). |
| The default model, and the models that can be chosen for |
| Per-model overrides of the thinking level (comma-separated |
| Read the AI Gateway BYOK keys so that models of providers without a key cannot be selected. If any of them is empty, no decision is made and only |
| The amount reserved from the monthly allowance for each |
| The limit on tasks a user can run concurrently (an integer of 1 or more; default 2; invalid or unset values become 2). When the limit is reached, |
| Only when |
Secrets (wrangler secret put <NAME>, or .dev.vars locally)
Secret | Description |
| The GitHub App's Client secret |
| The GitHub App's private key (a PKCS#8 PEM). GitHub lets you download PKCS#1 ( |
| (gateway) The key used to store GitHub tokens encrypted in UserDO. 32 bytes in base64 ( |
Secrets / variables of the agent (apps/agent)
Name | Description |
| The key used to store the Installation Token encrypted in SandboxDO's storage. 32 bytes in base64 ( |
| The AI Gateway ID. If empty, |
| The default model (default |
| The models usable for |
| Per-model overrides of the thinking level (if empty, the defaults). Use the same value as the gateway's variable of the same name. This is the value actually passed to Pi |
|
|
| WEB_SEARCH_LIMIT_PER_TASK / WEB_FETCH_LIMIT_PER_TASK / WEB_BROWSE_LIMIT_PER_TASK (variables) | The per-task limits on the worker's web_search / web_fetch / web_browse (defaults 50 / 100 / 20). Integers of 0 or more only. Invalid values revert to the default |
| WEB_BROWSE_ENABLED (variable) | Whether to expose web_browse to the worker (only when "true"; default "false") |
| WEB (service binding) | WebResearch of apps/web (props: {caller: "child"}). Deploy in the order web → agent → gateway |
Locally, copy apps/agent/.dev.vars.example to apps/agent/.dev.vars and fill it in (mise run dev:all).
Variables of web (apps/web)
There are no secrets (it holds neither a GitHub token nor an Anthropic key). Its only binding is ai (Workers AI).
Variable | Description |
| The AI Gateway ID. Required. If empty, it calls neither |
| The search provider ( |
| Thresholds of the Policy Engine (defaults 0.8 / 0.4). If the model's score is at or above them, the verdict is block / caution. These are provisional values to be tuned in the acceptance test |
| For E2E only. Only when |
Web-related settings of the gateway
Name | Description |
|
|
| The per-user, per-UTC-day limits on the client's |
| Whether to expose |
Deployment order: web → agent → gateway. A service binding requires the referenced Worker (rovo-web) to exist first. The agent is referenced from the gateway by script_name.
Deployment
rovo is built so that anyone who clones it can deploy it by supplying their own values. The upstream provides these two things.
infra/: Terraform that creates the Cloudflare resources (two R2 buckets [archive and audit log], KV, AI Gateway, and the Logpush of the audit log). See infra/README.md for usage.mise run setupand thedeploy:*mise tasks: writerovo.config.json(non-secret values; the template is rovo.config.example.json) and runmise run setup.deploy:configgenerates the wrangler config indeploy/from the templates in deploy.example/ (commit it), followed bydeploy:all(web → agent → gateway) anddeploy:secrets(puts in only the secrets that are not there yet). IfROVO_DEPLOY_DIRis unset or the config files are missing,deploy:*does nothing and fails.
Downstream (the deployer's private repository) merges rovo as its upstream and only adds as new files rovo.config.json, the generated wrangler config in deploy/, and a backend. The only Cloudflare token a person creates is the one for Terraform, and the only secrets a person types are the GitHub App's Client secret and the path to the .pem. For the steps, see section 4 (quick start) of docs/deploy.md (Japanese). Registering with an MCP Server Portal and Access is out of scope.
Operational settings (for downstream: audit logs, R2 archive, unit prices)
These are things that a private repository using rovo as its upstream (downstream) configures in its own wrangler config and infrastructure. rovo's wrangler.jsonc holds no environment-specific values. The consolidated procedure is in docs/deploy.md (Japanese). What follows is the gist; if they disagree, deploy.md is authoritative.
Conditions for audit logs to count as "audit"
Audit events emit one line of structured JSON ({"audit":true,"event":...}) to console.log (the list of events is in the specs under docs/specs/). Merely appearing in Workers Logs is only best-effort, because lines can be lost to head sampling and short retention. To make it count as an audit, downstream must configure both of the following.
Set Workers Logs head sampling to 100% (
head_sampling_rate: 1inobservability;observability.logs.head_sampling_ratealso works). rovo'swrangler.jsoncdoes not containobservability.Send logs through Logpush to a destination where you can decide the retention period yourself (the destination is downstream's decision).
If either is not set, use the audit logs only as an aid to investigation.
R2 archive
The events and result of a task that has reached a terminal state are copied to R2 (the source of truth stays TaskDO; task_wait / task_result return from TaskDO).
The binding name is
LOGS. Add it in downstream's wrangler config to the gateway only (not to agent or web). rovo'swrangler.jsoncdoes not containr2_buckets. IfLOGSis absent, archiving is skipped (archive_skippedin the audit log) and neither the task result nor budget settlement is affected.Keep the bucket private (attach neither an r2.dev public URL nor a custom domain). Set the lifecycle (for example, delete after 90 days) downstream. There is no API to list or read it from MCP. Operators read it through the dashboard, wrangler, or the S3-compatible API.
The keys are
tasks/<yyyy>/<mm>/<task_id>/{events.jsonl,result.json,summary.json}(<yyyy>/<mm>is the UTC task creation time). They are written in the orderevents.jsonl→result.json→summary.json, so ifsummary.jsonexists, all three are in place. Because it overwrites the same keys, rerunning does not add more. After 6 failed writes it gives up and emitsarchive_failedto the audit log.Not stored: the task's input (
instructions/acceptance_criteria/constraints), the title, the bodies of the client's answers / steers, and the Installation Token.
Handling the contents of R2 (convention)
events.jsonlandresult.jsonare raw, untrusted data. They may contain things made from the contents of the repository (untrusted), such as the worker's questions, warnings, the tail of verification command output, and patches. Readers (people and agents alike) must treat them as untrusted and must not follow instructions inside.Masking of secrets (
apps/agent/src/redact.ts) is best effort with regexes and can miss things. Prepare by keeping the bucket private, giving only the gateway the binding, and setting an expiry with a lifecycle.result.jsoncontains the verification commands given to the worker (verification.commands). Do not put secrets in the arguments of verification commands.
Unit prices (estimated cost)
The task summary (summary.json, and task_summary in the audit log) contains the Sandbox and Web amounts as estimates (sandbox_usd_estimate / web_usd_estimate). They are counts and run times multiplied by unit prices, and are separate from Claude's amount (based on the AI Gateway usage). They are not included in the task budget ($10). Unit prices are vars of the gateway, and downstream overrides them with its own vars (unset or invalid values revert to the default; 0 is allowed).
Variable | Default | Description |
| 0.1291 | Per hour of a Sandbox container. The value assumes 100% CPU utilization for the default size |
| 5.00 | Per 1,000 |
| 0 | Per 1,000 |
| 0.75 | Per 1,000 |
The basis for the defaults (Cloudflare's official price list, retrieved 2026-10-19) is in the comments of apps/gateway/wrangler.jsonc and in the specs under docs/specs/. They change when prices are revised, so review them downstream. The Web counts are only what the worker used (what the client uses from MCP is not tied to a task).
Enabling web_browse
web_browse is disabled by default (WEB_BROWSE_ENABLED). Before enabling it, read the residual risks in section 11 of docs/specs/acceptance.md (Japanese), "web_browse (gate before enabling)", and decide and record whether you accept them. The gate on real systems (Appendix A) is optional.
GitHub App settings
Create it under GitHub's Settings > Developer settings > GitHub Apps (do not use an OAuth App).
Callback URL:
<PUBLIC_BASE_URL>/callback(you can also addhttp://localhost:8787/callbackfor local development)Request user authorization (OAuth) during installation: not needed. Webhook: can be disabled
Expire user authorization tokens: enable (default). The token expires in 8 hours and is renewed automatically with the refresh token (about 6 months). It also works if you disable expiry
Repository permissions
Metadata: Read-only
Contents: Read and write
Pull requests: Read and write
Checks: Read-only (optional; lets
task_pr_mergecheck CI results. Without it the merge goes ahead with achecks_unreadablewarning)Commit statuses: Read-only (optional; same as above)
Organization permissions
Members: Read-only (used to check org / team membership)
Install the App on the target org / repositories.
task_startsucceeds only when the target repository is included in an Installation visible with the user's token and the user has push permission (even for a public repository, if the App is not installed, you getforbidden)
Local development
cp apps/gateway/.dev.vars.example apps/gateway/.dev.vars # fill in the values (.dev.vars is gitignored)
mise run devThe __Host- cookie (binding of state) is Secure, so use a browser that accepts Secure cookies over loopback http, such as Chrome.
Errors
A tool error has isError: true, and the body is JSON of the form {"code", "message", "retry_after_seconds"?}. code is one of
not_found / conflict / expired / concurrency_limit / budget_exceeded (the monthly allowance is not enough and task_start is rejected; waiting does not free it) / quota_exceeded (the daily web limit) / not_ready (the target of task_result is not yet terminal) / invalid_cursor / forbidden / reauth_required / upstream_unavailable / invalid_input / internal. consult also returns model_rejected when the model rejects the contents of the request (4xx; waiting does not fix it; no retry_after_seconds). task_pr_ready / task_pr_merge also return merge_blocked (branch protection and the like; the message contains GitHub's reason) / head_moved (the branch moved after verification) / github_rejected, which represent rejections by GitHub.
reauth_required means the GitHub credentials cannot be recovered (even if the MCP token is valid), and a new login is needed. internal is an unexpected failure, and the details are not included in the response.
Security
Do not report vulnerabilities in a public Issue; use GitHub's private vulnerability reporting. See SECURITY.md for details.
Contributing
This is published as a personal project. Issues and Pull Requests are welcome, but I cannot promise when I will reply or merge. For large changes, please discuss in an Issue first.
License
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
This server cannot be deployed
Maintenance
Related MCP Connectors
Remote streamable-HTTP MCP server running on a single Cloudflare Worker. Your assistant gets live Airbnb, Amazon, Booking.com, Google Flights, Maps and Reddit data, social search on X, Instagram and TikTok, the Meta Ad Library, and image/video generation without any keys. Connect your own accounts to let it send WhatsApp or Telegram messages, work an IMAP inbox, manage Meta Ads campaigns and publish to X and LinkedIn. OAuth 2.1 with PKCE; stored credentials are AES-256-GCM encrypted.
GitHub Copilot MCP: code, repo, PR, workflow tools for AI assistants.
AI-native git hosting — repos, PRs, issues, CI gates, and AI code review over MCP (60 tools).
Hosted coding agent: start, follow up on and stop tasks that can open GitHub pull requests.
Related MCP Servers
- AlicenseAqualityFmaintenanceEnables Claude or any MCP client to drive the GitHub Copilot cloud agent via its REST API, allowing it to research, edit, and open pull requests on repositories without manual intervention.11MIT
- AlicenseAqualityDmaintenanceWraps Claude Code as tools for MCP clients, enabling autonomous coding tasks via a 4-tool lifecycle with session management, async polling, and permission controls.457 npm21MIT
- FlicenseNot gradedqualityCmaintenanceEnables GitHub repository operations (list/read files, create branches, commit files, open/list PRs) via an authless remote MCP server that keeps your GitHub token encrypted on Cloudflare, with access limited to allowed repositories.-
- AlicenseNot gradedqualityAmaintenanceEnables AI coding agents and human managers to coordinate work on GitHub Projects v2 through 20 tools and 169 actions for autonomous task checkout, heartbeats, reviews, token budgets, PRD generation, sprint planning, issue triage, and PRD-to-issues pipelines, backed by GitHub-native storage and MCP resources/prompts.1,038 npmMIT