@todoist/comms-mcp 0.0.0-stage → 11.0.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/LICENSE +21 -0
- package/README.md +208 -2
- package/dist/comms-tool.d.ts +14 -0
- package/dist/comms-tool.js +1 -0
- package/dist/index.d.ts +1324 -0
- package/dist/index.js +55 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +24 -0
- package/dist/mcp-helpers.d.ts +17 -0
- package/dist/mcp-helpers.js +40 -0
- package/dist/mcp-server.d.ts +10 -0
- package/dist/mcp-server.js +78 -0
- package/dist/tool-registry.d.ts +3 -0
- package/dist/tool-registry.js +49 -0
- package/dist/tools/build-link.d.ts +63 -0
- package/dist/tools/build-link.js +93 -0
- package/dist/tools/channel-output.d.ts +19 -0
- package/dist/tools/channel-output.js +47 -0
- package/dist/tools/create-channel.d.ts +55 -0
- package/dist/tools/create-channel.js +44 -0
- package/dist/tools/create-conversation.d.ts +49 -0
- package/dist/tools/create-conversation.js +75 -0
- package/dist/tools/create-thread.d.ts +67 -0
- package/dist/tools/create-thread.js +126 -0
- package/dist/tools/delete-object.d.ts +44 -0
- package/dist/tools/delete-object.js +84 -0
- package/dist/tools/fetch-inbox.d.ts +91 -0
- package/dist/tools/fetch-inbox.js +227 -0
- package/dist/tools/get-groups.d.ts +62 -0
- package/dist/tools/get-groups.js +116 -0
- package/dist/tools/get-mentions.d.ts +97 -0
- package/dist/tools/get-mentions.js +126 -0
- package/dist/tools/get-users.d.ts +58 -0
- package/dist/tools/get-users.js +107 -0
- package/dist/tools/get-workspaces.d.ts +61 -0
- package/dist/tools/get-workspaces.js +120 -0
- package/dist/tools/list-channels.d.ts +47 -0
- package/dist/tools/list-channels.js +89 -0
- package/dist/tools/list-conversations.d.ts +66 -0
- package/dist/tools/list-conversations.js +212 -0
- package/dist/tools/load-conversation.d.ts +92 -0
- package/dist/tools/load-conversation.js +128 -0
- package/dist/tools/load-thread.d.ts +127 -0
- package/dist/tools/load-thread.js +175 -0
- package/dist/tools/mark-done.d.ts +111 -0
- package/dist/tools/mark-done.js +275 -0
- package/dist/tools/mark-read.d.ts +73 -0
- package/dist/tools/mark-read.js +214 -0
- package/dist/tools/react.d.ts +59 -0
- package/dist/tools/react.js +97 -0
- package/dist/tools/reply.d.ts +69 -0
- package/dist/tools/reply.js +122 -0
- package/dist/tools/search-content.d.ts +103 -0
- package/dist/tools/search-content.js +138 -0
- package/dist/tools/update-channel.d.ts +53 -0
- package/dist/tools/update-channel.js +45 -0
- package/dist/tools/update-object.d.ts +52 -0
- package/dist/tools/update-object.js +161 -0
- package/dist/tools/update-user-info.d.ts +55 -0
- package/dist/tools/update-user-info.js +270 -0
- package/dist/tools/user-info.d.ts +32 -0
- package/dist/tools/user-info.js +16 -0
- package/dist/tools/user-output.d.ts +9 -0
- package/dist/tools/user-output.js +28 -0
- package/dist/usage-tracking.d.ts +23 -0
- package/dist/usage-tracking.js +41 -0
- package/dist/utils/attachments.d.ts +24 -0
- package/dist/utils/attachments.js +57 -0
- package/dist/utils/concurrency.d.ts +20 -0
- package/dist/utils/concurrency.js +24 -0
- package/dist/utils/constants.d.ts +5 -0
- package/dist/utils/constants.js +5 -0
- package/dist/utils/degrade.d.ts +81 -0
- package/dist/utils/degrade.js +149 -0
- package/dist/utils/output-schemas.d.ts +1283 -0
- package/dist/utils/output-schemas.js +657 -0
- package/dist/utils/read-position.d.ts +21 -0
- package/dist/utils/read-position.js +37 -0
- package/dist/utils/search-results.d.ts +36 -0
- package/dist/utils/search-results.js +82 -0
- package/dist/utils/target-types.d.ts +114 -0
- package/dist/utils/target-types.js +58 -0
- package/dist/utils/test-helpers.d.ts +95 -0
- package/dist/utils/test-helpers.js +231 -0
- package/dist/utils/tool-names.d.ts +25 -0
- package/dist/utils/tool-names.js +25 -0
- package/dist/utils/url-helpers.d.ts +3 -0
- package/dist/utils/url-helpers.js +10 -0
- package/dist/utils/user-status.d.ts +8 -0
- package/dist/utils/user-status.js +33 -0
- package/dist/utils/validate-comms-token.d.ts +13 -0
- package/dist/utils/validate-comms-token.js +22 -0
- package/package.json +86 -5
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the objIndex of the last comment in a thread, which is the read
|
|
3
|
+
* position that clears its unread marker.
|
|
4
|
+
*
|
|
5
|
+
* Comment objIndexes start at 0 for the thread post, so the last one is
|
|
6
|
+
* `commentCount - 1`. `lastObjIndex` carries that value directly; the
|
|
7
|
+
* count-based fallback covers records where the API omits it.
|
|
8
|
+
*/
|
|
9
|
+
function getThreadLastObjIndex(thread) {
|
|
10
|
+
return thread.lastObjIndex ?? Math.max(thread.commentCount - 1, 0);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Marks a thread as read through its latest comment.
|
|
14
|
+
*
|
|
15
|
+
* `threads.markRead` takes the objIndex the caller has read up to; the unread
|
|
16
|
+
* marker moves to the comment after it. Passing 0 only covers the thread post,
|
|
17
|
+
* so any thread with comments stays unread and keeps its badge. The read
|
|
18
|
+
* position is resolved from the thread record rather than guessed: overshooting
|
|
19
|
+
* (e.g. `commentCount`) also clears the badge, but pushes the marker past the
|
|
20
|
+
* next comment so it would arrive already read.
|
|
21
|
+
*/
|
|
22
|
+
export async function markThreadFullyRead(client, id) {
|
|
23
|
+
const thread = await client.threads.getThread(id);
|
|
24
|
+
await client.threads.markRead({ id, objIndex: getThreadLastObjIndex(thread) });
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Marks a conversation as read through its latest message.
|
|
28
|
+
*
|
|
29
|
+
* The API rejects `conversations.markRead` without a position (`Either
|
|
30
|
+
* obj_index or message_id should be provided`), and `objIndex: 0` only covers
|
|
31
|
+
* the first message, so the conversation record is fetched for its
|
|
32
|
+
* `lastObjIndex`.
|
|
33
|
+
*/
|
|
34
|
+
export async function markConversationFullyRead(client, id) {
|
|
35
|
+
const conversation = await client.conversations.getConversation(id);
|
|
36
|
+
await client.conversations.markRead({ id, objIndex: conversation.lastObjIndex });
|
|
37
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type SearchResult } from '@doist/comms-sdk';
|
|
2
|
+
import type { SearchResultItem } from './output-schemas.js';
|
|
3
|
+
export type RawSearchResult = {
|
|
4
|
+
id: string;
|
|
5
|
+
content: string;
|
|
6
|
+
creatorId: number;
|
|
7
|
+
created: string;
|
|
8
|
+
} & ({
|
|
9
|
+
type: 'thread';
|
|
10
|
+
threadId: string;
|
|
11
|
+
commentId?: string;
|
|
12
|
+
channelId?: string;
|
|
13
|
+
} | {
|
|
14
|
+
type: 'conversation';
|
|
15
|
+
conversationId: string;
|
|
16
|
+
messageId: string;
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* Normalizes SDK search results into the discriminated shape shared by
|
|
20
|
+
* search-content and get-mentions. The search API only emits 'thread' and
|
|
21
|
+
* 'conversation' results, each with its container id set; a comment match is a
|
|
22
|
+
* 'thread' result with commentId set, a message match a 'conversation' result
|
|
23
|
+
* with messageId set. Results missing a required id are dropped with a logged
|
|
24
|
+
* warning.
|
|
25
|
+
*/
|
|
26
|
+
export declare function toRawSearchResults(items: SearchResult[], toolName: string): RawSearchResult[];
|
|
27
|
+
/**
|
|
28
|
+
* Enriches raw search results with creator/channel names and permalinks.
|
|
29
|
+
* Comment deep-links require a channel (see build-link); without one the URL
|
|
30
|
+
* falls back to the plain thread link.
|
|
31
|
+
*/
|
|
32
|
+
export declare function toSearchResultItems(results: RawSearchResult[], { workspaceId, userLookup, channelLookup, }: {
|
|
33
|
+
workspaceId: number;
|
|
34
|
+
userLookup: Record<number, string>;
|
|
35
|
+
channelLookup: Record<string, string>;
|
|
36
|
+
}): SearchResultItem[];
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { getFullCommsURL } from '@doist/comms-sdk';
|
|
2
|
+
/**
|
|
3
|
+
* Normalizes SDK search results into the discriminated shape shared by
|
|
4
|
+
* search-content and get-mentions. The search API only emits 'thread' and
|
|
5
|
+
* 'conversation' results, each with its container id set; a comment match is a
|
|
6
|
+
* 'thread' result with commentId set, a message match a 'conversation' result
|
|
7
|
+
* with messageId set. Results missing a required id are dropped with a logged
|
|
8
|
+
* warning.
|
|
9
|
+
*/
|
|
10
|
+
export function toRawSearchResults(items, toolName) {
|
|
11
|
+
const results = items.flatMap((r) => {
|
|
12
|
+
const common = {
|
|
13
|
+
id: r.id,
|
|
14
|
+
content: r.snippet,
|
|
15
|
+
creatorId: r.snippetCreatorId,
|
|
16
|
+
created: r.snippetLastUpdated.toISOString(),
|
|
17
|
+
};
|
|
18
|
+
if (r.type === 'thread' && r.threadId != null) {
|
|
19
|
+
return [
|
|
20
|
+
{
|
|
21
|
+
...common,
|
|
22
|
+
type: 'thread',
|
|
23
|
+
threadId: r.threadId,
|
|
24
|
+
commentId: r.commentId ?? undefined,
|
|
25
|
+
channelId: r.channelId ?? undefined,
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
if (r.type === 'conversation' && r.conversationId != null && r.messageId != null) {
|
|
30
|
+
return [
|
|
31
|
+
{
|
|
32
|
+
...common,
|
|
33
|
+
type: 'conversation',
|
|
34
|
+
conversationId: r.conversationId,
|
|
35
|
+
messageId: r.messageId,
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
return [];
|
|
40
|
+
});
|
|
41
|
+
if (results.length < items.length) {
|
|
42
|
+
console.error(`${toolName}: dropped search result(s) missing a required id`, {
|
|
43
|
+
dropped: items.length - results.length,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return results;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Enriches raw search results with creator/channel names and permalinks.
|
|
50
|
+
* Comment deep-links require a channel (see build-link); without one the URL
|
|
51
|
+
* falls back to the plain thread link.
|
|
52
|
+
*/
|
|
53
|
+
export function toSearchResultItems(results, { workspaceId, userLookup, channelLookup, }) {
|
|
54
|
+
return results.map((r) => {
|
|
55
|
+
const common = {
|
|
56
|
+
creatorName: userLookup[r.creatorId],
|
|
57
|
+
workspaceId,
|
|
58
|
+
};
|
|
59
|
+
if (r.type === 'thread') {
|
|
60
|
+
return {
|
|
61
|
+
...r,
|
|
62
|
+
...common,
|
|
63
|
+
channelName: r.channelId ? channelLookup[r.channelId] : undefined,
|
|
64
|
+
url: getFullCommsURL({
|
|
65
|
+
workspaceId,
|
|
66
|
+
threadId: r.threadId,
|
|
67
|
+
channelId: r.channelId,
|
|
68
|
+
commentId: r.channelId ? r.commentId : undefined,
|
|
69
|
+
}),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
...r,
|
|
74
|
+
...common,
|
|
75
|
+
url: getFullCommsURL({
|
|
76
|
+
workspaceId,
|
|
77
|
+
conversationId: r.conversationId,
|
|
78
|
+
messageId: r.messageId,
|
|
79
|
+
}),
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Target types for reactions
|
|
4
|
+
*/
|
|
5
|
+
export declare const ReactionTargetType: {
|
|
6
|
+
readonly values: readonly ["thread", "comment", "message"];
|
|
7
|
+
readonly schema: z.ZodEnum<{
|
|
8
|
+
thread: "thread";
|
|
9
|
+
comment: "comment";
|
|
10
|
+
message: "message";
|
|
11
|
+
}>;
|
|
12
|
+
};
|
|
13
|
+
export declare const ReactionTargetTypeSchema: z.ZodEnum<{
|
|
14
|
+
thread: "thread";
|
|
15
|
+
comment: "comment";
|
|
16
|
+
message: "message";
|
|
17
|
+
}>;
|
|
18
|
+
export type ReactionTargetType = z.infer<typeof ReactionTargetTypeSchema>;
|
|
19
|
+
/**
|
|
20
|
+
* Target types for update-object
|
|
21
|
+
*/
|
|
22
|
+
export declare const UpdateTargetType: {
|
|
23
|
+
readonly values: readonly ["thread", "comment", "message"];
|
|
24
|
+
readonly schema: z.ZodEnum<{
|
|
25
|
+
thread: "thread";
|
|
26
|
+
comment: "comment";
|
|
27
|
+
message: "message";
|
|
28
|
+
}>;
|
|
29
|
+
};
|
|
30
|
+
export declare const UpdateTargetTypeSchema: z.ZodEnum<{
|
|
31
|
+
thread: "thread";
|
|
32
|
+
comment: "comment";
|
|
33
|
+
message: "message";
|
|
34
|
+
}>;
|
|
35
|
+
export type UpdateTargetType = z.infer<typeof UpdateTargetTypeSchema>;
|
|
36
|
+
/**
|
|
37
|
+
* Target types for delete-object
|
|
38
|
+
*/
|
|
39
|
+
export declare const DeleteTargetType: {
|
|
40
|
+
readonly values: readonly ["thread", "comment", "message"];
|
|
41
|
+
readonly schema: z.ZodEnum<{
|
|
42
|
+
thread: "thread";
|
|
43
|
+
comment: "comment";
|
|
44
|
+
message: "message";
|
|
45
|
+
}>;
|
|
46
|
+
};
|
|
47
|
+
export declare const DeleteTargetTypeSchema: z.ZodEnum<{
|
|
48
|
+
thread: "thread";
|
|
49
|
+
comment: "comment";
|
|
50
|
+
message: "message";
|
|
51
|
+
}>;
|
|
52
|
+
export type DeleteTargetType = z.infer<typeof DeleteTargetTypeSchema>;
|
|
53
|
+
/**
|
|
54
|
+
* Target types for replies
|
|
55
|
+
*/
|
|
56
|
+
export declare const ReplyTargetType: {
|
|
57
|
+
readonly values: readonly ["thread", "conversation"];
|
|
58
|
+
readonly schema: z.ZodEnum<{
|
|
59
|
+
thread: "thread";
|
|
60
|
+
conversation: "conversation";
|
|
61
|
+
}>;
|
|
62
|
+
};
|
|
63
|
+
export declare const ReplyTargetTypeSchema: z.ZodEnum<{
|
|
64
|
+
thread: "thread";
|
|
65
|
+
conversation: "conversation";
|
|
66
|
+
}>;
|
|
67
|
+
export type ReplyTargetType = z.infer<typeof ReplyTargetTypeSchema>;
|
|
68
|
+
/**
|
|
69
|
+
* Search scope types
|
|
70
|
+
*/
|
|
71
|
+
export declare const SearchScope: {
|
|
72
|
+
readonly values: readonly ["workspace", "thread", "conversation"];
|
|
73
|
+
readonly schema: z.ZodEnum<{
|
|
74
|
+
thread: "thread";
|
|
75
|
+
conversation: "conversation";
|
|
76
|
+
workspace: "workspace";
|
|
77
|
+
}>;
|
|
78
|
+
};
|
|
79
|
+
export declare const SearchScopeSchema: z.ZodEnum<{
|
|
80
|
+
thread: "thread";
|
|
81
|
+
conversation: "conversation";
|
|
82
|
+
workspace: "workspace";
|
|
83
|
+
}>;
|
|
84
|
+
export type SearchScope = z.infer<typeof SearchScopeSchema>;
|
|
85
|
+
/**
|
|
86
|
+
* Mark done types
|
|
87
|
+
*/
|
|
88
|
+
export declare const MarkDoneType: {
|
|
89
|
+
readonly values: readonly ["thread", "conversation"];
|
|
90
|
+
readonly schema: z.ZodEnum<{
|
|
91
|
+
thread: "thread";
|
|
92
|
+
conversation: "conversation";
|
|
93
|
+
}>;
|
|
94
|
+
};
|
|
95
|
+
export declare const MarkDoneTypeSchema: z.ZodEnum<{
|
|
96
|
+
thread: "thread";
|
|
97
|
+
conversation: "conversation";
|
|
98
|
+
}>;
|
|
99
|
+
export type MarkDoneType = z.infer<typeof MarkDoneTypeSchema>;
|
|
100
|
+
/**
|
|
101
|
+
* Mark read item types
|
|
102
|
+
*/
|
|
103
|
+
export declare const MarkReadItemType: {
|
|
104
|
+
readonly values: readonly ["thread", "conversation"];
|
|
105
|
+
readonly schema: z.ZodEnum<{
|
|
106
|
+
thread: "thread";
|
|
107
|
+
conversation: "conversation";
|
|
108
|
+
}>;
|
|
109
|
+
};
|
|
110
|
+
export declare const MarkReadItemTypeSchema: z.ZodEnum<{
|
|
111
|
+
thread: "thread";
|
|
112
|
+
conversation: "conversation";
|
|
113
|
+
}>;
|
|
114
|
+
export type MarkReadItemType = z.infer<typeof MarkReadItemTypeSchema>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Helper to create an enum schema with types.
|
|
4
|
+
* Requires at least 2 values (Zod enum requirement).
|
|
5
|
+
* Only allows valid Comms object types.
|
|
6
|
+
*/
|
|
7
|
+
function createEnumSchema(values) {
|
|
8
|
+
return {
|
|
9
|
+
values,
|
|
10
|
+
schema: z.enum(values),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Target types for reactions
|
|
15
|
+
*/
|
|
16
|
+
export const ReactionTargetType = createEnumSchema(['thread', 'comment', 'message']);
|
|
17
|
+
export const ReactionTargetTypeSchema = ReactionTargetType.schema;
|
|
18
|
+
/**
|
|
19
|
+
* Targets that can be both updated and deleted (mutating-object tools).
|
|
20
|
+
* Kept as a shared const so update-object and delete-object stay in lockstep
|
|
21
|
+
* if/when the supported surface changes.
|
|
22
|
+
*/
|
|
23
|
+
const objectTargets = ['thread', 'comment', 'message'];
|
|
24
|
+
/**
|
|
25
|
+
* Target types for update-object
|
|
26
|
+
*/
|
|
27
|
+
export const UpdateTargetType = createEnumSchema(objectTargets);
|
|
28
|
+
export const UpdateTargetTypeSchema = UpdateTargetType.schema;
|
|
29
|
+
/**
|
|
30
|
+
* Target types for delete-object
|
|
31
|
+
*/
|
|
32
|
+
export const DeleteTargetType = createEnumSchema(objectTargets);
|
|
33
|
+
export const DeleteTargetTypeSchema = DeleteTargetType.schema;
|
|
34
|
+
/**
|
|
35
|
+
* Items that carry an unread marker and accept replies. Shared so the tools
|
|
36
|
+
* that act on "a thread or a conversation" stay in lockstep.
|
|
37
|
+
*/
|
|
38
|
+
const threadOrConversation = ['thread', 'conversation'];
|
|
39
|
+
/**
|
|
40
|
+
* Target types for replies
|
|
41
|
+
*/
|
|
42
|
+
export const ReplyTargetType = createEnumSchema(threadOrConversation);
|
|
43
|
+
export const ReplyTargetTypeSchema = ReplyTargetType.schema;
|
|
44
|
+
/**
|
|
45
|
+
* Search scope types
|
|
46
|
+
*/
|
|
47
|
+
export const SearchScope = createEnumSchema(['workspace', 'thread', 'conversation']);
|
|
48
|
+
export const SearchScopeSchema = SearchScope.schema;
|
|
49
|
+
/**
|
|
50
|
+
* Mark done types
|
|
51
|
+
*/
|
|
52
|
+
export const MarkDoneType = createEnumSchema(threadOrConversation);
|
|
53
|
+
export const MarkDoneTypeSchema = MarkDoneType.schema;
|
|
54
|
+
/**
|
|
55
|
+
* Mark read item types
|
|
56
|
+
*/
|
|
57
|
+
export const MarkReadItemType = createEnumSchema(threadOrConversation);
|
|
58
|
+
export const MarkReadItemTypeSchema = MarkReadItemType.schema;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { Channel, Comment, Conversation, ConversationMessage, Thread, User, Workspace } from '@doist/comms-sdk';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a mock Channel with all required properties and sensible defaults.
|
|
4
|
+
* Pass only the properties you want to override for your specific test.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createMockChannel(overrides?: Partial<Channel>): Channel;
|
|
7
|
+
/**
|
|
8
|
+
* Creates a mock Thread with all required properties and sensible defaults.
|
|
9
|
+
* Pass only the properties you want to override for your specific test.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createMockThread(overrides?: Partial<Thread>): Thread;
|
|
12
|
+
/**
|
|
13
|
+
* Creates a mock Comment with all required properties and sensible defaults.
|
|
14
|
+
* Pass only the properties you want to override for your specific test.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createMockComment(overrides?: Partial<Comment>): Comment;
|
|
17
|
+
/**
|
|
18
|
+
* Creates a mock Conversation with all required properties and sensible defaults.
|
|
19
|
+
* Pass only the properties you want to override for your specific test.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createMockConversation(overrides?: Partial<Conversation>): Conversation;
|
|
22
|
+
/**
|
|
23
|
+
* Creates a mock ConversationMessage with all required properties and sensible defaults.
|
|
24
|
+
* Pass only the properties you want to override for your specific test.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createMockConversationMessage(overrides?: Partial<ConversationMessage>): ConversationMessage;
|
|
27
|
+
/**
|
|
28
|
+
* Creates a mock User with all required properties and sensible defaults.
|
|
29
|
+
* Pass only the properties you want to override for your specific test.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createMockUser(overrides?: Partial<User>): User;
|
|
32
|
+
/**
|
|
33
|
+
* Creates a mock Workspace with all required properties and sensible defaults.
|
|
34
|
+
* Pass only the properties you want to override for your specific test.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createMockWorkspace(overrides?: Partial<Workspace>): Workspace;
|
|
37
|
+
/**
|
|
38
|
+
* Common error messages used across tests.
|
|
39
|
+
*/
|
|
40
|
+
export declare const TEST_ERRORS: {
|
|
41
|
+
readonly API_RATE_LIMIT: "API Error: Rate limit exceeded";
|
|
42
|
+
readonly API_UNAUTHORIZED: "API Error: Unauthorized";
|
|
43
|
+
readonly THREAD_NOT_FOUND: "Thread not found";
|
|
44
|
+
readonly CONVERSATION_NOT_FOUND: "Conversation not found";
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Extracts the text content from a tool output for snapshot testing.
|
|
48
|
+
* This allows tests to match against just the text content while tools return structured output.
|
|
49
|
+
*/
|
|
50
|
+
export declare function extractTextContent(toolOutput: unknown): string;
|
|
51
|
+
/**
|
|
52
|
+
* Extracts the structured content from a tool output for testing: a tool's own result, or the
|
|
53
|
+
* MCP result registration builds from it, which carries it in `structuredContent`.
|
|
54
|
+
*/
|
|
55
|
+
export declare function extractStructuredContent(output: object): Record<string, unknown>;
|
|
56
|
+
/**
|
|
57
|
+
* Common mock IDs used across tests for consistency.
|
|
58
|
+
*
|
|
59
|
+
* Channel/thread/comment/conversation/message/group IDs are opaque base58
|
|
60
|
+
* UUIDv7 strings in the Comms API; workspace/user IDs are numeric.
|
|
61
|
+
* The string IDs below are stable fixtures — not real base58 — and exist
|
|
62
|
+
* purely so tests have predictable values to assert on.
|
|
63
|
+
*/
|
|
64
|
+
export declare const TEST_IDS: {
|
|
65
|
+
readonly THREAD_1: "thread-id-1";
|
|
66
|
+
readonly THREAD_2: "thread-id-2";
|
|
67
|
+
readonly THREAD_3: "thread-id-3";
|
|
68
|
+
readonly COMMENT_1: "comment-id-1";
|
|
69
|
+
readonly COMMENT_2: "comment-id-2";
|
|
70
|
+
readonly CONVERSATION_1: "conv-id-1";
|
|
71
|
+
readonly CONVERSATION_2: "conv-id-2";
|
|
72
|
+
readonly MESSAGE_1: "msg-id-1";
|
|
73
|
+
readonly MESSAGE_2: "msg-id-2";
|
|
74
|
+
readonly CHANNEL_1: "channel-id-1";
|
|
75
|
+
readonly GROUP_1: "group-id-1";
|
|
76
|
+
readonly GROUP_2: "group-id-2";
|
|
77
|
+
readonly WORKSPACE_1: 11111;
|
|
78
|
+
readonly WORKSPACE_2: 11112;
|
|
79
|
+
readonly USER_1: 22222;
|
|
80
|
+
readonly USER_2: 44444;
|
|
81
|
+
readonly USER_3: 55555;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Fixed date for consistent test snapshots.
|
|
85
|
+
* Use this instead of new Date() in tests to avoid snapshot drift.
|
|
86
|
+
*/
|
|
87
|
+
export declare const TODAY: "2025-01-01";
|
|
88
|
+
/**
|
|
89
|
+
* A `getUsersByIds` mock implementation answering from `users`, with `null` for
|
|
90
|
+
* any other requested id — what the API returns for a user the workspace has no
|
|
91
|
+
* record of.
|
|
92
|
+
*/
|
|
93
|
+
export declare function usersByIds<T extends {
|
|
94
|
+
id: number;
|
|
95
|
+
}>(users: readonly T[]): never;
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a mock Channel with all required properties and sensible defaults.
|
|
3
|
+
* Pass only the properties you want to override for your specific test.
|
|
4
|
+
*/
|
|
5
|
+
export function createMockChannel(overrides = {}) {
|
|
6
|
+
return {
|
|
7
|
+
id: TEST_IDS.CHANNEL_1,
|
|
8
|
+
name: 'General',
|
|
9
|
+
creator: TEST_IDS.USER_1,
|
|
10
|
+
public: true,
|
|
11
|
+
workspaceId: TEST_IDS.WORKSPACE_1,
|
|
12
|
+
archived: false,
|
|
13
|
+
created: new Date('2024-01-01T00:00:00Z'),
|
|
14
|
+
version: 1,
|
|
15
|
+
url: `https://comms.todoist.com/${TEST_IDS.WORKSPACE_1}/ch/${TEST_IDS.CHANNEL_1}/`,
|
|
16
|
+
...overrides,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Creates a mock Thread with all required properties and sensible defaults.
|
|
21
|
+
* Pass only the properties you want to override for your specific test.
|
|
22
|
+
*/
|
|
23
|
+
export function createMockThread(overrides = {}) {
|
|
24
|
+
return {
|
|
25
|
+
id: TEST_IDS.THREAD_1,
|
|
26
|
+
title: 'Test Thread',
|
|
27
|
+
content: 'Test thread content',
|
|
28
|
+
channelId: TEST_IDS.CHANNEL_1,
|
|
29
|
+
workspaceId: TEST_IDS.WORKSPACE_1,
|
|
30
|
+
creator: TEST_IDS.USER_1,
|
|
31
|
+
posted: new Date('2024-01-01T00:00:00Z'),
|
|
32
|
+
lastUpdated: new Date('2024-01-01T00:00:00Z'),
|
|
33
|
+
pinned: false,
|
|
34
|
+
snippet: 'Test thread content',
|
|
35
|
+
snippetCreator: TEST_IDS.USER_1,
|
|
36
|
+
systemMessage: null,
|
|
37
|
+
attachments: [],
|
|
38
|
+
groups: [],
|
|
39
|
+
reactions: {},
|
|
40
|
+
recipients: [],
|
|
41
|
+
commentCount: 0,
|
|
42
|
+
isArchived: false,
|
|
43
|
+
inInbox: true,
|
|
44
|
+
participants: [TEST_IDS.USER_1],
|
|
45
|
+
url: `https://comms.todoist.com/${TEST_IDS.WORKSPACE_1}/ch/${TEST_IDS.CHANNEL_1}/t/${TEST_IDS.THREAD_1}/`,
|
|
46
|
+
...overrides,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Creates a mock Comment with all required properties and sensible defaults.
|
|
51
|
+
* Pass only the properties you want to override for your specific test.
|
|
52
|
+
*/
|
|
53
|
+
export function createMockComment(overrides = {}) {
|
|
54
|
+
return {
|
|
55
|
+
id: TEST_IDS.COMMENT_1,
|
|
56
|
+
content: 'Test comment content',
|
|
57
|
+
threadId: TEST_IDS.THREAD_1,
|
|
58
|
+
workspaceId: TEST_IDS.WORKSPACE_1,
|
|
59
|
+
channelId: TEST_IDS.CHANNEL_1,
|
|
60
|
+
creator: TEST_IDS.USER_1,
|
|
61
|
+
posted: new Date('2024-01-01T00:00:00Z'),
|
|
62
|
+
systemMessage: null,
|
|
63
|
+
attachments: [],
|
|
64
|
+
reactions: {},
|
|
65
|
+
objIndex: 1,
|
|
66
|
+
url: `https://comms.todoist.com/${TEST_IDS.WORKSPACE_1}/ch/${TEST_IDS.CHANNEL_1}/t/${TEST_IDS.THREAD_1}/c/${TEST_IDS.COMMENT_1}`,
|
|
67
|
+
...overrides,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Creates a mock Conversation with all required properties and sensible defaults.
|
|
72
|
+
* Pass only the properties you want to override for your specific test.
|
|
73
|
+
*/
|
|
74
|
+
export function createMockConversation(overrides = {}) {
|
|
75
|
+
return {
|
|
76
|
+
id: TEST_IDS.CONVERSATION_1,
|
|
77
|
+
workspaceId: TEST_IDS.WORKSPACE_1,
|
|
78
|
+
userIds: [TEST_IDS.USER_1, TEST_IDS.USER_2],
|
|
79
|
+
messageCount: 0,
|
|
80
|
+
lastObjIndex: 0,
|
|
81
|
+
snippet: '',
|
|
82
|
+
snippetCreators: [],
|
|
83
|
+
archived: false,
|
|
84
|
+
creator: TEST_IDS.USER_1,
|
|
85
|
+
created: new Date('2024-01-01T00:00:00Z'),
|
|
86
|
+
lastActive: new Date('2024-01-01T00:00:00Z'),
|
|
87
|
+
url: `https://comms.todoist.com/${TEST_IDS.WORKSPACE_1}/msg/${TEST_IDS.CONVERSATION_1}/`,
|
|
88
|
+
...overrides,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Creates a mock ConversationMessage with all required properties and sensible defaults.
|
|
93
|
+
* Pass only the properties you want to override for your specific test.
|
|
94
|
+
*/
|
|
95
|
+
export function createMockConversationMessage(overrides = {}) {
|
|
96
|
+
return {
|
|
97
|
+
id: TEST_IDS.MESSAGE_1,
|
|
98
|
+
content: 'Test message content',
|
|
99
|
+
creator: TEST_IDS.USER_1,
|
|
100
|
+
conversationId: TEST_IDS.CONVERSATION_1,
|
|
101
|
+
workspaceId: TEST_IDS.WORKSPACE_1,
|
|
102
|
+
posted: new Date('2024-01-01T00:00:00Z'),
|
|
103
|
+
systemMessage: null,
|
|
104
|
+
attachments: [],
|
|
105
|
+
reactions: {},
|
|
106
|
+
objIndex: 1,
|
|
107
|
+
lastEdited: null,
|
|
108
|
+
url: `https://comms.todoist.com/${TEST_IDS.WORKSPACE_1}/msg/${TEST_IDS.CONVERSATION_1}/m/${TEST_IDS.MESSAGE_1}`,
|
|
109
|
+
...overrides,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Creates a mock User with all required properties and sensible defaults.
|
|
114
|
+
* Pass only the properties you want to override for your specific test.
|
|
115
|
+
*/
|
|
116
|
+
export function createMockUser(overrides = {}) {
|
|
117
|
+
return {
|
|
118
|
+
id: TEST_IDS.USER_1,
|
|
119
|
+
email: 'test@example.com',
|
|
120
|
+
fullName: 'Test User',
|
|
121
|
+
shortName: 'Test',
|
|
122
|
+
timezone: 'UTC',
|
|
123
|
+
lang: 'en',
|
|
124
|
+
removed: false,
|
|
125
|
+
...overrides,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Creates a mock Workspace with all required properties and sensible defaults.
|
|
130
|
+
* Pass only the properties you want to override for your specific test.
|
|
131
|
+
*/
|
|
132
|
+
export function createMockWorkspace(overrides = {}) {
|
|
133
|
+
return {
|
|
134
|
+
id: TEST_IDS.WORKSPACE_1,
|
|
135
|
+
name: 'Test Workspace',
|
|
136
|
+
creator: TEST_IDS.USER_1,
|
|
137
|
+
created: new Date('2024-01-01T00:00:00Z'),
|
|
138
|
+
defaultConversation: TEST_IDS.CONVERSATION_1,
|
|
139
|
+
plan: 'free',
|
|
140
|
+
...overrides,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Common error messages used across tests.
|
|
145
|
+
*/
|
|
146
|
+
export const TEST_ERRORS = {
|
|
147
|
+
API_RATE_LIMIT: 'API Error: Rate limit exceeded',
|
|
148
|
+
API_UNAUTHORIZED: 'API Error: Unauthorized',
|
|
149
|
+
THREAD_NOT_FOUND: 'Thread not found',
|
|
150
|
+
CONVERSATION_NOT_FOUND: 'Conversation not found',
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* Extracts the text content from a tool output for snapshot testing.
|
|
154
|
+
* This allows tests to match against just the text content while tools return structured output.
|
|
155
|
+
*/
|
|
156
|
+
export function extractTextContent(toolOutput) {
|
|
157
|
+
if (typeof toolOutput === 'string') {
|
|
158
|
+
return toolOutput;
|
|
159
|
+
}
|
|
160
|
+
// A tool's own result, as `execute` returns it.
|
|
161
|
+
if (typeof toolOutput === 'object' &&
|
|
162
|
+
toolOutput !== null &&
|
|
163
|
+
'textContent' in toolOutput &&
|
|
164
|
+
typeof toolOutput.textContent === 'string') {
|
|
165
|
+
return toolOutput.textContent;
|
|
166
|
+
}
|
|
167
|
+
if (typeof toolOutput === 'object' && toolOutput !== null && 'content' in toolOutput) {
|
|
168
|
+
const output = toolOutput;
|
|
169
|
+
if (Array.isArray(output.content) &&
|
|
170
|
+
output.content[0] &&
|
|
171
|
+
typeof output.content[0] === 'object' &&
|
|
172
|
+
output.content[0] !== null &&
|
|
173
|
+
'type' in output.content[0] &&
|
|
174
|
+
'text' in output.content[0] &&
|
|
175
|
+
output.content[0].type === 'text') {
|
|
176
|
+
return output.content[0].text;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
throw new Error('Expected tool output to have text content');
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Extracts the structured content from a tool output for testing: a tool's own result, or the
|
|
183
|
+
* MCP result registration builds from it, which carries it in `structuredContent`.
|
|
184
|
+
*/
|
|
185
|
+
export function extractStructuredContent(output) {
|
|
186
|
+
if ('structuredContent' in output && typeof output.structuredContent === 'object') {
|
|
187
|
+
return output.structuredContent;
|
|
188
|
+
}
|
|
189
|
+
throw new Error('Expected tool output to have structured content');
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Common mock IDs used across tests for consistency.
|
|
193
|
+
*
|
|
194
|
+
* Channel/thread/comment/conversation/message/group IDs are opaque base58
|
|
195
|
+
* UUIDv7 strings in the Comms API; workspace/user IDs are numeric.
|
|
196
|
+
* The string IDs below are stable fixtures — not real base58 — and exist
|
|
197
|
+
* purely so tests have predictable values to assert on.
|
|
198
|
+
*/
|
|
199
|
+
export const TEST_IDS = {
|
|
200
|
+
THREAD_1: 'thread-id-1',
|
|
201
|
+
THREAD_2: 'thread-id-2',
|
|
202
|
+
THREAD_3: 'thread-id-3',
|
|
203
|
+
COMMENT_1: 'comment-id-1',
|
|
204
|
+
COMMENT_2: 'comment-id-2',
|
|
205
|
+
CONVERSATION_1: 'conv-id-1',
|
|
206
|
+
CONVERSATION_2: 'conv-id-2',
|
|
207
|
+
MESSAGE_1: 'msg-id-1',
|
|
208
|
+
MESSAGE_2: 'msg-id-2',
|
|
209
|
+
CHANNEL_1: 'channel-id-1',
|
|
210
|
+
GROUP_1: 'group-id-1',
|
|
211
|
+
GROUP_2: 'group-id-2',
|
|
212
|
+
WORKSPACE_1: 11111,
|
|
213
|
+
WORKSPACE_2: 11112,
|
|
214
|
+
USER_1: 22222,
|
|
215
|
+
USER_2: 44444,
|
|
216
|
+
USER_3: 55555,
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* Fixed date for consistent test snapshots.
|
|
220
|
+
* Use this instead of new Date() in tests to avoid snapshot drift.
|
|
221
|
+
*/
|
|
222
|
+
export const TODAY = '2025-01-01';
|
|
223
|
+
/**
|
|
224
|
+
* A `getUsersByIds` mock implementation answering from `users`, with `null` for
|
|
225
|
+
* any other requested id — what the API returns for a user the workspace has no
|
|
226
|
+
* record of.
|
|
227
|
+
*/
|
|
228
|
+
export function usersByIds(users) {
|
|
229
|
+
const byId = new Map(users.map((user) => [user.id, user]));
|
|
230
|
+
return (async ({ userIds }) => Object.fromEntries(userIds.map((id) => [id, byId.get(id) ?? null])));
|
|
231
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare const ToolNames: {
|
|
2
|
+
readonly USER_INFO: "user-info";
|
|
3
|
+
readonly UPDATE_USER_INFO: "update-user-info";
|
|
4
|
+
readonly FETCH_INBOX: "fetch-inbox";
|
|
5
|
+
readonly LOAD_THREAD: "load-thread";
|
|
6
|
+
readonly LOAD_CONVERSATION: "load-conversation";
|
|
7
|
+
readonly SEARCH_CONTENT: "search-content";
|
|
8
|
+
readonly GET_MENTIONS: "get-mentions";
|
|
9
|
+
readonly CREATE_CHANNEL: "create-channel";
|
|
10
|
+
readonly UPDATE_CHANNEL: "update-channel";
|
|
11
|
+
readonly CREATE_THREAD: "create-thread";
|
|
12
|
+
readonly CREATE_CONVERSATION: "create-conversation";
|
|
13
|
+
readonly UPDATE_OBJECT: "update-object";
|
|
14
|
+
readonly DELETE_OBJECT: "delete-object";
|
|
15
|
+
readonly REPLY: "reply";
|
|
16
|
+
readonly REACT: "react";
|
|
17
|
+
readonly MARK_DONE: "mark-done";
|
|
18
|
+
readonly MARK_READ: "mark-read";
|
|
19
|
+
readonly BUILD_LINK: "build-link";
|
|
20
|
+
readonly GET_WORKSPACES: "get-workspaces";
|
|
21
|
+
readonly GET_USERS: "get-users";
|
|
22
|
+
readonly GET_GROUPS: "get-groups";
|
|
23
|
+
readonly LIST_CHANNELS: "list-channels";
|
|
24
|
+
readonly LIST_CONVERSATIONS: "list-conversations";
|
|
25
|
+
};
|