docs: add MCP server and API key documentation

- Update FEATURES.md, ARCHITECTURE.md, DEPLOYMENT.md with MCP sections
- Add MCP endpoints to OpenAPI spec and regenerate API.md
- Add rebase-before-PR best practice to CLAUDE.md git conventions
- Update README with MCP/API feature mention

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Frederic Cabassut
2026-03-09 02:03:04 +01:00
committed by Fred Cabassut
co-authored by Claude Opus 4.6
parent f327e8dcb2
commit fe88b15a0d
7 changed files with 353 additions and 3 deletions
+1
View File
@@ -130,6 +130,7 @@ A `Makefile` wraps all common tasks for tool-agnostic usage. Run `make help` to
- **Conventional commits**: Use `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`, `test:` prefixes
- **Commit regularly**: Bundle related changes into logical commits after each step or feature slice — don't accumulate a large diff
- **Feature branches**: Use `feat/<name>` branches with worktrees for isolation
- **Rebase before PR**: Always `git rebase origin/main` (not merge) before pushing or creating a PR to keep a clean linear history and avoid merge conflicts in the PR
## CI/CD
+2 -1
View File
@@ -42,7 +42,8 @@ pnpm build # Production build
- Organize with #tags, pinning, archive, and trash
- Full-text search across titles, content, and tags
- PWA — installable, works offline via IndexedDB + LWW CRDT sync
- Single-user password auth (Argon2)
- MCP server — let AI assistants (Claude Code, etc.) manage your notes
- Single-user password auth (Argon2) + API keys for MCP access
- Docker deployment with a single command
See [docs/FEATURES.md](docs/FEATURES.md) for detailed feature documentation.
+158
View File
@@ -111,6 +111,17 @@ List notes
"version": 1,
"tags": [
"item"
],
"attachments": [
{
"id": "id",
"noteId": "noteId",
"filename": "filename",
"mimeType": "mimeType",
"size": 1,
"thumbnailPath": "thumbnailPath",
"createdAt": "2025-01-01T00:00:00.000Z"
}
]
}
]
@@ -157,6 +168,17 @@ Create a note
"version": 1,
"tags": [
"item"
],
"attachments": [
{
"id": "id",
"noteId": "noteId",
"filename": "filename",
"mimeType": "mimeType",
"size": 1,
"thumbnailPath": "thumbnailPath",
"createdAt": "2025-01-01T00:00:00.000Z"
}
]
}
```
@@ -192,6 +214,17 @@ Get a note
"version": 1,
"tags": [
"item"
],
"attachments": [
{
"id": "id",
"noteId": "noteId",
"filename": "filename",
"mimeType": "mimeType",
"size": 1,
"thumbnailPath": "thumbnailPath",
"createdAt": "2025-01-01T00:00:00.000Z"
}
]
}
```
@@ -247,6 +280,17 @@ Partial update — only include changed fields.
"version": 1,
"tags": [
"item"
],
"attachments": [
{
"id": "id",
"noteId": "noteId",
"filename": "filename",
"mimeType": "mimeType",
"size": 1,
"thumbnailPath": "thumbnailPath",
"createdAt": "2025-01-01T00:00:00.000Z"
}
]
}
```
@@ -291,6 +335,7 @@ Without `attachmentId`, lists all attachments. With `attachmentId`, returns the
|------|----|------|----------|-------------|
| `id` | path | string | yes | |
| `attachmentId` | query | string | no | |
| `thumb` | query | string | no | Set to "1" to return the thumbnail instead of the full image (requires attachmentId) (1) |
**Response:** `200`
@@ -302,6 +347,7 @@ Without `attachmentId`, lists all attachments. With `attachmentId`, returns the
"filename": "filename",
"mimeType": "mimeType",
"size": 1,
"thumbnailPath": "thumbnailPath",
"createdAt": "2025-01-01T00:00:00.000Z"
}
]
@@ -330,6 +376,7 @@ Upload an image attachment
"filename": "filename",
"mimeType": "mimeType",
"size": 1,
"thumbnailPath": "thumbnailPath",
"createdAt": "2025-01-01T00:00:00.000Z"
}
```
@@ -393,6 +440,17 @@ Searches across note titles, content, and tags. Excludes trashed notes.
"version": 1,
"tags": [
"item"
],
"attachments": [
{
"id": "id",
"noteId": "noteId",
"filename": "filename",
"mimeType": "mimeType",
"size": 1,
"thumbnailPath": "thumbnailPath",
"createdAt": "2025-01-01T00:00:00.000Z"
}
]
}
]
@@ -451,6 +509,17 @@ Pull changes since timestamp
"version": 1,
"tags": [
"item"
],
"attachments": [
{
"id": "id",
"noteId": "noteId",
"filename": "filename",
"mimeType": "mimeType",
"size": 1,
"thumbnailPath": "thumbnailPath",
"createdAt": "2025-01-01T00:00:00.000Z"
}
]
}
]
@@ -498,3 +567,92 @@ Push local changes
**Errors:** `400` Invalid changes payload
---
## Settings
### `GET /api/settings/api-keys`
List API keys
**Response:** `200`
```json
[
{
"id": "uuid",
"name": "name",
"keyPrefix": "keyPrefix",
"createdAt": "2025-01-01T00:00:00.000Z",
"lastUsedAt": "2025-01-01T00:00:00.000Z"
}
]
```
---
### `POST /api/settings/api-keys`
Create API key
**Request:**
```json
{
"name": "name"
}
```
**Response:** `201`
```json
{
"id": "id",
"key": "key",
"name": "name",
"keyPrefix": "keyPrefix"
}
```
---
### `DELETE /api/settings/api-keys/{id}`
Revoke API key
**Parameters:**
| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| `id` | path | string | yes | |
**Response:** `200`
```json
{
"success": true
}
```
**Errors:** `404` Key not found
---
## MCP
### `POST /api/mcp`
MCP Streamable HTTP endpoint
Model Context Protocol endpoint. Accepts JSON-RPC 2.0 messages. Authenticate via Bearer token (API key).
**Request:**
```json
{}
```
**Response:** `200`
**Errors:** `400` Invalid session, `401` Invalid or missing API key
---
+26 -2
View File
@@ -98,17 +98,41 @@ Key tables:
- `tags` - Unique tag names
- `note_tags` - Many-to-many note ↔ tag
- `attachments` - Image file metadata (path + thumbnailPath)
- `api_keys` - MCP API keys (SHA-256 hashed, prefix for display)
- `sync_log` - Sync operation history
## MCP Server
Crumbs includes a built-in [Model Context Protocol](https://modelcontextprotocol.io/) server that allows AI assistants (Claude Code, Claude Desktop, etc.) to interact with notes programmatically.
### Architecture
- **Endpoint**: `POST /api/mcp` (Streamable HTTP transport)
- **Auth**: Bearer token via API keys (generated in Settings)
- **Sessions**: Stateful per MCP spec — session ID in `mcp-session-id` header
- **Transport**: `WebStandardStreamableHTTPServerTransport` (works with SvelteKit's Web Standard Request/Response)
### Shared Logic
MCP tool handlers and REST API routes both call the same service layer (`src/lib/server/notes-service.ts`). This avoids duplicating business logic:
```
REST API routes ──┐
├──► notes-service.ts ──► Drizzle DB
MCP tool handlers ┘
```
### Tools (14)
`list_notes`, `get_note`, `create_note`, `update_note`, `trash_note`, `restore_note`, `archive_note`, `unarchive_note`, `delete_note`, `search_notes`, `list_tags`, `pin_note`, `reorder_notes`, `upload_image`
## File Organization
```
src/lib/server/ # Server-only code (DB, auth, attachments)
src/lib/server/ # Server-only code (DB, auth, attachments, notes-service)
src/lib/server/mcp/ # MCP server + tool definitions
src/lib/sync/ # Sync engine (shared types, server + client implementations)
src/lib/stores/ # Svelte reactive stores (notes, theme, auth, search)
src/lib/components/ # Svelte UI components
src/lib/utils/ # Pure utility functions (markdown, tags, colors)
src/lib/types/ # TypeScript type definitions
src/routes/api/ # REST API endpoints
src/routes/api/ # REST API + MCP endpoints
src/routes/ # Page routes (SvelteKit file-based routing)
```
+43
View File
@@ -116,6 +116,48 @@ The Docker image includes a health check that pings `/login`:
docker inspect --format='{{.State.Health.Status}}' crumbs
```
## MCP Server Configuration
Crumbs includes a built-in MCP (Model Context Protocol) server that allows AI assistants to interact with your notes.
### Setup
1. Open **Settings** in the Crumbs UI
2. Create an API key (give it a name like "Claude Code")
3. Copy the key (shown only once)
4. Configure your MCP client:
```json
{
"mcpServers": {
"crumbs": {
"type": "streamable-http",
"url": "https://your-crumbs-instance/api/mcp",
"headers": {
"Authorization": "Bearer YOUR_API_KEY"
}
}
}
}
```
### Available Tools
| Tool | Description |
|------|-------------|
| `list_notes` | List notes (filter by status/tag) |
| `get_note` | Get a note by ID |
| `create_note` | Create a new note |
| `update_note` | Update a note |
| `trash_note` / `restore_note` | Move to/from trash |
| `archive_note` / `unarchive_note` | Archive/unarchive |
| `delete_note` | Permanently delete |
| `search_notes` | Full-text search |
| `list_tags` | List all tags |
| `pin_note` | Pin/unpin a note |
| `reorder_notes` | Set sort orders |
| `upload_image` | Attach image from URL |
## Security Notes
- Always use HTTPS in production (via reverse proxy)
@@ -123,3 +165,4 @@ docker inspect --format='{{.State.Health.Status}}' crumbs
- The `ORIGIN` variable must match your actual domain for CSRF protection
- Sessions expire after 30 days
- Passwords are hashed with Argon2
- API keys are SHA-256 hashed (never stored in plain text)
+15
View File
@@ -102,6 +102,21 @@
- LWW conflict resolution for multi-device use
- Sync status indicator (synced/syncing/offline/error)
### MCP Server (Model Context Protocol)
- Built-in MCP endpoint at `POST /api/mcp` (Streamable HTTP transport)
- 14 tools: list, get, create, update, trash, restore, archive, unarchive, delete, search notes; list tags; pin, reorder notes; upload images
- API key authentication (Bearer token)
- Compatible with Claude Code, Claude Desktop, and other MCP clients
- Stateful sessions with automatic cleanup
### API Key Management
- Generate API keys from the Settings page
- Keys use `crumbs_` prefix for easy identification
- SHA-256 hashed storage (keys shown once at creation)
- Track last-used timestamps
- Revoke keys with confirmation dialog
- MCP client config example shown in Settings
### Docker Deployment
- Multi-stage Dockerfile (build + slim runtime)
- docker-compose.yml with persistent volume
+108
View File
@@ -325,12 +325,101 @@ paths:
'400':
description: Invalid changes payload
/api/settings/api-keys:
get:
summary: List API keys
tags: [Settings]
responses:
'200':
description: List of API keys (without hashes)
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ApiKey'
post:
summary: Create API key
tags: [Settings]
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name]
properties:
name:
type: string
responses:
'201':
description: API key created (key shown only once)
content:
application/json:
schema:
type: object
properties:
id:
type: string
key:
type: string
name:
type: string
keyPrefix:
type: string
/api/settings/api-keys/{id}:
delete:
summary: Revoke API key
tags: [Settings]
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: Key revoked
content:
application/json:
schema:
$ref: '#/components/schemas/Success'
'404':
description: Key not found
/api/mcp:
post:
summary: MCP Streamable HTTP endpoint
description: Model Context Protocol endpoint. Accepts JSON-RPC 2.0 messages. Authenticate via Bearer token (API key).
tags: [MCP]
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
description: JSON-RPC 2.0 message
responses:
'200':
description: JSON-RPC response (may be SSE stream)
'400':
description: Invalid session
'401':
description: Invalid or missing API key
components:
securitySchemes:
sessionCookie:
type: apiKey
in: cookie
name: session
bearerAuth:
type: http
scheme: bearer
description: API key (crumbs_...)
schemas:
Success:
@@ -452,3 +541,22 @@ components:
description: Unix timestamp in milliseconds
data:
$ref: '#/components/schemas/NoteInput'
ApiKey:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
keyPrefix:
type: string
description: First 12 characters of the key for display
createdAt:
type: string
format: date-time
lastUsedAt:
type: string
format: date-time
nullable: true