From 0ebf10344d04fc626c67d696afee3952e28b1102 Mon Sep 17 00:00:00 2001 From: Antigravity Date: Sun, 3 May 2026 20:49:11 +0000 Subject: [PATCH] feat(mcp): add all 4 note types, translate N8N docs to English, add N8N workflow examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tools.js: expose type enum ['text','markdown','richtext','checklist'] in create_note & update_note - default changed from 'text' to 'richtext' (matches Prisma schema) - isMarkdown marked as deprecated in favour of type='markdown' - N8N-CONFIG.md: full French → English translation - N8N-WORKFLOWS.md: full French → English translation - N8N-EXAMPLES.md: new comprehensive examples for all 22 MCP tools + workflow patterns - n8n-workflow-mcp-reminder-bot.json: cron → get_due_reminders → Telegram → mark done - n8n-workflow-mcp-email-to-note.json: IMAP → create_note → urgent Slack alert - n8n-workflow-mcp-daily-digest.json: 8AM cron → notes + reminders digest → save + Slack - n8n-workflow-mcp-webhook-to-note.json: universal webhook → create_note → respond - notebooks-list.tsx: fix truncated notebook names (pe-24→pe-14), replace hover overlay with Tooltip --- mcp-server/N8N-CONFIG.md | 137 +++-- mcp-server/N8N-EXAMPLES.md | 571 ++++++++++++++++++ mcp-server/N8N-WORKFLOWS.md | 56 +- mcp-server/n8n-workflow-mcp-daily-digest.json | 137 +++++ .../n8n-workflow-mcp-email-to-note.json | 109 ++++ mcp-server/n8n-workflow-mcp-reminder-bot.json | 155 +++++ .../n8n-workflow-mcp-webhook-to-note.json | 92 +++ mcp-server/tools.js | 19 +- memento-note/components/notebooks-list.tsx | 40 +- 9 files changed, 1203 insertions(+), 113 deletions(-) create mode 100644 mcp-server/N8N-EXAMPLES.md create mode 100644 mcp-server/n8n-workflow-mcp-daily-digest.json create mode 100644 mcp-server/n8n-workflow-mcp-email-to-note.json create mode 100644 mcp-server/n8n-workflow-mcp-reminder-bot.json create mode 100644 mcp-server/n8n-workflow-mcp-webhook-to-note.json diff --git a/mcp-server/N8N-CONFIG.md b/mcp-server/N8N-CONFIG.md index 1be33b3..3d3de55 100644 --- a/mcp-server/N8N-CONFIG.md +++ b/mcp-server/N8N-CONFIG.md @@ -1,34 +1,34 @@ -# Configuration N8N - Memento MCP Server +# N8N Configuration — Memento MCP Server -## Configuration MCP Client dans N8N +## MCP Client Setup in N8N -Le serveur MCP utilise le transport **Streamable HTTP** (remplace l'ancien SSE). +The MCP server uses **Streamable HTTP** transport (replaces the legacy SSE transport). -### 1. Generer une cle API +### 1. Generate an API Key -Dans Memento : **Parametres > MCP > Generer une cle** +In Memento: **Settings > MCP > Generate a key** -La cle a le format `mcp_sk_...` et est associee a votre compte utilisateur. Seules vos notes seront accessibles. +The key has the format `mcp_sk_...` and is scoped to your user account. Only your notes will be accessible. -### 2. Configurer le noeud MCP Client dans N8N +### 2. Configure the MCP Client Node in N8N -1. Ajouter un noeud **MCP Client** dans votre workflow -2. **Server Transport** : `Streamable HTTP` -3. **MCP Endpoint URL** : `http://memento-mcp:3001/mcp` (Docker) ou `http://VOTRE_IP:3001/mcp` -4. **Authentication** : Header Auth - - Header Name : `x-api-key` - - Header Value : votre cle API (`mcp_sk_...`) +1. Add an **MCP Client** node to your workflow +2. **Server Transport**: `Streamable HTTP` +3. **MCP Endpoint URL**: `http://memento-mcp:3001/mcp` (Docker) or `http://YOUR_IP:3001/mcp` +4. **Authentication**: Header Auth + - Header Name: `x-api-key` + - Header Value: your API key (`mcp_sk_...`) -### Alternative : curl +### Alternative: curl ```bash # Health check -curl -H "x-api-key: mcp_sk_votrecle" http://localhost:3001/ +curl -H "x-api-key: mcp_sk_yourkey" http://localhost:3001/ -# Lister les outils +# List available tools curl -X POST http://localhost:3001/mcp \ -H "Content-Type: application/json" \ - -H "x-api-key: mcp_sk_votrecle" \ + -H "x-api-key: mcp_sk_yourkey" \ -d '{ "jsonrpc": "2.0", "id": 1, @@ -36,10 +36,10 @@ curl -X POST http://localhost:3001/mcp \ "params": {} }' -# Creer une note +# Create a note curl -X POST http://localhost:3001/mcp \ -H "Content-Type: application/json" \ - -H "x-api-key: mcp_sk_votrecle" \ + -H "x-api-key: mcp_sk_yourkey" \ -d '{ "jsonrpc": "2.0", "id": 2, @@ -47,72 +47,81 @@ curl -X POST http://localhost:3001/mcp \ "params": { "name": "create_note", "arguments": { - "title": "Ma note", - "content": "Contenu de la note" + "title": "My note", + "content": "Note content here" } } }' ``` -## Outils disponibles (22) +## Available Tools (22) ### Notes (11) -| Outil | Description | -|-------|-------------| -| `create_note` | Creer une note | -| `get_notes` | Lister les notes | -| `get_note` | Recuperer une note par ID | -| `update_note` | Modifier une note | -| `delete_note` | Supprimer une note | -| `search_notes` | Rechercher par mot-cle | -| `move_note` | Deplacer vers un notebook | -| `toggle_pin` | Epingler/Depingler | -| `toggle_archive` | Archiver/Desarchiver | -| `export_notes` | Exporter en JSON | -| `import_notes` | Importer depuis JSON | +| Tool | Description | +|------|-------------| +| `create_note` | Create a note | +| `get_notes` | List notes | +| `get_note` | Get a note by ID | +| `update_note` | Update a note | +| `delete_note` | Delete a note | +| `search_notes` | Search by keyword | +| `move_note` | Move to a notebook | +| `toggle_pin` | Pin / unpin | +| `toggle_archive` | Archive / unarchive | +| `export_notes` | Export as JSON | +| `import_notes` | Import from JSON | + +#### Note Types + +| `type` value | Description | +|--------------|-------------| +| `richtext` | Rich text editor — **default** | +| `markdown` | Markdown rendered (use instead of `isMarkdown`) | +| `text` | Plain text | +| `checklist` | Interactive checklist with `checkItems` | ### Notebooks (6) -| Outil | Description | -|-------|-------------| -| `create_notebook` | Creer un notebook | -| `get_notebooks` | Lister les notebooks | -| `get_notebook` | Details d'un notebook | -| `update_notebook` | Modifier un notebook | -| `delete_notebook` | Supprimer un notebook | -| `reorder_notebooks` | Reordonner | +| Tool | Description | +|------|-------------| +| `create_notebook` | Create a notebook | +| `get_notebooks` | List all notebooks | +| `get_notebook` | Get notebook details | +| `update_notebook` | Update a notebook | +| `delete_notebook` | Delete a notebook | +| `reorder_notebooks` | Reorder notebooks | ### Labels (4) -| Outil | Description | -|-------|-------------| -| `create_label` | Creer un label | -| `get_labels` | Lister les labels | -| `update_label` | Modifier un label | -| `delete_label` | Supprimer un label | +| Tool | Description | +|------|-------------| +| `create_label` | Create a label | +| `get_labels` | List labels | +| `update_label` | Update a label | +| `delete_label` | Delete a label | -### Rappels (1) +### Reminders (1) -| Outil | Description | -|-------|-------------| -| `get_due_reminders` | Recuperer les rappels dus | +| Tool | Description | +|------|-------------| +| `get_due_reminders` | Get due reminders | -## Endpoints HTTP +## HTTP Endpoints -| Endpoint | Methode | Description | -|----------|---------|-------------| +| Endpoint | Method | Description | +|----------|--------|-------------| | `/` | GET | Health check | -| `/mcp` | GET/POST | Endpoint MCP principal | -| `/sse` | GET/POST | Legacy (redirige vers `/mcp`) | -| `/sessions` | GET | Sessions actives | +| `/mcp` | GET/POST | Main MCP endpoint | +| `/sse` | GET/POST | Legacy (redirects to `/mcp`) | +| `/sessions` | GET | Active sessions | -## Securite +## Security -- **Authentification obligatoire** en production (`MCP_REQUIRE_AUTH=true` dans Docker) -- Les cles API sont gerees depuis **Parametres > MCP** dans Memento -- Chaque cle est scopee a un utilisateur : seules ses notes sont accessibles -- Les cles sont hashees en base (SHA256), seul le raw key est montre a la creation +- **Authentication required** in production (`MCP_REQUIRE_AUTH=true` in Docker) +- API keys are managed from **Settings > MCP** in Memento +- Each key is scoped to a single user: only their notes are accessible +- Keys are hashed in the database (SHA256); the raw key is only shown once at creation ## Ports diff --git a/mcp-server/N8N-EXAMPLES.md b/mcp-server/N8N-EXAMPLES.md new file mode 100644 index 0000000..4cb78f9 --- /dev/null +++ b/mcp-server/N8N-EXAMPLES.md @@ -0,0 +1,571 @@ +# N8N Examples — Memento MCP Server + +Practical, copy-paste-ready N8N workflow snippets for every Memento MCP tool. +All examples assume an MCP Client node configured with Streamable HTTP and `x-api-key` authentication. + +## Note Types + +| `type` value | Description | +|--------------|-------------| +| `richtext` | Rich text editor — **default** | +| `markdown` | Markdown rendered (preferred over deprecated `isMarkdown`) | +| `text` | Plain text | +| `checklist` | Interactive checklist with `checkItems` | + +> **Note**: `isMarkdown: true` is deprecated. Use `"type": "markdown"` instead. + +--- + + +## Notes + +### Create a simple text note + +```json +{ + "tool": "create_note", + "arguments": { + "title": "Quick idea", + "content": "Explore serverless architecture for the new API gateway.", + "color": "yellow", + "labels": ["idea", "architecture"] + } +} +``` + +### Create a pinned markdown note + +```json +{ + "tool": "create_note", + "arguments": { + "title": "Project README", + "content": "# My Project\n\nThis is the main documentation.\n\n## Getting Started\n\n```bash\nnpm install\nnpm run dev\n```", + "isMarkdown": true, + "isPinned": true, + "color": "blue", + "size": "large" + } +} +``` + +### Create a checklist note + +```json +{ + "tool": "create_note", + "arguments": { + "title": "Weekly tasks", + "content": "Tasks for this week", + "type": "checklist", + "checkItems": [ + { "id": "1", "text": "Review pull requests", "checked": false }, + { "id": "2", "text": "Update deployment docs", "checked": false }, + { "id": "3", "text": "Team standup notes", "checked": true } + ], + "color": "teal" + } +} +``` + +### Create a note with a reminder + +```json +{ + "tool": "create_note", + "arguments": { + "title": "Dentist appointment", + "content": "Dr. Martin — 10:30 AM. Bring insurance card.", + "reminder": "2025-06-15T10:00:00.000Z", + "reminderRecurrence": "yearly", + "color": "red", + "labels": ["health", "personal"] + } +} +``` + +### Create a note in a specific notebook + +```json +{ + "tool": "create_note", + "arguments": { + "title": "Sprint planning notes", + "content": "**Goal**: Ship user authentication by Friday.\n\n- Design review at 2PM\n- Backend API ready by Wednesday", + "isMarkdown": true, + "notebookId": "{{ $json.notebookId }}", + "labels": ["sprint", "planning"], + "color": "purple" + } +} +``` + +### Create a note with external links + +```json +{ + "tool": "create_note", + "arguments": { + "title": "Research resources", + "content": "Curated links for the ML paper review.", + "links": [ + "https://arxiv.org/abs/2304.15004", + "https://huggingface.co/papers", + "https://paperswithcode.com" + ], + "labels": ["research", "ml"], + "color": "green" + } +} +``` + +### Get all notes (lightweight) + +```json +{ + "tool": "get_notes", + "arguments": { + "limit": 50 + } +} +``` + +### Get notes from a specific notebook + +```json +{ + "tool": "get_notes", + "arguments": { + "notebookId": "{{ $json.notebookId }}", + "limit": 100 + } +} +``` + +### Get unfiled notes (Inbox) + +```json +{ + "tool": "get_notes", + "arguments": { + "notebookId": "inbox", + "limit": 50 + } +} +``` + +### Get all notes including archived + +```json +{ + "tool": "get_notes", + "arguments": { + "includeArchived": true, + "limit": 200, + "fullDetails": true + } +} +``` + +### Get a single note by ID + +```json +{ + "tool": "get_note", + "arguments": { + "id": "{{ $json.noteId }}" + } +} +``` + +### Search notes by keyword + +```json +{ + "tool": "search_notes", + "arguments": { + "query": "deployment kubernetes" + } +} +``` + +### Search in a specific notebook + +```json +{ + "tool": "search_notes", + "arguments": { + "query": "{{ $json.searchTerm }}", + "notebookId": "{{ $json.notebookId }}", + "includeArchived": false + } +} +``` + +### Update a note's content + +```json +{ + "tool": "update_note", + "arguments": { + "id": "{{ $json.noteId }}", + "content": "{{ $json.newContent }}", + "color": "green" + } +} +``` + +### Mark a checklist item as done + +```json +{ + "tool": "update_note", + "arguments": { + "id": "{{ $json.noteId }}", + "checkItems": [ + { "id": "1", "text": "Review pull requests", "checked": true }, + { "id": "2", "text": "Update deployment docs", "checked": false } + ] + } +} +``` + +### Add labels to an existing note + +```json +{ + "tool": "update_note", + "arguments": { + "id": "{{ $json.noteId }}", + "labels": ["ai-generated", "reviewed", "{{ $json.category }}"] + } +} +``` + +### Mark a reminder as done + +```json +{ + "tool": "update_note", + "arguments": { + "id": "{{ $json.noteId }}", + "isReminderDone": true + } +} +``` + +### Move a note to a notebook + +```json +{ + "tool": "move_note", + "arguments": { + "id": "{{ $json.noteId }}", + "notebookId": "{{ $json.targetNotebookId }}" + } +} +``` + +### Move a note to Inbox (no notebook) + +```json +{ + "tool": "move_note", + "arguments": { + "id": "{{ $json.noteId }}", + "notebookId": null + } +} +``` + +### Pin a note + +```json +{ + "tool": "toggle_pin", + "arguments": { + "id": "{{ $json.noteId }}" + } +} +``` + +### Archive a note + +```json +{ + "tool": "toggle_archive", + "arguments": { + "id": "{{ $json.noteId }}" + } +} +``` + +### Delete a note permanently + +```json +{ + "tool": "delete_note", + "arguments": { + "id": "{{ $json.noteId }}" + } +} +``` + +### Export all notes as JSON backup + +```json +{ + "tool": "export_notes", + "arguments": {} +} +``` + +### Import notes from a JSON backup + +```json +{ + "tool": "import_notes", + "arguments": { + "data": "{{ $json.exportPayload }}" + } +} +``` + +--- + +## Notebooks + +### Create a notebook + +```json +{ + "tool": "create_notebook", + "arguments": { + "name": "Work Projects", + "icon": "💼", + "color": "#3B82F6" + } +} +``` + +### Create a notebook for a team/topic + +```json +{ + "tool": "create_notebook", + "arguments": { + "name": "Machine Learning Research", + "icon": "🤖", + "color": "#8B5CF6" + } +} +``` + +### List all notebooks + +```json +{ + "tool": "get_notebooks", + "arguments": {} +} +``` + +### Get a notebook with its notes + +```json +{ + "tool": "get_notebook", + "arguments": { + "id": "{{ $json.notebookId }}" + } +} +``` + +### Rename a notebook + +```json +{ + "tool": "update_notebook", + "arguments": { + "id": "{{ $json.notebookId }}", + "name": "Q3 2025 Projects", + "color": "#10B981" + } +} +``` + +### Reorder notebooks + +```json +{ + "tool": "reorder_notebooks", + "arguments": { + "notebookIds": [ + "{{ $json.ids[0] }}", + "{{ $json.ids[1] }}", + "{{ $json.ids[2] }}" + ] + } +} +``` + +### Delete a notebook (notes go to Inbox) + +```json +{ + "tool": "delete_notebook", + "arguments": { + "id": "{{ $json.notebookId }}" + } +} +``` + +--- + +## Labels + +### Create a label inside a notebook + +```json +{ + "tool": "create_label", + "arguments": { + "name": "urgent", + "color": "red", + "notebookId": "{{ $json.notebookId }}" + } +} +``` + +### Create common project labels + +```json +{ + "tool": "create_label", + "arguments": { + "name": "in-progress", + "color": "orange", + "notebookId": "{{ $json.notebookId }}" + } +} +``` + +```json +{ + "tool": "create_label", + "arguments": { + "name": "done", + "color": "green", + "notebookId": "{{ $json.notebookId }}" + } +} +``` + +### List labels in a notebook + +```json +{ + "tool": "get_labels", + "arguments": { + "notebookId": "{{ $json.notebookId }}" + } +} +``` + +### List all labels (global) + +```json +{ + "tool": "get_labels", + "arguments": {} +} +``` + +### Rename a label + +```json +{ + "tool": "update_label", + "arguments": { + "id": "{{ $json.labelId }}", + "name": "high-priority", + "color": "red" + } +} +``` + +### Delete a label + +```json +{ + "tool": "delete_label", + "arguments": { + "id": "{{ $json.labelId }}" + } +} +``` + +--- + +## Reminders + +### Get all due reminders (for cron automation) + +```json +{ + "tool": "get_due_reminders", + "arguments": {} +} +``` + +> **Tip**: Schedule this every 30 minutes with a cron trigger (`0 */30 * * * *`), then loop over results and send notifications via Slack, Telegram, or email. + +--- + +## Common Workflow Patterns + +### Pattern: Email → Note +1. **Email Trigger (IMAP)** — fires on new email +2. **MCP Client** → `create_note` + ```json + { + "title": "{{ $json.subject }}", + "content": "**From**: {{ $json.from }}\n\n{{ $json.text }}", + "labels": ["email"], + "color": "blue", + "isMarkdown": true + } + ``` + +### Pattern: Reminder Notifications +1. **Schedule Trigger** — every 30 minutes +2. **MCP Client** → `get_due_reminders` +3. **IF** — results not empty +4. **Loop over items** → send Slack/Telegram message per reminder +5. **MCP Client** → `update_note` with `isReminderDone: true` + +### Pattern: AI Summarization +1. **MCP Client** → `search_notes` with a topic query +2. **OpenAI / Anthropic node** — summarize the returned notes +3. **MCP Client** → `create_note` with the summary as content, labeled `ai-summary` + +### Pattern: Daily Digest +1. **Schedule Trigger** — every day at 8 AM +2. **MCP Client** → `get_notes` with `limit: 20` (most recent) +3. **AI node** — generate a brief digest +4. **Email / Slack node** — send the digest +5. *(Optional)* **MCP Client** → `create_note` to archive the digest + +### Pattern: Webhook → Structured Note +1. **Webhook Trigger** — receives JSON payload (e.g., from a form or CI/CD pipeline) +2. **Code node** — format the payload into markdown +3. **MCP Client** → `create_note` + ```json + { + "title": "Build #{{ $json.buildNumber }} — {{ $json.status }}", + "content": "{{ $node['Code'].json.markdown }}", + "isMarkdown": true, + "color": "{{ $json.status === 'success' ? 'green' : 'red' }}", + "labels": ["ci-cd", "{{ $json.repo }}"] + } + ``` diff --git a/mcp-server/N8N-WORKFLOWS.md b/mcp-server/N8N-WORKFLOWS.md index b7346af..9bd2e8b 100644 --- a/mcp-server/N8N-WORKFLOWS.md +++ b/mcp-server/N8N-WORKFLOWS.md @@ -1,24 +1,24 @@ -# Workflows N8N pour Memento MCP +# N8N Workflows for Memento MCP ## Introduction -Exemples de workflows N8N utilisant le serveur MCP de Memento via le transport **Streamable HTTP**. +Example N8N workflows using the Memento MCP server via **Streamable HTTP** transport. -**Prerequis** : une cle API generee depuis **Parametres > MCP** dans Memento. +**Prerequisite**: an API key generated from **Settings > MCP** in Memento. -## Configuration du noeud MCP Client +## MCP Client Node Configuration -Dans chaque workflow, le noeud MCP Client doit etre configure ainsi : +In every workflow, the MCP Client node must be configured as follows: -- **Server Transport** : `Streamable HTTP` -- **MCP Endpoint URL** : `http://memento-mcp:3001/mcp` (Docker) ou `http://VOTRE_IP:3001/mcp` -- **Authentication** : Header Auth avec `x-api-key` = votre cle API +- **Server Transport**: `Streamable HTTP` +- **MCP Endpoint URL**: `http://memento-mcp:3001/mcp` (Docker) or `http://YOUR_IP:3001/mcp` +- **Authentication**: Header Auth with `x-api-key` = your API key -## Workflows disponibles +## Available Workflows ### 1. Create Note (`n8n-workflow-create-note.json`) -Cree des notes dans Memento avec classification par IA. +Creates notes in Memento with AI-based classification. ```json { @@ -34,7 +34,7 @@ Cree des notes dans Memento avec classification par IA. ### 2. Search & Summary (`n8n-workflow-search-summary.json`) -Recherche des notes et genere un resume. +Searches notes and generates a summary. ```json { @@ -47,7 +47,7 @@ Recherche des notes et genere un resume. ### 3. Notebook Manager (`n8n-workflow-notebook-management.json`) -CRUD complet des notebooks. +Full CRUD for notebooks. ```json { @@ -62,14 +62,14 @@ CRUD complet des notebooks. ### 4. Reminder Notifications (`n8n-workflow-reminder-notifications.json`) -Automatisation des rappels avec notifications. +Automates reminders with notifications. -- Declencheur : Schedule (cron: `0 */30 * * * *`) -- Appelle `get_due_reminders` et envoie les notifications +- Trigger: Schedule (cron: `0 */30 * * * *`) +- Calls `get_due_reminders` and sends notifications ### 5. Label Manager (`n8n-workflow-label-management.json`) -Gestion des labels. +Label management. ```json { @@ -84,20 +84,20 @@ Gestion des labels. ### 6. Email to Note (`n8n-workflow-email-integration.json`) -Conversion automatique d'emails en notes. +Automatically converts emails into notes. -- Declencheur : Email Trigger (IMAP) -- Cree une note avec le contenu de l'email +- Trigger: Email Trigger (IMAP) +- Creates a note from the email content -## Importation +## Import Instructions -1. Ouvrir N8N -2. **Import from File** -> selectionner le fichier JSON -3. Configurer le noeud MCP Client (URL + cle API) -4. Activer le workflow +1. Open N8N +2. **Import from File** → select the JSON file +3. Configure the MCP Client node (URL + API key) +4. Activate the workflow -## Securite +## Security -- Toujours utiliser une cle API dediee par workflow -- Ne jamais exposer la cle dans les logs -- Restreindre l'acces reseau au port 3001 si possible +- Always use a dedicated API key per workflow +- Never expose the key in logs +- Restrict network access to port 3001 when possible diff --git a/mcp-server/n8n-workflow-mcp-daily-digest.json b/mcp-server/n8n-workflow-mcp-daily-digest.json new file mode 100644 index 0000000..a8b5c0a --- /dev/null +++ b/mcp-server/n8n-workflow-mcp-daily-digest.json @@ -0,0 +1,137 @@ +{ + "name": "Memento MCP — Daily Digest", + "nodes": [ + { + "parameters": { + "rule": { + "interval": [ + { + "field": "cronExpression", + "expression": "0 0 8 * * 1-5" + } + ] + } + }, + "id": "schedule-trigger", + "name": "Daily 8AM (Mon-Fri)", + "type": "n8n-nodes-base.scheduleTrigger", + "typeVersion": 1.2, + "position": [240, 300] + }, + { + "parameters": { + "method": "POST", + "url": "http://memento-mcp:3001/mcp", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { "name": "Content-Type", "value": "application/json" }, + { "name": "x-api-key", "value": "={{ $vars.MEMENTO_API_KEY }}" } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "{\n \"jsonrpc\": \"2.0\",\n \"id\": 1,\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"get_notes\",\n \"arguments\": {\n \"limit\": 30,\n \"includeArchived\": false\n }\n }\n}" + }, + "id": "mcp-get-notes", + "name": "MCP — Get Recent Notes", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [480, 300] + }, + { + "parameters": { + "method": "POST", + "url": "http://memento-mcp:3001/mcp", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { "name": "Content-Type", "value": "application/json" }, + { "name": "x-api-key", "value": "={{ $vars.MEMENTO_API_KEY }}" } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "{\n \"jsonrpc\": \"2.0\",\n \"id\": 2,\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"get_due_reminders\",\n \"arguments\": {}\n }\n}" + }, + "id": "mcp-get-reminders", + "name": "MCP — Get Due Reminders", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [480, 480] + }, + { + "parameters": { + "jsCode": "const notesResponse = $('MCP — Get Recent Notes').item.json;\nconst remindersResponse = $('MCP — Get Due Reminders').item.json;\n\nconst notes = JSON.parse(notesResponse.result?.content?.[0]?.text || '[]');\nconst reminders = JSON.parse(remindersResponse.result?.content?.[0]?.text || '[]');\n\nconst today = new Date().toLocaleDateString('en-GB', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });\n\nconst pinnedNotes = notes.filter(n => n.isPinned).slice(0, 5);\nconst recentNotes = notes.filter(n => !n.isPinned).slice(0, 10);\n\nlet digest = `# 📋 Daily Digest — ${today}\\n\\n`;\n\nif (reminders.length > 0) {\n digest += `## ⏰ Reminders Due (${reminders.length})\\n\\n`;\n reminders.forEach(r => {\n const time = new Date(r.reminder).toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });\n digest += `- **${r.title || 'Untitled'}** @ ${time}\\n`;\n });\n digest += '\\n';\n}\n\nif (pinnedNotes.length > 0) {\n digest += `## 📌 Pinned Notes (${pinnedNotes.length})\\n\\n`;\n pinnedNotes.forEach(n => {\n digest += `- **${n.title || 'Untitled'}**\\n`;\n });\n digest += '\\n';\n}\n\ndigest += `## 📝 Recent Notes (${recentNotes.length})\\n\\n`;\nrecentNotes.forEach(n => {\n const date = new Date(n.updatedAt).toLocaleDateString('en-GB');\n digest += `- ${n.title || 'Untitled'} *(${date})*\\n`;\n});\n\nreturn [{ json: { digest, noteCount: notes.length, reminderCount: reminders.length, today } }];" + }, + "id": "build-digest", + "name": "Build Digest", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [720, 390] + }, + { + "parameters": { + "method": "POST", + "url": "http://memento-mcp:3001/mcp", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { "name": "Content-Type", "value": "application/json" }, + { "name": "x-api-key", "value": "={{ $vars.MEMENTO_API_KEY }}" } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"jsonrpc\": \"2.0\",\n \"id\": 3,\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"create_note\",\n \"arguments\": {\n \"title\": \"Daily Digest — {{ $json.today }}\",\n \"content\": {{ JSON.stringify($json.digest) }},\n \"isMarkdown\": true,\n \"color\": \"teal\",\n \"isPinned\": false,\n \"labels\": [\"digest\", \"auto\"]\n }\n }\n}" + }, + "id": "mcp-save-digest", + "name": "MCP — Save Digest as Note", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [960, 390] + }, + { + "parameters": { + "channel": "general", + "text": "📋 *Daily Digest ready!*\n\n📝 {{ $('Build Digest').item.json.noteCount }} notes · ⏰ {{ $('Build Digest').item.json.reminderCount }} reminders\n\nView in Memento: http://memento:3000", + "additionalFields": { "parse_mode": "Markdown" } + }, + "id": "slack-digest", + "name": "Slack — Share Digest", + "type": "n8n-nodes-base.slack", + "typeVersion": 2.1, + "position": [1200, 390], + "continueOnFail": true + } + ], + "connections": { + "Daily 8AM (Mon-Fri)": { + "main": [ + [ + { "node": "MCP — Get Recent Notes", "type": "main", "index": 0 }, + { "node": "MCP — Get Due Reminders", "type": "main", "index": 0 } + ] + ] + }, + "MCP — Get Recent Notes": { + "main": [[{ "node": "Build Digest", "type": "main", "index": 0 }]] + }, + "MCP — Get Due Reminders": { + "main": [[{ "node": "Build Digest", "type": "main", "index": 0 }]] + }, + "Build Digest": { + "main": [[{ "node": "MCP — Save Digest as Note", "type": "main", "index": 0 }]] + }, + "MCP — Save Digest as Note": { + "main": [[{ "node": "Slack — Share Digest", "type": "main", "index": 0 }]] + } + }, + "pinData": {}, + "settings": { "executionOrder": "v1" }, + "staticData": null, + "tags": [{ "id": "memento-mcp", "name": "Memento MCP" }], + "triggerCount": 1, + "updatedAt": "2026-05-03T00:00:00.000Z", + "versionId": "1" +} diff --git a/mcp-server/n8n-workflow-mcp-email-to-note.json b/mcp-server/n8n-workflow-mcp-email-to-note.json new file mode 100644 index 0000000..5ea27d9 --- /dev/null +++ b/mcp-server/n8n-workflow-mcp-email-to-note.json @@ -0,0 +1,109 @@ +{ + "name": "Memento MCP — Email to Note", + "nodes": [ + { + "parameters": { + "pollTimes": { + "item": [{ "mode": "everyMinute" }] + }, + "filters": { + "hasReadStatus": true, + "readStatus": "unread" + } + }, + "id": "email-trigger", + "name": "Email Trigger (IMAP)", + "type": "n8n-nodes-base.emailTrigger", + "typeVersion": 1.1, + "position": [240, 300] + }, + { + "parameters": { + "jsCode": "const email = $input.item.json;\nconst subject = email.subject || 'Email (no subject)';\nconst from = email.from?.value?.[0]?.address || email.from || 'unknown';\nconst body = email.text || email.html?.replace(/<[^>]+>/g, '') || '';\nconst date = email.date ? new Date(email.date).toISOString() : new Date().toISOString();\n\nconst isUrgent = /(urgent|asap|important|deadline|critical)/i.test(subject + body);\n\nreturn [{\n json: {\n title: `📧 ${subject}`,\n content: `**From:** ${from}\\n**Date:** ${new Date(date).toLocaleString('en-GB')}\\n\\n---\\n\\n${body.trim().substring(0, 5000)}`,\n isMarkdown: true,\n color: isUrgent ? 'red' : 'blue',\n isPinned: isUrgent,\n labels: isUrgent ? ['email', 'urgent'] : ['email'],\n isUrgent\n }\n}];" + }, + "id": "format-email", + "name": "Format Email as Note", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [480, 300] + }, + { + "parameters": { + "method": "POST", + "url": "http://memento-mcp:3001/mcp", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { "name": "Content-Type", "value": "application/json" }, + { "name": "x-api-key", "value": "={{ $vars.MEMENTO_API_KEY }}" } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"jsonrpc\": \"2.0\",\n \"id\": 1,\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"create_note\",\n \"arguments\": {\n \"title\": \"{{ $json.title }}\",\n \"content\": \"{{ $json.content }}\",\n \"isMarkdown\": {{ $json.isMarkdown }},\n \"color\": \"{{ $json.color }}\",\n \"isPinned\": {{ $json.isPinned }},\n \"labels\": {{ JSON.stringify($json.labels) }}\n }\n }\n}" + }, + "id": "mcp-create-note", + "name": "MCP — Create Note", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [720, 300] + }, + { + "parameters": { + "conditions": { + "conditions": [ + { + "id": "urgent", + "leftValue": "={{ $('Format Email as Note').item.json.isUrgent }}", + "rightValue": true, + "operator": { "type": "boolean", "operation": "equals" } + } + ], + "combinator": "and" + } + }, + "id": "if-urgent", + "name": "Urgent?", + "type": "n8n-nodes-base.if", + "typeVersion": 2.1, + "position": [960, 300] + }, + { + "parameters": { + "channel": "alerts", + "text": "🚨 *Urgent email saved to Memento!*\n\n{{ $('Format Email as Note').item.json.title }}\n\nOpen: http://memento:3000", + "additionalFields": { "parse_mode": "Markdown" } + }, + "id": "slack-alert", + "name": "Slack Alert", + "type": "n8n-nodes-base.slack", + "typeVersion": 2.1, + "position": [1200, 200], + "continueOnFail": true + } + ], + "connections": { + "Email Trigger (IMAP)": { + "main": [[{ "node": "Format Email as Note", "type": "main", "index": 0 }]] + }, + "Format Email as Note": { + "main": [[{ "node": "MCP — Create Note", "type": "main", "index": 0 }]] + }, + "MCP — Create Note": { + "main": [[{ "node": "Urgent?", "type": "main", "index": 0 }]] + }, + "Urgent?": { + "main": [ + [{ "node": "Slack Alert", "type": "main", "index": 0 }], + [] + ] + } + }, + "pinData": {}, + "settings": { "executionOrder": "v1" }, + "staticData": null, + "tags": [{ "id": "memento-mcp", "name": "Memento MCP" }], + "triggerCount": 1, + "updatedAt": "2026-05-03T00:00:00.000Z", + "versionId": "1" +} diff --git a/mcp-server/n8n-workflow-mcp-reminder-bot.json b/mcp-server/n8n-workflow-mcp-reminder-bot.json new file mode 100644 index 0000000..3b8904f --- /dev/null +++ b/mcp-server/n8n-workflow-mcp-reminder-bot.json @@ -0,0 +1,155 @@ +{ + "name": "Memento MCP — Reminder Bot", + "nodes": [ + { + "parameters": { + "rule": { + "interval": [ + { + "field": "cronExpression", + "expression": "0 */30 * * * *" + } + ] + } + }, + "id": "schedule-trigger", + "name": "Every 30 Minutes", + "type": "n8n-nodes-base.scheduleTrigger", + "typeVersion": 1.2, + "position": [240, 300] + }, + { + "parameters": { + "method": "POST", + "url": "http://memento-mcp:3001/mcp", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "x-api-key", + "value": "={{ $vars.MEMENTO_API_KEY }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "{\n \"jsonrpc\": \"2.0\",\n \"id\": 1,\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"get_due_reminders\",\n \"arguments\": {}\n }\n}" + }, + "id": "mcp-get-reminders", + "name": "MCP — Get Due Reminders", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [480, 300] + }, + { + "parameters": { + "jsCode": "// Parse MCP response\nconst response = $input.item.json;\nconst result = JSON.parse(response.result?.content?.[0]?.text || '[]');\n\nif (!Array.isArray(result) || result.length === 0) {\n return [];\n}\n\nreturn result.map(note => ({\n json: {\n noteId: note.id,\n title: note.title || 'Untitled reminder',\n reminder: note.reminder,\n reminderFormatted: new Date(note.reminder).toLocaleString('en-GB'),\n content: (note.content || '').substring(0, 200),\n labels: Array.isArray(note.labels) ? note.labels.join(', ') : ''\n }\n}));" + }, + "id": "parse-reminders", + "name": "Parse Reminders", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [720, 300] + }, + { + "parameters": { + "conditions": { + "options": { "caseSensitive": true }, + "conditions": [ + { + "id": "has-items", + "leftValue": "={{ $items().length }}", + "rightValue": 0, + "operator": { + "type": "number", + "operation": "gt" + } + } + ], + "combinator": "and" + } + }, + "id": "check-has-reminders", + "name": "Has Reminders?", + "type": "n8n-nodes-base.if", + "typeVersion": 2.1, + "position": [960, 300] + }, + { + "parameters": { + "chatId": "={{ $vars.TELEGRAM_CHAT_ID }}", + "text": "⏰ *Reminder — Memento*\n\n📌 *{{ $json.title }}*\n🕐 {{ $json.reminderFormatted }}\n\n{{ $json.content }}{{ $json.labels ? '\n🏷️ ' + $json.labels : '' }}", + "additionalFields": { + "parse_mode": "Markdown" + } + }, + "id": "send-telegram", + "name": "Send Telegram Notification", + "type": "n8n-nodes-base.telegram", + "typeVersion": 1.2, + "position": [1200, 200], + "continueOnFail": true + }, + { + "parameters": { + "method": "POST", + "url": "http://memento-mcp:3001/mcp", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "x-api-key", + "value": "={{ $vars.MEMENTO_API_KEY }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"jsonrpc\": \"2.0\",\n \"id\": 2,\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"update_note\",\n \"arguments\": {\n \"id\": \"{{ $json.noteId }}\",\n \"isReminderDone\": true\n }\n }\n}" + }, + "id": "mcp-mark-done", + "name": "MCP — Mark Reminder Done", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [1440, 200], + "continueOnFail": true + } + ], + "connections": { + "Every 30 Minutes": { + "main": [[{ "node": "MCP — Get Due Reminders", "type": "main", "index": 0 }]] + }, + "MCP — Get Due Reminders": { + "main": [[{ "node": "Parse Reminders", "type": "main", "index": 0 }]] + }, + "Parse Reminders": { + "main": [[{ "node": "Has Reminders?", "type": "main", "index": 0 }]] + }, + "Has Reminders?": { + "main": [ + [{ "node": "Send Telegram Notification", "type": "main", "index": 0 }], + [] + ] + }, + "Send Telegram Notification": { + "main": [[{ "node": "MCP — Mark Reminder Done", "type": "main", "index": 0 }]] + } + }, + "pinData": {}, + "settings": { "executionOrder": "v1" }, + "staticData": null, + "tags": [{ "id": "memento-mcp", "name": "Memento MCP" }], + "triggerCount": 1, + "updatedAt": "2026-05-03T00:00:00.000Z", + "versionId": "1" +} diff --git a/mcp-server/n8n-workflow-mcp-webhook-to-note.json b/mcp-server/n8n-workflow-mcp-webhook-to-note.json new file mode 100644 index 0000000..17e831a --- /dev/null +++ b/mcp-server/n8n-workflow-mcp-webhook-to-note.json @@ -0,0 +1,92 @@ +{ + "name": "Memento MCP — Webhook to Note", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "memento-note", + "responseMode": "responseNode", + "options": {} + }, + "id": "webhook-trigger", + "name": "Webhook", + "type": "n8n-nodes-base.webhook", + "typeVersion": 2, + "position": [240, 300], + "webhookId": "memento-create-note" + }, + { + "parameters": { + "jsCode": "// Flexible input: accept anything and build a note from it\nconst body = $input.item.json.body || $input.item.json;\n\nconst title = body.title || body.subject || body.name || null;\nconst content = body.content || body.text || body.message || body.description || JSON.stringify(body, null, 2);\nconst color = body.color || 'default';\nconst labels = Array.isArray(body.labels) ? body.labels : body.labels ? [body.labels] : [];\nconst notebookId = body.notebookId || body.notebook_id || null;\nconst isPinned = body.isPinned === true || body.pinned === true;\nconst isMarkdown = body.isMarkdown === true || body.markdown === true;\nconst reminder = body.reminder || body.dueDate || body.due_date || null;\nconst color_valid = ['default','red','orange','yellow','green','teal','blue','purple','pink','gray'].includes(color) ? color : 'default';\n\nreturn [{ json: { title, content, color: color_valid, labels, notebookId, isPinned, isMarkdown, reminder } }];" + }, + "id": "prepare-note", + "name": "Prepare Note Data", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [480, 300] + }, + { + "parameters": { + "method": "POST", + "url": "http://memento-mcp:3001/mcp", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { "name": "Content-Type", "value": "application/json" }, + { "name": "x-api-key", "value": "={{ $vars.MEMENTO_API_KEY }}" } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"jsonrpc\": \"2.0\",\n \"id\": 1,\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"create_note\",\n \"arguments\": {\n \"title\": {{ $json.title ? JSON.stringify($json.title) : 'null' }},\n \"content\": {{ JSON.stringify($json.content) }},\n \"color\": \"{{ $json.color }}\",\n \"labels\": {{ JSON.stringify($json.labels) }},\n \"isPinned\": {{ $json.isPinned }},\n \"isMarkdown\": {{ $json.isMarkdown }}\n {{ $json.notebookId ? ', \"notebookId\": \"' + $json.notebookId + '\"' : '' }}\n {{ $json.reminder ? ', \"reminder\": \"' + $json.reminder + '\"' : '' }}\n }\n }\n}" + }, + "id": "mcp-create-note", + "name": "MCP — Create Note", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [720, 300] + }, + { + "parameters": { + "jsCode": "const response = $input.item.json;\nconst result = JSON.parse(response.result?.content?.[0]?.text || '{}');\n\nreturn [{\n json: {\n success: true,\n noteId: result.id,\n title: result.title,\n createdAt: result.createdAt\n }\n}];" + }, + "id": "format-response", + "name": "Format Response", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [960, 300] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={{ $json }}" + }, + "id": "webhook-response", + "name": "Respond to Webhook", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1.1, + "position": [1200, 300] + } + ], + "connections": { + "Webhook": { + "main": [[{ "node": "Prepare Note Data", "type": "main", "index": 0 }]] + }, + "Prepare Note Data": { + "main": [[{ "node": "MCP — Create Note", "type": "main", "index": 0 }]] + }, + "MCP — Create Note": { + "main": [[{ "node": "Format Response", "type": "main", "index": 0 }]] + }, + "Format Response": { + "main": [[{ "node": "Respond to Webhook", "type": "main", "index": 0 }]] + } + }, + "pinData": {}, + "settings": { "executionOrder": "v1" }, + "staticData": null, + "tags": [{ "id": "memento-mcp", "name": "Memento MCP" }], + "triggerCount": 1, + "updatedAt": "2026-05-03T00:00:00.000Z", + "versionId": "1" +} diff --git a/mcp-server/tools.js b/mcp-server/tools.js index a7f3f56..6ec20b0 100644 --- a/mcp-server/tools.js +++ b/mcp-server/tools.js @@ -78,10 +78,15 @@ const toolDefinitions = [ title: { type: 'string', description: 'Note title' }, content: { type: 'string', description: 'Note body text (required)' }, color: { type: 'string', description: `Color: ${NOTE_COLORS}`, default: 'default' }, - type: { type: 'string', enum: ['text', 'checklist'], description: 'Note type', default: 'text' }, + type: { + type: 'string', + enum: ['text', 'markdown', 'richtext', 'checklist'], + description: 'Note type. "text" = plain text, "markdown" = Markdown rendered, "richtext" = rich text editor (default), "checklist" = interactive checklist', + default: 'richtext', + }, checkItems: { type: 'array', - description: 'Checklist items (when type=list)', + description: 'Checklist items (when type=checklist)', items: { type: 'object', properties: { id: { type: 'string' }, text: { type: 'string' }, checked: { type: 'boolean' } }, @@ -97,7 +102,7 @@ const toolDefinitions = [ isReminderDone: { type: 'boolean', default: false }, reminderRecurrence: { type: 'string', description: 'daily, weekly, monthly, yearly' }, reminderLocation: { type: 'string', description: 'Location string' }, - isMarkdown: { type: 'boolean', description: 'Render as markdown', default: false }, + isMarkdown: { type: 'boolean', description: '(Deprecated — use type="markdown" instead) Render as markdown', default: false }, size: { type: 'string', enum: ['small', 'medium', 'large'], default: 'small' }, notebookId: { type: 'string', description: 'Assign to notebook' }, }, @@ -137,7 +142,11 @@ const toolDefinitions = [ title: { type: 'string' }, content: { type: 'string' }, color: { type: 'string', description: `One of: ${NOTE_COLORS}` }, - type: { type: 'string', enum: ['text', 'checklist'] }, + type: { + type: 'string', + enum: ['text', 'markdown', 'richtext', 'checklist'], + description: 'Note type. "text" = plain text, "markdown" = Markdown rendered, "richtext" = rich text editor, "checklist" = interactive checklist', + }, checkItems: { type: 'array', items: { @@ -154,7 +163,7 @@ const toolDefinitions = [ isReminderDone: { type: 'boolean' }, reminderRecurrence: { type: 'string' }, reminderLocation: { type: 'string' }, - isMarkdown: { type: 'boolean' }, + isMarkdown: { type: 'boolean', description: '(Deprecated — use type="markdown" instead)' }, size: { type: 'string', enum: ['small', 'medium', 'large'] }, notebookId: { type: 'string' }, }, diff --git a/memento-note/components/notebooks-list.tsx b/memento-note/components/notebooks-list.tsx index 460a537..2ef4752 100644 --- a/memento-note/components/notebooks-list.tsx +++ b/memento-note/components/notebooks-list.tsx @@ -5,6 +5,7 @@ import Link from 'next/link' import { usePathname, useRouter, useSearchParams } from 'next/navigation' import { cn } from '@/lib/utils' import { StickyNote, Plus, Tag, Folder, ChevronDown, ChevronRight } from 'lucide-react' +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' import { useNotebooks } from '@/context/notebooks-context' import { useNotebookDrag } from '@/context/notebook-drag-context' import { Button } from '@/components/ui/button' @@ -245,23 +246,30 @@ export function NotebooksList() { isDragOver && "ring-2 ring-blue-500 ring-dashed rounded-e-full me-2" )} > - + + {notebook.name} - - - {(notebook as any).notesCount > 0 && ( - ({new Intl.NumberFormat(language).format((notebook as any).notesCount)}) - )} - + + + {/* Actions + expand on the right — always rendered, visible on hover */}