feat(mcp): update server with hierarchy support, batch operations, and tree view
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 16s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 16s
This commit is contained in:
@@ -409,6 +409,29 @@ All examples assume an MCP Client node configured with Streamable HTTP and `x-ap
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Create a sub-notebook (hierarchical)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tool": "create_notebook",
|
||||||
|
"arguments": {
|
||||||
|
"name": "Project Alpha",
|
||||||
|
"icon": "🚀",
|
||||||
|
"color": "#10B981",
|
||||||
|
"parentId": "{{ $json.parentNotebookId }}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get notebook hierarchy (tree view)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tool": "get_notebook_hierarchy",
|
||||||
|
"arguments": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Delete a notebook (notes go to Inbox)
|
### Delete a notebook (notes go to Inbox)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -422,6 +445,33 @@ All examples assume an MCP Client node configured with Streamable HTTP and `x-ap
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Batch Operations
|
||||||
|
|
||||||
|
### Batch move notes to a notebook
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tool": "batch_move_notes",
|
||||||
|
"arguments": {
|
||||||
|
"ids": ["{{ $json.noteId1 }}", "{{ $json.noteId2 }}", "{{ $json.noteId3 }}"],
|
||||||
|
"notebookId": "{{ $json.targetNotebookId }}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Batch delete notes
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tool": "batch_delete_notes",
|
||||||
|
"arguments": {
|
||||||
|
"ids": ["{{ $json.noteId1 }}", "{{ $json.noteId2 }}"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Labels
|
## Labels
|
||||||
|
|
||||||
### Create a label inside a notebook
|
### Create a label inside a notebook
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ Generate API keys from the Memento web UI: **Settings > MCP**.
|
|||||||
curl -H "x-api-key: mcp_sk_xxx" http://localhost:3001/
|
curl -H "x-api-key: mcp_sk_xxx" http://localhost:3001/
|
||||||
```
|
```
|
||||||
|
|
||||||
## Available Tools (22)
|
## Available Tools (25)
|
||||||
|
|
||||||
### Notes (11)
|
### Notes (11)
|
||||||
|
|
||||||
@@ -65,6 +65,8 @@ curl -H "x-api-key: mcp_sk_xxx" http://localhost:3001/
|
|||||||
| `toggle_archive` | Archive/unarchive a note |
|
| `toggle_archive` | Archive/unarchive a note |
|
||||||
| `export_notes` | Export notes as JSON |
|
| `export_notes` | Export notes as JSON |
|
||||||
| `import_notes` | Import notes from JSON |
|
| `import_notes` | Import notes from JSON |
|
||||||
|
| `batch_move_notes` | Move multiple notes at once |
|
||||||
|
| `batch_delete_notes` | Delete multiple notes at once |
|
||||||
|
|
||||||
### Notebooks (6)
|
### Notebooks (6)
|
||||||
|
|
||||||
@@ -76,6 +78,7 @@ curl -H "x-api-key: mcp_sk_xxx" http://localhost:3001/
|
|||||||
| `update_notebook` | Update a notebook |
|
| `update_notebook` | Update a notebook |
|
||||||
| `delete_notebook` | Delete a notebook |
|
| `delete_notebook` | Delete a notebook |
|
||||||
| `reorder_notebooks` | Reorder notebooks |
|
| `reorder_notebooks` | Reorder notebooks |
|
||||||
|
| `get_notebook_hierarchy` | Get tree structure of notebooks |
|
||||||
|
|
||||||
### Labels (4)
|
### Labels (4)
|
||||||
|
|
||||||
|
|||||||
@@ -96,6 +96,10 @@
|
|||||||
{
|
{
|
||||||
"name": "color",
|
"name": "color",
|
||||||
"value": "={{ $json.color || '#3B82F6' }}"
|
"value": "={{ $json.color || '#3B82F6' }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "parentId",
|
||||||
|
"value": "={{ $json.parentId }}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -155,6 +159,10 @@
|
|||||||
{
|
{
|
||||||
"name": "color",
|
"name": "color",
|
||||||
"value": "={{ $json.color }}"
|
"value": "={{ $json.color }}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "parentId",
|
||||||
|
"value": "={{ $json.parentId }}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -204,6 +204,29 @@ const toolDefinitions = [
|
|||||||
required: ['id'],
|
required: ['id'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'batch_move_notes',
|
||||||
|
description: 'Move multiple notes to a notebook simultaneously.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
ids: { type: 'array', items: { type: 'string' }, description: 'Array of Note IDs' },
|
||||||
|
notebookId: { type: 'string', description: 'Target notebook ID, or null for Inbox' },
|
||||||
|
},
|
||||||
|
required: ['ids'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'batch_delete_notes',
|
||||||
|
description: 'Permanently delete multiple notes.',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
ids: { type: 'array', items: { type: 'string' }, description: 'Array of Note IDs' },
|
||||||
|
},
|
||||||
|
required: ['ids'],
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'toggle_pin',
|
name: 'toggle_pin',
|
||||||
description: 'Toggle the pin status of a note.',
|
description: 'Toggle the pin status of a note.',
|
||||||
@@ -264,6 +287,7 @@ const toolDefinitions = [
|
|||||||
icon: { type: 'string', description: 'Icon (emoji)', default: '📁' },
|
icon: { type: 'string', description: 'Icon (emoji)', default: '📁' },
|
||||||
color: { type: 'string', description: 'Hex color', default: '#3B82F6' },
|
color: { type: 'string', description: 'Hex color', default: '#3B82F6' },
|
||||||
order: { type: 'number', description: 'Sort position (auto if omitted)' },
|
order: { type: 'number', description: 'Sort position (auto if omitted)' },
|
||||||
|
parentId: { type: 'string', description: 'Parent notebook ID for sub-notebooks' },
|
||||||
},
|
},
|
||||||
required: ['name'],
|
required: ['name'],
|
||||||
},
|
},
|
||||||
@@ -293,6 +317,7 @@ const toolDefinitions = [
|
|||||||
icon: { type: 'string' },
|
icon: { type: 'string' },
|
||||||
color: { type: 'string' },
|
color: { type: 'string' },
|
||||||
order: { type: 'number' },
|
order: { type: 'number' },
|
||||||
|
parentId: { type: 'string', description: 'Parent notebook ID (set to null to move to root)' },
|
||||||
},
|
},
|
||||||
required: ['id'],
|
required: ['id'],
|
||||||
},
|
},
|
||||||
@@ -321,6 +346,11 @@ const toolDefinitions = [
|
|||||||
required: ['notebookIds'],
|
required: ['notebookIds'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'get_notebook_hierarchy',
|
||||||
|
description: 'Get a nested tree structure of all notebooks (parents and children).',
|
||||||
|
inputSchema: { type: 'object', properties: {} },
|
||||||
|
},
|
||||||
|
|
||||||
// ═══ LABELS ═══
|
// ═══ LABELS ═══
|
||||||
{
|
{
|
||||||
@@ -546,6 +576,26 @@ export function registerTools(server, prisma) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'batch_move_notes': {
|
||||||
|
const targetId = args.notebookId || null;
|
||||||
|
const ids = args.ids || [];
|
||||||
|
|
||||||
|
await prisma.note.updateMany({
|
||||||
|
where: { id: { in: ids }, ...(uid ? { userId: uid } : {}), trashedAt: null },
|
||||||
|
data: { notebookId: targetId, updatedAt: new Date() },
|
||||||
|
});
|
||||||
|
|
||||||
|
return textResult({ success: true, count: ids.length, notebookId: targetId });
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'batch_delete_notes': {
|
||||||
|
const ids = args.ids || [];
|
||||||
|
await prisma.note.deleteMany({
|
||||||
|
where: { id: { in: ids }, ...(uid ? { userId: uid } : {}), trashedAt: null },
|
||||||
|
});
|
||||||
|
return textResult({ success: true, count: ids.length });
|
||||||
|
}
|
||||||
|
|
||||||
case 'toggle_pin': {
|
case 'toggle_pin': {
|
||||||
const note = await prisma.note.findUnique({
|
const note = await prisma.note.findUnique({
|
||||||
where: { id: args.id, ...(uid ? { userId: uid } : {}), trashedAt: null },
|
where: { id: args.id, ...(uid ? { userId: uid } : {}), trashedAt: null },
|
||||||
@@ -711,6 +761,7 @@ export function registerTools(server, prisma) {
|
|||||||
icon: args.icon || '📁',
|
icon: args.icon || '📁',
|
||||||
color: args.color || '#3B82F6',
|
color: args.color || '#3B82F6',
|
||||||
order: nextOrder,
|
order: nextOrder,
|
||||||
|
parentId: args.parentId || null,
|
||||||
userId: uid || await ensureUserId(),
|
userId: uid || await ensureUserId(),
|
||||||
},
|
},
|
||||||
include: { labels: true, _count: { select: { notes: true } } },
|
include: { labels: true, _count: { select: { notes: true } } },
|
||||||
@@ -754,6 +805,7 @@ export function registerTools(server, prisma) {
|
|||||||
if ('icon' in args) d.icon = args.icon;
|
if ('icon' in args) d.icon = args.icon;
|
||||||
if ('color' in args) d.color = args.color;
|
if ('color' in args) d.color = args.color;
|
||||||
if ('order' in args) d.order = args.order;
|
if ('order' in args) d.order = args.order;
|
||||||
|
if ('parentId' in args) d.parentId = args.parentId;
|
||||||
|
|
||||||
const where = { id: args.id, ...(uid ? { userId: uid } : {}) };
|
const where = { id: args.id, ...(uid ? { userId: uid } : {}) };
|
||||||
const notebook = await prisma.notebook.update({
|
const notebook = await prisma.notebook.update({
|
||||||
@@ -799,6 +851,32 @@ export function registerTools(server, prisma) {
|
|||||||
return textResult({ success: true });
|
return textResult({ success: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'get_notebook_hierarchy': {
|
||||||
|
const where = uid ? { userId: uid } : {};
|
||||||
|
const notebooks = await prisma.notebook.findMany({
|
||||||
|
where,
|
||||||
|
include: {
|
||||||
|
_count: { select: { notes: true } },
|
||||||
|
},
|
||||||
|
orderBy: { order: 'asc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build tree
|
||||||
|
const nbMap = new Map();
|
||||||
|
notebooks.forEach(nb => nbMap.set(nb.id, { ...nb, notesCount: nb._count.notes, children: [] }));
|
||||||
|
|
||||||
|
const root = [];
|
||||||
|
nbMap.forEach(nb => {
|
||||||
|
if (nb.parentId && nbMap.has(nb.parentId)) {
|
||||||
|
nbMap.get(nb.parentId).children.push(nb);
|
||||||
|
} else {
|
||||||
|
root.push(nb);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return textResult(root);
|
||||||
|
}
|
||||||
|
|
||||||
// ═══ LABELS ═══
|
// ═══ LABELS ═══
|
||||||
case 'create_label': {
|
case 'create_label': {
|
||||||
const existing = await prisma.label.findFirst({
|
const existing = await prisma.label.findFirst({
|
||||||
|
|||||||
Reference in New Issue
Block a user