@todoist/comms-mcp 0.0.1 → 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.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +198 -34
  3. package/dist/comms-tool.d.ts +14 -0
  4. package/dist/comms-tool.js +1 -0
  5. package/dist/index.d.ts +1324 -0
  6. package/dist/index.js +55 -0
  7. package/dist/main.d.ts +2 -0
  8. package/dist/main.js +24 -0
  9. package/dist/mcp-helpers.d.ts +17 -0
  10. package/dist/mcp-helpers.js +40 -0
  11. package/dist/mcp-server.d.ts +10 -0
  12. package/dist/mcp-server.js +78 -0
  13. package/dist/tool-registry.d.ts +3 -0
  14. package/dist/tool-registry.js +49 -0
  15. package/dist/tools/build-link.d.ts +63 -0
  16. package/dist/tools/build-link.js +93 -0
  17. package/dist/tools/channel-output.d.ts +19 -0
  18. package/dist/tools/channel-output.js +47 -0
  19. package/dist/tools/create-channel.d.ts +55 -0
  20. package/dist/tools/create-channel.js +44 -0
  21. package/dist/tools/create-conversation.d.ts +49 -0
  22. package/dist/tools/create-conversation.js +75 -0
  23. package/dist/tools/create-thread.d.ts +67 -0
  24. package/dist/tools/create-thread.js +126 -0
  25. package/dist/tools/delete-object.d.ts +44 -0
  26. package/dist/tools/delete-object.js +84 -0
  27. package/dist/tools/fetch-inbox.d.ts +91 -0
  28. package/dist/tools/fetch-inbox.js +227 -0
  29. package/dist/tools/get-groups.d.ts +62 -0
  30. package/dist/tools/get-groups.js +116 -0
  31. package/dist/tools/get-mentions.d.ts +97 -0
  32. package/dist/tools/get-mentions.js +126 -0
  33. package/dist/tools/get-users.d.ts +58 -0
  34. package/dist/tools/get-users.js +107 -0
  35. package/dist/tools/get-workspaces.d.ts +61 -0
  36. package/dist/tools/get-workspaces.js +120 -0
  37. package/dist/tools/list-channels.d.ts +47 -0
  38. package/dist/tools/list-channels.js +89 -0
  39. package/dist/tools/list-conversations.d.ts +66 -0
  40. package/dist/tools/list-conversations.js +212 -0
  41. package/dist/tools/load-conversation.d.ts +92 -0
  42. package/dist/tools/load-conversation.js +128 -0
  43. package/dist/tools/load-thread.d.ts +127 -0
  44. package/dist/tools/load-thread.js +175 -0
  45. package/dist/tools/mark-done.d.ts +111 -0
  46. package/dist/tools/mark-done.js +275 -0
  47. package/dist/tools/mark-read.d.ts +73 -0
  48. package/dist/tools/mark-read.js +214 -0
  49. package/dist/tools/react.d.ts +59 -0
  50. package/dist/tools/react.js +97 -0
  51. package/dist/tools/reply.d.ts +69 -0
  52. package/dist/tools/reply.js +122 -0
  53. package/dist/tools/search-content.d.ts +103 -0
  54. package/dist/tools/search-content.js +138 -0
  55. package/dist/tools/update-channel.d.ts +53 -0
  56. package/dist/tools/update-channel.js +45 -0
  57. package/dist/tools/update-object.d.ts +52 -0
  58. package/dist/tools/update-object.js +161 -0
  59. package/dist/tools/update-user-info.d.ts +55 -0
  60. package/dist/tools/update-user-info.js +270 -0
  61. package/dist/tools/user-info.d.ts +32 -0
  62. package/dist/tools/user-info.js +16 -0
  63. package/dist/tools/user-output.d.ts +9 -0
  64. package/dist/tools/user-output.js +28 -0
  65. package/dist/usage-tracking.d.ts +23 -0
  66. package/dist/usage-tracking.js +41 -0
  67. package/dist/utils/attachments.d.ts +24 -0
  68. package/dist/utils/attachments.js +57 -0
  69. package/dist/utils/concurrency.d.ts +20 -0
  70. package/dist/utils/concurrency.js +24 -0
  71. package/dist/utils/constants.d.ts +5 -0
  72. package/dist/utils/constants.js +5 -0
  73. package/dist/utils/degrade.d.ts +81 -0
  74. package/dist/utils/degrade.js +149 -0
  75. package/dist/utils/output-schemas.d.ts +1283 -0
  76. package/dist/utils/output-schemas.js +657 -0
  77. package/dist/utils/read-position.d.ts +21 -0
  78. package/dist/utils/read-position.js +37 -0
  79. package/dist/utils/search-results.d.ts +36 -0
  80. package/dist/utils/search-results.js +82 -0
  81. package/dist/utils/target-types.d.ts +114 -0
  82. package/dist/utils/target-types.js +58 -0
  83. package/dist/utils/test-helpers.d.ts +95 -0
  84. package/dist/utils/test-helpers.js +231 -0
  85. package/dist/utils/tool-names.d.ts +25 -0
  86. package/dist/utils/tool-names.js +25 -0
  87. package/dist/utils/url-helpers.d.ts +3 -0
  88. package/dist/utils/url-helpers.js +10 -0
  89. package/dist/utils/user-status.d.ts +8 -0
  90. package/dist/utils/user-status.js +33 -0
  91. package/dist/utils/validate-comms-token.d.ts +13 -0
  92. package/dist/utils/validate-comms-token.js +22 -0
  93. package/package.json +85 -8
