forked from mirror/crumbs-original
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:
committed by
Fred Cabassut
co-authored by
Claude Opus 4.6
parent
f327e8dcb2
commit
fe88b15a0d
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
```
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user