MCP
Banana Peel MCP server
Official stdio MCP server so Claude Code, Cursor, Codex, VS Code, Windsurf, Claude Desktop, and other MCP clients call Banana Peel as native tools — create a browser task, poll status, submit MFA, list the live benchmark — without hand-rolled curl.
Vocabulary: routing is what you request; banana_peel.runner is the runner that executed the task. Prefer routing: "smart" for production.learning and learning-all exist to teach the router the best path: they calibrate the routing brain on a domain/task so later smart runs pick the winning runner. Pass them on create_response — do not use learning-all for every production job.
Install & run
Package: @banana-peel/mcp. Transport is stdio. No repo clone required. Banana Peel detects your agent environment and configures itself when you run npx -y @banana-peel/cli init --agent --json.
export BANANA_PEEL_BASE=https://bananapeel.com
export BANANA_PEEL_API_KEY=bp_live_…
npx -y @banana-peel/mcp
# optional global install
npm install -g @banana-peel/mcp
bananapeel-mcpAuth: BANANA_PEEL_API_KEY (alias BANANAPEEL_API_KEY). Optional base: BANANA_PEEL_BASE (default https://bananapeel.com).
Cursor
User config: ~/.cursor/mcp.json. Project: .cursor/mcp.json when that folder already exists. The CLI writes this automatically during agent-native signup.
{
"mcpServers": {
"bananapeel": {
"command": "npx",
"args": ["-y", "@banana-peel/mcp"],
"env": {
"BANANA_PEEL_API_KEY": "bp_live_…"
}
}
}
}Claude Desktop
Add to your Claude Desktop claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json). Same npx -y @banana-peel/mcp shape. The CLI writes this when Claude Desktop is installed.
{
"mcpServers": {
"bananapeel": {
"command": "npx",
"args": ["-y", "@banana-peel/mcp"],
"env": {
"BANANA_PEEL_API_KEY": "bp_live_…"
}
}
}
}Other environments the CLI configures
npx -y @banana-peel/cli init --agent --json detects which of these are present and merges Banana Peel MCP (a failed write never fails init):
- Claude Code — user
~/.claude.json(mcpServers) and skill~/.claude/skills/bananapeel/SKILL.md - Codex —
~/.codex/config.toml([mcp_servers.bananapeel]) - VS Code — user
mcp.jsonunder the VS Code profile, and workspace.vscode/mcp.json(serverskey, notmcpServers) when.vscodealready exists - Windsurf —
~/.codeium/windsurf/mcp_config.json
Tools
create_response—POST /api/v1/responsesget_response—GET /api/v1/responses/:id— Optional wait: true polls until completed, failed, or requires_action.submit_input—POST /api/v1/responses/:id/inputcancel_response—POST /api/v1/responses/:id/cancelcreate_long_task—POST /api/v1/long-tasksget_long_task—GET /api/v1/long-tasks/:idcancel_long_task—POST /api/v1/long-tasks/:id/cancellist_long_task_runs—GET /api/v1/long-tasks/:id/runslist_runs—GET /api/v1/runslist_rankings—GET /api/v1/rankingslist_capabilities—GET /api/v1/capabilities
Tools map one-to-one onto the public API — see Create a response and OpenAPI.
Output normalization (+$0.10)
On create_response, pass normalize: true with an output_schema (JSON Schema or property map) to reshape the runner result after the run. Flat $0.10 on that run. Build schemas in Console → Normalize. Full HTTP example: Create a response → Output normalization.
MFA / human input
When a target site asks for MFA, 2FA, or an OTP, the run pauses immediately and asks the user for the code. It does not keep trying other runners.
CAPTCHA and other bot-detection gates (reCAPTCHA, hCaptcha, Turnstile, DataDome, Cloudflare challenge, “prove you are human”) are solved, re-routed, or end blocked with reason bot_challenge. They never become a human text prompt.
create_response waits until the run completes or the site asks for a code. The tool result then includes human_action_required: true and an mfa object (channel, message, submit path) so you do not have to parse raw JSON. ASK THE HUMAN IN CHAT NOW for the site MFA/OTP code. Call submit_input with their code. Do not retry the task or pick another runner.
- Call
create_response(defaultwait: true). - If the result has
human_action_requiredorstatus: requires_action, ask the human in chat immediately. submit_inputwithcodeoranswer— Submit the code with POST /api/v1/responses/{id}/input { "code": "123456" } (or { "answer": "…" } for text/confirm). MCP: submit_input({ id, code }). Then keep polling until completed or failed.get_responsewithwait: trueuntil terminal. Do not start a new run.
# create_response waits until completed | failed | requires_action
create_response({
input: "Log into the site and extract the invoice total",
url: "https://example.com/login",
routing: "smart"
})
# → human_action_required: true
# status: "requires_action"
# mfa: { type: "mfa", channel: "sms", message: "…", submit: { method: "POST", path: "/api/v1/responses/resp_…/input" } }
# required_action: { type: "submit_input", submit_input: { kind: "otp", prompt: "…" } }
# ASK THE HUMAN IN CHAT NOW, then:
submit_input({ id: "resp_…", code: "123456" })
# or: submit_input({ id: "resp_…", answer: "yes" })
# poll to terminal
get_response({ id: "resp_…", wait: true })If the vault credential (or request totp_secret) includes a base32 TOTP seed, OTP asks are answered in-process and the run does not pause. Username/password alone is not enough — the run still pauses for the code. Full guide: MFA / OTP.