keepsake-mcp 1.14.1 → 1.15.0
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 +5 -4
- package/build/tools.js +30 -9
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -114,7 +114,7 @@ rather than in the chat, which disappears.
|
|
|
114
114
|
|--------|-----------|-------------|
|
|
115
115
|
| `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
116
|
|
|
117
|
-
## Available tools (
|
|
117
|
+
## Available tools (84)
|
|
118
118
|
|
|
119
119
|
### Contacts
|
|
120
120
|
| Tool | Description |
|
|
@@ -165,10 +165,11 @@ rather than in the chat, which disappears.
|
|
|
165
165
|
### QuickNotes
|
|
166
166
|
| Tool | Description |
|
|
167
167
|
|------|-------------|
|
|
168
|
-
| `list_notes` | List notes — filter by pinned/archived, day, company, page (`tag_id`) |
|
|
168
|
+
| `list_notes` | List notes — filter by pinned/archived, day, company, page (`tag_id`), publication-flow stage (`status`) |
|
|
169
|
+
| `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
170
|
| `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 |
|
|
171
|
+
| `create_note` | Create a note — supports `#tag#` and `[[tag]]` syntax; `status` puts it straight into the publication flow |
|
|
172
|
+
| `update_note` | Update note content, links, days, or its publication-flow stage (`status`) |
|
|
172
173
|
| `delete_note` | Soft-delete (or permanent) |
|
|
173
174
|
| `pin_note` | Pin as a post-it (short reference always at hand) |
|
|
174
175
|
| `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 }) => {
|
|
@@ -661,7 +682,7 @@ export function registerAllTools(server, fetchApi) {
|
|
|
661
682
|
// DAYS (Daily Summaries)
|
|
662
683
|
// ===========================================================================
|
|
663
684
|
server.registerTool("list_days", {
|
|
664
|
-
description: "List stored days with their intention or question of the day
|
|
685
|
+
description: "List stored days with their intention or question of the day (`note`; `intention` repeats it, null when empty). An intention is valid for its own day only — it does not carry over to the following days. Filter by date range.",
|
|
665
686
|
inputSchema: {
|
|
666
687
|
from: z.string().optional().describe("Start date (YYYY-MM-DD)"),
|
|
667
688
|
to: z.string().optional().describe("End date (YYYY-MM-DD)"),
|
|
@@ -673,7 +694,7 @@ export function registerAllTools(server, fetchApi) {
|
|
|
673
694
|
return toContent(await fetchApi(`/days${qs({ from, to, limit, offset })}`));
|
|
674
695
|
});
|
|
675
696
|
server.registerTool("get_day", {
|
|
676
|
-
description: "Get a specific day by date: its intention or question of the day AND the notes linked to that day (field `notes`, via link_note_date / `dates`).
|
|
697
|
+
description: "Get a specific day by date: its intention or question of the day AND the notes linked to that day (field `notes`, via link_note_date / `dates`). `note` is the intention written for that day (null or '' = none; `intention` repeats it). An intention is valid for its own day only — it does not carry over. Always 200 — `exists: false` means no row is stored for that date; `intention` and the linked notes are returned regardless.",
|
|
677
698
|
inputSchema: {
|
|
678
699
|
date: z.string().describe("Date (YYYY-MM-DD)"),
|
|
679
700
|
},
|
|
@@ -682,10 +703,10 @@ export function registerAllTools(server, fetchApi) {
|
|
|
682
703
|
return toContent(await fetchApi(`/days/${date}`));
|
|
683
704
|
});
|
|
684
705
|
server.registerTool("update_day", {
|
|
685
|
-
description: "Create or update a day's intention or question of the day (upsert on the date). The `note` field is the intention or question of the day — one short line at the top of the Today view (a mantra, an intention, a single priority, or a question to keep in mind). Not a journal: never write a summary of the day here.\n\nAn intention
|
|
706
|
+
description: "Create or update a day's intention or question of the day (upsert on the date). The `note` field is the intention or question of the day — one short line at the top of the Today view (a mantra, an intention, a single priority, or a question to keep in mind). Not a journal: never write a summary of the day here.\n\nAn intention is valid for its own day only: it does not carry over to the following days. The user types it by hand each morning, on purpose — do not set it for them unless they ask. `null` or an empty string removes it.\n\nNOT for attaching a note to a day: this field is a single line and you would overwrite the user's intention. To put a note on a day, use create_note with `dates` or link_note_date.",
|
|
686
707
|
inputSchema: {
|
|
687
708
|
date: z.string().describe("Date (YYYY-MM-DD)"),
|
|
688
|
-
note: z.string().nullable().describe("The intention or question of the day: one short line (mantra, intention, single priority, or a question to keep in mind). Not a journal summary. null
|
|
709
|
+
note: z.string().nullable().describe("The intention or question of the day: one short line (mantra, intention, single priority, or a question to keep in mind). Not a journal summary. null or empty string = no intention that day."),
|
|
689
710
|
},
|
|
690
711
|
annotations: { title: "Update day", destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
691
712
|
}, async (params) => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "keepsake-mcp",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "MCP server for Keepsake personal CRM
|
|
3
|
+
"version": "1.15.0",
|
|
4
|
+
"description": "MCP server for Keepsake personal CRM — connect your AI agent to your contacts, tasks, notes, and more",
|
|
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
|
}
|