reddit-mcp-server 1.0.6 → 1.0.7
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/CHANGELOG.md +34 -0
- package/package.json +7 -1
- package/.claude/settings.local.json +0 -35
- package/.env.example +0 -7
- package/.prettierignore +0 -7
- package/.prettierrc +0 -8
- package/CLAUDE.md +0 -104
- package/Dockerfile +0 -17
- package/ROADMAP.md +0 -77
- package/eslint.config.js +0 -76
- package/pnpm-workspace.yaml +0 -2
- package/smithery.yaml +0 -37
- package/src/bin.ts +0 -7
- package/src/client/__tests__/reddit-client.test.ts +0 -478
- package/src/client/reddit-client.ts +0 -638
- package/src/index.ts +0 -518
- package/src/tools/__tests__/comment-tools.test.ts +0 -119
- package/src/tools/__tests__/search-tools.test.ts +0 -100
- package/src/tools/__tests__/user-tools.test.ts +0 -258
- package/src/tools/comment-tools.ts +0 -61
- package/src/tools/index.ts +0 -5
- package/src/tools/post-tools.ts +0 -178
- package/src/tools/search-tools.ts +0 -67
- package/src/tools/subreddit-tools.ts +0 -91
- package/src/tools/user-tools.ts +0 -147
- package/src/types.ts +0 -148
- package/src/utils/formatters.ts +0 -316
- package/tsconfig.json +0 -19
- package/tsup.config.ts +0 -12
- package/vitest.config.ts +0 -20
package/src/index.ts
DELETED
|
@@ -1,518 +0,0 @@
|
|
|
1
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js"
|
|
2
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
3
|
-
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from "@modelcontextprotocol/sdk/types.js"
|
|
4
|
-
import { initializeRedditClient } from "./client/reddit-client"
|
|
5
|
-
import * as tools from "./tools"
|
|
6
|
-
import dotenv from "dotenv"
|
|
7
|
-
|
|
8
|
-
// Load environment variables
|
|
9
|
-
dotenv.config()
|
|
10
|
-
|
|
11
|
-
class RedditServer {
|
|
12
|
-
private server: Server
|
|
13
|
-
|
|
14
|
-
constructor() {
|
|
15
|
-
this.server = new Server(
|
|
16
|
-
{
|
|
17
|
-
name: "reddit-mcp-server",
|
|
18
|
-
version: "0.1.0",
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
capabilities: {
|
|
22
|
-
tools: {},
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
// Initialize the Reddit client
|
|
28
|
-
this.initializeRedditClient()
|
|
29
|
-
|
|
30
|
-
this.setupToolHandlers()
|
|
31
|
-
|
|
32
|
-
this.server.onerror = async (error) => {
|
|
33
|
-
await this.server.sendLoggingMessage({
|
|
34
|
-
level: "error",
|
|
35
|
-
logger: "reddit-server",
|
|
36
|
-
data: `Server error: ${error}`,
|
|
37
|
-
})
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
process.on("SIGINT", async () => {
|
|
41
|
-
await this.server.close()
|
|
42
|
-
process.exit(0)
|
|
43
|
-
})
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
private initializeRedditClient() {
|
|
47
|
-
const clientId = process.env.REDDIT_CLIENT_ID
|
|
48
|
-
const clientSecret = process.env.REDDIT_CLIENT_SECRET
|
|
49
|
-
const userAgent = process.env.REDDIT_USER_AGENT || "RedditMCPServer/0.1.0"
|
|
50
|
-
const username = process.env.REDDIT_USERNAME
|
|
51
|
-
const password = process.env.REDDIT_PASSWORD
|
|
52
|
-
|
|
53
|
-
if (!clientId || !clientSecret) {
|
|
54
|
-
// Can't use server logging here as server isn't initialized yet
|
|
55
|
-
// Exit silently with error code
|
|
56
|
-
process.exit(1)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
initializeRedditClient({
|
|
61
|
-
clientId,
|
|
62
|
-
clientSecret,
|
|
63
|
-
userAgent,
|
|
64
|
-
username,
|
|
65
|
-
password,
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
// Client initialized successfully
|
|
69
|
-
} catch {
|
|
70
|
-
// Can't use server logging here as server isn't connected yet
|
|
71
|
-
// Exit silently with error code
|
|
72
|
-
process.exit(1)
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
private setupToolHandlers() {
|
|
77
|
-
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
78
|
-
tools: [
|
|
79
|
-
{
|
|
80
|
-
name: "test_reddit_mcp_server",
|
|
81
|
-
description: "Test the Reddit MCP Server",
|
|
82
|
-
inputSchema: {
|
|
83
|
-
type: "object",
|
|
84
|
-
properties: {
|
|
85
|
-
// No input parameters, this will just return a test message
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
name: "get_reddit_post",
|
|
91
|
-
description: "Get a Reddit post",
|
|
92
|
-
inputSchema: {
|
|
93
|
-
type: "object",
|
|
94
|
-
properties: {
|
|
95
|
-
subreddit: {
|
|
96
|
-
type: "string",
|
|
97
|
-
description: "The subreddit to fetch posts from",
|
|
98
|
-
},
|
|
99
|
-
post_id: {
|
|
100
|
-
type: "string",
|
|
101
|
-
description: "The ID of the post to fetch",
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
required: ["subreddit", "post_id"],
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
name: "get_top_posts",
|
|
109
|
-
description: "Get top posts from a subreddit",
|
|
110
|
-
inputSchema: {
|
|
111
|
-
type: "object",
|
|
112
|
-
properties: {
|
|
113
|
-
subreddit: {
|
|
114
|
-
type: "string",
|
|
115
|
-
description: "Name of the subreddit",
|
|
116
|
-
},
|
|
117
|
-
time_filter: {
|
|
118
|
-
type: "string",
|
|
119
|
-
description: "Time period to filter posts (e.g. 'day', 'week', 'month', 'year', 'all')",
|
|
120
|
-
enum: ["day", "week", "month", "year", "all"],
|
|
121
|
-
default: "week",
|
|
122
|
-
},
|
|
123
|
-
limit: {
|
|
124
|
-
type: "integer",
|
|
125
|
-
description: "Number of posts to fetch",
|
|
126
|
-
default: 10,
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
required: ["subreddit"],
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
name: "get_user_info",
|
|
134
|
-
description: "Get information about a Reddit user",
|
|
135
|
-
inputSchema: {
|
|
136
|
-
type: "object",
|
|
137
|
-
properties: {
|
|
138
|
-
username: {
|
|
139
|
-
type: "string",
|
|
140
|
-
description: "The username of the Reddit user to get info for",
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
required: ["username"],
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
name: "get_subreddit_info",
|
|
148
|
-
description: "Get information about a subreddit",
|
|
149
|
-
inputSchema: {
|
|
150
|
-
type: "object",
|
|
151
|
-
properties: {
|
|
152
|
-
subreddit_name: {
|
|
153
|
-
type: "string",
|
|
154
|
-
description: "Name of the subreddit",
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
required: ["subreddit_name"],
|
|
158
|
-
},
|
|
159
|
-
},
|
|
160
|
-
{
|
|
161
|
-
name: "get_trending_subreddits",
|
|
162
|
-
description: "Get currently trending subreddits",
|
|
163
|
-
inputSchema: {
|
|
164
|
-
type: "object",
|
|
165
|
-
properties: {},
|
|
166
|
-
},
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
name: "create_post",
|
|
170
|
-
description: "Create a new post in a subreddit",
|
|
171
|
-
inputSchema: {
|
|
172
|
-
type: "object",
|
|
173
|
-
properties: {
|
|
174
|
-
subreddit: {
|
|
175
|
-
type: "string",
|
|
176
|
-
description: "Name of the subreddit to post in",
|
|
177
|
-
},
|
|
178
|
-
title: {
|
|
179
|
-
type: "string",
|
|
180
|
-
description: "Title of the post",
|
|
181
|
-
},
|
|
182
|
-
content: {
|
|
183
|
-
type: "string",
|
|
184
|
-
description: "Content of the post (text for self posts, URL for link posts)",
|
|
185
|
-
},
|
|
186
|
-
is_self: {
|
|
187
|
-
type: "boolean",
|
|
188
|
-
description: "Whether this is a self (text) post (true) or link post (false)",
|
|
189
|
-
default: true,
|
|
190
|
-
},
|
|
191
|
-
},
|
|
192
|
-
required: ["subreddit", "title", "content"],
|
|
193
|
-
},
|
|
194
|
-
},
|
|
195
|
-
{
|
|
196
|
-
name: "reply_to_post",
|
|
197
|
-
description: "Post a reply to an existing Reddit post",
|
|
198
|
-
inputSchema: {
|
|
199
|
-
type: "object",
|
|
200
|
-
properties: {
|
|
201
|
-
post_id: {
|
|
202
|
-
type: "string",
|
|
203
|
-
description: "The ID of the post to reply to",
|
|
204
|
-
},
|
|
205
|
-
content: {
|
|
206
|
-
type: "string",
|
|
207
|
-
description: "The content of the reply",
|
|
208
|
-
},
|
|
209
|
-
subreddit: {
|
|
210
|
-
type: "string",
|
|
211
|
-
description: "The subreddit name if known (for validation)",
|
|
212
|
-
},
|
|
213
|
-
},
|
|
214
|
-
required: ["post_id", "content"],
|
|
215
|
-
},
|
|
216
|
-
},
|
|
217
|
-
{
|
|
218
|
-
name: "search_reddit",
|
|
219
|
-
description: "Search for posts on Reddit",
|
|
220
|
-
inputSchema: {
|
|
221
|
-
type: "object",
|
|
222
|
-
properties: {
|
|
223
|
-
query: {
|
|
224
|
-
type: "string",
|
|
225
|
-
description: "The search query",
|
|
226
|
-
},
|
|
227
|
-
subreddit: {
|
|
228
|
-
type: "string",
|
|
229
|
-
description: "Search within a specific subreddit (optional)",
|
|
230
|
-
},
|
|
231
|
-
sort: {
|
|
232
|
-
type: "string",
|
|
233
|
-
description: "Sort order: relevance, hot, top, new, comments",
|
|
234
|
-
enum: ["relevance", "hot", "top", "new", "comments"],
|
|
235
|
-
default: "relevance",
|
|
236
|
-
},
|
|
237
|
-
time_filter: {
|
|
238
|
-
type: "string",
|
|
239
|
-
description: "Time filter: hour, day, week, month, year, all",
|
|
240
|
-
enum: ["hour", "day", "week", "month", "year", "all"],
|
|
241
|
-
default: "all",
|
|
242
|
-
},
|
|
243
|
-
limit: {
|
|
244
|
-
type: "number",
|
|
245
|
-
description: "Maximum number of results to return",
|
|
246
|
-
minimum: 1,
|
|
247
|
-
maximum: 100,
|
|
248
|
-
default: 10,
|
|
249
|
-
},
|
|
250
|
-
type: {
|
|
251
|
-
type: "string",
|
|
252
|
-
description: "Type of content: link (posts), sr (subreddits), user (users)",
|
|
253
|
-
enum: ["link", "sr", "user"],
|
|
254
|
-
default: "link",
|
|
255
|
-
},
|
|
256
|
-
},
|
|
257
|
-
required: ["query"],
|
|
258
|
-
},
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
name: "get_post_comments",
|
|
262
|
-
description: "Get comments for a specific Reddit post",
|
|
263
|
-
inputSchema: {
|
|
264
|
-
type: "object",
|
|
265
|
-
properties: {
|
|
266
|
-
post_id: {
|
|
267
|
-
type: "string",
|
|
268
|
-
description: "The ID of the post",
|
|
269
|
-
},
|
|
270
|
-
subreddit: {
|
|
271
|
-
type: "string",
|
|
272
|
-
description: "The subreddit where the post is located",
|
|
273
|
-
},
|
|
274
|
-
sort: {
|
|
275
|
-
type: "string",
|
|
276
|
-
description: "Comment sort order: best, top, new, controversial, old, qa",
|
|
277
|
-
enum: ["best", "top", "new", "controversial", "old", "qa"],
|
|
278
|
-
default: "best",
|
|
279
|
-
},
|
|
280
|
-
limit: {
|
|
281
|
-
type: "number",
|
|
282
|
-
description: "Maximum number of comments to load",
|
|
283
|
-
minimum: 1,
|
|
284
|
-
maximum: 500,
|
|
285
|
-
default: 100,
|
|
286
|
-
},
|
|
287
|
-
},
|
|
288
|
-
required: ["post_id", "subreddit"],
|
|
289
|
-
},
|
|
290
|
-
},
|
|
291
|
-
{
|
|
292
|
-
name: "get_user_posts",
|
|
293
|
-
description: "Get posts submitted by a specific user",
|
|
294
|
-
inputSchema: {
|
|
295
|
-
type: "object",
|
|
296
|
-
properties: {
|
|
297
|
-
username: {
|
|
298
|
-
type: "string",
|
|
299
|
-
description: "The username to get posts for",
|
|
300
|
-
},
|
|
301
|
-
sort: {
|
|
302
|
-
type: "string",
|
|
303
|
-
description: "Sort order: new, hot, top, controversial",
|
|
304
|
-
enum: ["new", "hot", "top", "controversial"],
|
|
305
|
-
default: "new",
|
|
306
|
-
},
|
|
307
|
-
time_filter: {
|
|
308
|
-
type: "string",
|
|
309
|
-
description: "Time filter for top/controversial: hour, day, week, month, year, all",
|
|
310
|
-
enum: ["hour", "day", "week", "month", "year", "all"],
|
|
311
|
-
default: "all",
|
|
312
|
-
},
|
|
313
|
-
limit: {
|
|
314
|
-
type: "number",
|
|
315
|
-
description: "Maximum number of posts to return",
|
|
316
|
-
minimum: 1,
|
|
317
|
-
maximum: 100,
|
|
318
|
-
default: 10,
|
|
319
|
-
},
|
|
320
|
-
},
|
|
321
|
-
required: ["username"],
|
|
322
|
-
},
|
|
323
|
-
},
|
|
324
|
-
{
|
|
325
|
-
name: "get_user_comments",
|
|
326
|
-
description: "Get comments made by a specific user",
|
|
327
|
-
inputSchema: {
|
|
328
|
-
type: "object",
|
|
329
|
-
properties: {
|
|
330
|
-
username: {
|
|
331
|
-
type: "string",
|
|
332
|
-
description: "The username to get comments for",
|
|
333
|
-
},
|
|
334
|
-
sort: {
|
|
335
|
-
type: "string",
|
|
336
|
-
description: "Sort order: new, hot, top, controversial",
|
|
337
|
-
enum: ["new", "hot", "top", "controversial"],
|
|
338
|
-
default: "new",
|
|
339
|
-
},
|
|
340
|
-
time_filter: {
|
|
341
|
-
type: "string",
|
|
342
|
-
description: "Time filter for top/controversial: hour, day, week, month, year, all",
|
|
343
|
-
enum: ["hour", "day", "week", "month", "year", "all"],
|
|
344
|
-
default: "all",
|
|
345
|
-
},
|
|
346
|
-
limit: {
|
|
347
|
-
type: "number",
|
|
348
|
-
description: "Maximum number of comments to return",
|
|
349
|
-
minimum: 1,
|
|
350
|
-
maximum: 100,
|
|
351
|
-
default: 10,
|
|
352
|
-
},
|
|
353
|
-
},
|
|
354
|
-
required: ["username"],
|
|
355
|
-
},
|
|
356
|
-
},
|
|
357
|
-
],
|
|
358
|
-
}))
|
|
359
|
-
|
|
360
|
-
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
361
|
-
try {
|
|
362
|
-
const toolName = request.params.name
|
|
363
|
-
const toolParams = request.params.arguments || {}
|
|
364
|
-
|
|
365
|
-
// Log tool call
|
|
366
|
-
await this.server.sendLoggingMessage({
|
|
367
|
-
level: "debug",
|
|
368
|
-
logger: "reddit-server",
|
|
369
|
-
data: `Tool call: ${toolName}`,
|
|
370
|
-
})
|
|
371
|
-
|
|
372
|
-
switch (toolName) {
|
|
373
|
-
case "test_reddit_mcp_server":
|
|
374
|
-
return {
|
|
375
|
-
content: [
|
|
376
|
-
{
|
|
377
|
-
type: "text",
|
|
378
|
-
text: "Hello, world! The Reddit MCP Server is working correctly.",
|
|
379
|
-
},
|
|
380
|
-
],
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
case "get_reddit_post":
|
|
384
|
-
return await tools.getRedditPost(toolParams as { subreddit: string; post_id: string })
|
|
385
|
-
|
|
386
|
-
case "get_top_posts":
|
|
387
|
-
return await tools.getTopPosts(
|
|
388
|
-
toolParams as {
|
|
389
|
-
subreddit: string
|
|
390
|
-
time_filter?: string
|
|
391
|
-
limit?: number
|
|
392
|
-
},
|
|
393
|
-
)
|
|
394
|
-
|
|
395
|
-
case "get_user_info":
|
|
396
|
-
return await tools.getUserInfo(toolParams as { username: string })
|
|
397
|
-
|
|
398
|
-
case "get_subreddit_info":
|
|
399
|
-
return await tools.getSubredditInfo(toolParams as { subreddit_name: string })
|
|
400
|
-
|
|
401
|
-
case "get_trending_subreddits":
|
|
402
|
-
return await tools.getTrendingSubreddits()
|
|
403
|
-
|
|
404
|
-
case "create_post":
|
|
405
|
-
return await tools.createPost(
|
|
406
|
-
toolParams as {
|
|
407
|
-
subreddit: string
|
|
408
|
-
title: string
|
|
409
|
-
content: string
|
|
410
|
-
is_self?: boolean
|
|
411
|
-
},
|
|
412
|
-
)
|
|
413
|
-
|
|
414
|
-
case "reply_to_post":
|
|
415
|
-
return await tools.replyToPost(
|
|
416
|
-
toolParams as {
|
|
417
|
-
post_id: string
|
|
418
|
-
content: string
|
|
419
|
-
subreddit?: string
|
|
420
|
-
},
|
|
421
|
-
)
|
|
422
|
-
|
|
423
|
-
case "search_reddit":
|
|
424
|
-
return await tools.searchReddit(
|
|
425
|
-
toolParams as {
|
|
426
|
-
query: string
|
|
427
|
-
subreddit?: string
|
|
428
|
-
sort?: string
|
|
429
|
-
time_filter?: string
|
|
430
|
-
limit?: number
|
|
431
|
-
type?: string
|
|
432
|
-
},
|
|
433
|
-
)
|
|
434
|
-
|
|
435
|
-
case "get_post_comments":
|
|
436
|
-
return await tools.getPostComments(
|
|
437
|
-
toolParams as {
|
|
438
|
-
post_id: string
|
|
439
|
-
subreddit: string
|
|
440
|
-
sort?: string
|
|
441
|
-
limit?: number
|
|
442
|
-
},
|
|
443
|
-
)
|
|
444
|
-
|
|
445
|
-
case "get_user_posts":
|
|
446
|
-
return await tools.getUserPosts(
|
|
447
|
-
toolParams as {
|
|
448
|
-
username: string
|
|
449
|
-
sort?: string
|
|
450
|
-
time_filter?: string
|
|
451
|
-
limit?: number
|
|
452
|
-
},
|
|
453
|
-
)
|
|
454
|
-
|
|
455
|
-
case "get_user_comments":
|
|
456
|
-
return await tools.getUserComments(
|
|
457
|
-
toolParams as {
|
|
458
|
-
username: string
|
|
459
|
-
sort?: string
|
|
460
|
-
time_filter?: string
|
|
461
|
-
limit?: number
|
|
462
|
-
},
|
|
463
|
-
)
|
|
464
|
-
|
|
465
|
-
default:
|
|
466
|
-
throw new McpError(ErrorCode.MethodNotFound, `Tool with name ${toolName} not found`)
|
|
467
|
-
}
|
|
468
|
-
} catch (error: unknown) {
|
|
469
|
-
if (error instanceof Error) {
|
|
470
|
-
await this.server.sendLoggingMessage({
|
|
471
|
-
level: "error",
|
|
472
|
-
logger: "reddit-server",
|
|
473
|
-
data: `Error calling tool: ${error.message}`,
|
|
474
|
-
})
|
|
475
|
-
|
|
476
|
-
throw new McpError(ErrorCode.InternalError, `Failed to fetch data: ${error.message}`)
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
throw error
|
|
480
|
-
}
|
|
481
|
-
})
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
async run() {
|
|
485
|
-
const transport = new StdioServerTransport()
|
|
486
|
-
await this.server.connect(transport)
|
|
487
|
-
|
|
488
|
-
// Log server startup
|
|
489
|
-
await this.server.sendLoggingMessage({
|
|
490
|
-
level: "info",
|
|
491
|
-
logger: "reddit-server",
|
|
492
|
-
data: "Reddit MCP Server is running",
|
|
493
|
-
})
|
|
494
|
-
|
|
495
|
-
// Log authentication status
|
|
496
|
-
const username = process.env.REDDIT_USERNAME
|
|
497
|
-
const password = process.env.REDDIT_PASSWORD
|
|
498
|
-
await this.server.sendLoggingMessage({
|
|
499
|
-
level: "info",
|
|
500
|
-
logger: "reddit-server",
|
|
501
|
-
data:
|
|
502
|
-
username && password
|
|
503
|
-
? `Authenticated as user: ${username}`
|
|
504
|
-
: "Running in read-only mode (no user authentication)",
|
|
505
|
-
})
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
// Only run if this is the main module
|
|
510
|
-
if (require.main === module) {
|
|
511
|
-
const server = new RedditServer()
|
|
512
|
-
server.run().catch(() => {
|
|
513
|
-
// Exit silently on error
|
|
514
|
-
process.exit(1)
|
|
515
|
-
})
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
export { RedditServer }
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest"
|
|
2
|
-
import { getPostComments } from "../comment-tools"
|
|
3
|
-
import { getRedditClient } from "../../client/reddit-client"
|
|
4
|
-
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js"
|
|
5
|
-
|
|
6
|
-
vi.mock("../../client/reddit-client")
|
|
7
|
-
|
|
8
|
-
describe("getPostComments", () => {
|
|
9
|
-
const mockRedditClient = {
|
|
10
|
-
getPostComments: vi.fn(),
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
vi.clearAllMocks()
|
|
15
|
-
vi.mocked(getRedditClient).mockReturnValue(mockRedditClient as any)
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it("should fetch post comments with default parameters", async () => {
|
|
19
|
-
const mockData = {
|
|
20
|
-
post: {
|
|
21
|
-
id: "test123",
|
|
22
|
-
title: "Test Post Title",
|
|
23
|
-
author: "testauthor",
|
|
24
|
-
subreddit: "testsubreddit",
|
|
25
|
-
score: 500,
|
|
26
|
-
upvoteRatio: 0.9,
|
|
27
|
-
numComments: 25,
|
|
28
|
-
createdUtc: 1700000000,
|
|
29
|
-
selftext: "This is the post content",
|
|
30
|
-
permalink: "/r/testsubreddit/comments/test123",
|
|
31
|
-
over18: false,
|
|
32
|
-
spoiler: false,
|
|
33
|
-
},
|
|
34
|
-
comments: [
|
|
35
|
-
{
|
|
36
|
-
id: "comment1",
|
|
37
|
-
author: "commenter1",
|
|
38
|
-
body: "This is a comment",
|
|
39
|
-
score: 10,
|
|
40
|
-
createdUtc: 1700000100,
|
|
41
|
-
edited: false,
|
|
42
|
-
isSubmitter: false,
|
|
43
|
-
depth: 0,
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
id: "comment2",
|
|
47
|
-
author: "testauthor",
|
|
48
|
-
body: "This is a reply from OP",
|
|
49
|
-
score: 5,
|
|
50
|
-
createdUtc: 1700000200,
|
|
51
|
-
edited: true,
|
|
52
|
-
isSubmitter: true,
|
|
53
|
-
depth: 1,
|
|
54
|
-
},
|
|
55
|
-
],
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
mockRedditClient.getPostComments.mockResolvedValue(mockData)
|
|
59
|
-
|
|
60
|
-
const result = await getPostComments({
|
|
61
|
-
post_id: "test123",
|
|
62
|
-
subreddit: "testsubreddit",
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
expect(mockRedditClient.getPostComments).toHaveBeenCalledWith("test123", "testsubreddit", {
|
|
66
|
-
sort: "best",
|
|
67
|
-
limit: 100,
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
expect(result.content).toHaveLength(1)
|
|
71
|
-
expect(result.content[0].type).toBe("text")
|
|
72
|
-
expect(result.content[0].text).toContain("Comments for: Test Post Title")
|
|
73
|
-
expect(result.content[0].text).toContain("**u/commenter1**")
|
|
74
|
-
expect(result.content[0].text).toContain("**u/testauthor** **[OP]**")
|
|
75
|
-
expect(result.content[0].text).toContain("└─")
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
it("should fetch comments with custom parameters", async () => {
|
|
79
|
-
mockRedditClient.getPostComments.mockResolvedValue({ post: {}, comments: [] })
|
|
80
|
-
|
|
81
|
-
await getPostComments({
|
|
82
|
-
post_id: "test456",
|
|
83
|
-
subreddit: "programming",
|
|
84
|
-
sort: "controversial",
|
|
85
|
-
limit: 50,
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
expect(mockRedditClient.getPostComments).toHaveBeenCalledWith("test456", "programming", {
|
|
89
|
-
sort: "controversial",
|
|
90
|
-
limit: 50,
|
|
91
|
-
})
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it("should throw error if Reddit client is not initialized", async () => {
|
|
95
|
-
vi.mocked(getRedditClient).mockReturnValue(null)
|
|
96
|
-
|
|
97
|
-
await expect(getPostComments({ post_id: "test", subreddit: "test" })).rejects.toThrow(
|
|
98
|
-
new McpError(ErrorCode.InternalError, "Reddit client not initialized"),
|
|
99
|
-
)
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it("should throw error if required parameters are missing", async () => {
|
|
103
|
-
await expect(getPostComments({ post_id: "", subreddit: "test" })).rejects.toThrow(
|
|
104
|
-
new McpError(ErrorCode.InvalidParams, "post_id and subreddit are required"),
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
await expect(getPostComments({ post_id: "test", subreddit: "" })).rejects.toThrow(
|
|
108
|
-
new McpError(ErrorCode.InvalidParams, "post_id and subreddit are required"),
|
|
109
|
-
)
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
it("should handle API errors", async () => {
|
|
113
|
-
mockRedditClient.getPostComments.mockRejectedValue(new Error("API Error"))
|
|
114
|
-
|
|
115
|
-
await expect(getPostComments({ post_id: "test", subreddit: "test" })).rejects.toThrow(
|
|
116
|
-
new McpError(ErrorCode.InternalError, "Failed to fetch comments: Error: API Error"),
|
|
117
|
-
)
|
|
118
|
-
})
|
|
119
|
-
})
|