keepsake-mcp 1.11.0 → 1.12.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 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 (76)
117
+ ## Available tools (83)
118
118
 
119
119
  ### Contacts
120
120
  | Tool | Description |
@@ -152,6 +152,7 @@ rather than in the chat, which disappears.
152
152
  | Tool | Description |
153
153
  |------|-------------|
154
154
  | `list_tasks` | List tasks with status/date filters |
155
+ | `get_task` | Get a task with its tags, contacts, companies and linked notes |
155
156
  | `create_task` | Create a task — supports `#tag#` and `[[tag]]` syntax |
156
157
  | `update_task` | Update task fields |
157
158
  | `delete_task` | Delete a task |
@@ -220,13 +221,16 @@ Material kept *alongside* a note without entering its text — an idea, a refere
220
221
  | `update_task_header` | Update a task header (name, description, collapsed) |
221
222
  | `delete_task_header` | Permanently delete a task header |
222
223
 
223
- ### Contact Links
224
+ ### Contact & company links
224
225
  | Tool | Description |
225
226
  |------|-------------|
227
+ | `link_note_company` / `unlink_note_company` | Link / unlink a company to a note |
226
228
  | `link_note_contact` | Link a contact to a note |
227
229
  | `unlink_note_contact` | Remove a contact link from a note |
230
+ | `link_entry_company` / `unlink_entry_company` | Add / remove a company as participant of an entry |
228
231
  | `link_entry_contact` | Link a contact to an entry |
229
232
  | `unlink_entry_contact` | Remove a contact link from an entry |
233
+ | `link_task_company` / `unlink_task_company` | Link / unlink a company to a task |
230
234
  | `link_task_contact` | Link a contact to a task |
231
235
  | `unlink_task_contact` | Remove a contact link from a task |
232
236
  | `link_task_note` | Link a note to a task (non-destructive, the note survives) |
package/build/tools.js CHANGED
@@ -302,14 +302,15 @@ export function registerAllTools(server, fetchApi) {
302
302
  .optional()
303
303
  .describe("Filter by entry type"),
304
304
  contact_id: z.string().uuid().optional().describe("Filter by associated contact ID"),
305
+ company_id: z.string().uuid().optional().describe("Only entries where this company is a participant"),
305
306
  from: z.string().optional().describe("Start date (YYYY-MM-DD)"),
306
307
  to: z.string().optional().describe("End date (YYYY-MM-DD)"),
307
308
  limit: z.number().int().positive().optional().describe("Max results (default 20)"),
308
309
  offset: z.number().int().nonnegative().optional().describe("Pagination offset"),
309
310
  },
310
311
  annotations: { title: "List entries", readOnlyHint: true, openWorldHint: false },
311
- }, async ({ type, contact_id, from, to, limit, offset }) => {
312
- return toContent(await fetchApi(`/entries${qs({ type, contact_id, from, to, limit, offset })}`));
312
+ }, async ({ type, contact_id, company_id, from, to, limit, offset }) => {
313
+ return toContent(await fetchApi(`/entries${qs({ type, contact_id, company_id, from, to, limit, offset })}`));
313
314
  });
