R2 Bucket MCP
by ingsamcas
README.md
# r2-bucket-mcp
Servidor MCP **stdio** para Cursor y otros clientes MCP. Sube archivos locales a **Cloudflare R2** (S3-compatible) sin ejecutar scripts shell. Pensado para APKs de proyectos (`vitalo`, `linkbox`, `nestle`, …) y cualquier otro archivo.
## Qué hace
Expone cinco tools MCP:
| Tool | Uso |
|------|-----|
| `upload_apk` | APKs con key `{project}/{version}/{filename}` |
| `upload_file` | Cualquier archivo con key opcional |
| `get_file` | Descargar por key (URL o guardar local) |
| `list_files` | Listar objetos por prefijo |
| `delete_file` | Borrar objeto por key |
La respuesta incluye `downloadUrl` (URL pública CDN si `R2_PUBLIC_BASE_URL` está configurada, o presigned URL temporal).
## Instalación
```bash
cd /Users/samuelgerardocastrolopez/Desktop/r2-bucket-mcp
npm install
```
## Credenciales R2
Usa las mismas variables que `opencode_whatsapp/.env` (sin copiar secretos al repo):
1. Abre `opencode_whatsapp/.env` en tu máquina.
2. Copia los valores de `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `R2_BUCKET`, `R2_ENDPOINT` (o `R2_ACCOUNT_ID`), y opcionalmente `R2_PUBLIC_BASE_URL`.
3. Para pruebas locales, crea `.env` en este repo desde `.env.example` (nunca lo commitees).
| Variable | Requerida | Descripción |
|----------|-----------|-------------|
| `R2_ACCESS_KEY_ID` | sí | Access key R2 |
| `R2_SECRET_ACCESS_KEY` | sí | Secret key R2 |
| `R2_BUCKET` | sí | Bucket (default: `apks`) |
| `R2_ENDPOINT` | sí* | Endpoint S3-compatible |
| `R2_ACCOUNT_ID` | sí* | Alternativa a endpoint explícito |
| `R2_KEY_PREFIX` | no | Prefijo global opcional |
| `R2_PUBLIC_BASE_URL` | no | CDN pública; si falta, presigned URL |
| `R2_PRESIGNED_TTL_SECS` | no | Default: 604800 (7 días) |
| `R2_MAX_FILE_BYTES` | no | Límite validación MCP (default: 500 MB) |
\* Al menos uno de `R2_ENDPOINT` o `R2_ACCOUNT_ID`.
## Prueba manual
Arranque stdio (proceso queda esperando; Ctrl+C para salir):
```bash
node index.js
# stderr: [r2-bucket-mcp] ready (stdio)
```
Smoke test con credenciales en `.env` local (sube un `.smoke-test.txt` temporal):
```bash
npm run smoke
```
## Registrar en Cursor
**Settings → MCP** o edita `~/.cursor/mcp.json`.
Usa la ruta absoluta de `node` (`which node`). Ejemplo:
```json
{
"mcpServers": {
"r2-bucket": {
"type": "stdio",
"command": "/Users/samuelgerardocastrolopez/.nvm/versions/node/v22.17.0/bin/node",
"args": [
"/Users/samuelgerardocastrolopez/Desktop/r2-bucket-mcp/index.js"
],
"env": {
"R2_ACCESS_KEY_ID": "PEGAR_ACCESS_KEY",
"R2_SECRET_ACCESS_KEY": "PEGAR_SECRET_KEY",
"R2_BUCKET": "apks",
"R2_ENDPOINT": "https://<account-id>.r2.cloudflarestorage.com",
"R2_PUBLIC_BASE_URL": "https://cdn.tudominio.com"
}
}
}
}
```
Reinicia Cursor o recarga servidores MCP tras cambiar `mcp.json`.
## Uso desde agentes
- **APKs:** preferir `upload_apk` con `project`, `version` y `filePath` absoluto.
- **Otros archivos:** `upload_file` con `filePath` y opcionalmente `key`, `prefix`, `contentType`.
- **Listar:** `list_files` con `prefix` opcional (ej. `vitalo/`).
- **Descargar:** `get_file` con `key`; opcional `filePath` absoluto para guardar en disco.
- **Borrar:** `delete_file` con `key` completa (ej. `vitalo/2.1.0/app-release.apk`).
Ejemplo de key generada por `upload_apk`:
```
vitalo/2.1.0/app-release.apk
```
Respuesta JSON (campo principal para compartir el enlace):
```json
{
"ok": true,
"bucket": "apks",
"key": "vitalo/2.1.0/app-release.apk",
"size": 12345678,
"contentType": "application/vnd.android.package-archive",
"downloadUrl": "https://cdn.tudominio.com/vitalo/2.1.0/app-release.apk",
"publicUrl": "https://cdn.tudominio.com/vitalo/2.1.0/app-release.apk",
"presignedUrl": null
}
```
Si no hay `R2_PUBLIC_BASE_URL`, `downloadUrl` será una presigned URL y `publicUrl` será `null`.
## Integración SDK
Con un agente SDK local y `settingSources: ['user']`, el agente carga el mismo MCP definido en `~/.cursor/mcp.json`. También puedes pasar `mcpServers` inline en la config del agente con las mismas credenciales en `env`.
## Tools
### `upload_file`
| Parámetro | Requerido | Descripción |
|-----------|-----------|-------------|
| `filePath` | sí | Ruta absoluta al archivo local |
| `key` | no | Clave en el bucket (default: basename) |
| `prefix` | no | Prefijo override (sin `R2_KEY_PREFIX` global) |
| `contentType` | no | MIME explícito |
### `upload_apk`
| Parámetro | Requerido | Descripción |
|-----------|-----------|-------------|
| `filePath` | sí | Ruta absoluta al APK |
| `project` | sí | Alias (`vitalo`, `linkbox`, …) |
| `version` | sí | Versión (`2.1.0`) |
| `filename` | no | Default: `app-release.apk` |
### `get_file`
| Parámetro | Requerido | Descripción |
|-----------|-----------|-------------|
| `key` | sí | Clave del objeto (ej. `vitalo/2.1.0/app-release.apk`) |
| `filePath` | no | Ruta absoluta local para guardar; sin ella solo devuelve URLs |
### `list_files`
| Parámetro | Requerido | Descripción |
|-----------|-----------|-------------|
| `prefix` | no | Filtro de prefijo (ej. `vitalo/`, `vitalo/2.1.0/`) |
| `maxKeys` | no | Máximo por página (default: 100, max: 1000) |
| `continuationToken` | no | Token de paginación de una respuesta anterior |
### `delete_file`
| Parámetro | Requerido | Descripción |
|-----------|-----------|-------------|
| `key` | sí | Clave del objeto (ej. `vitalo/2.1.0/app-release.apk`) |
TDQS
A4/5.0
Scored across 5 tools
Disambiguation5/5
Each tool has a clearly distinct purpose: delete, get, list, upload specifically for APKs, and general upload. No ambiguity between operations.
Naming Consistency5/5
All tool names follow a consistent verb_noun pattern in snake_case (delete_file, get_file, list_files, upload_apk, upload_file).
Tool Count5/5
Five tools cover the essential CRUD operations for interacting with an R2 bucket: get, delete, list, upload general, and upload for APKs. This is well-scoped.
Completeness5/5
The tool set covers the core lifecycle for objects in a bucket: listing, retrieving, deleting, and uploading (with a specialized variant for APKs). No obvious gaps for the stated purpose.
Maintenance
ActivityInactive
ResponsivenessNo issues