keepsake-mcp 1.10.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 +12 -5
- package/build/tools.js +147 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -114,17 +114,17 @@ 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 (83)
|
|
118
118
|
|
|
119
119
|
### Contacts
|
|
120
120
|
| Tool | Description |
|
|
121
121
|
|------|-------------|
|
|
122
|
-
| `list_contacts` | List
|
|
122
|
+
| `list_contacts` | List contacts with pagination, sorting, field selection and filters (linked company, has_company, updated_since) |
|
|
123
123
|
| `get_contact` | Get a contact with recent interactions, tags, and stats |
|
|
124
|
-
| `create_contact` | Create a new contact |
|
|
124
|
+
| `create_contact` | Create a new contact (`company` links it to a company record, created if missing) |
|
|
125
125
|
| `update_contact` | Update contact fields |
|
|
126
126
|
| `delete_contact` | Permanently delete a contact |
|
|
127
|
-
| `search_contacts` | Accent-insensitive search by name, email, company |
|
|
127
|
+
| `search_contacts` | Accent-insensitive search by name, email, notes, phone and linked company names |
|
|
128
128
|
| `get_contact_timeline` | Unified chronological feed of all items for a contact |
|
|
129
129
|
|
|
130
130
|
### Companies
|
|
@@ -136,6 +136,9 @@ rather than in the chat, which disappears.
|
|
|
136
136
|
| `update_company` | Update company fields |
|
|
137
137
|
| `delete_company` | Soft-delete (or permanent delete) a company |
|
|
138
138
|
| `search_companies` | Accent-insensitive company search |
|
|
139
|
+
| `link_contact_company` | Link a contact to a company (optional role) |
|
|
140
|
+
| `unlink_contact_company` | Remove a contact–company link |
|
|
141
|
+
| `merge_companies` | Merge a duplicate company into another (contacts, entries, tags, details, notes) |
|
|
139
142
|
|
|
140
143
|
### Entries (Interactions)
|
|
141
144
|
| Tool | Description |
|
|
@@ -149,6 +152,7 @@ rather than in the chat, which disappears.
|
|
|
149
152
|
| Tool | Description |
|
|
150
153
|
|------|-------------|
|
|
151
154
|
| `list_tasks` | List tasks with status/date filters |
|
|
155
|
+
| `get_task` | Get a task with its tags, contacts, companies and linked notes |
|
|
152
156
|
| `create_task` | Create a task — supports `#tag#` and `[[tag]]` syntax |
|
|
153
157
|
| `update_task` | Update task fields |
|
|
154
158
|
| `delete_task` | Delete a task |
|
|
@@ -217,13 +221,16 @@ Material kept *alongside* a note without entering its text — an idea, a refere
|
|
|
217
221
|
| `update_task_header` | Update a task header (name, description, collapsed) |
|
|
218
222
|
| `delete_task_header` | Permanently delete a task header |
|
|
219
223
|
|
|
220
|
-
### Contact
|
|
224
|
+
### Contact & company links
|
|
221
225
|
| Tool | Description |
|
|
222
226
|
|------|-------------|
|
|
227
|
+
| `link_note_company` / `unlink_note_company` | Link / unlink a company to a note |
|
|
223
228
|
| `link_note_contact` | Link a contact to a note |
|
|
224
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 |
|
|
225
231
|
| `link_entry_contact` | Link a contact to an entry |
|
|
226
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 |
|
|
227
234
|
| `link_task_contact` | Link a contact to a task |
|
|
228
235
|
| `unlink_task_contact` | Remove a contact link from a task |
|
|
229
236
|
| `link_task_note` | Link a note to a task (non-destructive, the note survives) |
|
package/build/tools.js
CHANGED
|
@@ -109,17 +109,24 @@ export function registerAllTools(server, fetchApi) {
|
|
|
109
109
|
// CONTACTS
|
|
110
110
|
// ===========================================================================
|
|
111
111
|
server.registerTool("list_contacts", {
|
|
112
|
-
description: "List
|
|
112
|
+
description: "List contacts in the user's Keepsake CRM. Supports pagination, sorting, field selection (use `fields` to skip long notes when scanning many contacts), filters on linked companies and on last update, and optional last_interaction_date enrichment. Each contact's companies are company records, returned in `companies`.",
|
|
113
113
|
inputSchema: {
|
|
114
|
-
limit: z.number().int().positive().optional().describe("Max results (default 20)"),
|
|
114
|
+
limit: z.number().int().positive().optional().describe("Max results (default 20, max 100)"),
|
|
115
115
|
offset: z.number().int().nonnegative().optional().describe("Pagination offset"),
|
|
116
|
-
sort: z.string().optional().describe("Sort field: last_name, first_name, created_at"),
|
|
116
|
+
sort: z.string().optional().describe("Sort field: last_name, first_name, created_at, updated_at"),
|
|
117
117
|
order: z.enum(["asc", "desc"]).optional().describe("Sort order"),
|
|
118
118
|
include_last_interaction: z.boolean().optional().describe("Include last_interaction_date for each contact (default: false)"),
|
|
119
|
+
fields: z
|
|
120
|
+
.array(z.string())
|
|
121
|
+
.optional()
|
|
122
|
+
.describe("Only return these fields (id is always included). Columns: first_name, last_name, email, phone, job_title, address, birth_day, birth_month, birth_year, notes, created_at, updated_at. Computed: companies, last_interaction_date. Omit for everything."),
|
|
123
|
+
company: z.string().optional().describe("Only contacts linked to this company: a company UUID, or part of its name (case and accents ignored)"),
|
|
124
|
+
has_company: z.boolean().optional().describe("true: only contacts linked to at least one company; false: only contacts without any company"),
|
|
125
|
+
updated_since: z.string().optional().describe("Only contacts updated after this ISO date or timestamp"),
|
|
119
126
|
},
|
|
120
127
|
annotations: { title: "List contacts", readOnlyHint: true, openWorldHint: false },
|
|
121
|
-
}, async ({ limit, offset, sort, order, include_last_interaction }) => {
|
|
122
|
-
return toContent(await fetchApi(`/contacts${qs({ limit, offset, sort, order, include_last_interaction })}`));
|
|
128
|
+
}, async ({ limit, offset, sort, order, include_last_interaction, fields, company, has_company, updated_since }) => {
|
|
129
|
+
return toContent(await fetchApi(`/contacts${qs({ limit, offset, sort, order, include_last_interaction, fields: fields?.join(","), company, has_company, updated_since })}`));
|
|
123
130
|
});
|
|
124
131
|
server.registerTool("get_contact", {
|
|
125
132
|
description: "Get a single contact by ID, including recent entries (interactions), tags, last_interaction_date, and total_entries count.",
|
|
@@ -138,7 +145,7 @@ export function registerAllTools(server, fetchApi) {
|
|
|
138
145
|
last_name: z.string().optional().describe("Last name (optional)"),
|
|
139
146
|
email: z.string().optional().describe("Email address"),
|
|
140
147
|
phone: z.string().optional().describe("Phone number"),
|
|
141
|
-
company: z.string().optional().describe("Company name"),
|
|
148
|
+
company: z.string().optional().describe("Company name. Links the contact to that company record (matched by name ignoring case and accents, created if missing). Adds a link, never removes one. The contact's companies are returned in `companies`."),
|
|
142
149
|
birthday: z.string().optional().describe("Birthday as ISO date string (YYYY-MM-DD), e.g. '1980-02-14'"),
|
|
143
150
|
notes: z.string().optional().describe("Notes about the contact"),
|
|
144
151
|
},
|
|
@@ -154,7 +161,7 @@ export function registerAllTools(server, fetchApi) {
|
|
|
154
161
|
last_name: z.string().optional().describe("Last name"),
|
|
155
162
|
email: z.string().optional().describe("Email address"),
|
|
156
163
|
phone: z.string().optional().describe("Phone number"),
|
|
157
|
-
company: z.string().optional().describe("Company name"),
|
|
164
|
+
company: z.string().optional().describe("Company name. Links the contact to that company record (matched by name ignoring case and accents, created if missing). Adds a link, never removes one. The contact's companies are returned in `companies`."),
|
|
158
165
|
birthday: z.string().nullable().optional().describe("Birthday as ISO date string (YYYY-MM-DD), e.g. '1980-02-14'. Set to null to clear."),
|
|
159
166
|
notes: z.string().optional().describe("Notes about the contact"),
|
|
160
167
|
},
|
|
@@ -244,6 +251,37 @@ export function registerAllTools(server, fetchApi) {
|
|
|
244
251
|
const query = permanent ? "?permanent=true" : "";
|
|
245
252
|
return toContent(await fetchApi(`/companies/${id}${query}`, "DELETE"));
|
|
246
253
|
});
|
|
254
|
+
server.registerTool("link_contact_company", {
|
|
255
|
+
description: "Link a contact to a company record (idempotent: linking twice keeps one link). A contact can belong to several companies. Optionally set the contact's role there. Shortcut when you only know the company's name: create_contact / update_contact with `company`.",
|
|
256
|
+
inputSchema: {
|
|
257
|
+
company_id: z.string().uuid().describe("Company UUID"),
|
|
258
|
+
contact_id: z.string().uuid().describe("Contact UUID"),
|
|
259
|
+
role: z.string().optional().describe("Role or job at this company (e.g. 'Training advisor')"),
|
|
260
|
+
},
|
|
261
|
+
annotations: { title: "Link contact to company", destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
262
|
+
}, async ({ company_id, contact_id, role }) => {
|
|
263
|
+
return toContent(await fetchApi(`/companies/${company_id}/contacts`, "POST", { contact_id, ...(role !== undefined && { role }) }));
|
|
264
|
+
});
|
|
265
|
+
server.registerTool("unlink_contact_company", {
|
|
266
|
+
description: "Remove the link between a contact and a company. Neither the contact nor the company is deleted.",
|
|
267
|
+
inputSchema: {
|
|
268
|
+
company_id: z.string().uuid().describe("Company UUID"),
|
|
269
|
+
contact_id: z.string().uuid().describe("Contact UUID"),
|
|
270
|
+
},
|
|
271
|
+
annotations: { title: "Unlink contact from company", destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
272
|
+
}, async ({ company_id, contact_id }) => {
|
|
273
|
+
return toContent(await fetchApi(`/companies/${company_id}/contacts${qs({ contact_id })}`, "DELETE"));
|
|
274
|
+
});
|
|
275
|
+
server.registerTool("merge_companies", {
|
|
276
|
+
description: "Merge a duplicate company INTO another one: its contacts, entries and tags move to the target (no duplicates), the target's empty details (website, email, phone, address) are filled from it, its notes are appended to the target's, then it is deleted. Use it when two records describe the same organization (e.g. 'CNCP' and 'Cncp'). Confirm with your user which record to keep.",
|
|
277
|
+
inputSchema: {
|
|
278
|
+
source_id: z.string().uuid().describe("UUID of the company to merge away (it will be deleted)"),
|
|
279
|
+
target_id: z.string().uuid().describe("UUID of the company to keep"),
|
|
280
|
+
},
|
|
281
|
+
annotations: { title: "Merge companies", destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
282
|
+
}, async ({ source_id, target_id }) => {
|
|
283
|
+
return toContent(await fetchApi(`/companies/${source_id}/merge`, "POST", { target_id }));
|
|
284
|
+
});
|
|
247
285
|
server.registerTool("search_companies", {
|
|
248
286
|
description: "Search companies by name, email, website, or address. Search is accent-insensitive.",
|
|
249
287
|
inputSchema: {
|
|
@@ -264,14 +302,15 @@ export function registerAllTools(server, fetchApi) {
|
|
|
264
302
|
.optional()
|
|
265
303
|
.describe("Filter by entry type"),
|
|
266
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"),
|
|
267
306
|
from: z.string().optional().describe("Start date (YYYY-MM-DD)"),
|
|
268
307
|
to: z.string().optional().describe("End date (YYYY-MM-DD)"),
|
|
269
308
|
limit: z.number().int().positive().optional().describe("Max results (default 20)"),
|
|
270
309
|
offset: z.number().int().nonnegative().optional().describe("Pagination offset"),
|
|
271
310
|
},
|
|
272
311
|
annotations: { title: "List entries", readOnlyHint: true, openWorldHint: false },
|
|
273
|
-
}, async ({ type, contact_id, from, to, limit, offset }) => {
|
|
274
|
-
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 })}`));
|
|
275
314
|
});
|
|
276
315
|
server.registerTool("create_entry", {
|
|
277
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.",
|
|
@@ -285,6 +324,10 @@ export function registerAllTools(server, fetchApi) {
|
|
|
285
324
|
.array(z.string().uuid())
|
|
286
325
|
.optional()
|
|
287
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)"),
|
|
288
331
|
tag_ids: z
|
|
289
332
|
.array(z.string().uuid())
|
|
290
333
|
.optional()
|
|
@@ -308,6 +351,10 @@ export function registerAllTools(server, fetchApi) {
|
|
|
308
351
|
.array(z.string().uuid())
|
|
309
352
|
.optional()
|
|
310
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."),
|
|
311
358
|
tag_ids: z
|
|
312
359
|
.array(z.string().uuid())
|
|
313
360
|
.optional()
|
|
@@ -338,12 +385,22 @@ export function registerAllTools(server, fetchApi) {
|
|
|
338
385
|
.optional()
|
|
339
386
|
.describe("Filter by date type: specific (has a due date), asap (do as soon as possible), one_day (someday/no rush)"),
|
|
340
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"),
|
|
341
389
|
limit: z.number().int().positive().optional().describe("Max results (default 20)"),
|
|
342
390
|
offset: z.number().int().nonnegative().optional().describe("Pagination offset"),
|
|
343
391
|
},
|
|
344
392
|
annotations: { title: "List tasks", readOnlyHint: true, openWorldHint: false },
|
|
345
|
-
}, async ({ status, date_type, date, limit, offset }) => {
|
|
346
|
-
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}`));
|
|
347
404
|
});
|
|
348
405
|
server.registerTool("create_task", {
|
|
349
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.",
|
|
@@ -382,6 +439,10 @@ export function registerAllTools(server, fetchApi) {
|
|
|
382
439
|
.array(z.string().uuid())
|
|
383
440
|
.optional()
|
|
384
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)"),
|
|
385
446
|
tag_ids: z
|
|
386
447
|
.array(z.string().uuid())
|
|
387
448
|
.optional()
|
|
@@ -421,6 +482,10 @@ export function registerAllTools(server, fetchApi) {
|
|
|
421
482
|
.array(z.string().uuid())
|
|
422
483
|
.optional()
|
|
423
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."),
|
|
424
489
|
tag_ids: z
|
|
425
490
|
.array(z.string().uuid())
|
|
426
491
|
.optional()
|
|
@@ -482,12 +547,13 @@ export function registerAllTools(server, fetchApi) {
|
|
|
482
547
|
date: z.string().optional().describe("Only notes linked to this day (YYYY-MM-DD)"),
|
|
483
548
|
date_from: z.string().optional().describe("Only notes linked to a day on or after this date (YYYY-MM-DD)"),
|
|
484
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"),
|
|
485
551
|
limit: z.number().int().positive().optional().describe("Max results (default 20)"),
|
|
486
552
|
offset: z.number().int().nonnegative().optional().describe("Pagination offset"),
|
|
487
553
|
},
|
|
488
554
|
annotations: { title: "List notes", readOnlyHint: true, openWorldHint: false },
|
|
489
|
-
}, async ({ pinned, archived, date, date_from, date_to, limit, offset }) => {
|
|
490
|
-
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 })}`));
|
|
491
557
|
});
|
|
492
558
|
server.registerTool("get_note", {
|
|
493
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.",
|
|
@@ -507,6 +573,10 @@ export function registerAllTools(server, fetchApi) {
|
|
|
507
573
|
.array(z.string().uuid())
|
|
508
574
|
.optional()
|
|
509
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)"),
|
|
510
580
|
dates: z
|
|
511
581
|
.array(z.string())
|
|
512
582
|
.optional()
|
|
@@ -525,6 +595,10 @@ export function registerAllTools(server, fetchApi) {
|
|
|
525
595
|
.array(z.string().uuid())
|
|
526
596
|
.optional()
|
|
527
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."),
|
|
528
602
|
tag_ids: z
|
|
529
603
|
.array(z.string().uuid())
|
|
530
604
|
.optional()
|
|
@@ -901,6 +975,66 @@ export function registerAllTools(server, fetchApi) {
|
|
|
901
975
|
}, async ({ task_id, contact_id }) => {
|
|
902
976
|
return toContent(await fetchApi(`/tasks/${task_id}/contacts/${contact_id}`, "DELETE"));
|
|
903
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
|
+
});
|
|
904
1038
|
server.registerTool("link_task_note", {
|
|
905
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.",
|
|
906
1040
|
inputSchema: {
|