SeaTable MCP
Official# SeaTable MCP
The official Model Context Protocol (MCP) server for [SeaTable](https://seatable.com), built and maintained by SeaTable GmbH. It lets AI agents interact with data in your bases — reading, writing, searching, linking, and querying rows through a focused set of tools. The server intentionally focuses on data operations, not schema management (creating/deleting tables or columns), keeping the tool set lean and safe for autonomous agent use.
## Quick Start
The fastest way to get started depends on your setup:
- **SeaTable Cloud** — Use the hosted MCP server at `mcp.seatable.com`, no installation needed
- **Self-hosted SeaTable** — Run the MCP server locally via `npx` in your IDE
### SeaTable Cloud (hosted MCP server)
If you use [SeaTable Cloud](https://cloud.seatable.io), there is a hosted MCP server ready to use — no installation required. Configure your MCP client with the Streamable HTTP endpoint:
**Claude Desktop** — add to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"seatable": {
"type": "streamable-http",
"url": "https://mcp.seatable.com/mcp",
"headers": {
"Authorization": "Bearer your-api-token"
}
}
}
}
```
**Cursor / VSCode** — add to your MCP settings (JSON):
```json
{
"mcp.servers": {
"seatable": {
"type": "streamable-http",
"url": "https://mcp.seatable.com/mcp",
"headers": {
"Authorization": "Bearer your-api-token"
}
}
}
}
```
**ChatGPT and other OAuth-compatible clients** — use the built-in OAuth flow. In ChatGPT's developer mode, configure:
- **Server URL:** `https://mcp.seatable.com/mcp`
- **Auth type:** OAuth
- **Authorization URL:** `https://mcp.seatable.com/authorize`
- **Token URL:** `https://mcp.seatable.com/token`
You will be prompted to enter your SeaTable API token during the authorization step.
### Self-hosted SeaTable
For self-hosted SeaTable instances, run the MCP server locally via `npx`. Your IDE starts and manages the process automatically.
**Claude Desktop** — add to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"seatable": {
"command": "npx",
"args": ["-y", "@seatable/mcp-seatable"],
"env": {
"SEATABLE_SERVER_URL": "https://your-seatable-server.com",
"SEATABLE_API_TOKEN": "your-api-token"
}
}
}
}
```
**Cursor / VSCode** — add to your MCP settings (JSON):
```json
{
"mcp.servers": {
"seatable": {
"command": "npx",
"args": ["-y", "@seatable/mcp-seatable"],
"env": {
"SEATABLE_SERVER_URL": "https://your-seatable-server.com",
"SEATABLE_API_TOKEN": "your-api-token"
}
}
}
}
```
## Deployment Options
If you need to run your own server instance — for example on your own infrastructure, with multi-base support, or in multi-tenant mode — use one of the options below.
### HTTP Server (Network Access)
Run a local HTTP server with Streamable HTTP transport:
```bash
PORT=3001 npx -y @seatable/mcp-seatable --sse
# Health check
curl http://localhost:3001/health
# MCP endpoint: POST/GET/DELETE http://localhost:3001/mcp
```
### Multi-Base (Selfhosted)
Serve multiple bases from a single process:
```bash
SEATABLE_SERVER_URL=https://your-seatable-server.com \
SEATABLE_BASES='[{"base_name":"CRM","api_token":"token_abc"},{"base_name":"Projects","api_token":"token_def"}]' \
npx -y @seatable/mcp-seatable
```
Each tool automatically gets a `base` parameter. Use `list_bases` to see available bases.
### Managed Mode (Multi-Tenant HTTP)
For hosting an MCP endpoint where each client authenticates with their own SeaTable API token:
```bash
SEATABLE_MODE=managed \
SEATABLE_SERVER_URL=https://your-seatable-server.com \
SEATABLE_TOKEN_SECRET=$(openssl rand -hex 32) \
PORT=3000 npx -y @seatable/mcp-seatable --sse
```
`SEATABLE_TOKEN_SECRET` is **required** in managed mode. It seals the OAuth tokens the server issues, so the underlying SeaTable API token never has to be handed to a client. Keep it stable across restarts — changing it invalidates every issued access and refresh token and forces all clients to re-authorize.
Clients pass their credential via `Authorization: Bearer <token>` — on session initialization **and on every subsequent request**, including `GET` and `DELETE`. The `mcp-session-id` header is a routing value only; it is never accepted on its own. Each request is re-validated and must resolve to the same identity that created the session, otherwise the server answers `401` (missing/invalid credential) or `403` (valid credential, wrong session). Rate limits apply as before (60 req/min per token, 120/min per IP, 20 concurrent connections per token).
**OAuth support:** Managed mode also exposes OAuth 2.0 endpoints (`/authorize` and `/token`), enabling OAuth-compatible clients like ChatGPT to connect — no external OAuth provider required. During the flow the user enters their SeaTable API token; the server seals it into its own short-lived access token (1 h) and a rotating refresh token (14 d). The raw SeaTable API token is never returned to a client.
Clients must register at `/register` first: the returned `client_id` carries the client's name and its `redirect_uris`, and the server accepts a callback only if it is one the client registered (loopback callbacks may vary the port, per RFC 8252). PKCE with `S256` is mandatory, and every authorization code is bound to the client, the exact callback and the challenge.
**Where a code may be delivered.** With open dynamic registration, "registered client" is not a trust statement — anyone can register. What matters is whether the code leaves the user's machine:
| Callback | Behaviour |
|---|---|
| Loopback (`http://127.0.0.1:…`, `localhost`, `[::1]`) | allowed, no extra step — the code stays on the user's machine |
| Private-use scheme (`cursor://`, `vscode://`, `com.example.app:/…`) | allowed, no extra step — handed to a local application |
| `https` on a host in `SEATABLE_OAUTH_TRUSTED_REDIRECT_HOSTS` | allowed, no extra step |
| `https` on any other host | allowed **after** the user confirms the destination on a separate page |
| Remote plaintext `http`, `javascript:`, `data:`, `file:`, `blob:` | rejected |
The confirmation cannot be skipped from the entry link: it is read from the form body only, and a POST auto-submitted by a foreign page is refused via `Sec-Fetch-Site`. The trusted-host list therefore removes friction — it is not a gate, and leaving it unset breaks nothing.
The consent screen leads with the destination the authorization will be sent to. The application's name is shown as **self-reported**, because with open registration it is chosen by whoever registered the client and cannot be verified.
The OAuth endpoints are rate limited per IP (30/min overall, 10/min for token submissions), so `/authorize` cannot be used as an unthrottled oracle for testing SeaTable API tokens.
OAuth endpoints follow the MCP specification (RFC 8414 metadata discovery, PKCE, dynamic client registration):
| Endpoint | Path |
|---|---|
| Metadata Discovery | `/.well-known/oauth-authorization-server` |
| Authorization | `/authorize` |
| Token | `/token` |
| Client Registration | `/register` |
Client ID and secret are not validated — dynamic client registration generates one automatically.
### Docker
```bash
docker run -d --name seatable-mcp \
-p 3000:3000 \
-e SEATABLE_SERVER_URL=https://your-seatable-server.com \
-e SEATABLE_API_TOKEN=your-api-token \
seatable/seatable-mcp:latest
# Health check
curl http://localhost:3000/health
```
### Security Model
The security characteristics differ significantly between transport modes:
| | stdio (default) | Selfhosted HTTP | Managed HTTP |
|---|---|---|---|
| **Network exposure** | None (local process) | TCP port, **no auth** | TCP port, Bearer auth |
| **Authentication** | Not needed (local) | None | Bearer token or OAuth 2.0, validated against SeaTable |
| **Rate limiting** | None | None | Per-token, per-IP, global |
| **Connection limits** | N/A | None | 20 concurrent sessions per token |
| **Data scope** | All configured bases | All configured bases | One base per client token |
> **⚠️ Warning:** Selfhosted HTTP mode (`--sse` / `--http`) has **no authentication**. Anyone who can reach the port gets full access to all configured bases, including write and delete operations. Only run it in trusted networks (localhost, Docker-internal) or behind a reverse proxy that handles authentication. For untrusted networks, use **managed mode** instead.
### Rate Limiting
SeaTable's own API gateway enforces rate limits **per base** (default: 500 requests/minute per `base_uuid`) and **per organization** (monthly quota). These limits apply regardless of whether requests come from the MCP server, the web UI, or direct API calls. The MCP server does not duplicate these limits — instead, it retries automatically with exponential backoff when SeaTable returns `429 Too Many Requests`.
In **managed mode**, the MCP server adds its own rate limits to protect the server process itself (not the SeaTable backend): 60 req/min per token, 120/min per IP, 30/min for new session creation, and 20 concurrent connections per token.
### Input Validation
All tool inputs are validated with Zod schemas before execution. Write tools (`add_row`, `append_rows`, `update_rows`, `upsert_rows`) additionally validate row data against the table schema — unknown columns are rejected, and read-only columns (formula, auto-number, creator, etc.) are stripped with a note in the response.
Tool schemas are published with `additionalProperties: true` to remain compatible with MCP clients that may attach internal fields (e.g. `_meta`). Unexpected fields are ignored by the server — they do not cause errors but are not processed either. This is a deliberate trade-off: stricter validation would improve error messages for typos but risk breaking compatibility with MCP clients.
### Row Responses
Row responses include all columns and SeaTable system fields (`_id`, `_mtime`, `_ctime`, `_creator`, `_last_modifier`). System fields are not filtered — `_id` is required for updates and deletes, timestamps are useful for sorting and freshness checks, and creator/modifier fields can be resolved to display names via `list_collaborators`.
### Caching
The server caches base metadata (table/column definitions) for 60 seconds to avoid redundant API calls during write operations. Schema-reading tools (`get_schema`, `list_tables`) always bypass the cache and return fresh data. If a cached schema becomes stale (e.g. a column was renamed), the SeaTable API will reject the write and the AI agent can call `get_schema` to refresh.
## Environment Variables
Required:
- `SEATABLE_SERVER_URL` — Your SeaTable server URL
Authentication (one of these is required in selfhosted mode):
- `SEATABLE_API_TOKEN` — Single-base API token
- `SEATABLE_BASES` — Multi-base: JSON array (e.g. `'[{"base_name":"CRM","api_token":"..."}]'`)
Optional:
- `SEATABLE_MODE` — `selfhosted` (default) or `managed` (multi-tenant HTTP with per-client auth)
- `SEATABLE_TOKEN_SECRET` — **required in managed mode**, min. 32 chars. Seals issued OAuth tokens and client registrations; must be stable across restarts (`openssl rand -hex 32`)
- `SEATABLE_ACCESS_TOKEN_TTL` — lifetime of an issued access token in seconds (default `3600`, range `30`–`2592000`). Lower narrows the window after a SeaTable token is revoked; higher spares users a re-prompt if their client renews badly. The refresh token is never issued shorter-lived than the access token.
- `SEATABLE_MOCK=true` — Enable mock mode for offline testing
- `CORS_ALLOWED_ORIGINS` — Comma-separated list of allowed origins for CORS (HTTP mode only, disabled if unset)
- `METRICS_PORT` — Prometheus metrics port (default: `9090`, HTTP mode only)
## Monitoring
In HTTP mode, the server exposes Prometheus metrics on a separate port (default `9090`):
```bash
curl http://localhost:9090/metrics
```
Available metrics:
| Metric | Type | Description |
|---|---|---|
| `mcp_tool_calls_total{tool, status}` | Counter | Tool calls by name and result (success/error) |
| `mcp_tool_calls_by_tool_total{tool}` | Counter | Total calls per tool (regardless of outcome) |
| `mcp_tool_duration_seconds{tool}` | Histogram | Tool execution time |
| `mcp_http_requests_total{method, status}` | Counter | HTTP requests by method and status code |
| `mcp_rate_limit_exceeded_total{type}` | Counter | Rate limit rejections (global/per_ip/per_token) |
| `mcp_auth_validations_total{result}` | Counter | Auth validations (success/failure/cache_hit) |
| `mcp_active_sessions` | Gauge | Currently active HTTP sessions |
| `mcp_active_connections` | Gauge | Currently active connections |
| `seatable_api_requests_total{operation, status}` | Counter | SeaTable API calls by operation |
| `seatable_api_duration_seconds{operation}` | Histogram | SeaTable API latency |
Plus standard Node.js metrics (memory, CPU, event loop) via `prom-client`.
The metrics server only starts in HTTP mode (not stdio) and binds to `0.0.0.0` — in Docker, expose the port only within your internal network.
## MCP Tools
### Schema Introspection
- **`list_tables`** — Get all tables with metadata
- **`get_schema`** — Get complete database structure
- **`list_bases`** — List available bases (multi-base mode only)
- **`list_collaborators`** — List users with access to the base (for collaborator columns)
### Reading Data
- **`list_rows`** — Paginated row listing (use query_sql for filtering/sorting)
- **`get_row`** — Retrieve specific row by ID
- **`find_rows`** — Client-side filtering with DSL
- **`search_rows`** — Search via SQL WHERE clauses
- **`query_sql`** — Execute SQL queries with parameterized inputs
### Writing Data
- **`add_row`** — Add single new row
- **`append_rows`** — Batch insert rows
- **`update_rows`** — Batch update rows
- **`upsert_rows`** — Insert or update rows by key columns
- **`delete_rows`** — Remove rows by ID
### Files
- **`upload_file`** — Upload a file or image to a row (base64-encoded)
- **`download_file`** — Read file content from a file or image column (text files and PDFs as text, binary files as download link, max 1 MB)
### Linking
- **`link_rows`** — Create relationships between rows
- **`unlink_rows`** — Remove relationships between rows
### Utilities
- **`get_row_activities`** — Get change history of a row (who changed what, when, old/new values)
- **`create_snapshot`** — Create a snapshot of the current base (10 min cooldown)
- **`add_select_options`** — Add new options to single-select or multi-select columns (existing options are skipped, no duplicates)
- **`ping_seatable`** — Health check with latency monitoring
## Supported Column Types
SeaTable bases can contain many different column types. The following table shows which types can be written via the API and what format to use.
| Column Type | Writable | Value Format |
|---|---|---|
| Text | Yes | `"string"` |
| Long Text | Yes | `"Markdown string"` |
| Number (incl. percent, currency) | Yes | `123.45` |
| Checkbox | Yes | `true` / `false` |
| Date | Yes | `"YYYY-MM-DD"` or `"YYYY-MM-DD HH:mm"` |
| Duration | Yes | `"h:mm"` or `"h:mm:ss"` |
| Single Select | Yes | `"option name"` |
| Multiple Select | Yes | `["option a", "option b"]` |
| Email | Yes | `"user@example.com"` |
| URL | Yes | `"https://..."` |
| Rating | Yes | `4` (integer) |
| Geolocation | Yes | `{"lat": 52.52, "lng": 13.40}` |
| Collaborator | Yes | `["0b995819003140ed8e9efe05e817b000@auth.local"]` — use `list_collaborators` to get user IDs |
| Link | Yes | Use `link_rows` / `unlink_rows` tools |
| Image / File | Yes | Use `upload_file` to upload (base64), `download_file` to read content |
| Formula / Link Formula | No | Read-only, computed by SeaTable |
| Creator / Created Time / Modified Time | No | Read-only, set automatically |
| Auto Number | No | Read-only, set automatically |
| Button / Digital Signature | No | Not accessible via API |
## Tool Examples
```json
// List all tables
{ "tool": "list_tables", "args": {} }
// Get rows with pagination
{ "tool": "list_rows", "args": { "table": "Tasks", "page_size": 10 } }
// Add rows
{ "tool": "append_rows", "args": { "table": "Tasks", "rows": [{ "Title": "New Task", "Status": "Todo" }] } }
// SQL query
{ "tool": "query_sql", "args": { "sql": "SELECT Status, COUNT(*) as count FROM Tasks GROUP BY Status" } }
```
## Programmatic Usage
```typescript
import { createMcpServer } from '@seatable/mcp-seatable'
const server = await createMcpServer({
serverUrl: 'https://your-seatable-server.com',
apiToken: 'your-api-token',
})
```
## Mock Mode
```bash
SEATABLE_MOCK=true npm run dev
```
In-memory tables and rows for demos and tests without a live SeaTable instance.
## Development
### Prerequisites
- Node.js >= 20
### Setup
```bash
git clone https://github.com/seatable/seatable-mcp
cd seatable-mcp
npm install
cp .env.example .env # Configure your SeaTable settings
npm run dev # Start in watch mode
```
### Scripts
- `npm run dev` — Start server in watch mode (tsx)
- `npm run build` — Compile TypeScript
- `npm run start` — Run compiled server
- `npm test` — Run tests (vitest)
- `npm run lint` — Lint code
- `npm run typecheck` — TypeScript type check
### Testing Tools
```bash
node scripts/mcp-call.cjs ping_seatable '{}'
node scripts/mcp-call.cjs list_tables '{}'
node scripts/mcp-call.cjs list_rows '{"table": "Tasks", "page_size": 5}'
```
## Troubleshooting
| Issue | Solution |
|---|---|
| `Invalid API token` | Check `SEATABLE_API_TOKEN` |
| `Base not found` | Check API token permissions |
| `Connection timeout` | Check `SEATABLE_SERVER_URL` and network access |
| `Permission denied` | Ensure API token has required base permissions |
| `You don't have permission to perform this operation on this base.` | API token is read-only or row limit exceeded |
| `Asset quota exceeded.` | Storage quota reached — delete files or upgrade plan |
| `too many requests` | Rate-limited by SeaTable — requests are automatically retried with backoff (3 attempts) |
## License
MIT
TDQS
Scored across 21 tools
Most tools have distinct purposes (e.g., add_row vs. append_rows, find_rows vs. list_rows vs. search_rows). However, add_row and append_rows could cause confusion, though descriptions clearly advise using append_rows for multiple rows. Similarly, list_rows, find_rows, search_rows, and query_sql overlap in querying but differ in method, with clear guidance on when to use each.
Tool names follow a consistent verb_noun pattern (e.g., add_row, delete_rows, list_rows, update_rows, upload_file). Minor deviations: ping_seatable (ping is not a data operation) and query_sql (verb before the type). Overall, most tools are predictable and follow the same style.
With 21 tools, the count is slightly high but reasonable given the breadth of operations (CRUD, linking, file handling, SQL queries, snapshots). Each tool serves a specific need, and the number aligns well with a comprehensive database-like API. No obvious bloat.
The tool set covers most essential operations: CRUD for rows (add, get, update, delete, upsert), linking, file upload/download, search, schema inspection, and snapshots. Minor gaps: no tool to modify table schema directly (only add_select_options), and no bulk delete or export functionality. Overall, very thorough for a typical use case.