@@ -0,0 +1,58 @@
1
+ import { type UserType } from '@doist/comms-sdk';
2
+ import { z } from 'zod';
3
+ type UserData = {
4
+ id: number;
5
+ name: string;
6
+ shortName: string;
7
+ email?: string;
8
+ userType?: UserType;
9
+ removed: boolean;
10
+ timezone?: string;
11
+ restricted?: boolean;
12
+ };
13
+ type GetUsersStructured = Record<string, unknown> & {
14
+ type: 'get_users';
15
+ workspaceId: number;
16
+ users: UserData[];
17
+ totalUsers: number;
18
+ filteredUsers: number;
19
+ };
20
+ declare const getUsers: {
21
+ name: "get-users";
22
+ description: string;
23
+ parameters: {
24
+ workspaceId: z.ZodNumber;
25
+ userIds: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
26
+ searchText: z.ZodOptional<z.ZodString>;
27
+ };
28
+ outputSchema: {
29
+ type: z.ZodLiteral<"get_users">;
30
+ workspaceId: z.ZodNumber;
31
+ users: z.ZodArray<z.ZodObject<{
32
+ id: z.ZodNumber;
33
+ name: z.ZodString;
34
+ shortName: z.ZodString;
35
+ email: z.ZodOptional<z.ZodString>;
36
+ userType: z.ZodOptional<z.ZodString>;
37
+ removed: z.ZodBoolean;
38
+ timezone: z.ZodOptional<z.ZodString>;
39
+ restricted: z.ZodOptional<z.ZodBoolean>;
40
+ }, z.core.$strip>>;
41
+ totalUsers: z.ZodNumber;
42
+ filteredUsers: z.ZodNumber;
43
+ };
44
+ annotations: {
45
+ readOnlyHint: true;
46
+ destructiveHint: false;
47
+ idempotentHint: true;
48
+ };
49
+ execute(args: {
50
+ workspaceId: number;
51
+ userIds?: number[] | undefined;
52
+ searchText?: string | undefined;
53
+ }, client: import("@doist/comms-sdk").CommsApi): Promise<{
54
+ textContent: string;
55
+ structuredContent: GetUsersStructured;
56
+ }>;
57
+ };
58
+ export { getUsers, type GetUsersStructured };
@@ -0,0 +1,107 @@
1
+ import { isRestrictedWorkspaceUser, } from '@doist/comms-sdk';
2
+ import { z } from 'zod';
3
+ import { resolveUsersWithLog } from '../utils/degrade.js';
4
+ import { GetUsersOutputSchema } from '../utils/output-schemas.js';
5
+ import { ToolNames } from '../utils/tool-names.js';
6
+ const ArgsSchema = {
7
+ workspaceId: z.number().describe('The workspace ID to get users from.'),
8
+ userIds: z
9
+ .array(z.number())
10
+ .optional()
11
+ .describe('Optional array of specific user IDs to fetch. If not provided or empty array, fetches all workspace users.'),
12
+ searchText: z
13
+ .string()
14
+ .optional()
15
+ .describe('Optional search text to filter users by name or email (case-insensitive).'),
16
+ };
17
+ // A restricted profile carries no email, user type or timezone, so those stay
18
+ // absent rather than being filled with a placeholder.
19
+ function toUserData(user) {
20
+ const base = {
21
+ id: user.id,
22
+ name: user.fullName,
23
+ shortName: user.shortName,
24
+ removed: user.removed,
25
+ };
26
+ if (isRestrictedWorkspaceUser(user)) {
27
+ return { ...base, restricted: true };
28
+ }
29
+ return {
30
+ ...base,
31
+ ...(user.email && { email: user.email }),
32
+ userType: user.userType,
33
+ timezone: user.timezone,
34
+ };
35
+ }
36
+ const getUsers = {
37
+ name: ToolNames.GET_USERS,
38
+ description: 'Get users from a workspace. Retrieves all workspace users by default, or specific users if userIds array is provided. Supports optional case-insensitive search filtering by name or email.',
39
+ parameters: ArgsSchema,
40
+ outputSchema: GetUsersOutputSchema.shape,
41
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
42
+ async execute(args, client) {
43
+ const { workspaceId, userIds, searchText } = args;
44
+ let users;
45
+ if (!userIds || userIds.length === 0) {
46
+ users = await client.workspaceUsers.getWorkspaceUsers({ workspaceId });
47
+ }
48
+ else {
49
+ const byId = await resolveUsersWithLog(client, ToolNames.GET_USERS, 'failed to resolve user', workspaceId, userIds);
50
+ users = userIds.flatMap((id) => byId.get(id) ?? []);
51
+ }
52
+ const totalUsers = users.length;
53
+ // Apply search filter if provided, before building output objects
54
+ let matchedUsers = users;
55
+ if (searchText) {
56
+ const searchLower = searchText.toLowerCase();
57
+ matchedUsers = users.filter((user) => {
58
+ const nameMatch = user.fullName.toLowerCase().includes(searchLower);
59
+ const email = isRestrictedWorkspaceUser(user) ? undefined : user.email;
60
+ const emailMatch = email?.toLowerCase().includes(searchLower) || false;
61
+ return nameMatch || emailMatch;
62
+ });
63
+ }
64
+ const filteredUsers = matchedUsers.map(toUserData);
65
+ // Build text content
66
+ const lines = ['# Workspace Users', ''];
67
+ lines.push(`**Workspace ID:** ${workspaceId}`);
68
+ lines.push(`**Total Users:** ${totalUsers}${searchText ? ` (${filteredUsers.length} matching search)` : ''}`);
69
+ lines.push('');
70
+ if (filteredUsers.length === 0) {
71
+ lines.push('No users found.');
72
+ }
73
+ else {
74
+ for (const user of filteredUsers) {
75
+ lines.push(`## ${user.name}`);
76
+ lines.push(`**ID:** ${user.id}`);
77
+ if (user.email) {
78
+ lines.push(`**Email:** ${user.email}`);
79
+ }
80
+ if (user.userType) {
81
+ lines.push(`**User Type:** ${user.userType}`);
82
+ }
83
+ if (user.timezone) {
84
+ lines.push(`**Timezone:** ${user.timezone}`);
85
+ }
86
+ if (user.restricted) {
87
+ lines.push('**Profile:** Restricted, limited visibility of this user');
88
+ }
89
+ lines.push(`**Status:** ${user.removed ? 'Removed' : 'Active'}`);
90
+ lines.push('');
91
+ }
92
+ }
93
+ const textContent = lines.join('\n');
94
+ const structuredContent = {
95
+ type: 'get_users',
96
+ workspaceId,
97
+ users: filteredUsers,
98
+ totalUsers,
99
+ filteredUsers: filteredUsers.length,
100
+ };
101
+ return {
102
+ textContent,
103
+ structuredContent,
104
+ };
105
+ },
106
+ };
107
+ export { getUsers };
@@ -0,0 +1,61 @@
1
+ import type { CommsApi, WorkspacePlan } from '@doist/comms-sdk';
2
+ type WorkspaceData = {
3
+ id: number;
4
+ name: string;
5
+ creator: number;
6
+ creatorName?: string;
7
+ created: string;
8
+ url: string;
9
+ defaultConversation?: string;
10
+ defaultConversationTitle?: string;
11
+ defaultConversationUrl?: string;
12
+ plan?: WorkspacePlan;
13
+ avatarId?: string;
14
+ avatarUrls?: {
15
+ s35: string;
16
+ s60: string;
17
+ s195: string;
18
+ s640: string;
19
+ };
20
+ };
21
+ type GetWorkspacesStructured = Record<string, unknown> & {
22
+ type: 'get_workspaces';
23
+ workspaces: WorkspaceData[];
24
+ };
25
+ declare const getWorkspaces: {
26
+ name: "get-workspaces";
27
+ description: string;
28
+ parameters: {};
29
+ outputSchema: {
30
+ type: import("zod").ZodLiteral<"get_workspaces">;
31
+ workspaces: import("zod").ZodArray<import("zod").ZodObject<{
32
+ id: import("zod").ZodNumber;
33
+ name: import("zod").ZodString;
34
+ creator: import("zod").ZodNumber;
35
+ creatorName: import("zod").ZodOptional<import("zod").ZodString>;
36
+ created: import("zod").ZodString;
37
+ url: import("zod").ZodURL;
38
+ defaultConversation: import("zod").ZodOptional<import("zod").ZodString>;
39
+ defaultConversationTitle: import("zod").ZodOptional<import("zod").ZodString>;
40
+ defaultConversationUrl: import("zod").ZodOptional<import("zod").ZodURL>;
41
+ plan: import("zod").ZodOptional<import("zod").ZodString>;
42
+ avatarId: import("zod").ZodOptional<import("zod").ZodString>;
43
+ avatarUrls: import("zod").ZodOptional<import("zod").ZodObject<{
44
+ s35: import("zod").ZodString;
45
+ s60: import("zod").ZodString;
46
+ s195: import("zod").ZodString;
47
+ s640: import("zod").ZodString;
48
+ }, import("zod/v4/core").$strip>>;
49
+ }, import("zod/v4/core").$strip>>;
50
+ };
51
+ annotations: {
52
+ readOnlyHint: true;
53
+ destructiveHint: false;
54
+ idempotentHint: true;
55
+ };
56
+ execute(_args: Record<string, never>, client: CommsApi): Promise<{
57
+ textContent: string;
58
+ structuredContent: GetWorkspacesStructured;
59
+ }>;
60
+ };
61
+ export { getWorkspaces, type GetWorkspacesStructured };
@@ -0,0 +1,120 @@
1
+ import { degradeAllWithLog } from '../utils/degrade.js';
2
+ import { GetWorkspacesOutputSchema } from '../utils/output-schemas.js';
3
+ import { ToolNames } from '../utils/tool-names.js';
4
+ import { getConversationUrl, getWorkspaceUrl } from '../utils/url-helpers.js';
5
+ const ArgsSchema = {};
6
+ async function generateWorkspacesList(client) {
7
+ const workspaces = await client.workspaces.getWorkspaces();
8
+ if (workspaces.length === 0) {
9
+ return {
10
+ textContent: '# Workspaces\n\nNo workspaces found.',
11
+ structuredContent: {
12
+ type: 'get_workspaces',
13
+ workspaces: [],
14
+ },
15
+ };
16
+ }
17
+ // Collect default conversation IDs (paired with the workspace they belong to)
18
+ // and unique creator IDs (paired with workspace IDs so we can do per-workspace
19
+ // user lookups).
20
+ const defaultConversationPairs = [];
21
+ const creatorPairs = [];
22
+ for (const workspace of workspaces) {
23
+ if (workspace.defaultConversation) {
24
+ defaultConversationPairs.push({
25
+ workspaceId: workspace.id,
26
+ conversationId: workspace.defaultConversation,
27
+ });
28
+ }
29
+ creatorPairs.push({ workspaceId: workspace.id, creatorId: workspace.creator });
30
+ }
31
+ // Fetch default conversations
32
+ const conversationLookup = {};
33
+ if (defaultConversationPairs.length > 0) {
34
+ const conversations = await degradeAllWithLog(ToolNames.GET_WORKSPACES, 'failed to resolve default conversation', defaultConversationPairs, ({ conversationId }) => client.conversations.getConversation(conversationId));
35
+ for (let i = 0; i < defaultConversationPairs.length; i++) {
36
+ const pair = defaultConversationPairs[i];
37
+ const conversation = conversations[i];
38
+ if (pair && conversation) {
39
+ const title = conversation.title ||
40
+ `Conversation with users: ${conversation.userIds.join(', ')}`;
41
+ conversationLookup[pair.conversationId] = title;
42
+ }
43
+ }
44
+ }
45
+ // Fetch all workspace creators
46
+ const creatorLookup = {};
47
+ if (creatorPairs.length > 0) {
48
+ const users = await degradeAllWithLog(ToolNames.GET_WORKSPACES, 'failed to resolve workspace creator', creatorPairs, ({ workspaceId, creatorId }) => client.workspaceUsers.getUserById({ workspaceId, userId: creatorId }));
49
+ for (let i = 0; i < creatorPairs.length; i++) {
50
+ const pair = creatorPairs[i];
51
+ const user = users[i];
52
+ if (pair && user) {
53
+ creatorLookup[pair.creatorId] = user.fullName;
54
+ }
55
+ }
56
+ }
57
+ const lines = ['# Workspaces', ''];
58
+ lines.push(`Found ${workspaces.length} workspace${workspaces.length === 1 ? '' : 's'}:`, '');
59
+ for (const workspace of workspaces) {
60
+ const creatorName = creatorLookup[workspace.creator];
61
+ const defaultConversationTitle = workspace.defaultConversation
62
+ ? conversationLookup[workspace.defaultConversation]
63
+ : undefined;
64
+ lines.push(`## [${workspace.name}](${getWorkspaceUrl(workspace.id)})`);
65
+ lines.push(`**ID:** ${workspace.id}`);
66
+ lines.push(`**Creator:** ${creatorName ? `${creatorName} (${workspace.creator})` : workspace.creator}`);
67
+ lines.push(`**Created:** ${workspace.created.toISOString()}`);
68
+ if (workspace.defaultConversation) {
69
+ const conversationUrl = getConversationUrl(workspace.id, workspace.defaultConversation);
70
+ lines.push(`**Default Conversation:** ${defaultConversationTitle ? `[${defaultConversationTitle}](${conversationUrl}) (${workspace.defaultConversation})` : `[${workspace.defaultConversation}](${conversationUrl})`}`);
71
+ }
72
+ if (workspace.plan) {
73
+ lines.push(`**Plan:** ${workspace.plan}`);
74
+ }
75
+ lines.push('');
76
+ }
77
+ const textContent = lines.join('\n');
78
+ const structuredContent = {
79
+ type: 'get_workspaces',
80
+ workspaces: workspaces.map((workspace) => ({
81
+ id: workspace.id,
82
+ name: workspace.name,
83
+ creator: workspace.creator,
84
+ ...(creatorLookup[workspace.creator] && {
85
+ creatorName: creatorLookup[workspace.creator],
86
+ }),
87
+ created: workspace.created.toISOString(),
88
+ url: getWorkspaceUrl(workspace.id),
89
+ ...(workspace.defaultConversation && {
90
+ defaultConversation: workspace.defaultConversation,
91
+ }),
92
+ ...(workspace.defaultConversation &&
93
+ conversationLookup[workspace.defaultConversation] && {
94
+ defaultConversationTitle: conversationLookup[workspace.defaultConversation],
95
+ }),
96
+ ...(workspace.defaultConversation && {
97
+ defaultConversationUrl: getConversationUrl(workspace.id, workspace.defaultConversation),
98
+ }),
99
+ ...(workspace.plan && { plan: workspace.plan }),
100
+ ...(workspace.avatarId && { avatarId: workspace.avatarId }),
101
+ ...(workspace.avatarUrls && { avatarUrls: workspace.avatarUrls }),
102
+ })),
103
+ };
104
+ return { textContent, structuredContent };
105
+ }
106
+ const getWorkspaces = {
107
+ name: ToolNames.GET_WORKSPACES,
108
+ description: 'Get all workspaces that the user belongs to. Returns a list of workspaces with their IDs, names, creators, creation dates, default conversation, and plan information.',
109
+ parameters: ArgsSchema,
110
+ outputSchema: GetWorkspacesOutputSchema.shape,
111
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
112
+ async execute(_args, client) {
113
+ const result = await generateWorkspacesList(client);
114
+ return {
115
+ textContent: result.textContent,
116
+ structuredContent: result.structuredContent,
117
+ };
118
+ },
119
+ };
120
+ export { getWorkspaces };
@@ -0,0 +1,47 @@
1
+ import type { CommsApi } from '@doist/comms-sdk';
2
+ import { z } from 'zod';
3
+ import { type ChannelListData } from './channel-output.js';
4
+ type ListChannelsStructured = Record<string, unknown> & {
5
+ type: 'list_channels';
6
+ workspaceId: number;
7
+ channels: ChannelListData[];
8
+ totalChannels: number;
9
+ };
10
+ declare const listChannels: {
11
+ name: "list-channels";
12
+ description: string;
13
+ parameters: {
14
+ workspaceId: z.ZodNumber;
15
+ includeArchived: z.ZodOptional<z.ZodBoolean>;
16
+ };
17
+ outputSchema: {
18
+ type: z.ZodLiteral<"list_channels">;
19
+ workspaceId: z.ZodNumber;
20
+ channels: z.ZodArray<z.ZodObject<{
21
+ name: z.ZodString;
22
+ description: z.ZodOptional<z.ZodString>;
23
+ public: z.ZodBoolean;
24
+ archived: z.ZodBoolean;
25
+ created: z.ZodString;
26
+ channelUrl: z.ZodString;
27
+ id: z.ZodString;
28
+ creatorId: z.ZodNumber;
29
+ creatorName: z.ZodOptional<z.ZodString>;
30
+ color: z.ZodOptional<z.ZodNumber>;
31
+ }, z.core.$strip>>;
32
+ totalChannels: z.ZodNumber;
33
+ };
34
+ annotations: {
35
+ readOnlyHint: true;
36
+ destructiveHint: false;
37
+ idempotentHint: true;
38
+ };
39
+ execute(args: {
40
+ workspaceId: number;
41
+ includeArchived?: boolean | undefined;
42
+ }, client: CommsApi): Promise<{
43
+ textContent: string;
44
+ structuredContent: ListChannelsStructured;
45
+ }>;
46
+ };
47
+ export { listChannels, type ListChannelsStructured };
@@ -0,0 +1,89 @@
1
+ import { z } from 'zod';
2
+ import { resolveUsersWithLog } from '../utils/degrade.js';
3
+ import { ListChannelsOutputSchema } from '../utils/output-schemas.js';
4
+ import { ToolNames } from '../utils/tool-names.js';
5
+ import { getChannelListData } from './channel-output.js';
6
+ const ArgsSchema = {
7
+ workspaceId: z.number().describe('The workspace ID to list channels from.'),
8
+ includeArchived: z
9
+ .boolean()
10
+ .optional()
11
+ .describe('Whether to include archived channels. If true, both active and archived channels are returned. Defaults to false (active channels only).'),
12
+ };
13
+ async function generateChannelsList(client, workspaceId, includeArchived) {
14
+ // By default only fetch active channels; optionally include archived ones too
15
+ let channels;
16
+ if (includeArchived) {
17
+ const [active, archived] = await Promise.all([
18
+ client.channels.getChannels({ workspaceId }),
19
+ client.channels.getChannels({ workspaceId, archived: true }),
20
+ ]);
21
+ channels = [...active, ...archived];
22
+ }
23
+ else {
24
+ channels = await client.channels.getChannels({ workspaceId });
25
+ }
26
+ if (channels.length === 0) {
27
+ return {
28
+ textContent: '# Channels\n\nNo channels found.',
29
+ structuredContent: {
30
+ type: 'list_channels',
31
+ workspaceId,
32
+ channels: [],
33
+ totalChannels: 0,
34
+ },
35
+ };
36
+ }
37
+ // Collect unique creator IDs and fetch their names
38
+ const creatorIds = new Set();
39
+ for (const channel of channels) {
40
+ creatorIds.add(channel.creator);
41
+ }
42
+ // A deleted/inaccessible creator doesn't fail the whole list — the fallback
43
+ // path (creator ID without a name) is exercised by the text output.
44
+ const users = await resolveUsersWithLog(client, ToolNames.LIST_CHANNELS, 'failed to resolve channel creator', workspaceId, Array.from(creatorIds));
45
+ const creatorLookup = {};
46
+ for (const [id, user] of users) {
47
+ creatorLookup[id] = user.fullName;
48
+ }
49
+ const lines = ['# Channels', ''];
50
+ lines.push(`Found ${channels.length} channel${channels.length === 1 ? '' : 's'} in workspace ${workspaceId}:`, '');
51
+ for (const channel of channels) {
52
+ const creatorName = creatorLookup[channel.creator];
53
+ const channelData = getChannelListData(channel, creatorName);
54
+ lines.push(`## [${channelData.name}](${channelData.channelUrl})`);
55
+ lines.push(`**ID:** ${channel.id}`);
56
+ lines.push(`**Public:** ${channelData.public ? 'Yes' : 'No'}`);
57
+ lines.push(`**Archived:** ${channelData.archived ? 'Yes' : 'No'}`);
58
+ lines.push(`**Creator:** ${creatorName ? `${creatorName} (${channel.creator})` : channel.creator}`);
59
+ lines.push(`**Created:** ${channelData.created}`);
60
+ if (channelData.description != null) {
61
+ lines.push(`**Description:** ${channelData.description}`);
62
+ }
63
+ lines.push('');
64
+ }
65
+ const textContent = lines.join('\n');
66
+ const structuredContent = {
67
+ type: 'list_channels',
68
+ workspaceId,
69
+ channels: channels.map((channel) => getChannelListData(channel, creatorLookup[channel.creator])),
70
+ totalChannels: channels.length,
71
+ };
72
+ return { textContent, structuredContent };
73
+ }
74
+ const listChannels = {
75
+ name: ToolNames.LIST_CHANNELS,
76
+ description: 'List channels in a workspace. By default returns only active channels; set includeArchived to true to also include archived channels. Returns channel IDs, names, descriptions, visibility (public/private), archive status, creators, creation dates, URLs, and colors.',
77
+ parameters: ArgsSchema,
78
+ outputSchema: ListChannelsOutputSchema.shape,
79
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
80
+ async execute(args, client) {
81
+ const { workspaceId, includeArchived = false } = args;
82
+ const result = await generateChannelsList(client, workspaceId, includeArchived);
83
+ return {
84
+ textContent: result.textContent,
85
+ structuredContent: result.structuredContent,
86
+ };
87
+ },
88
+ };
89
+ export { listChannels };
@@ -0,0 +1,66 @@
1
+ import { type CommsApi } from '@doist/comms-sdk';
2
+ import { z } from 'zod';
3
+ type ConversationData = {
4
+ id: string;
5
+ workspaceId: number;
6
+ title?: string;
7
+ userIds: number[];
8
+ participantNames?: string[];
9
+ archived: boolean;
10
+ lastActive: string;
11
+ snippet?: string;
12
+ conversationUrl: string;
13
+ };
14
+ type ListConversationsStructured = Record<string, unknown> & {
15
+ type: 'list_conversations';
16
+ workspaceId: number;
17
+ conversations: ConversationData[];
18
+ totalConversations: number;
19
+ hasMore?: boolean;
20
+ cursor?: string;
21
+ };
22
+ declare const listConversations: {
23
+ name: "list-conversations";
24
+ description: string;
25
+ parameters: {
26
+ workspaceId: z.ZodNumber;
27
+ includeArchived: z.ZodOptional<z.ZodBoolean>;
28
+ searchText: z.ZodOptional<z.ZodString>;
29
+ limit: z.ZodOptional<z.ZodNumber>;
30
+ cursor: z.ZodOptional<z.ZodString>;
31
+ };
32
+ outputSchema: {
33
+ type: z.ZodLiteral<"list_conversations">;
34
+ workspaceId: z.ZodNumber;
35
+ conversations: z.ZodArray<z.ZodObject<{
36
+ id: z.ZodString;
37
+ workspaceId: z.ZodNumber;
38
+ title: z.ZodOptional<z.ZodString>;
39
+ userIds: z.ZodArray<z.ZodNumber>;
40
+ participantNames: z.ZodOptional<z.ZodArray<z.ZodString>>;
41
+ archived: z.ZodBoolean;
42
+ lastActive: z.ZodString;
43
+ snippet: z.ZodOptional<z.ZodString>;
44
+ conversationUrl: z.ZodString;
45
+ }, z.core.$strip>>;
46
+ totalConversations: z.ZodNumber;
47
+ hasMore: z.ZodOptional<z.ZodBoolean>;
48
+ cursor: z.ZodOptional<z.ZodString>;
49
+ };
50
+ annotations: {
51
+ readOnlyHint: true;
52
+ destructiveHint: false;
53
+ idempotentHint: true;
54
+ };
55
+ execute(args: {
56
+ workspaceId: number;
57
+ includeArchived?: boolean | undefined;
58
+ searchText?: string | undefined;
59
+ limit?: number | undefined;
60
+ cursor?: string | undefined;
61
+ }, client: CommsApi): Promise<{
62
+ textContent: string;
63
+ structuredContent: ListConversationsStructured;
64
+ }>;
65
+ };
66
+ export { listConversations, type ListConversationsStructured };