conduyt-mcp 4.76.0 → 4.78.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 +14 -2
- package/dist/client.d.ts +2 -1
- package/dist/client.js +11 -3
- package/dist/index.js +2 -0
- package/dist/tools/chat.d.ts +6 -0
- package/dist/tools/chat.js +55 -0
- package/dist/tools/contacts.d.ts +2 -0
- package/dist/tools/contacts.js +26 -4
- package/dist/tools/notifications.js +1 -1
- package/package.json +1 -1
- package/scripts/coverage-manifest.json +5 -0
- package/scripts/coverage-matrix.json +33 -14
package/README.md
CHANGED
|
@@ -17,7 +17,9 @@ MCP server for [Conduyt CRM](https://conduyt.app) — expose your CRM as AI-acce
|
|
|
17
17
|
| `conduyt_list_contacts` | List and search contacts with filters |
|
|
18
18
|
| `conduyt_get_contact` | Get full contact details with timeline |
|
|
19
19
|
| `conduyt_create_contact` | Create a new contact |
|
|
20
|
-
| `conduyt_update_contact` | Update contact fields |
|
|
20
|
+
| `conduyt_update_contact` | Update contact fields; optional `expectedRevision` (0..2147483647) refuses a stale save with 409 `contact_changed` (code + current revision in the error) |
|
|
21
|
+
| `conduyt_add_contact_collaborator` | Add a team member as a collaborator (`POST /contacts/{id}/collaborators`); optional `expectedRevision`, 409 `contact_changed` |
|
|
22
|
+
| `conduyt_remove_contact_collaborator` | Remove a collaborator (`DELETE /contacts/{id}/collaborators`, body); optional `expectedRevision`, 409 `contact_changed`, 404 when not a collaborator |
|
|
21
23
|
| `conduyt_add_tags` | Add tags to a contact |
|
|
22
24
|
|
|
23
25
|
### Deals
|
|
@@ -195,9 +197,19 @@ The Mailchimp OAuth connect and callback are browser-only: connect Mailchimp in
|
|
|
195
197
|
### Notifications
|
|
196
198
|
| Tool | Description |
|
|
197
199
|
|------|-------------|
|
|
198
|
-
| `conduyt_list_notifications` | List notifications for current user |
|
|
200
|
+
| `conduyt_list_notifications` | List notifications for current user; `meta.latestByType` is `{type: {latest, count}}` over the last 24 h (newest createdAt + row count per type, read state ignored) next to `unreadCount` / `unreadByType` |
|
|
199
201
|
| `conduyt_create_notification` | Send in-app + push notification to a team member |
|
|
200
202
|
|
|
203
|
+
### Team Chat
|
|
204
|
+
| Tool | Description |
|
|
205
|
+
|------|-------------|
|
|
206
|
+
| `conduyt_list_chat_channels` | `GET /chat/channels`: your channels; also CREATES your memberships in the auto channels the first time |
|
|
207
|
+
| `conduyt_get_chat_unread` | `GET /chat/unread`: per channel `{ channelId, unread, soundUnread, newestSeq, latestSeq }`; may list an auto channel you have not opened yet (no membership row) |
|
|
208
|
+
| `conduyt_list_chat_messages` | List a channel's messages, newest first; every message carries `seq` (commit-ordered position in the channel); does not move the read marker; on a 403 "Not a member of this channel" it calls `GET /chat/channels` once and retries once |
|
|
209
|
+
| `conduyt_mark_chat_read` | Acknowledge read up to one message (`readUpTo` = message UUID; the marker only moves forward, an older or foreign message is a no-op; same one-retry membership bootstrap) |
|
|
210
|
+
|
|
211
|
+
The rest of team chat (sending, reactions, pins, presence, uploads) is not wrapped yet; `GET /chat/messages/{messageId}/location` is a UI deep-link helper and deliberately not wrapped.
|
|
212
|
+
|
|
201
213
|
### Custom Fields
|
|
202
214
|
| Tool | Description |
|
|
203
215
|
|------|-------------|
|
package/dist/client.d.ts
CHANGED
|
@@ -60,7 +60,8 @@ export declare class ConduytClient {
|
|
|
60
60
|
postText(path: string, body: unknown, opts?: RequestOptions): Promise<string>;
|
|
61
61
|
patch(path: string, body: unknown, opts?: RequestOptions): Promise<unknown>;
|
|
62
62
|
put(path: string, body: unknown, opts?: RequestOptions): Promise<unknown>;
|
|
63
|
-
|
|
63
|
+
/** `body` is for the few DELETE routes that take one (contact collaborators with an expectedRevision pin). */
|
|
64
|
+
del(path: string, opts?: RequestOptions, body?: unknown): Promise<unknown>;
|
|
64
65
|
}
|
|
65
66
|
export declare function formatResult(data: unknown): {
|
|
66
67
|
content: Array<{
|
package/dist/client.js
CHANGED
|
@@ -171,7 +171,14 @@ export class ConduytClient {
|
|
|
171
171
|
// lostReasonOptions) must reach the agent, or a "must be one of the
|
|
172
172
|
// configured options" error is unactionable. Bounded so a pathological
|
|
173
173
|
// body can't bloat the message.
|
|
174
|
-
|
|
174
|
+
// Scalars first (code, revision), nested objects last (a 409 contact_changed carries the whole current
|
|
175
|
+
// `contact`): the bound below may cut a large object, never the code or the current revision.
|
|
176
|
+
const isScalar = (v) => v === null || typeof v !== "object";
|
|
177
|
+
const entries = Object.entries(json ?? {}).filter(([k]) => k !== "error");
|
|
178
|
+
const details = Object.fromEntries([
|
|
179
|
+
...entries.filter(([, v]) => isScalar(v)),
|
|
180
|
+
...entries.filter(([, v]) => !isScalar(v)),
|
|
181
|
+
]);
|
|
175
182
|
if (Object.keys(details).length > 0) {
|
|
176
183
|
msg = `${msg} ${JSON.stringify(details).slice(0, 1000)}`;
|
|
177
184
|
}
|
|
@@ -293,8 +300,9 @@ export class ConduytClient {
|
|
|
293
300
|
put(path, body, opts) {
|
|
294
301
|
return this.request("PUT", path, body, opts);
|
|
295
302
|
}
|
|
296
|
-
|
|
297
|
-
|
|
303
|
+
/** `body` is for the few DELETE routes that take one (contact collaborators with an expectedRevision pin). */
|
|
304
|
+
del(path, opts, body) {
|
|
305
|
+
return this.request("DELETE", path, body, opts);
|
|
298
306
|
}
|
|
299
307
|
}
|
|
300
308
|
export function formatResult(data) {
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,7 @@ import { registerBoardSettingsTools } from "./tools/board-settings.js";
|
|
|
26
26
|
import { registerMailchimpTools } from "./tools/mailchimp.js";
|
|
27
27
|
import { registerStatusMetadataTools } from "./tools/status-metadata.js";
|
|
28
28
|
import { registerNotificationTools } from "./tools/notifications.js";
|
|
29
|
+
import { registerChatTools } from "./tools/chat.js";
|
|
29
30
|
import { registerCustomFieldTools } from "./tools/custom-fields.js";
|
|
30
31
|
import { registerSmartListTools } from "./tools/smart-lists.js";
|
|
31
32
|
import { registerUserTools } from "./tools/users.js";
|
|
@@ -102,6 +103,7 @@ registerBoardSettingsTools(server, client);
|
|
|
102
103
|
registerMailchimpTools(server, client);
|
|
103
104
|
registerStatusMetadataTools(server, client);
|
|
104
105
|
registerNotificationTools(server, client);
|
|
106
|
+
registerChatTools(server, client);
|
|
105
107
|
registerCustomFieldTools(server, client);
|
|
106
108
|
registerSmartListTools(server, client);
|
|
107
109
|
registerUserTools(server, client);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { ConduytClient } from "../client.js";
|
|
3
|
+
/** The messages route's membership refusal (403 "Not a member of this channel"); any other 403 (a permission) is NOT this. */
|
|
4
|
+
export declare function isMembershipRefusal(err: unknown): boolean;
|
|
5
|
+
/** Team chat read side (app 10-09, live sha 3869c79ba): the unread vector, messages carrying `seq`, and the read acknowledgement. */
|
|
6
|
+
export declare function registerChatTools(server: McpServer, client: ConduytClient): void;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ConduytApiError, formatResult } from "../client.js";
|
|
3
|
+
import { strictInput } from "./strict.js";
|
|
4
|
+
/** The messages route's membership refusal (403 "Not a member of this channel"); any other 403 (a permission) is NOT this. */
|
|
5
|
+
export function isMembershipRefusal(err) {
|
|
6
|
+
return err instanceof Error && /\b403\b/.test(err.message) && /Not a member of this channel/.test(err.message)
|
|
7
|
+
&& (!(err instanceof ConduytApiError) || err.status === 403);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* GET /chat/unread lists the AUTO channels even when the caller has no membership row yet, while the messages route answers
|
|
11
|
+
* 403 without one; GET /chat/channels is what lazily creates it. So on that one refusal: call GET /chat/channels ONCE, retry
|
|
12
|
+
* the read ONCE. Bounded: a second refusal, or a 403 for any other reason, reaches the caller untouched.
|
|
13
|
+
*/
|
|
14
|
+
async function withMembershipBootstrap(client, read) {
|
|
15
|
+
try {
|
|
16
|
+
return await read();
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
if (!isMembershipRefusal(err))
|
|
20
|
+
throw err;
|
|
21
|
+
await client.get("/api/v1/chat/channels");
|
|
22
|
+
return read();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Team chat read side (app 10-09, live sha 3869c79ba): the unread vector, messages carrying `seq`, and the read acknowledgement. */
|
|
26
|
+
export function registerChatTools(server, client) {
|
|
27
|
+
strictInput(server.tool("conduyt_list_chat_channels", "List the team chat channels the API key's user belongs to (GET /api/v1/chat/channels): id, name, kind, members, last message, counts. SIDE EFFECT: it also creates the caller's memberships in the account's AUTO channels (needs-a-hand, new-leads, wins) the first time, so a user who never opened Team Chat can read them. conduyt_list_chat_messages and conduyt_mark_chat_read already do this once for you when a channel listed by conduyt_get_chat_unread answers 403 'Not a member of this channel'; call this tool directly to discover channel ids.", {}, async () => {
|
|
28
|
+
const result = await client.get("/api/v1/chat/channels");
|
|
29
|
+
return formatResult(result);
|
|
30
|
+
}));
|
|
31
|
+
strictInput(server.tool("conduyt_get_chat_unread", "Team chat unread vector for the API key's user (GET /api/v1/chat/unread): one row per channel the user is a member of, { channelId, unread, soundUnread, newestSeq, latestSeq }. unread is the badge count (auto channels are 0 by design); soundUnread counts unread messages in every channel, auto ones included; newestSeq is the seq of the channel's newest message not written by the user (null = none, panic cards left out); latestSeq is the same including panic cards. Read-only: no writes, never creates a membership, so a row can be an AUTO channel the caller has not opened yet (no membership row: reading it first answers 403, which conduyt_list_chat_messages / conduyt_mark_chat_read recover from by calling conduyt_list_chat_channels once and retrying once). Use it to decide which channel to read next, then conduyt_list_chat_messages.", {}, async () => {
|
|
32
|
+
const result = await client.get("/api/v1/chat/unread");
|
|
33
|
+
return formatResult(result);
|
|
34
|
+
}));
|
|
35
|
+
strictInput(server.tool("conduyt_list_chat_messages", "List messages in a team chat channel (GET /api/v1/chat/channels/{id}/messages), newest first, paged by cursor. The caller must be a channel member (else 403). Every message carries seq, its commit-ordered position inside the channel (a total order: the list and the cursor follow it). Read-only: this tool does not move the read marker. On a 403 'Not a member of this channel' it calls GET /chat/channels once (which creates the caller's auto-channel memberships) and retries ONCE; a second refusal, or a 403 for any other reason, is returned as is; acknowledge with conduyt_mark_chat_read.", {
|
|
36
|
+
channelId: z.string().uuid().describe("Channel UUID"),
|
|
37
|
+
limit: z.number().int().min(1).max(100).optional().describe("Messages per page (1-100, default 50)"),
|
|
38
|
+
cursor: z.string().uuid().optional().describe("nextCursor from the previous page (a message UUID)"),
|
|
39
|
+
}, async ({ channelId, limit, cursor }) => {
|
|
40
|
+
const params = new URLSearchParams();
|
|
41
|
+
if (limit !== undefined)
|
|
42
|
+
params.set("limit", String(limit));
|
|
43
|
+
if (cursor !== undefined)
|
|
44
|
+
params.set("cursor", cursor);
|
|
45
|
+
const result = await withMembershipBootstrap(client, () => client.get(`/api/v1/chat/channels/${channelId}/messages?${params}`));
|
|
46
|
+
return formatResult(result);
|
|
47
|
+
}));
|
|
48
|
+
strictInput(server.tool("conduyt_mark_chat_read", "Acknowledge team chat as read up to one message (GET /api/v1/chat/channels/{id}/messages?readUpTo=<messageId>&limit=1; the API takes the acknowledgement as a query on the message list). readUpTo is the UUID of a message you were served (its seq becomes your read marker); take it from conduyt_list_chat_messages. The marker only moves FORWARD, never to 'now': an older (stale) message is a no-op, and a message that is not in this channel moves nothing (the call still succeeds, so re-check conduyt_get_chat_unread). A malformed id is a 400. On a 403 'Not a member of this channel' it calls GET /chat/channels once (creating the caller's auto-channel memberships) and retries ONCE; a second refusal, or a 403 for any other reason, is returned as is. The response is the newest message of the channel (limit 1), not a confirmation of the move.", {
|
|
49
|
+
channelId: z.string().uuid().describe("Channel UUID"),
|
|
50
|
+
readUpTo: z.string().uuid().describe("UUID of the newest message you have seen in this channel"),
|
|
51
|
+
}, async ({ channelId, readUpTo }) => {
|
|
52
|
+
const result = await withMembershipBootstrap(client, () => client.get(`/api/v1/chat/channels/${channelId}/messages?limit=1&readUpTo=${readUpTo}`));
|
|
53
|
+
return formatResult(result);
|
|
54
|
+
}));
|
|
55
|
+
}
|
package/dist/tools/contacts.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { ConduytClient } from "../client.js";
|
|
3
|
+
/** The contact save revision pin (catalog bounds: integer 0..2147483647). */
|
|
4
|
+
export declare const CONTACT_REVISION_MAX = 2147483647;
|
|
3
5
|
export declare function registerContactTools(server: McpServer, client: ConduytClient): void;
|
package/dist/tools/contacts.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { formatResult } from "../client.js";
|
|
3
|
+
import { strictInput } from "./strict.js";
|
|
4
|
+
/** The contact save revision pin (catalog bounds: integer 0..2147483647). */
|
|
5
|
+
export const CONTACT_REVISION_MAX = 2147483647;
|
|
6
|
+
const CONTACT_CHANGED = "A stale pin is refused with 409 and code contact_changed (nothing written): the error text carries `code`, the contact's CURRENT `revision` and the current `contact`; reload, decide, and call again with that revision (never retry the same pin). A malformed expectedRevision is a 400; only an omitted field means unpinned.";
|
|
7
|
+
const expectedRevision = () => z.number().int().min(0).max(CONTACT_REVISION_MAX).optional().describe("Optimistic-concurrency pin: the contact `revision` as you last read it (conduyt_get_contact / conduyt_list_contacts / a previous change). Omit for last-write-wins per field.");
|
|
3
8
|
/**
|
|
4
9
|
* Accept BOTH the canonical `customFields` and the REST-style `custom_fields`
|
|
5
10
|
* spelling and return the canonical value to send to the API. The MCP SDK
|
|
@@ -16,7 +21,7 @@ function resolveCustomFields(camel, snake) {
|
|
|
16
21
|
return camel !== undefined ? camel : snake;
|
|
17
22
|
}
|
|
18
23
|
export function registerContactTools(server, client) {
|
|
19
|
-
server.tool("conduyt_list_contacts", "List and search contacts with filtering. Returns paginated results with name, email, phone, tags, source, and assignee.", {
|
|
24
|
+
server.tool("conduyt_list_contacts", "List and search contacts with filtering. Returns paginated results with name, email, phone, tags, source, and assignee. Each row carries `revision` (integer save revision; pass it back as expectedRevision on conduyt_update_contact or the collaborator tools).", {
|
|
20
25
|
search: z.string().optional().describe("Search by name, email, or phone number"),
|
|
21
26
|
tag: z.string().optional().describe("Filter by tag name"),
|
|
22
27
|
source: z.string().optional().describe("Filter by source (e.g. 'signup', 'import', 'manual')"),
|
|
@@ -46,7 +51,7 @@ export function registerContactTools(server, client) {
|
|
|
46
51
|
const result = await client.get(`/api/v1/contacts?${params}`);
|
|
47
52
|
return formatResult(result);
|
|
48
53
|
});
|
|
49
|
-
server.tool("conduyt_get_contact", "Get a single contact with full details — profile, tags, custom fields, timeline, deals, and activity history.", {
|
|
54
|
+
server.tool("conduyt_get_contact", "Get a single contact with full details — profile, tags, custom fields, timeline, deals, and activity history. Includes `revision` (integer save revision): pass it as expectedRevision on conduyt_update_contact or the collaborator tools so a newer change is never overwritten.", {
|
|
50
55
|
id: z.string().describe("Contact UUID"),
|
|
51
56
|
}, async ({ id }) => {
|
|
52
57
|
const result = await client.get(`/api/v1/contacts/${id}`);
|
|
@@ -70,7 +75,7 @@ export function registerContactTools(server, client) {
|
|
|
70
75
|
const result = await client.post("/api/v1/contacts", body);
|
|
71
76
|
return formatResult(result);
|
|
72
77
|
});
|
|
73
|
-
server.tool("conduyt_update_contact", "Update an existing contact's fields — name, email, phone, company, custom fields. customFields merge into existing values (omit a key to leave it unchanged).", {
|
|
78
|
+
strictInput(server.tool("conduyt_update_contact", "Update an existing contact's fields — name, email, phone, company, custom fields. customFields merge into existing values (omit a key to leave it unchanged). Pass expectedRevision (the contact's `revision` as you read it) to refuse the save when the contact changed since. " + CONTACT_CHANGED, {
|
|
74
79
|
id: z.string().describe("Contact UUID"),
|
|
75
80
|
email: z.string().optional().describe("Updated email"),
|
|
76
81
|
firstName: z.string().optional().describe("Updated first name"),
|
|
@@ -82,12 +87,29 @@ export function registerContactTools(server, client) {
|
|
|
82
87
|
masterStatus: z.string().trim().min(1).max(40).optional().describe("Lifecycle status of the LEAD (master record), distinct from any deal's status: 'open' (default), 'won', 'lost', 'abandoned', 'disqualified', or an account-defined custom status. A terminal status (won/lost/abandoned/disqualified) SILENCES all outbound automations for this contact — drips, sequences, and scheduled sends stop. Use conduyt_get_lifecycle_settings to see the account's configured statuses."),
|
|
83
88
|
customFields: z.record(z.any()).optional().describe("Structured custom-field values to merge, keyed by field key. Omit a key to leave it unchanged."),
|
|
84
89
|
custom_fields: z.record(z.any()).optional().describe("Alias for customFields (snake_case). Provide only one of customFields / custom_fields."),
|
|
90
|
+
expectedRevision: expectedRevision(),
|
|
85
91
|
}, async ({ id, customFields, custom_fields, ...updates }) => {
|
|
86
92
|
const cf = resolveCustomFields(customFields, custom_fields);
|
|
87
93
|
const body = cf !== undefined ? { ...updates, customFields: cf } : updates;
|
|
88
94
|
const result = await client.patch(`/api/v1/contacts/${id}`, body);
|
|
89
95
|
return formatResult(result);
|
|
90
|
-
});
|
|
96
|
+
}));
|
|
97
|
+
strictInput(server.tool("conduyt_add_contact_collaborator", "Add a team member as a collaborator on a contact (POST /api/v1/contacts/{id}/collaborators; needs edit authority over the contact). Answers { added, revision } where revision is the contact's NEW revision. Pass expectedRevision to refuse the change when the contact changed since you read it. " + CONTACT_CHANGED + " 409 without a code: the user is already a collaborator; 404: no such contact for you or no edit authority; 413: body over 16 KB.", {
|
|
98
|
+
contactId: z.string().uuid().describe("Contact UUID"),
|
|
99
|
+
userId: z.string().min(1).describe("The team member to add"),
|
|
100
|
+
expectedRevision: expectedRevision(),
|
|
101
|
+
}, async ({ contactId, userId, expectedRevision }) => {
|
|
102
|
+
const result = await client.post(`/api/v1/contacts/${encodeURIComponent(contactId)}/collaborators`, { userId, expectedRevision });
|
|
103
|
+
return formatResult(result);
|
|
104
|
+
}));
|
|
105
|
+
strictInput(server.tool("conduyt_remove_contact_collaborator", "Remove a collaborator from a contact (DELETE /api/v1/contacts/{id}/collaborators with a body; needs edit authority). Answers { removed, revision } where revision is the contact's NEW revision. Pass expectedRevision to refuse the change when the contact changed since you read it. " + CONTACT_CHANGED + " 404 also when the user is not a collaborator of the contact; 409 without a code: the user is still the assignee of a deal or hosts an appointment on this contact; 413: body over 16 KB.", {
|
|
106
|
+
contactId: z.string().uuid().describe("Contact UUID"),
|
|
107
|
+
userId: z.string().min(1).describe("The team member to remove"),
|
|
108
|
+
expectedRevision: expectedRevision(),
|
|
109
|
+
}, async ({ contactId, userId, expectedRevision }) => {
|
|
110
|
+
const result = await client.del(`/api/v1/contacts/${encodeURIComponent(contactId)}/collaborators`, undefined, { userId, expectedRevision });
|
|
111
|
+
return formatResult(result);
|
|
112
|
+
}));
|
|
91
113
|
server.tool("conduyt_delete_contact", "Delete a contact by UUID (soft-delete — the contact is removed from lists/searches but the row is retained for audit, and its deals are unaffected). Use this to clean up test or duplicate contacts. There is no undelete endpoint, so treat it as irreversible via the API.", {
|
|
92
114
|
// .uuid() so a malformed id with slashes can't form a different DELETE
|
|
93
115
|
// subroute (e.g. /contacts/:id/tags/:tagId); encode the segment too.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { formatResult } from "../client.js";
|
|
3
3
|
export function registerNotificationTools(server, client) {
|
|
4
|
-
server.tool("conduyt_list_notifications", "List notifications for the current user (determined by API key's associated user).", {
|
|
4
|
+
server.tool("conduyt_list_notifications", "List notifications for the current user (determined by API key's associated user). The response meta carries page, per_page, total, unreadCount, unreadByType ({type: count}) and latestByType ({type: {latest, count}}: per type over the last 24 hours, the newest createdAt as an ISO string and the row count, read state ignored; a poller sees a type advance when latest moves or count rises).", {
|
|
5
5
|
page: z.number().optional().describe("Page number (default 1)"),
|
|
6
6
|
per_page: z.number().optional().describe("Results per page"),
|
|
7
7
|
}, async ({ page, per_page }) => {
|
package/package.json
CHANGED
|
@@ -115,6 +115,11 @@
|
|
|
115
115
|
"status": "planned",
|
|
116
116
|
"reason": "file listing/metadata is agent-useful; upload stays browser-side"
|
|
117
117
|
},
|
|
118
|
+
{
|
|
119
|
+
"match": "GET /api/v1/chat/messages/",
|
|
120
|
+
"status": "excluded",
|
|
121
|
+
"reason": "UI-only deep-link helper (resolves a message to its channel/position for the app's router); not an agent surface"
|
|
122
|
+
},
|
|
118
123
|
{
|
|
119
124
|
"match": "/api/v1/chat",
|
|
120
125
|
"status": "planned",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-10-
|
|
3
|
-
"toolCount":
|
|
4
|
-
"totalEndpoints":
|
|
2
|
+
"generatedAt": "2026-10-09T04:00:29.593Z",
|
|
3
|
+
"toolCount": 334,
|
|
4
|
+
"totalEndpoints": 1066,
|
|
5
5
|
"counts": {
|
|
6
|
-
"covered":
|
|
7
|
-
"excluded":
|
|
8
|
-
"planned":
|
|
6
|
+
"covered": 299,
|
|
7
|
+
"excluded": 319,
|
|
8
|
+
"planned": 448,
|
|
9
9
|
"uncategorized": 0
|
|
10
10
|
},
|
|
11
11
|
"matrix": {
|
|
@@ -1235,8 +1235,10 @@
|
|
|
1235
1235
|
"reason": "team chat read/post for agents"
|
|
1236
1236
|
},
|
|
1237
1237
|
"GET /api/v1/chat/channels": {
|
|
1238
|
-
"status": "
|
|
1239
|
-
"
|
|
1238
|
+
"status": "covered",
|
|
1239
|
+
"tools": [
|
|
1240
|
+
"conduyt_list_chat_channels"
|
|
1241
|
+
]
|
|
1240
1242
|
},
|
|
1241
1243
|
"POST /api/v1/chat/channels": {
|
|
1242
1244
|
"status": "planned",
|
|
@@ -1251,8 +1253,11 @@
|
|
|
1251
1253
|
"reason": "team chat read/post for agents"
|
|
1252
1254
|
},
|
|
1253
1255
|
"GET /api/v1/chat/channels/:id/messages": {
|
|
1254
|
-
"status": "
|
|
1255
|
-
"
|
|
1256
|
+
"status": "covered",
|
|
1257
|
+
"tools": [
|
|
1258
|
+
"conduyt_list_chat_messages",
|
|
1259
|
+
"conduyt_mark_chat_read"
|
|
1260
|
+
]
|
|
1256
1261
|
},
|
|
1257
1262
|
"POST /api/v1/chat/channels/:id/messages": {
|
|
1258
1263
|
"status": "planned",
|
|
@@ -1318,6 +1323,10 @@
|
|
|
1318
1323
|
"status": "planned",
|
|
1319
1324
|
"reason": "team chat read/post for agents"
|
|
1320
1325
|
},
|
|
1326
|
+
"GET /api/v1/chat/messages/:messageId/location": {
|
|
1327
|
+
"status": "excluded",
|
|
1328
|
+
"reason": "UI-only deep-link helper (resolves a message to its channel/position for the app's router); not an agent surface"
|
|
1329
|
+
},
|
|
1321
1330
|
"GET /api/v1/chat/presence": {
|
|
1322
1331
|
"status": "planned",
|
|
1323
1332
|
"reason": "team chat read/post for agents"
|
|
@@ -1326,6 +1335,12 @@
|
|
|
1326
1335
|
"status": "planned",
|
|
1327
1336
|
"reason": "team chat read/post for agents"
|
|
1328
1337
|
},
|
|
1338
|
+
"GET /api/v1/chat/unread": {
|
|
1339
|
+
"status": "covered",
|
|
1340
|
+
"tools": [
|
|
1341
|
+
"conduyt_get_chat_unread"
|
|
1342
|
+
]
|
|
1343
|
+
},
|
|
1329
1344
|
"GET /api/v1/companies": {
|
|
1330
1345
|
"status": "covered",
|
|
1331
1346
|
"tools": [
|
|
@@ -1450,12 +1465,16 @@
|
|
|
1450
1465
|
"reason": "contact subroutes beyond the core CRUD/search tools"
|
|
1451
1466
|
},
|
|
1452
1467
|
"POST /api/v1/contacts/:id/collaborators": {
|
|
1453
|
-
"status": "
|
|
1454
|
-
"
|
|
1468
|
+
"status": "covered",
|
|
1469
|
+
"tools": [
|
|
1470
|
+
"conduyt_add_contact_collaborator"
|
|
1471
|
+
]
|
|
1455
1472
|
},
|
|
1456
1473
|
"DELETE /api/v1/contacts/:id/collaborators": {
|
|
1457
|
-
"status": "
|
|
1458
|
-
"
|
|
1474
|
+
"status": "covered",
|
|
1475
|
+
"tools": [
|
|
1476
|
+
"conduyt_remove_contact_collaborator"
|
|
1477
|
+
]
|
|
1459
1478
|
},
|
|
1460
1479
|
"GET /api/v1/contacts/:id/dnc": {
|
|
1461
1480
|
"status": "planned",
|