# 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)
