Baserow Streamable MCP
by bitingwaxen
README.md
# Baserow Streamable MCP
A remote MCP server for Baserow using Streamable HTTP and the Baserow REST API.
This repository provides a generic Baserow MCP server with discovery, schema inspection, paginated reads, filtering, sorting, search, batch CRUD, safe upsert, and relation helpers.
## Tools
- `list_databases()` derives accessible database IDs and table counts from the token-visible tables. Database names are `null` because Baserow's Database Token discovery endpoint does not return them.
- `list_tables()` lists every table accessible to the Database Token across its workspace.
- `get_table_schema(table_id)` lists field definitions for a table.
- `get_row(table_id, row_id)` gets one row using user-facing field names.
- `list_rows(table_id, page?, size?)` returns a paginated row page; defaults to 50 and caps at 200 rows.
- `search_rows(table_id, search, page?, size?)` searches rows and returns one paginated page.
- `filter_rows(table_id, filters, filter_type?, order_by?, page?, size?)` applies Baserow filters and field-ID sorting.
- `create_rows(table_id, rows)` creates up to 200 rows using user-facing field names.
- `update_rows(table_id, rows, dry_run?)` updates up to 200 rows; every row must include its `id`.
- `upsert_rows(table_id, match_field_id, records, dry_run?)` creates or updates rows and rejects non-unique matches.
- `set_linked_rows(table_id, row_id, field_name, linked_row_ids, dry_run?)` replaces a link-row relation after schema validation.
- `delete_rows(table_id, row_ids, dry_run?)` deletes up to 200 rows.
Set `dry_run: true` on these tools to return the planned mutation without changing Baserow. `create_rows` and every other mutating tool are audit logged whether they succeed or fail. Audit entries contain operation metadata and row IDs, but never Baserow tokens or row values.
Table schemas are cached in memory for 60 seconds. Relation writes use that cache to verify that the requested field exists and is a `link_row` field.
Database Tokens are workspace-scoped and Baserow exposes a token-specific all-tables endpoint. Its response contains each table's `database_id`, allowing database IDs to be discovered and grouped without a JWT. It does not include database names, so the server returns `name: null` rather than inventing a value or requiring short-lived user authentication.
The Baserow calls follow these API contracts:
- `GET /api/database/tables/all-tables/` for token-visible table discovery.
- `GET /api/database/fields/table/{table_id}/` for table schema.
- `GET /api/database/rows/table/{table_id}/...` for individual and paginated row reads.
- `POST|PATCH /api/database/rows/table/{table_id}/batch/` for batch create/update.
- `POST /api/database/rows/table/{table_id}/batch-delete/` for batch deletion.
## Requirements
- Node.js 22 or later
- A Baserow Database Token with the required table permissions
## Run locally
```bash
cp .env.example .env
# Edit .env and set a long random MCP_AUTH_TOKEN, then export it into the shell.
set -a && . ./.env && set +a
npm install
npm run build
npm start
```
The MCP endpoint is `http://127.0.0.1:7317/mcp` by default.
## Run with Docker Compose
```bash
cp .env.example .env
# Edit .env, then start the service.
docker network create mcp-network 2>/dev/null || true
docker compose up --build -d
```
The container is named `baserow-streamable-mcp` and joins the external `mcp-network` network so another container can reach it at `http://baserow-streamable-mcp:7317/mcp`. The Compose example also publishes the port on the host loopback interface, persists the audit log in the `audit-data` volume, and checks `GET /health`. To reach a self-hosted Baserow service by Docker service name, attach this service to the same external Docker network and set `BASEROW_URL` accordingly.
Every `/mcp` request must include `Authorization: Bearer <MCP_AUTH_TOKEN>`. The unauthenticated `GET /health` endpoint returns only the service name, version, and status. For remote access, also place the server behind HTTPS; bearer authentication does not encrypt traffic.
## Configuration
| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `BASEROW_URL` | Yes | — | Baserow origin, such as `https://api.baserow.io` or `http://baserow` |
| `BASEROW_TOKEN` | Yes | — | Baserow Database Token |
| `MCP_AUTH_TOKEN` | Yes | — | Bearer token required by every request to `/mcp` |
| `HOST` | No | `127.0.0.1` | Bind address; use `0.0.0.0` in a container |
| `PORT` | No | `7317` | HTTP port |
| `REQUEST_TIMEOUT_MS` | No | `15000` | Baserow request timeout |
| `AUDIT_LOG_PATH` | No | `./data/audit.jsonl` | Append-only JSONL audit log path |
| `LOG_LEVEL` | No | `info` | Minimum structured log level: `debug`, `info`, `warn`, or `error` |
## Development
```bash
npm run typecheck
npm test
npm run build
```
## Security
- Never commit `.env` or tokens.
- The Baserow token is sent only in the `Authorization: Token ...` header.
- The server binds to localhost by default.
- `/mcp` uses constant-time bearer-token comparison and rejects missing or invalid credentials.
- `/health` intentionally does not test Baserow or reveal configuration data.
- Structured operational logs go to stderr; mutation audit records go to the configured JSONL file.
- Configure HTTPS before remote production use.
## License
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues