@tightknitai/cli 0.1.0-alpha.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 +156 -0
- package/bin/tightknit-mcp.js +14 -0
- package/bin/tightknit.js +14 -0
- package/bin/tightknit.ts +12 -0
- package/package.json +47 -0
- package/src/cli/awards/assign.ts +28 -0
- package/src/cli/config/get.ts +27 -0
- package/src/cli/config/set.ts +23 -0
- package/src/cli/error-handler.ts +26 -0
- package/src/cli/events/create.ts +45 -0
- package/src/cli/events/delete.ts +21 -0
- package/src/cli/events/get.ts +17 -0
- package/src/cli/events/list.ts +29 -0
- package/src/cli/events/update.ts +27 -0
- package/src/cli/feeds/get.ts +17 -0
- package/src/cli/feeds/list.ts +25 -0
- package/src/cli/feeds/posts.ts +24 -0
- package/src/cli/groups/add-member.ts +24 -0
- package/src/cli/index.ts +99 -0
- package/src/cli/members/add.ts +28 -0
- package/src/cli/members/check.ts +17 -0
- package/src/cli/messages/send.ts +27 -0
- package/src/cli/posts/get.ts +17 -0
- package/src/cli/search/query.ts +25 -0
- package/src/core/client.ts +297 -0
- package/src/core/config.ts +85 -0
- package/src/core/output.ts +79 -0
- package/src/core/types.ts +79 -0
- package/src/mcp/server.ts +14 -0
- package/src/mcp/tools.ts +275 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/** Schema for a calendar event returned by the Tightknit API */
|
|
4
|
+
export const CalendarEventSchema = z.object({
|
|
5
|
+
id: z.string(),
|
|
6
|
+
title: z.string(),
|
|
7
|
+
description: z.unknown().nullable().optional(),
|
|
8
|
+
start_date: z.string(),
|
|
9
|
+
end_date: z.string(),
|
|
10
|
+
location: z.string().nullable().optional(),
|
|
11
|
+
link: z.string().nullable().optional(),
|
|
12
|
+
slug: z.string().nullable().optional(),
|
|
13
|
+
status: z.string().optional(),
|
|
14
|
+
publish_to_site: z.boolean().optional(),
|
|
15
|
+
is_unlisted: z.boolean().optional(),
|
|
16
|
+
attendees_count: z.number().optional(),
|
|
17
|
+
created_at: z.string().optional(),
|
|
18
|
+
updated_at: z.string().optional(),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export type CalendarEvent = z.infer<typeof CalendarEventSchema>;
|
|
22
|
+
|
|
23
|
+
/** Schema for a paginated list response */
|
|
24
|
+
export const PaginatedResponseSchema = <T extends z.ZodTypeAny>(itemSchema: T) =>
|
|
25
|
+
z.object({
|
|
26
|
+
data: z.array(itemSchema),
|
|
27
|
+
page: z.number().optional(),
|
|
28
|
+
per_page: z.number().optional(),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/** Schema for a feed */
|
|
32
|
+
export const FeedSchema = z.object({
|
|
33
|
+
id: z.string(),
|
|
34
|
+
name: z.string().optional(),
|
|
35
|
+
slug: z.string().optional(),
|
|
36
|
+
is_unlisted: z.boolean().optional(),
|
|
37
|
+
is_archived: z.boolean().optional(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export type Feed = z.infer<typeof FeedSchema>;
|
|
41
|
+
|
|
42
|
+
/** Schema for a post */
|
|
43
|
+
export const PostSchema = z.object({
|
|
44
|
+
id: z.string(),
|
|
45
|
+
title: z.string().nullable().optional(),
|
|
46
|
+
body: z.unknown().nullable().optional(),
|
|
47
|
+
author: z.unknown().nullable().optional(),
|
|
48
|
+
created_at: z.string().optional(),
|
|
49
|
+
updated_at: z.string().optional(),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export type Post = z.infer<typeof PostSchema>;
|
|
53
|
+
|
|
54
|
+
/** Schema for a community member */
|
|
55
|
+
export const MemberSchema = z.object({
|
|
56
|
+
id: z.string(),
|
|
57
|
+
full_name: z.string().nullable().optional(),
|
|
58
|
+
email: z.string().nullable().optional(),
|
|
59
|
+
avatar_url_original: z.string().nullable().optional(),
|
|
60
|
+
created_at: z.string().optional(),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export type Member = z.infer<typeof MemberSchema>;
|
|
64
|
+
|
|
65
|
+
/** User identifier — one of slack_user_id, email, or profile_id */
|
|
66
|
+
export const UserIdentifierSchema = z.union([
|
|
67
|
+
z.object({ slack_user_id: z.string() }),
|
|
68
|
+
z.object({ email: z.string() }),
|
|
69
|
+
z.object({ profile_id: z.string() }),
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
export type UserIdentifier = z.infer<typeof UserIdentifierSchema>;
|
|
73
|
+
|
|
74
|
+
/** Structured API error */
|
|
75
|
+
export interface ApiError {
|
|
76
|
+
error: true;
|
|
77
|
+
code: number;
|
|
78
|
+
message: string;
|
|
79
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { registerTools } from "./tools";
|
|
5
|
+
|
|
6
|
+
const server = new McpServer({
|
|
7
|
+
name: "tightknit",
|
|
8
|
+
version: "0.1.0-alpha.0",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
registerTools(server);
|
|
12
|
+
|
|
13
|
+
const transport = new StdioServerTransport();
|
|
14
|
+
await server.connect(transport);
|
package/src/mcp/tools.ts
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import {
|
|
4
|
+
listEvents,
|
|
5
|
+
getEvent,
|
|
6
|
+
createEvent,
|
|
7
|
+
deleteEvent,
|
|
8
|
+
updateAttendee,
|
|
9
|
+
assignAward,
|
|
10
|
+
listFeeds,
|
|
11
|
+
getFeed,
|
|
12
|
+
listPostsInFeed,
|
|
13
|
+
getPost,
|
|
14
|
+
addMember,
|
|
15
|
+
checkMembership,
|
|
16
|
+
sendMessage,
|
|
17
|
+
addUserToGroup,
|
|
18
|
+
search,
|
|
19
|
+
} from "../core/client";
|
|
20
|
+
import { getConfigValue, setConfigValue } from "../core/config";
|
|
21
|
+
|
|
22
|
+
const UserIdentifierSchema = z.union([
|
|
23
|
+
z.object({ slack_user_id: z.string() }),
|
|
24
|
+
z.object({ email: z.string() }),
|
|
25
|
+
z.object({ profile_id: z.string() }),
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
/** Register all Tightknit tools on the MCP server */
|
|
29
|
+
export function registerTools(server: McpServer): void {
|
|
30
|
+
// --- Calendar Events ---
|
|
31
|
+
|
|
32
|
+
server.tool(
|
|
33
|
+
"tightknit_events_list",
|
|
34
|
+
"List calendar events with pagination and filters",
|
|
35
|
+
{
|
|
36
|
+
page: z.number().optional().describe("Page number (0-indexed)"),
|
|
37
|
+
per_page: z.number().optional().describe("Records per page"),
|
|
38
|
+
time_filter: z.enum(["upcoming", "past"]).optional().describe("Filter by upcoming or past"),
|
|
39
|
+
status: z.enum(["draft", "needs_approval", "published"]).optional().describe("Filter by status"),
|
|
40
|
+
feed_id: z.string().optional().describe("Filter by feed ID"),
|
|
41
|
+
tag_ids: z.string().optional().describe("Comma-separated tag UUIDs"),
|
|
42
|
+
},
|
|
43
|
+
async (params) => {
|
|
44
|
+
const result = await listEvents(params);
|
|
45
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
server.tool(
|
|
50
|
+
"tightknit_events_get",
|
|
51
|
+
"Retrieve a calendar event by ID",
|
|
52
|
+
{ id: z.string().describe("Calendar event ID") },
|
|
53
|
+
async ({ id }) => {
|
|
54
|
+
const result = await getEvent(id);
|
|
55
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
server.tool(
|
|
60
|
+
"tightknit_events_create",
|
|
61
|
+
"Create a new calendar event",
|
|
62
|
+
{
|
|
63
|
+
title: z.string().describe("Event title (3-70 chars)"),
|
|
64
|
+
description: z.string().describe("Event description"),
|
|
65
|
+
start_date: z.string().describe("Start date/time (ISO 8601)"),
|
|
66
|
+
end_date: z.string().describe("End date/time (ISO 8601)"),
|
|
67
|
+
location: z.string().optional().describe("Event location"),
|
|
68
|
+
link: z.string().optional().describe("Event link/URL"),
|
|
69
|
+
slug: z.string().optional().describe("URL slug"),
|
|
70
|
+
status: z.enum(["needs_approval", "published"]).optional().describe("Event status"),
|
|
71
|
+
publish_to_site: z.boolean().optional().describe("Publish to companion site"),
|
|
72
|
+
enable_registration_button: z.boolean().optional().describe("Enable registration button"),
|
|
73
|
+
triggers_webhooks: z.boolean().optional().describe("Trigger webhooks"),
|
|
74
|
+
},
|
|
75
|
+
async (input) => {
|
|
76
|
+
const result = await createEvent(input);
|
|
77
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
server.tool(
|
|
82
|
+
"tightknit_events_delete",
|
|
83
|
+
"Delete a calendar event by ID",
|
|
84
|
+
{ id: z.string().describe("Calendar event ID") },
|
|
85
|
+
async ({ id }) => {
|
|
86
|
+
await deleteEvent(id);
|
|
87
|
+
return { content: [{ type: "text" as const, text: JSON.stringify({ success: true }, null, 2) }] };
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
server.tool(
|
|
92
|
+
"tightknit_events_update_attendee",
|
|
93
|
+
"Update a calendar event attendee",
|
|
94
|
+
{
|
|
95
|
+
event_id: z.string().describe("Calendar event ID"),
|
|
96
|
+
user: UserIdentifierSchema.describe("User identifier (email, slack_user_id, or profile_id)"),
|
|
97
|
+
personal_join_link: z.string().optional().describe("Personal join link for the attendee"),
|
|
98
|
+
},
|
|
99
|
+
async ({ event_id, user, personal_join_link }) => {
|
|
100
|
+
const result = await updateAttendee(event_id, { user, personal_join_link });
|
|
101
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// --- Awards ---
|
|
106
|
+
|
|
107
|
+
server.tool(
|
|
108
|
+
"tightknit_awards_assign",
|
|
109
|
+
"Assign an award to a user and send a Slack notification",
|
|
110
|
+
{
|
|
111
|
+
award_id: z.string().describe("Award UUID"),
|
|
112
|
+
recipient: UserIdentifierSchema.describe("Recipient user identifier"),
|
|
113
|
+
sender: UserIdentifierSchema.optional().describe("Sender user identifier"),
|
|
114
|
+
send_anonymously: z.boolean().optional().describe("Send anonymously"),
|
|
115
|
+
},
|
|
116
|
+
async ({ award_id, recipient, sender, send_anonymously }) => {
|
|
117
|
+
const result = await assignAward(award_id, { recipient, sender, send_anonymously });
|
|
118
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// --- Feeds ---
|
|
123
|
+
|
|
124
|
+
server.tool(
|
|
125
|
+
"tightknit_feeds_list",
|
|
126
|
+
"List feeds with pagination",
|
|
127
|
+
{
|
|
128
|
+
page: z.number().optional().describe("Page number (0-indexed)"),
|
|
129
|
+
per_page: z.number().optional().describe("Records per page"),
|
|
130
|
+
is_unlisted: z.boolean().optional().describe("Filter to unlisted feeds"),
|
|
131
|
+
is_archived: z.boolean().optional().describe("Filter to archived feeds"),
|
|
132
|
+
},
|
|
133
|
+
async (params) => {
|
|
134
|
+
const result = await listFeeds(params);
|
|
135
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
server.tool(
|
|
140
|
+
"tightknit_feeds_get",
|
|
141
|
+
"Retrieve a feed by ID",
|
|
142
|
+
{ feed_id: z.string().describe("Feed ID") },
|
|
143
|
+
async ({ feed_id }) => {
|
|
144
|
+
const result = await getFeed(feed_id);
|
|
145
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
server.tool(
|
|
150
|
+
"tightknit_feeds_posts",
|
|
151
|
+
"List posts in a feed",
|
|
152
|
+
{
|
|
153
|
+
feed_id: z.string().describe('Feed ID or "home" for the Home feed'),
|
|
154
|
+
page: z.number().optional().describe("Page number (0-indexed)"),
|
|
155
|
+
per_page: z.number().optional().describe("Records per page"),
|
|
156
|
+
sort: z.enum(["oldest", "newest", "most-recent-activity"]).optional().describe("Sort method"),
|
|
157
|
+
},
|
|
158
|
+
async ({ feed_id, ...params }) => {
|
|
159
|
+
const result = await listPostsInFeed(feed_id, params);
|
|
160
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// --- Posts ---
|
|
165
|
+
|
|
166
|
+
server.tool(
|
|
167
|
+
"tightknit_posts_get",
|
|
168
|
+
"Retrieve a post by ID",
|
|
169
|
+
{ post_id: z.string().describe("Post ID") },
|
|
170
|
+
async ({ post_id }) => {
|
|
171
|
+
const result = await getPost(post_id);
|
|
172
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
// --- Members ---
|
|
177
|
+
|
|
178
|
+
server.tool(
|
|
179
|
+
"tightknit_members_add",
|
|
180
|
+
"Add a member to the community (Enterprise plan required)",
|
|
181
|
+
{
|
|
182
|
+
email: z.string().describe("Member email address"),
|
|
183
|
+
full_name: z.string().describe("Member full name"),
|
|
184
|
+
avatar_url_original: z.string().optional().describe("Avatar image URL"),
|
|
185
|
+
},
|
|
186
|
+
async (input) => {
|
|
187
|
+
const result = await addMember(input);
|
|
188
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
189
|
+
}
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
server.tool(
|
|
193
|
+
"tightknit_members_check",
|
|
194
|
+
"Check if an email is a community member",
|
|
195
|
+
{ email: z.string().describe("Email address to check") },
|
|
196
|
+
async ({ email }) => {
|
|
197
|
+
const result = await checkMembership(email);
|
|
198
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
// --- Messages ---
|
|
203
|
+
|
|
204
|
+
server.tool(
|
|
205
|
+
"tightknit_messages_send",
|
|
206
|
+
"Send a Slack message to a channel or user",
|
|
207
|
+
{
|
|
208
|
+
channel: z.string().describe("Slack channel ID or user ID"),
|
|
209
|
+
text: z.string().describe("Message text (plain text or Slack mrkdwn)"),
|
|
210
|
+
thread_ts: z.string().optional().describe("Thread timestamp to reply to"),
|
|
211
|
+
},
|
|
212
|
+
async (input) => {
|
|
213
|
+
const result = await sendMessage(input);
|
|
214
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
// --- Groups ---
|
|
219
|
+
|
|
220
|
+
server.tool(
|
|
221
|
+
"tightknit_groups_add_member",
|
|
222
|
+
"Add a user to a group",
|
|
223
|
+
{
|
|
224
|
+
group_id: z.string().describe("Group ID"),
|
|
225
|
+
user: UserIdentifierSchema.describe("User identifier"),
|
|
226
|
+
},
|
|
227
|
+
async ({ group_id, user }) => {
|
|
228
|
+
const result = await addUserToGroup(group_id, { user });
|
|
229
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// --- Search ---
|
|
234
|
+
|
|
235
|
+
server.tool(
|
|
236
|
+
"tightknit_search",
|
|
237
|
+
"[Beta] Search documents by query",
|
|
238
|
+
{
|
|
239
|
+
q: z.string().describe("Search query string"),
|
|
240
|
+
type: z.enum(["post", "comment", "content_resource"]).describe("Entity type to search"),
|
|
241
|
+
page: z.number().optional().describe("Page number (0-indexed)"),
|
|
242
|
+
per_page: z.number().optional().describe("Records per page"),
|
|
243
|
+
},
|
|
244
|
+
async (params) => {
|
|
245
|
+
const result = await search(params);
|
|
246
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
// --- Config ---
|
|
251
|
+
|
|
252
|
+
server.tool(
|
|
253
|
+
"tightknit_config_get",
|
|
254
|
+
"Get a CLI configuration value",
|
|
255
|
+
{ key: z.string().describe("Config key (api-key, community-id, default-output)") },
|
|
256
|
+
async ({ key }) => {
|
|
257
|
+
const value = getConfigValue(key);
|
|
258
|
+
const displayValue = key === "api-key" && value ? `${value.slice(0, 8)}...` : value;
|
|
259
|
+
return { content: [{ type: "text" as const, text: JSON.stringify({ key, value: displayValue }, null, 2) }] };
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
server.tool(
|
|
264
|
+
"tightknit_config_set",
|
|
265
|
+
"Set a CLI configuration value",
|
|
266
|
+
{
|
|
267
|
+
key: z.string().describe("Config key (api-key, community-id, default-output)"),
|
|
268
|
+
value: z.string().describe("Config value"),
|
|
269
|
+
},
|
|
270
|
+
async ({ key, value }) => {
|
|
271
|
+
setConfigValue(key, value);
|
|
272
|
+
return { content: [{ type: "text" as const, text: JSON.stringify({ success: true, key, value: key === "api-key" ? "***" : value }, null, 2) }] };
|
|
273
|
+
}
|
|
274
|
+
);
|
|
275
|
+
}
|