MCP Protocol
Baponi is a native Model Context Protocol (MCP) server that exposes sandboxed code execution and file access as tools. Any MCP-compatible client (Claude Desktop, Claude Code, Cursor, Windsurf, or custom agents) can connect with a URL and API key. No SDK installation, no local process management, no Docker containers.
Endpoint: POST https://api.baponi.ai/mcp
Protocol version: 2025-11-25
Transport: Streamable HTTP (JSON-RPC 2.0 over POST, Server-Sent Events over GET)
Auth: Authorization: Bearer sk-us-YOUR_API_KEY
Connecting from MCP clients
Section titled “Connecting from MCP clients”Configure your MCP client with the Baponi endpoint URL and an API key. Create API keys in the admin console under API Keys.
Add to your claude_desktop_config.json:
{ "mcpServers": { "baponi": { "url": "https://api.baponi.ai/mcp", "headers": { "Authorization": "Bearer sk-us-YOUR_API_KEY" } } }}Config file location:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
After saving, restart Claude Desktop. Baponi tools appear in the tool picker (hammer icon).
claude mcp add baponi \ --transport http \ --url https://api.baponi.ai/mcp \ --header "Authorization: Bearer sk-us-YOUR_API_KEY"Claude Code stores the configuration automatically. Run claude mcp list to verify.
Add to .cursor/mcp.json in your project root:
{ "mcpServers": { "baponi": { "url": "https://api.baponi.ai/mcp", "headers": { "Authorization": "Bearer sk-us-YOUR_API_KEY" } } }}Reload the Cursor window after saving. Baponi tools appear in the MCP tools panel.
Add to your Windsurf MCP configuration file (~/.windsurf/mcp.json or project-level):
{ "mcpServers": { "baponi": { "url": "https://api.baponi.ai/mcp", "headers": { "Authorization": "Bearer sk-us-YOUR_API_KEY" } } }}Baponi uses the standard Streamable HTTP transport. Any MCP client that supports remote HTTP servers can connect:
- URL:
https://api.baponi.ai/mcp - Transport: Streamable HTTP (POST for JSON-RPC, GET for SSE)
- Auth header:
Authorization: Bearer sk-us-YOUR_API_KEY - No local process needed. Baponi runs in the cloud, not as a local stdio server
Transport details
Section titled “Transport details”Baponi implements the MCP Streamable HTTP transport defined in the MCP specification:
| HTTP Method | Content-Type | Purpose |
|---|---|---|
POST | application/json | Send JSON-RPC requests and receive responses |
GET | text/event-stream | Open an SSE stream for server-initiated messages |
All requests go to a single endpoint: https://api.baponi.ai/mcp. There is no separate SSE URL.
Stateless design: zero session management
Section titled “Stateless design: zero session management”Unlike many MCP servers, Baponi does not use Mcp-Session-Id headers or maintain server-side session state. This is intentional:
- No session IDs or reconnection tokens. Every request stands alone.
- No
initializerequirement. The server responds totools/listandtools/callwithout prior initialization. Sendinginitializeis accepted and returns capabilities, but it is not a prerequisite. - No sticky routing. Requests can hit any backend pod. Scaling, deploys, and pod restarts never break connections.
- No session cleanup. Nothing to expire, nothing to garbage-collect.
This means MCP clients that expect session negotiation still work (the initialize handshake succeeds), but clients that skip it and go straight to tools/list or tools/call also work.
Methods
Section titled “Methods”initialize
Section titled “initialize”Negotiate protocol version and discover server capabilities. MCP clients typically send this as the first request. On Baponi, it is accepted but not required. The response is the same regardless of prior state.
Request:
{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-11-25", "capabilities": {}, "clientInfo": { "name": "my-agent", "version": "1.0" } }}Response:
{ "jsonrpc": "2.0", "id": 1, "result": { "protocolVersion": "2025-11-25", "capabilities": { "tools": { "listChanged": false } }, "serverInfo": { "name": "baponi", "version": "0.1.0" }, "instructions": "Baponi is a sandboxed code execution platform for AI agents. Use sandbox_execute to run Python, Node.js, or Bash code. Use files_download to retrieve files produced by executions." }}Fields in result:
| Field | Description |
|---|---|
protocolVersion | The MCP spec version the server implements (2025-11-25) |
capabilities.tools.listChanged | Whether the server sends notifications/tools/list_changed. Always false. Tool list changes require a new tools/list call. |
serverInfo.name | Server identifier (baponi) |
serverInfo.version | Server version |
instructions | Human-readable description of the server’s purpose, surfaced to LLMs by MCP clients |
notifications/initialized
Section titled “notifications/initialized”After receiving the initialize response, MCP clients send this notification to signal readiness. Baponi accepts and acknowledges it, but takes no action (stateless design).
{ "jsonrpc": "2.0", "method": "notifications/initialized"}No response is returned (JSON-RPC notifications have no id).
tools/list
Section titled “tools/list”Discover the tools available to your API key. The response depends on your organization’s configuration. Tools can be enabled or disabled per-organization in the admin console.
Request:
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list"}Response:
{ "jsonrpc": "2.0", "id": 2, "result": { "tools": [ { "name": "sandbox_execute", "description": "Execute code in a sandboxed environment", "inputSchema": { "type": "object", "properties": { "code": { "type": "string", "description": "Code to execute" }, "language": { "type": "string", "enum": ["python", "node", "bash"], "description": "Programming language. Defaults to the sandbox's configured language if omitted." }, "timeout": { "type": "integer", "minimum": 1, "maximum": 3600, "description": "Execution timeout in seconds. Defaults to sandbox configuration." }, "thread_id": { "type": "string", "description": "Persistent thread ID. Files written to /home/baponi persist across calls with the same thread_id." }, "metadata": { "type": "object", "description": "Arbitrary key-value metadata attached to the execution record." }, "env_vars": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Environment variables injected into the sandbox. Keys must be uppercase letters, digits, and underscores, starting with a letter." } }, "required": ["code"] }, "annotations": { "readOnlyHint": false, "destructiveHint": false, "idempotentHint": false } }, { "name": "files_download", "description": "Download file content from a thread or volume", "inputSchema": { "type": "object", "properties": { "source": { "type": "string", "enum": ["thread", "volume"], "description": "Where to read the file from." }, "id": { "type": "string", "description": "The thread_id or volume name." }, "path": { "type": "string", "description": "File path relative to the storage root." } }, "required": ["source", "id", "path"] }, "annotations": { "readOnlyHint": true, "destructiveHint": false, "idempotentHint": true } } ] }}Tool annotations
Section titled “Tool annotations”Each tool includes MCP annotations that help clients decide how to present and approve tool calls:
| Annotation | sandbox_execute | files_download | Meaning |
|---|---|---|---|
readOnlyHint | false | true | Whether the tool only reads data without side effects |
destructiveHint | false | false | Whether the tool may delete or overwrite external data |
idempotentHint | false | true | Whether repeated calls with the same arguments produce the same result |
Tool visibility
Section titled “Tool visibility”Not all tools appear for every API key:
sandbox_executeis always available when code execution is enabled for the organization.files_downloadappears only when the API key’s sandbox has storage configured (a storage connection or volume).- Tools disabled in the admin console Tool Configuration settings are excluded from the
tools/listresponse entirely.
tools/call: sandbox_execute
Section titled “tools/call: sandbox_execute”Execute code in an isolated sandbox. Each execution gets its own multi-layer sandbox with dedicated CPU, RAM, filesystem, and network namespace. Code runs to completion and the result is returned.
Request:
{ "jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": { "name": "sandbox_execute", "arguments": { "code": "import sys; print(f'Python {sys.version}')", "language": "python" } }}Response (success):
{ "jsonrpc": "2.0", "id": 3, "result": { "content": [ { "type": "text", "text": "## Execution Result\n\n**stdout:**\n```\nPython 3.14.0a1 (main, Oct 15 2025, 08:00:00)\n```\n\n**Exit code:** 0" } ], "isError": false }}Response (execution error, code ran but returned non-zero exit code):
{ "jsonrpc": "2.0", "id": 3, "result": { "content": [ { "type": "text", "text": "## Execution Result\n\n**stderr:**\n```\nTraceback (most recent call last):\n File \"<stdin>\", line 1, in <module>\nNameError: name 'undefined_var' is not defined\n```\n\n**Exit code:** 1" } ], "isError": true }}sandbox_execute parameters
Section titled “sandbox_execute parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Source code to execute. Max 1 MB. |
language | string | No | "python", "node", or "bash". Defaults to the sandbox’s configured default language. |
timeout | integer | No | Execution timeout in seconds (1-3600). Defaults to sandbox configuration. Plan limits apply: Free tier max 60s, Pro max 3600s. |
thread_id | string | No | Associates the execution with a persistent thread. Files written to /home/baponi are preserved across executions sharing the same thread_id. Omit for ephemeral executions where no files need to persist. |
metadata | object | No | Arbitrary JSON metadata attached to the execution record. Useful for correlating executions with your application’s data model. Appears in audit logs and execution history. |
env_vars | object | No | Environment variables injected into the sandbox for this execution. Keys must be uppercase letters, digits, and underscores (starting with a letter). Merged with sandbox-level and API key-level env vars (request takes highest precedence). See environment variables for limits. |
Persistent threads
Section titled “Persistent threads”When you pass a thread_id, the sandbox mounts persistent storage at /home/baponi. Files written there are preserved and available to subsequent executions with the same thread_id. This enables multi-step workflows:
// Step 1: Generate a file{ "method": "tools/call", "params": { "name": "sandbox_execute", "arguments": { "code": "import json; data = {'results': [1,2,3]}; open('/home/baponi/data.json','w').write(json.dumps(data))", "language": "python", "thread_id": "analysis-session-42" } }}
// Step 2: Read and process the file (same thread_id){ "method": "tools/call", "params": { "name": "sandbox_execute", "arguments": { "code": "import json; data = json.load(open('/home/baponi/data.json')); print(sum(data['results']))", "language": "python", "thread_id": "analysis-session-42" } }}tools/call: files_download
Section titled “tools/call: files_download”Download a file from a thread’s persistent storage or a managed volume. Useful for retrieving artifacts produced by code executions: charts, CSVs, processed data, generated images.
Request:
{ "jsonrpc": "2.0", "id": 4, "method": "tools/call", "params": { "name": "files_download", "arguments": { "source": "thread", "id": "analysis-session-42", "path": "data.json" } }}Response (text file):
{ "jsonrpc": "2.0", "id": 4, "result": { "content": [ { "type": "text", "text": "{\"results\": [1, 2, 3]}" } ], "isError": false }}Response (image file: PNG, JPG, GIF, WebP):
{ "jsonrpc": "2.0", "id": 4, "result": { "content": [ { "type": "image", "data": "iVBORw0KGgoAAAANSUhEUgAA...", "mimeType": "image/png" } ], "isError": false }}files_download parameters
Section titled “files_download parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
source | string | Yes | "thread" for thread persistent storage, "volume" for managed volumes. |
id | string | Yes | The thread_id (if source is "thread") or volume name (if source is "volume"). |
path | string | Yes | File path relative to the storage root. Example: "output/chart.png". |
File type handling
Section titled “File type handling”| File type | MCP content type | Behavior |
|---|---|---|
Text files (.txt, .json, .csv, .py, .md, etc.) | TextContent | Returned inline as text |
Images (.png, .jpg, .gif, .webp) | ImageContent | Returned base64-encoded with MIME type |
| Files larger than 1 MiB | Error | Returns an error with instructions to use the REST Files API for large file downloads |
| Binary files (non-text, non-image) | Error | Returns an error with instructions to use the REST Files API |
Error handling
Section titled “Error handling”JSON-RPC error codes
Section titled “JSON-RPC error codes”Protocol-level errors use standard JSON-RPC error codes. These indicate the request itself was invalid, not that code execution failed.
{ "jsonrpc": "2.0", "id": 3, "error": { "code": -32601, "message": "Method not found: tools/unknown" }}| Code | Name | When it occurs |
|---|---|---|
-32700 | Parse error | Request body is not valid JSON |
-32600 | Invalid request | Missing required JSON-RPC fields (jsonrpc, method) |
-32601 | Method not found | Unknown method name (e.g., tools/unknown) |
-32602 | Invalid params | Method parameters fail validation (e.g., missing code in sandbox_execute) |
-32603 | Internal error | Server-side failure. Retry with exponential backoff. |
Authentication errors
Section titled “Authentication errors”Invalid or missing API keys return HTTP 401 Unauthorized before JSON-RPC processing:
HTTP/1.1 401 UnauthorizedContent-Type: application/json
{"error": "unauthorized", "message": "Invalid or revoked API key."}After 3 failed authentication attempts from the same IP within 60 seconds, subsequent requests are rate-limited with HTTP 429.
Execution errors vs. protocol errors
Section titled “Execution errors vs. protocol errors”Baponi distinguishes between two kinds of errors:
- Execution errors (code ran but failed): Returned as a normal
tools/callresult withisError: true. The LLM sees the stderr and exit code, and can decide to fix and retry. - Protocol errors (request was invalid): Returned as a JSON-RPC
errorobject. The client should fix the request format.
This separation is intentional. LLMs are effective at recovering from execution errors when they can see the error output. Protocol errors indicate a client bug that needs human attention.
Request headers
Section titled “Request headers”| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer sk-us-YOUR_API_KEY. API key created in the admin console. |
Content-Type | Yes (POST) | application/json for POST requests. |
Accept | No | application/json or text/event-stream. Defaults to application/json. |
Mcp-Protocol-Version | No | 2025-11-25. Validated if present, but not required. The server accepts requests without this header. |
Complete example: end-to-end flow
Section titled “Complete example: end-to-end flow”This example shows a full MCP session with curl: initialize, list tools, execute code, and download a file.
1. Initialize (optional):
curl -s -X POST https://api.baponi.ai/mcp \ -H "Authorization: Bearer sk-us-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-11-25", "capabilities": {}, "clientInfo": {"name": "curl-example", "version": "1.0"} } }'2. List available tools:
curl -s -X POST https://api.baponi.ai/mcp \ -H "Authorization: Bearer sk-us-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }'3. Execute code with a persistent thread:
curl -s -X POST https://api.baponi.ai/mcp \ -H "Authorization: Bearer sk-us-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": { "name": "sandbox_execute", "arguments": { "code": "import csv\nwith open(\"/home/baponi/report.csv\", \"w\") as f:\n w = csv.writer(f)\n w.writerow([\"metric\", \"value\"])\n w.writerow([\"latency_p50\", \"12ms\"])\n w.writerow([\"latency_p99\", \"45ms\"])\nprint(\"Report written.\")", "language": "python", "thread_id": "demo-thread-1" } } }'4. Download the generated file:
curl -s -X POST https://api.baponi.ai/mcp \ -H "Authorization: Bearer sk-us-YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": 4, "method": "tools/call", "params": { "name": "files_download", "arguments": { "source": "thread", "id": "demo-thread-1", "path": "report.csv" } } }'Comparison with the REST API
Section titled “Comparison with the REST API”Baponi exposes the same execution capabilities through both MCP and REST. Choose based on your integration pattern:
MCP (/mcp) | REST (/v1/sandbox/execute) | |
|---|---|---|
| Best for | MCP-native clients (Claude, Cursor, IDEs) | Direct HTTP integration, custom agents |
| Protocol | JSON-RPC 2.0 | Standard REST |
| Tool discovery | Built-in via tools/list | Read the API docs |
| File download | Inline via files_download tool (text and images up to 1 MiB) | Signed URLs via Files API (any size) |
| Streaming | SSE via GET | Not available on execute endpoint |
| Auth | Same API keys | Same API keys |
| Sandbox config | Same, determined by API key | Same, determined by API key |
Both surfaces are backed by the same execution infrastructure. Latency, isolation, and security guarantees are identical.
Frequently asked questions
Section titled “Frequently asked questions”Do I need to run a local MCP server process?
Section titled “Do I need to run a local MCP server process?”No. Baponi’s MCP server runs in the cloud. You configure your MCP client with a URL. No Docker, no npx, no local process. This is the Streamable HTTP transport, not the stdio transport.
What happens if the server pod restarts during my session?
Section titled “What happens if the server pod restarts during my session?”Nothing. Baponi’s MCP implementation is stateless. Your next request is routed to any available pod and authenticated independently. There is no session to lose.
Can I use both MCP and REST in the same application?
Section titled “Can I use both MCP and REST in the same application?”Yes. Both use the same API keys, the same sandboxes, and the same thread storage. An execution started via MCP’s sandbox_execute produces files that are accessible via the REST Files API, and vice versa.
Why does tools/list only show sandbox_execute?
Section titled “Why does tools/list only show sandbox_execute?”The files_download tool only appears when your API key’s sandbox has storage configured (a storage connection or volume). Configure storage in the admin console under your sandbox settings.
Is there a message size limit?
Section titled “Is there a message size limit?”The maximum request body size is 1 MB, matching the code size limit for sandbox_execute. File downloads via files_download are limited to 1 MiB inline. For larger files, use the REST Files API which provides signed URLs for direct download.
Does Baponi support MCP resources or prompts?
Section titled “Does Baponi support MCP resources or prompts?”Baponi currently implements the MCP tools capability only. Resources and prompts are not exposed. Tool discovery via tools/list is the primary integration surface.