314
315
  server.registerTool("create_entry", {
315
316
  description: "An ENTRY is a dated interaction log tied to contacts. Records something that happened (call, meeting, email…) on a specific date.\n\nCreate a new interaction entry. Content supports #tag# and [[tag]] syntax for automatic tag linking.",
@@ -323,6 +324,10 @@ export function registerAllTools(server, fetchApi) {
323
324
  .array(z.string().uuid())
324
325
  .optional()
325
326
  .describe("Array of contact UUIDs to associate"),
327
+ company_ids: z
328
+ .array(z.string().uuid())
329
+ .optional()
330
+ .describe("Array of company UUIDs to associate (companies are separate from contacts)"),
326
331
  tag_ids: z
327
332
  .array(z.string().uuid())
328
333
  .optional()
@@ -346,6 +351,10 @@ export function registerAllTools(server, fetchApi) {
346
351
  .array(z.string().uuid())
347
352
  .optional()
348
353
  .describe("Replace associated contacts"),
354
+ company_ids: z
355
+ .array(z.string().uuid())
356
+ .optional()
357
+ .describe("Replace associated companies (send [] to unlink all). Contacts are unaffected."),
349
358
  tag_ids: z
350
359
  .array(z.string().uuid())
351
360
  .optional()
@@ -376,12 +385,22 @@ export function registerAllTools(server, fetchApi) {
376
385
  .optional()
377
386
  .describe("Filter by date type: specific (has a due date), asap (do as soon as possible), one_day (someday/no rush)"),
378
387
  date: z.string().optional().describe("Filter by specific date (YYYY-MM-DD)"),
388
+ company_id: z.string().uuid().optional().describe("Only tasks linked directly to this company"),
379
389
  limit: z.number().int().positive().optional().describe("Max results (default 20)"),
380
390
  offset: z.number().int().nonnegative().optional().describe("Pagination offset"),
381
391
  },
382
392
  annotations: { title: "List tasks", readOnlyHint: true, openWorldHint: false },
383
- }, async ({ status, date_type, date, limit, offset }) => {
384
- return toContent(await fetchApi(`/tasks${qs({ status, date_type, date, limit, offset })}`));
393
+ }, async ({ status, date_type, date, company_id, limit, offset }) => {
394
+ return toContent(await fetchApi(`/tasks${qs({ status, date_type, date, company_id, limit, offset })}`));
395
+ });
396
+ server.registerTool("get_task", {
397
+ description: "Get a single task by ID, with its tags, linked contacts (contact_ids), linked companies (company_ids) and linked notes (note_ids). Use it when you already know which task you need instead of listing everything.",
398
+ inputSchema: {
399
+ id: z.string().uuid().describe("Task UUID"),
400
+ },
401
+ annotations: { title: "Get task", readOnlyHint: true, openWorldHint: false },
402
+ }, async ({ id }) => {
403
+ return toContent(await fetchApi(`/tasks/${id}`));
385
404
  });
386
405
  server.registerTool("create_task", {
387
406
  description: "A TASK is an action item to accomplish. Can have due date, recurrence, priority, linked to contacts and tags.\n\nCreate a new task. Title supports #tag# and [[tag]] for automatic tag linking.",
@@ -420,6 +439,10 @@ export function registerAllTools(server, fetchApi) {
420
439
  .array(z.string().uuid())
421
440
  .optional()
422
441
  .describe("Array of contact UUIDs to associate"),
442
+ company_ids: z
443
+ .array(z.string().uuid())
444
+ .optional()
445
+ .describe("Array of company UUIDs to associate (companies are separate from contacts)"),
423
446
  tag_ids: z
424
447
  .array(z.string().uuid())
425
448
  .optional()
@@ -459,6 +482,10 @@ export function registerAllTools(server, fetchApi) {
459
482
  .array(z.string().uuid())
460
483
  .optional()
461
484
  .describe("Replace associated contacts"),
485
+ company_ids: z
486
+ .array(z.string().uuid())
487
+ .optional()
488
+ .describe("Replace associated companies (send [] to unlink all). Contacts are unaffected."),
462
489
  tag_ids: z
463
490
  .array(z.string().uuid())
464
491
  .optional()
@@ -520,12 +547,13 @@ export function registerAllTools(server, fetchApi) {
520
547
  date: z.string().optional().describe("Only notes linked to this day (YYYY-MM-DD)"),
521
548
  date_from: z.string().optional().describe("Only notes linked to a day on or after this date (YYYY-MM-DD)"),
522
549
  date_to: z.string().optional().describe("Only notes linked to a day on or before this date (YYYY-MM-DD)"),
550
+ company_id: z.string().uuid().optional().describe("Only notes linked directly to this company"),
523
551
  limit: z.number().int().positive().optional().describe("Max results (default 20)"),
524
552
  offset: z.number().int().nonnegative().optional().describe("Pagination offset"),
525
553
  },
526
554
  annotations: { title: "List notes", readOnlyHint: true, openWorldHint: false },
527
- }, async ({ pinned, archived, date, date_from, date_to, limit, offset }) => {
528
- return toContent(await fetchApi(`/notes${qs({ pinned, archived, date, date_from, date_to, limit, offset })}`));
555
+ }, async ({ pinned, archived, date, date_from, date_to, company_id, limit, offset }) => {
556
+ return toContent(await fetchApi(`/notes${qs({ pinned, archived, date, date_from, date_to, company_id, limit, offset })}`));
529
557
  });
530
558
  server.registerTool("get_note", {
531
559
  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.",
@@ -545,6 +573,10 @@ export function registerAllTools(server, fetchApi) {
545
573
  .array(z.string().uuid())
546
574
  .optional()
547
575
  .describe("Array of contact UUIDs to associate"),
576
+ company_ids: z
577
+ .array(z.string().uuid())
578
+ .optional()
579
+ .describe("Array of company UUIDs to associate (companies are separate from contacts)"),
548
580
  dates: z
549
581
  .array(z.string())
550
582
  .optional()
@@ -563,6 +595,10 @@ export function registerAllTools(server, fetchApi) {
563
595
  .array(z.string().uuid())
564
596
  .optional()
565
597
  .describe("Replace associated contacts"),
598
+ company_ids: z
599
+ .array(z.string().uuid())
600
+ .optional()
601
+ .describe("Replace associated companies (send [] to unlink all). Contacts are unaffected."),
566
602
  tag_ids: z
567
603
  .array(z.string().uuid())
568
604
  .optional()
@@ -939,6 +975,66 @@ export function registerAllTools(server, fetchApi) {
939
975
  }, async ({ task_id, contact_id }) => {
940
976
  return toContent(await fetchApi(`/tasks/${task_id}/contacts/${contact_id}`, "DELETE"));
941
977
  });
978
+ server.registerTool("link_entry_company", {
979
+ description: "Link a company to an entry (the company becomes a participant of that interaction). Idempotent. Companies are separate records from contacts: use this (or company_ids) for an organization, link_entry_contact for a person.",
980
+ inputSchema: {
981
+ entry_id: z.string().uuid().describe("Entry UUID"),
982
+ company_id: z.string().uuid().describe("Company UUID"),
983
+ },
984
+ annotations: { title: "Link entry company", destructiveHint: false, idempotentHint: true, openWorldHint: false },
985
+ }, async ({ entry_id, company_id }) => {
986
+ return toContent(await fetchApi(`/entries/${entry_id}/companies/${company_id}`, "POST"));
987
+ });
988
+ server.registerTool("unlink_entry_company", {
989
+ description: "Remove the link between a company and an entry.",
990
+ inputSchema: {
991
+ entry_id: z.string().uuid().describe("Entry UUID"),
992
+ company_id: z.string().uuid().describe("Company UUID"),
993
+ },
994
+ annotations: { title: "Unlink entry company", destructiveHint: true, idempotentHint: true, openWorldHint: false },
995
+ }, async ({ entry_id, company_id }) => {
996
+ return toContent(await fetchApi(`/entries/${entry_id}/companies/${company_id}`, "DELETE"));
997
+ });
998
+ server.registerTool("link_task_company", {
999
+ description: "Link a company to a task. Idempotent. Companies are separate records from contacts: use this (or company_ids) for an organization, link_task_contact for a person.",
1000
+ inputSchema: {
1001
+ task_id: z.string().uuid().describe("Task UUID"),
1002
+ company_id: z.string().uuid().describe("Company UUID"),
1003
+ },
1004
+ annotations: { title: "Link task company", destructiveHint: false, idempotentHint: true, openWorldHint: false },
1005
+ }, async ({ task_id, company_id }) => {
1006
+ return toContent(await fetchApi(`/tasks/${task_id}/companies/${company_id}`, "POST"));
1007
+ });
1008
+ server.registerTool("unlink_task_company", {
1009
+ description: "Remove the link between a company and a task.",
1010
+ inputSchema: {
1011
+ task_id: z.string().uuid().describe("Task UUID"),
1012
+ company_id: z.string().uuid().describe("Company UUID"),
1013
+ },
1014
+ annotations: { title: "Unlink task company", destructiveHint: true, idempotentHint: true, openWorldHint: false },
1015
+ }, async ({ task_id, company_id }) => {
1016
+ return toContent(await fetchApi(`/tasks/${task_id}/companies/${company_id}`, "DELETE"));
1017
+ });
1018
+ server.registerTool("link_note_company", {
1019
+ description: "Link a company to a note. Idempotent. Companies are separate records from contacts: use this (or company_ids) for an organization, link_note_contact for a person.",
1020
+ inputSchema: {
1021
+ note_id: z.string().uuid().describe("Note UUID"),
1022
+ company_id: z.string().uuid().describe("Company UUID"),
1023
+ },
1024
+ annotations: { title: "Link note company", destructiveHint: false, idempotentHint: true, openWorldHint: false },
1025
+ }, async ({ note_id, company_id }) => {
1026
+ return toContent(await fetchApi(`/notes/${note_id}/companies/${company_id}`, "POST"));
1027
+ });
1028
+ server.registerTool("unlink_note_company", {
1029
+ description: "Remove the link between a company and a note.",
1030
+ inputSchema: {
1031
+ note_id: z.string().uuid().describe("Note UUID"),
1032
+ company_id: z.string().uuid().describe("Company UUID"),
1033
+ },
1034
+ annotations: { title: "Unlink note company", destructiveHint: true, idempotentHint: true, openWorldHint: false },
1035
+ }, async ({ note_id, company_id }) => {
1036
+ return toContent(await fetchApi(`/notes/${note_id}/companies/${company_id}`, "DELETE"));
1037
+ });
942
1038
  server.registerTool("link_task_note", {
943
1039
  description: "Link an existing note to a task (non-destructive, N-N). The note stays in the inbox/notes list and the task keeps a live link back to it — use this instead of deleting a note after creating a task from it. The note shows its linked tasks; the task shows the source note.",
944
1040
  inputSchema: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keepsake-mcp",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "description": "MCP server for Keepsake personal CRM \u2014 connect your AI agent to your contacts, tasks, notes, and more",
5
5
  "type": "module",
6
6
  "bin": {