Add grist-mcp design document
This commit is contained in:
255
docs/plans/2025-12-03-grist-mcp-design.md
Normal file
255
docs/plans/2025-12-03-grist-mcp-design.md
Normal file
@@ -0,0 +1,255 @@
|
||||
# Grist MCP Server Design
|
||||
|
||||
## Overview
|
||||
|
||||
A dockerized MCP server that allows AI agents to interact with Grist documents. Each agent authenticates with a token that grants access to specific documents with defined permission levels.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ grist-mcp Container │
|
||||
│ │
|
||||
│ ┌──────────────────────┐ ┌──────────────────────┐ │
|
||||
│ │ MCP Server │ │ Grist Client │ │
|
||||
│ │ (SSE/HTTP) │─────────▶│ Layer │ │
|
||||
│ │ :8080 │ │ │ │
|
||||
│ └──────────┬───────────┘ └──────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌───────▼───────┐ │
|
||||
│ │ config.yaml │ ← Mounted volume (read-only) │
|
||||
│ └───────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────┐ ┌─────────────────┐
|
||||
│ AI Agents │ │ Grist Servers │
|
||||
│ (with tokens)│ │ │
|
||||
└─────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
**Key characteristics:**
|
||||
- Single container, single port (8080)
|
||||
- All configuration from one YAML file
|
||||
- No database, no admin API
|
||||
- Restart container to apply config changes
|
||||
|
||||
## Configuration
|
||||
|
||||
### config.yaml
|
||||
|
||||
```yaml
|
||||
# ============================================================
|
||||
# Token Generation:
|
||||
# python -c "import secrets; print(secrets.token_urlsafe(32))"
|
||||
# openssl rand -base64 32
|
||||
# ============================================================
|
||||
|
||||
# Document definitions (each is self-contained)
|
||||
documents:
|
||||
budget-2024:
|
||||
url: https://work.getgrist.com
|
||||
doc_id: mK7xB2pQ9mN4v
|
||||
api_key: ${GRIST_WORK_API_KEY}
|
||||
|
||||
expenses:
|
||||
url: https://work.getgrist.com
|
||||
doc_id: nL8yC3qR0oO5w
|
||||
api_key: ${GRIST_WORK_API_KEY}
|
||||
|
||||
personal-tracker:
|
||||
url: https://docs.getgrist.com
|
||||
doc_id: pN0zE5sT2qP7x
|
||||
api_key: ${GRIST_PERSONAL_API_KEY}
|
||||
|
||||
# Agent tokens with access scopes
|
||||
tokens:
|
||||
- token: dG9rZW4tZmluYW5jZS1hZ2VudC0xMjM0NTY3ODkw
|
||||
name: finance-agent
|
||||
scope:
|
||||
- document: budget-2024
|
||||
permissions: [read, write]
|
||||
- document: expenses
|
||||
permissions: [read]
|
||||
|
||||
- token: K7xB2pQ9mN4vR8wY1zA3cE6fH0jL5oI2sU7tD4gM
|
||||
name: analytics-agent
|
||||
scope:
|
||||
- document: personal-tracker
|
||||
permissions: [read, write, schema]
|
||||
```
|
||||
|
||||
### Permission Levels
|
||||
|
||||
| Permission | Description |
|
||||
|------------|-------------|
|
||||
| `read` | Query tables, get records, list structure |
|
||||
| `write` | Create, update, delete records |
|
||||
| `schema` | Create/modify tables, columns, formulas |
|
||||
|
||||
### Environment Variables
|
||||
|
||||
API keys are referenced via `${VAR}` syntax in config.yaml and resolved at startup. Store actual keys in `.env` file:
|
||||
|
||||
```bash
|
||||
GRIST_WORK_API_KEY=actual-api-key-here
|
||||
GRIST_PERSONAL_API_KEY=another-api-key
|
||||
```
|
||||
|
||||
## MCP Tools
|
||||
|
||||
### Discovery
|
||||
|
||||
| Tool | Permission | Description |
|
||||
|------|------------|-------------|
|
||||
| `list_documents` | (always available) | List documents this token can access with their permissions |
|
||||
|
||||
### Read Operations (requires `read`)
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `list_tables` | List all tables in a document |
|
||||
| `describe_table` | Get column names, types, and formulas for a table |
|
||||
| `get_records` | Fetch records with optional filters and sorting |
|
||||
| `sql_query` | Run read-only SQL against the document |
|
||||
|
||||
### Write Operations (requires `write`)
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `add_records` | Insert one or more records into a table |
|
||||
| `update_records` | Update existing records by ID |
|
||||
| `delete_records` | Delete records by ID |
|
||||
|
||||
### Schema Operations (requires `schema`)
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `create_table` | Create a new table with column definitions |
|
||||
| `add_column` | Add a column to an existing table |
|
||||
| `modify_column` | Change column type or formula |
|
||||
| `delete_column` | Remove a column from a table |
|
||||
|
||||
## Authentication Flow
|
||||
|
||||
1. Agent connects to MCP server at `http://host:8080/mcp`
|
||||
2. Agent provides token via `Authorization: Bearer <token>` header
|
||||
3. Server looks up token in config, retrieves associated scope
|
||||
4. All subsequent tool calls are validated against that scope
|
||||
|
||||
### Validation on Each Tool Call
|
||||
|
||||
```
|
||||
Agent calls: get_records(document="budget-2024", table="Transactions")
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────┐
|
||||
│ 1. Is token valid? │──No──▶ 401 Unauthorized
|
||||
└───────────────┬───────────────┘
|
||||
│ Yes
|
||||
▼
|
||||
┌───────────────────────────────┐
|
||||
│ 2. Does token have access to │──No──▶ 403 Forbidden
|
||||
│ document "budget-2024"? │
|
||||
└───────────────┬───────────────┘
|
||||
│ Yes
|
||||
▼
|
||||
┌───────────────────────────────┐
|
||||
│ 3. Does token have "read" │──No──▶ 403 Forbidden
|
||||
│ permission on this doc? │
|
||||
└───────────────┬───────────────┘
|
||||
│ Yes
|
||||
▼
|
||||
┌───────────────────────────────┐
|
||||
│ 4. Execute against Grist API │
|
||||
│ using doc's api_key │
|
||||
└───────────────────────────────┘
|
||||
```
|
||||
|
||||
### Error Responses
|
||||
|
||||
- Invalid/missing token → `401 Unauthorized` (no details)
|
||||
- Valid token, wrong document → `403 Forbidden: Document not in scope`
|
||||
- Valid token, wrong permission → `403 Forbidden: Permission denied`
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
grist-mcp/
|
||||
├── Dockerfile
|
||||
├── docker-compose.yaml
|
||||
├── config.yaml.example # Template with dummy values
|
||||
├── pyproject.toml
|
||||
├── uv.lock
|
||||
├── src/
|
||||
│ └── grist_mcp/
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py # Entry point, loads config, starts server
|
||||
│ ├── config.py # Config parsing, env var substitution
|
||||
│ ├── auth.py # Token validation, permission checking
|
||||
│ ├── grist_client.py # Grist API wrapper
|
||||
│ └── tools/
|
||||
│ ├── __init__.py
|
||||
│ ├── discovery.py # list_documents
|
||||
│ ├── read.py # list_tables, describe_table, get_records, sql_query
|
||||
│ ├── write.py # add_records, update_records, delete_records
|
||||
│ └── schema.py # create_table, add_column, modify_column, delete_column
|
||||
└── tests/
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Docker Setup
|
||||
|
||||
### docker-compose.yaml
|
||||
|
||||
```yaml
|
||||
services:
|
||||
grist-mcp:
|
||||
build: .
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ./config.yaml:/app/config.yaml:ro
|
||||
env_file:
|
||||
- .env
|
||||
```
|
||||
|
||||
### Dockerfile
|
||||
|
||||
```dockerfile
|
||||
FROM ghcr.io/astral-sh/uv:python3.14-bookworm-slim
|
||||
WORKDIR /app
|
||||
COPY pyproject.toml uv.lock ./
|
||||
RUN uv sync --frozen --no-dev
|
||||
COPY src/ ./src/
|
||||
CMD ["uv", "run", "python", "-m", "grist_mcp.main"]
|
||||
```
|
||||
|
||||
### pyproject.toml
|
||||
|
||||
```toml
|
||||
[project]
|
||||
name = "grist-mcp"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.14"
|
||||
dependencies = [
|
||||
"mcp",
|
||||
"httpx",
|
||||
"pyyaml",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
```
|
||||
|
||||
## Technology Stack
|
||||
|
||||
| Component | Choice |
|
||||
|-----------|--------|
|
||||
| Language | Python 3.14 |
|
||||
| Package Manager | uv |
|
||||
| MCP Framework | mcp (official SDK) |
|
||||
| HTTP Client | httpx |
|
||||
| Config Parsing | pyyaml |
|
||||
| Deployment | Docker |
|
||||
Reference in New Issue
Block a user