## Bug Fixes ### Note Card Actions - Fix broken size change functionality (missing state declaration) - Implement React 19 useOptimistic for instant UI feedback - Add startTransition for non-blocking updates - Ensure smooth animations without page refresh - All note actions now work: pin, archive, color, size, checklist ### Markdown LaTeX Rendering - Add remark-math and rehype-katex plugins - Support inline equations with dollar sign syntax - Support block equations with double dollar sign syntax - Import KaTeX CSS for proper styling - Equations now render correctly instead of showing raw LaTeX ## Technical Details - Replace undefined currentNote references with optimistic state - Add optimistic updates before server actions for instant feedback - Use router.refresh() in transitions for smart cache invalidation - Install remark-math, rehype-katex, and katex packages ## Testing - Build passes successfully with no TypeScript errors - Dev server hot-reloads changes correctly
453 lines
10 KiB
Markdown
453 lines
10 KiB
Markdown
# API Contracts - mcp-server (Memento MCP Server)
|
|
|
|
## Overview
|
|
|
|
The mcp-server provides a Model Context Protocol (MCP) interface for integrating Memento with AI assistants and automation tools like N8N. It exposes tools (functions) that can be called via the MCP protocol.
|
|
|
|
**Protocol:** Model Context Protocol (MCP) SDK v1.0.4
|
|
**Transport:** Stdio (standard input/output)
|
|
**Type:** JavaScript ES modules
|
|
**Database:** Shared Prisma SQLite database (connects to `keep-notes/prisma/dev.db`)
|
|
|
|
---
|
|
|
|
## MCP Tools
|
|
|
|
The server exposes the following MCP tools that can be invoked by MCP clients:
|
|
|
|
---
|
|
|
|
### create_note
|
|
Create a new note in Memento.
|
|
|
|
**Parameters:**
|
|
```json
|
|
{
|
|
"title": "string (optional) - Note title",
|
|
"content": "string (required) - Note content",
|
|
"color": "string (optional, default: 'default') - Note color: default|red|orange|yellow|green|teal|blue|purple|pink|gray",
|
|
"type": "string (optional, default: 'text') - Note type: 'text' or 'checklist'",
|
|
"checkItems": "array (optional) - Checklist items (if type is 'checklist')",
|
|
"labels": "array (optional) - Note labels/tags",
|
|
"isPinned": "boolean (optional, default: false) - Pin the note",
|
|
"isArchived": "boolean (optional, default: false) - Archive the note",
|
|
"images": "array (optional) - Note images as base64 encoded strings"
|
|
}
|
|
```
|
|
|
|
**Response (text content):**
|
|
```json
|
|
{
|
|
"id": "clxxxxxxx",
|
|
"title": "string or null",
|
|
"content": "string",
|
|
"color": "string",
|
|
"type": "text|checklist",
|
|
"checkItems": [...] | null,
|
|
"labels": [...] | null,
|
|
"images": [...] | null,
|
|
"isPinned": boolean,
|
|
"isArchived": boolean,
|
|
"createdAt": "ISO8601 datetime",
|
|
"updatedAt": "ISO8601 datetime"
|
|
}
|
|
```
|
|
|
|
**Error Response:**
|
|
- Throws `McpError` with `ErrorCode.InternalError` on failure
|
|
|
|
---
|
|
|
|
### get_notes
|
|
Get all notes from Memento with optional filtering.
|
|
|
|
**Parameters:**
|
|
```json
|
|
{
|
|
"includeArchived": "boolean (optional, default: false) - Include archived notes",
|
|
"search": "string (optional) - Search query to filter notes (case-insensitive)"
|
|
}
|
|
```
|
|
|
|
**Response (text content):**
|
|
```json
|
|
[
|
|
{
|
|
"id": "clxxxxxxx",
|
|
"title": "string or null",
|
|
"content": "string",
|
|
"color": "string",
|
|
"isPinned": boolean,
|
|
"isArchived": boolean,
|
|
"type": "text|checklist",
|
|
"checkItems": [...] | null,
|
|
"labels": [...] | null,
|
|
"images": [...] | null,
|
|
"order": number,
|
|
"createdAt": "ISO8601 datetime",
|
|
"updatedAt": "ISO8601 datetime"
|
|
}
|
|
]
|
|
```
|
|
|
|
**Ordering:** `isPinned DESC`, `order ASC`, `updatedAt DESC`
|
|
|
|
**Error Response:**
|
|
- Throws `McpError` with `ErrorCode.InternalError` on failure
|
|
|
|
---
|
|
|
|
### get_note
|
|
Get a specific note by ID.
|
|
|
|
**Parameters:**
|
|
```json
|
|
{
|
|
"id": "string (required) - Note ID"
|
|
}
|
|
```
|
|
|
|
**Response (text content):**
|
|
```json
|
|
{
|
|
"id": "clxxxxxxx",
|
|
"title": "string or null",
|
|
"content": "string",
|
|
"color": "string",
|
|
"isPinned": boolean,
|
|
"isArchived": boolean,
|
|
"type": "text|checklist",
|
|
"checkItems": [...] | null,
|
|
"labels": [...] | null,
|
|
"images": [...] | null,
|
|
"reminder": "ISO8601 datetime" | null,
|
|
"isReminderDone": boolean,
|
|
"reminderRecurrence": "string" | null,
|
|
"reminderLocation": "string" | null,
|
|
"isMarkdown": boolean,
|
|
"size": "small|medium|large",
|
|
"embedding": "JSON string" | null,
|
|
"userId": "string" | null,
|
|
"order": number,
|
|
"createdAt": "ISO8601 datetime",
|
|
"updatedAt": "ISO8601 datetime"
|
|
}
|
|
```
|
|
|
|
**Error Response:**
|
|
- Throws `McpError` with `ErrorCode.InvalidRequest` if note not found
|
|
- Throws `McpError` with `ErrorCode.InternalError` on other failures
|
|
|
|
---
|
|
|
|
### update_note
|
|
Update an existing note.
|
|
|
|
**Parameters:**
|
|
```json
|
|
{
|
|
"id": "string (required) - Note ID",
|
|
"title": "string (optional) - Note title",
|
|
"content": "string (optional) - Note content",
|
|
"color": "string (optional) - Note color",
|
|
"checkItems": "array (optional) - Checklist items",
|
|
"labels": "array (optional) - Note labels",
|
|
"isPinned": "boolean (optional) - Pin status",
|
|
"isArchived": "boolean (optional) - Archive status",
|
|
"images": "array (optional) - Note images as base64 encoded strings"
|
|
}
|
|
```
|
|
|
|
**Response (text content):**
|
|
```json
|
|
{
|
|
"id": "clxxxxxxx",
|
|
"title": "string or null",
|
|
"content": "string",
|
|
"color": "string",
|
|
"type": "text|checklist",
|
|
"checkItems": [...] | null,
|
|
"labels": [...] | null,
|
|
"images": [...] | null,
|
|
"isPinned": boolean,
|
|
"isArchived": boolean,
|
|
"createdAt": "ISO8601 datetime",
|
|
"updatedAt": "ISO8601 datetime"
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
- Automatically updates `updatedAt` timestamp
|
|
- Arrays (checkItems, labels, images) are JSON-encoded before storage
|
|
|
|
**Error Response:**
|
|
- Throws `McpError` with `ErrorCode.InternalError` on failure
|
|
|
|
---
|
|
|
|
### delete_note
|
|
Delete a note by ID.
|
|
|
|
**Parameters:**
|
|
```json
|
|
{
|
|
"id": "string (required) - Note ID"
|
|
}
|
|
```
|
|
|
|
**Response (text content):**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Note deleted"
|
|
}
|
|
```
|
|
|
|
**Error Response:**
|
|
- Throws `McpError` with `ErrorCode.InternalError` on failure
|
|
|
|
---
|
|
|
|
### search_notes
|
|
Search notes by query.
|
|
|
|
**Parameters:**
|
|
```json
|
|
{
|
|
"query": "string (required) - Search query"
|
|
}
|
|
```
|
|
|
|
**Response (text content):**
|
|
```json
|
|
[
|
|
{
|
|
"id": "clxxxxxxx",
|
|
"title": "string or null",
|
|
"content": "string",
|
|
"color": "string",
|
|
"isPinned": boolean,
|
|
"isArchived": boolean,
|
|
"type": "text|checklist",
|
|
"checkItems": [...] | null,
|
|
"labels": [...] | null,
|
|
"images": [...] | null,
|
|
"createdAt": "ISO8601 datetime",
|
|
"updatedAt": "ISO8601 datetime"
|
|
}
|
|
]
|
|
```
|
|
|
|
**Search Logic:**
|
|
- Only searches non-archived notes (`isArchived: false`)
|
|
- Case-insensitive search across `title` and `content` fields
|
|
- Uses SQL `LIKE` operator (contains matching)
|
|
|
|
**Ordering:** `isPinned DESC`, `updatedAt DESC`
|
|
|
|
**Error Response:**
|
|
- Throws `McpError` with `ErrorCode.InternalError` on failure
|
|
|
|
---
|
|
|
|
### get_labels
|
|
Get all unique labels from all notes.
|
|
|
|
**Parameters:**
|
|
```json
|
|
{}
|
|
```
|
|
|
|
**Response (text content):**
|
|
```json
|
|
[
|
|
"label1",
|
|
"label2",
|
|
"label3"
|
|
]
|
|
```
|
|
|
|
**Notes:**
|
|
- Extracts labels from all notes in the database
|
|
- Labels are stored as JSON arrays in the `labels` field
|
|
- Returns unique, sorted list of all label strings
|
|
- Does not create or use the Label table (reads directly from Note.labels)
|
|
|
|
**Error Response:**
|
|
- Throws `McpError` with `ErrorCode.InternalError` on failure
|
|
|
|
---
|
|
|
|
### toggle_pin
|
|
Toggle pin status of a note.
|
|
|
|
**Parameters:**
|
|
```json
|
|
{
|
|
"id": "string (required) - Note ID"
|
|
}
|
|
```
|
|
|
|
**Response (text content):**
|
|
```json
|
|
{
|
|
"id": "clxxxxxxx",
|
|
"title": "string or null",
|
|
"content": "string",
|
|
"color": "string",
|
|
"isPinned": boolean, // Toggled value
|
|
"isArchived": boolean,
|
|
"type": "text|checklist",
|
|
"checkItems": [...] | null,
|
|
"labels": [...] | null,
|
|
"images": [...] | null,
|
|
"createdAt": "ISO8601 datetime",
|
|
"updatedAt": "ISO8601 datetime"
|
|
}
|
|
```
|
|
|
|
**Error Response:**
|
|
- Throws `McpError` with `ErrorCode.InvalidRequest` if note not found
|
|
- Throws `McpError` with `ErrorCode.InternalError` on other failures
|
|
|
|
---
|
|
|
|
### toggle_archive
|
|
Toggle archive status of a note.
|
|
|
|
**Parameters:**
|
|
```json
|
|
{
|
|
"id": "string (required) - Note ID"
|
|
}
|
|
```
|
|
|
|
**Response (text content):**
|
|
```json
|
|
{
|
|
"id": "clxxxxxxx",
|
|
"title": "string or null",
|
|
"content": "string",
|
|
"color": "string",
|
|
"isPinned": boolean,
|
|
"isArchived": boolean, // Toggled value
|
|
"type": "text|checklist",
|
|
"checkItems": [...] | null,
|
|
"labels": [...] | null,
|
|
"images": [...] | null,
|
|
"createdAt": "ISO8601 datetime",
|
|
"updatedAt": "ISO8601 datetime"
|
|
}
|
|
```
|
|
|
|
**Error Response:**
|
|
- Throws `McpError` with `ErrorCode.InvalidRequest` if note not found
|
|
- Throws `McpError` with `ErrorCode.InternalError` on other failures
|
|
|
|
---
|
|
|
|
## Server Information
|
|
|
|
**Name:** `memento-mcp-server`
|
|
**Version:** `1.0.0`
|
|
**Capabilities:** Tools only (no resources or prompts)
|
|
|
|
## Connection Details
|
|
|
|
**Transport:** Stdio (Standard Input/Output)
|
|
**Entry Point:** `index.js`
|
|
**Alternative SSE Endpoint:** `index-sse.js` (Server-Sent Events variant)
|
|
|
|
## Database Connection
|
|
|
|
```javascript
|
|
const prisma = new PrismaClient({
|
|
datasources: {
|
|
db: {
|
|
url: `file:${join(__dirname, '../keep-notes/prisma/dev.db')}`
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
**Important:** The MCP server connects directly to the keep-notes SQLite database file, sharing the same data as the web application.
|
|
|
|
## Error Handling
|
|
|
|
All errors are returned as MCP errors with appropriate error codes:
|
|
- `ErrorCode.InvalidRequest`: Invalid parameters or resource not found
|
|
- `ErrorCode.MethodNotFound`: Unknown tool requested
|
|
- `ErrorCode.InternalError`: Server-side errors (database, parsing, etc.)
|
|
|
|
## Usage Example
|
|
|
|
With N8N or MCP-compatible client:
|
|
|
|
```javascript
|
|
// List available tools
|
|
server.tools()
|
|
|
|
// Create a note
|
|
server.callTool('create_note', {
|
|
title: 'Meeting Notes',
|
|
content: 'Discussed Q1 roadmap...',
|
|
labels: ['work', 'planning'],
|
|
color: 'blue'
|
|
})
|
|
|
|
// Search notes
|
|
server.callTool('search_notes', {
|
|
query: 'roadmap'
|
|
})
|
|
|
|
// Toggle pin
|
|
server.callTool('toggle_pin', {
|
|
id: 'clxxxxxxx'
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## Helper Functions
|
|
|
|
### parseNote(dbNote)
|
|
Internal helper to parse JSON fields from database:
|
|
|
|
```javascript
|
|
function parseNote(dbNote) {
|
|
return {
|
|
...dbNote,
|
|
checkItems: dbNote.checkItems ? JSON.parse(dbNote.checkItems) : null,
|
|
labels: dbNote.labels ? JSON.parse(dbNote.labels) : null,
|
|
images: dbNote.images ? JSON.parse(dbNote.images) : null,
|
|
};
|
|
}
|
|
```
|
|
|
|
This function is called on all note objects returned to clients to ensure JSON arrays are properly parsed from their string representation in the database.
|
|
|
|
---
|
|
|
|
## Integration with N8N
|
|
|
|
The MCP server is designed to work with N8N workflows:
|
|
1. Install N8N MCP integration
|
|
2. Configure connection to `memento-mcp-server`
|
|
3. Use tools in N8N nodes:
|
|
- Create notes from external sources
|
|
- Search and retrieve notes
|
|
- Update notes based on triggers
|
|
- Archive/delete old notes
|
|
- Extract labels for categorization
|
|
|
|
---
|
|
|
|
## Notes
|
|
|
|
- **No Authentication:** MCP server runs with direct database access (no auth layer)
|
|
- **Shared Database:** Uses same SQLite file as keep-notes web app
|
|
- **JSON Encoding:** Array fields (checkItems, labels, images) stored as JSON strings
|
|
- **Idempotent Operations:** Toggle operations (pin, archive) are idempotent
|
|
- **Case-Insensitive Search:** Search operations use `mode: 'insensitive'`
|
|
- **Ordering:** Results sorted by relevance (pinned first, then by date)
|