keepsake-mcp 1.14.2 → 1.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -6
- package/build/tools.js +26 -5
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# keepsake-mcp
|
|
2
2
|
|
|
3
|
-
MCP server for [Keepsake](https://keepsake.place)
|
|
3
|
+
MCP server for [Keepsake](https://keepsake.place) (keepsake.place), the app where creators and solo entrepreneurs empty their heads: ideas, tasks and the people who matter, together and linked in one place, all the way to publishing.
|
|
4
4
|
|
|
5
5
|
Connect your AI assistant (Claude, Cursor, or any MCP-compatible client) to your Keepsake data: contacts, interactions, tasks, notes, daily intentions, companies, and tags.
|
|
6
6
|
|
|
7
7
|
## Why
|
|
8
8
|
|
|
9
|
-
Your AI assistant
|
|
9
|
+
Your AI assistant forgets between conversations; Keepsake remembers. Ask it to:
|
|
10
10
|
|
|
11
|
+
- "Capture this idea for my next article in #newsletter#"
|
|
11
12
|
- "Who did I last talk to at Acme Corp?"
|
|
12
13
|
- "Add a note that I ran into Sarah at the conference"
|
|
13
14
|
- "What tasks are overdue?"
|
|
@@ -114,7 +115,7 @@ rather than in the chat, which disappears.
|
|
|
114
115
|
|--------|-----------|-------------|
|
|
115
116
|
| `review_note` | `note_id` | Act as the editor of a note: read it, judge form and substance, leave anchored remarks in the margin, never rewrite the text |
|
|
116
117
|
|
|
117
|
-
## Available tools (
|
|
118
|
+
## Available tools (84)
|
|
118
119
|
|
|
119
120
|
### Contacts
|
|
120
121
|
| Tool | Description |
|
|
@@ -165,10 +166,11 @@ rather than in the chat, which disappears.
|
|
|
165
166
|
### QuickNotes
|
|
166
167
|
| Tool | Description |
|
|
167
168
|
|------|-------------|
|
|
168
|
-
| `list_notes` | List notes — filter by pinned/archived, day, company, page (`tag_id`) |
|
|
169
|
+
| `list_notes` | List notes — filter by pinned/archived, day, company, page (`tag_id`), publication-flow stage (`status`) |
|
|
170
|
+
| `list_note_statuses` | The stages of the user's publication flow (Idea → In progress → To review → Ready → Published by default — the user can rename, add or remove them) |
|
|
169
171
|
| `get_note` | Get one note by ID with its tags, contacts, tasks and linked notes |
|
|
170
|
-
| `create_note` | Create a note — supports `#tag#` and `[[tag]]` syntax |
|
|
171
|
-
| `update_note` | Update note content |
|
|
172
|
+
| `create_note` | Create a note — supports `#tag#` and `[[tag]]` syntax; `status` puts it straight into the publication flow |
|
|
173
|
+
| `update_note` | Update note content, links, days, or its publication-flow stage (`status`) |
|
|
172
174
|
| `delete_note` | Soft-delete (or permanent) |
|
|
173
175
|
| `pin_note` | Pin as a post-it (short reference always at hand) |
|
|
174
176
|
| `archive_note` | Archive a note |
|
package/build/tools.js
CHANGED
|
@@ -97,7 +97,8 @@ function withNoteHint(result) {
|
|
|
97
97
|
if (count > 0) {
|
|
98
98
|
hints.push(`This note carries ${count} marginalia — read them with list_note_comments before adding your own, so you do not repeat what is already said.`);
|
|
99
99
|
}
|
|
100
|
-
|
|
100
|
+
const status = note.status;
|
|
101
|
+
if (status && (status.category === "unstarted" || status.category === "started")) {
|
|
101
102
|
hints.push("The user is still working on this text: if they ask you to review it, put your remarks in the margin (create_note_comment, anchored with a verbatim quote) rather than in the chat, and do not rewrite the note itself.");
|
|
102
103
|
}
|
|
103
104
|
if (!hints.length)
|
|
@@ -546,8 +547,12 @@ export function registerAllTools(server, fetchApi) {
|
|
|
546
547
|
// QUICK NOTES
|
|
547
548
|
// ===========================================================================
|
|
548
549
|
server.registerTool("list_notes", {
|
|
549
|
-
description: "List notes. QuickNotes (inbox, not yet archived) and Notes (archived, permanent). Filter by pinned or archived status, by tag (page) with `tag_id`,
|
|
550
|
+
description: "List notes. QuickNotes (inbox, not yet archived) and Notes (archived, permanent). Filter by pinned or archived status, by tag (page) with `tag_id`, by the day(s) a note is linked to (`date`, or `date_from`/`date_to`) — e.g. \"what did I note for tomorrow?\" —, or by publication-flow stage with `status` — e.g. \"which notes are waiting for review?\". Each note carries `dates` (the days it is linked to) and `status` (its stage in the publication flow, or null).",
|
|
550
551
|
inputSchema: {
|
|
552
|
+
status: z
|
|
553
|
+
.string()
|
|
554
|
+
.optional()
|
|
555
|
+
.describe("Publication-flow stage: a stage name, key or id from list_note_statuses (e.g. \"review\"), \"publication\" (every note in the flow that is not published yet), or \"none\" (notes outside the flow)"),
|
|
551
556
|
pinned: z.boolean().optional().describe("Filter pinned notes only"),
|
|
552
557
|
archived: z.boolean().optional().describe("Filter by status: true = Notes (archived/permanent), false = QuickNotes (inbox)"),
|
|
553
558
|
date: z.string().optional().describe("Only notes linked to this day (YYYY-MM-DD)"),
|
|
@@ -559,8 +564,15 @@ export function registerAllTools(server, fetchApi) {
|
|
|
559
564
|
offset: z.number().int().nonnegative().optional().describe("Pagination offset"),
|
|
560
565
|
},
|
|
561
566
|
annotations: { title: "List notes", readOnlyHint: true, openWorldHint: false },
|
|
562
|
-
}, async ({ pinned, archived, date, date_from, date_to, company_id, tag_id, limit, offset }) => {
|
|
563
|
-
return toContent(await fetchApi(`/notes${qs({ pinned, archived, date, date_from, date_to, company_id, tag_id, limit, offset })}`));
|
|
567
|
+
}, async ({ status, pinned, archived, date, date_from, date_to, company_id, tag_id, limit, offset }) => {
|
|
568
|
+
return toContent(await fetchApi(`/notes${qs({ status, pinned, archived, date, date_from, date_to, company_id, tag_id, limit, offset })}`));
|
|
569
|
+
});
|
|
570
|
+
server.registerTool("list_note_statuses", {
|
|
571
|
+
description: "List the stages of the user's publication flow, in order (by default: Idea → In progress → To review → Ready → Published). A note enters this flow when the user may publish it — most notes never do and have no stage. Stages are the user's own: they can rename, recolor, add or remove them, so read this list before setting a stage with update_note or create_note. `category` is fixed: unstarted, started, or published (exactly one stage). Names are in the user's app language.",
|
|
572
|
+
inputSchema: {},
|
|
573
|
+
annotations: { title: "List note statuses", readOnlyHint: true, openWorldHint: false },
|
|
574
|
+
}, async () => {
|
|
575
|
+
return toContent(await fetchApi("/note-statuses"));
|
|
564
576
|
});
|
|
565
577
|
server.registerTool("get_note", {
|
|
566
578
|
description: "Get a single note by ID, including its tags, linked contacts, linked tasks, linked notes and the days it is linked to (`dates`). Use this when the user points you at one specific note (e.g. gives you its URL — the UUID is the last path segment) instead of listing everything.",
|
|
@@ -588,13 +600,17 @@ export function registerAllTools(server, fetchApi) {
|
|
|
588
600
|
.array(z.string())
|
|
589
601
|
.optional()
|
|
590
602
|
.describe("Days to link the note to (YYYY-MM-DD each). The note appears in each day's view."),
|
|
603
|
+
status: z
|
|
604
|
+
.string()
|
|
605
|
+
.optional()
|
|
606
|
+
.describe("Put the note straight into the publication flow at this stage (name, key or id from list_note_statuses). The note is then created as a permanent Note, not an Inbox QuickNote — like \"Keep to publish\" in the app. Only when the user says they may publish it."),
|
|
591
607
|
},
|
|
592
608
|
annotations: { title: "Create note", destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
593
609
|
}, async (params) => {
|
|
594
610
|
return toContent(await fetchApi("/notes", "POST", params));
|
|
595
611
|
});
|
|
596
612
|
server.registerTool("update_note", {
|
|
597
|
-
description: "Update an existing QuickNote or Note. `dates` REPLACES the full set of days the note is linked to — the way to move a note to another day (\"not done, push it to tomorrow\"). To add or remove a single day without touching the others, prefer link_note_date / unlink_note_date.",
|
|
613
|
+
description: "Update an existing QuickNote or Note. `dates` REPLACES the full set of days the note is linked to — the way to move a note to another day (\"not done, push it to tomorrow\"). To add or remove a single day without touching the others, prefer link_note_date / unlink_note_date. `status` moves the note along the publication flow (\"this one is ready\"); setting a stage on an Inbox QuickNote also keeps it as a Note.",
|
|
598
614
|
inputSchema: {
|
|
599
615
|
id: z.string().uuid().describe("Note UUID"),
|
|
600
616
|
content: z.string().optional().describe("Updated content"),
|
|
@@ -614,6 +630,11 @@ export function registerAllTools(server, fetchApi) {
|
|
|
614
630
|
.array(z.string())
|
|
615
631
|
.optional()
|
|
616
632
|
.describe("Replace the days the note is linked to (YYYY-MM-DD each). Empty array = unlink from every day."),
|
|
633
|
+
status: z
|
|
634
|
+
.string()
|
|
635
|
+
.nullable()
|
|
636
|
+
.optional()
|
|
637
|
+
.describe("Publication-flow stage: name, key or id from list_note_statuses. null takes the note out of the flow."),
|
|
617
638
|
},
|
|
618
639
|
annotations: { title: "Update note", destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
619
640
|
}, async ({ id, ...body }) => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "keepsake-mcp",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "MCP server for Keepsake
|
|
3
|
+
"version": "1.15.1",
|
|
4
|
+
"description": "MCP server for Keepsake (keepsake.place), the app where creators and solo entrepreneurs empty their heads — connect your AI to your notes, tasks and contacts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"keepsake-mcp": "build/index.js"
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
"license": "MIT",
|
|
32
32
|
"mcpName": "io.github.nicolascroce/keepsake",
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
35
|
-
"zod": "^
|
|
34
|
+
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
35
|
+
"zod": "^4.6.5"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^22.0.0",
|
|
39
|
-
"typescript": "^
|
|
39
|
+
"typescript": "^7.0.2"
|
|
40
40
|
}
|
|
41
41
|
}
|