diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e24f829 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,84 @@ +# CLAUDE.md + +Project-specific instructions for Claude Code. + +## Project Overview + +grist-mcp is an MCP (Model Context Protocol) server that enables AI agents to interact with Grist spreadsheet documents. It provides secure, token-based access with granular permissions. + +## Tech Stack + +- Python 3.14+ +- MCP SDK (`mcp` package) +- httpx for async HTTP requests +- PyYAML for configuration +- pytest + pytest-asyncio + pytest-httpx for testing + +## Commands + +```bash +# Run tests +uv run pytest -v + +# Run a specific test file +uv run pytest tests/test_auth.py -v + +# Run the server (requires config and token) +CONFIG_PATH=./config.yaml GRIST_MCP_TOKEN=your-token uv run python -m grist_mcp.main +``` + +## Project Structure + +``` +src/grist_mcp/ +├── main.py # Entry point, runs stdio server +├── server.py # MCP server setup, tool registration, call_tool dispatch +├── config.py # YAML config loading with env var substitution +├── auth.py # Token auth and permission checking +├── grist_client.py # Async Grist API client +└── tools/ + ├── discovery.py # list_documents tool + ├── 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 +``` + +## Key Patterns + +### Authentication Flow +1. Token provided via `GRIST_MCP_TOKEN` env var or passed to `create_server()` +2. `Authenticator.authenticate()` validates token and returns `Agent` object +3. Each tool call uses `Authenticator.authorize()` to check document + permission + +### Permission Levels +- `read`: Query tables and records +- `write`: Add, update, delete records +- `schema`: Create tables, modify columns + +### Tool Implementation Pattern +All tools follow this pattern: +```python +async def tool_name(agent, auth, document, ..., client=None): + auth.authorize(agent, document, Permission.X) + if client is None: + doc = auth.get_document(document) + client = GristClient(doc) + result = await client.some_method(...) + return {"key": result} +``` + +The optional `client` parameter enables dependency injection for testing. + +## Testing + +Tests use pytest-httpx to mock Grist API responses. Each test file has fixtures for common setup: +- `test_auth.py`: Uses in-memory Config objects +- `test_grist_client.py`: Uses HTTPXMock for API mocking +- `test_tools_*.py`: Combine auth fixtures with mocked clients + +## Configuration + +See `config.yaml.example` for the configuration format. Key points: +- Documents define Grist instance URL, doc ID, and API key +- Tokens define agent access with document/permission scopes +- Environment variables can be used with `${VAR}` syntax