# 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 ` — 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