reddit-mcp-server 1.5.0 → 1.5.1
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/dist/bin.js +2 -2
- package/dist/index.js +206 -79
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/bin.js
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
process.env.TRANSPORT_TYPE ??= "stdio";
|
|
4
4
|
const args = process.argv.slice(2);
|
|
5
5
|
if (args.includes("--version") || args.includes("-v")) {
|
|
6
|
-
console.log("1.5.
|
|
6
|
+
console.log("1.5.1");
|
|
7
7
|
process.exit(0);
|
|
8
8
|
}
|
|
9
9
|
if (args.includes("--help") || args.includes("-h")) {
|
|
10
10
|
console.log(`
|
|
11
|
-
Reddit MCP Server v1.5.
|
|
11
|
+
Reddit MCP Server v1.5.1
|
|
12
12
|
|
|
13
13
|
Usage: reddit-mcp-server [options]
|
|
14
14
|
|
package/dist/index.js
CHANGED
|
@@ -992,7 +992,7 @@ function formatSubredditInfo(subreddit) {
|
|
|
992
992
|
//#endregion
|
|
993
993
|
//#region src/index.ts
|
|
994
994
|
dotenv.config({ quiet: true });
|
|
995
|
-
const VERSION = "1.5.
|
|
995
|
+
const VERSION = "1.5.1";
|
|
996
996
|
function validateUserAgent(userAgent, username) {
|
|
997
997
|
if (!/^[\w-]+:[\w-]+:[\d.]+ \(by \/u\/\w+\)$/.test(userAgent)) {
|
|
998
998
|
console.error("[Warning] User-Agent does not follow Reddit's recommended format");
|
|
@@ -1193,7 +1193,12 @@ For details: https://support.reddithelp.com/hc/en-us/articles/42728983564564-Res
|
|
|
1193
1193
|
});
|
|
1194
1194
|
server.addTool({
|
|
1195
1195
|
name: "test_reddit_mcp_server",
|
|
1196
|
-
description: "
|
|
1196
|
+
description: "Health check for the Reddit MCP server. Read-only and side-effect-free — inspects local configuration only and makes no Reddit API calls. Returns the server version, whether the Reddit client is initialized, whether OAuth credentials are present, and whether write access (REDDIT_USERNAME/REDDIT_PASSWORD) is configured. Use this first to diagnose setup/auth problems. Do NOT use it to check Reddit's own status or connectivity — it never contacts Reddit. A \"✗ Write Access\" result means the write tools (create_post, reply_to_post, edit_*, delete_*) will fail.",
|
|
1197
|
+
annotations: {
|
|
1198
|
+
title: "Test Reddit MCP Server",
|
|
1199
|
+
readOnlyHint: true,
|
|
1200
|
+
openWorldHint: false
|
|
1201
|
+
},
|
|
1197
1202
|
parameters: z.object({}),
|
|
1198
1203
|
execute: () => {
|
|
1199
1204
|
const client = getRedditClient();
|
|
@@ -1210,8 +1215,13 @@ Ready to handle Reddit API requests!`);
|
|
|
1210
1215
|
});
|
|
1211
1216
|
server.addTool({
|
|
1212
1217
|
name: "get_user_info",
|
|
1213
|
-
description: "Get
|
|
1214
|
-
|
|
1218
|
+
description: "Get a public profile for any Reddit user: comment/post/total karma, account age and status flags, plus a short activity analysis and engagement tips. Read-only; works in anonymous mode. Returns profile stats only — use get_user_posts / get_user_comments for their actual content. Use get_me instead for your own authenticated account; do NOT expect private fields here, as only public data is returned.",
|
|
1219
|
+
annotations: {
|
|
1220
|
+
title: "Get User Info",
|
|
1221
|
+
readOnlyHint: true,
|
|
1222
|
+
openWorldHint: true
|
|
1223
|
+
},
|
|
1224
|
+
parameters: z.object({ username: z.string().describe("The target user's Reddit username, without the u/ prefix (e.g. 'spez', not 'u/spez').") }),
|
|
1215
1225
|
execute: async (args) => {
|
|
1216
1226
|
return (await unwrapClient().getUser(args.username)).fold((err) => {
|
|
1217
1227
|
throw new Error(`Failed to get user info: ${err.message}`);
|
|
@@ -1239,7 +1249,12 @@ server.addTool({
|
|
|
1239
1249
|
});
|
|
1240
1250
|
server.addTool({
|
|
1241
1251
|
name: "get_me",
|
|
1242
|
-
description: "Get the authenticated user's own
|
|
1252
|
+
description: "Get the authenticated user's own profile (karma, account age, status flags). Read-only, but requires user credentials (REDDIT_USERNAME/REDDIT_PASSWORD) and fails in anonymous mode. Use this instead of get_user_info when you need the current account rather than an arbitrary user. Do NOT use it to look up other users — it always returns the logged-in account.",
|
|
1253
|
+
annotations: {
|
|
1254
|
+
title: "Get My Account",
|
|
1255
|
+
readOnlyHint: true,
|
|
1256
|
+
openWorldHint: true
|
|
1257
|
+
},
|
|
1243
1258
|
parameters: z.object({}),
|
|
1244
1259
|
execute: async () => {
|
|
1245
1260
|
return (await unwrapClient().getMe()).fold((err) => {
|
|
@@ -1262,10 +1277,15 @@ server.addTool({
|
|
|
1262
1277
|
});
|
|
1263
1278
|
server.addTool({
|
|
1264
1279
|
name: "get_my_overview",
|
|
1265
|
-
description: "Get
|
|
1280
|
+
description: "Get the authenticated user's own recent activity — posts and comments interleaved, newest first. Read-only but requires user credentials (REDDIT_USERNAME/REDDIT_PASSWORD). Returns up to `limit` items plus an `after` cursor for the next page. Use get_my_saved for saved items, or get_user_posts / get_user_comments for another user. Do NOT use this to fetch a specific post's thread — use get_post_comments.",
|
|
1281
|
+
annotations: {
|
|
1282
|
+
title: "Get My Overview",
|
|
1283
|
+
readOnlyHint: true,
|
|
1284
|
+
openWorldHint: true
|
|
1285
|
+
},
|
|
1266
1286
|
parameters: z.object({
|
|
1267
|
-
limit: z.number().min(1).max(100).default(25).describe("
|
|
1268
|
-
after: z.string().optional().describe("
|
|
1287
|
+
limit: z.number().min(1).max(100).default(25).describe("How many activity items to return, 1–100 (default 25)."),
|
|
1288
|
+
after: z.string().optional().describe("Forward pagination cursor: the `after` value returned by a previous call. Omit for the first page.")
|
|
1269
1289
|
}),
|
|
1270
1290
|
execute: async (args) => {
|
|
1271
1291
|
return (await unwrapClient().getMyOverview({
|
|
@@ -1278,10 +1298,15 @@ server.addTool({
|
|
|
1278
1298
|
});
|
|
1279
1299
|
server.addTool({
|
|
1280
1300
|
name: "get_my_saved",
|
|
1281
|
-
description: "Get
|
|
1301
|
+
description: "Get the authenticated user's saved posts and comments (private to the account). Read-only but requires user credentials (REDDIT_USERNAME/REDDIT_PASSWORD). Returns up to `limit` items plus an `after` pagination cursor. Use get_my_overview for your authored activity. Do NOT use this for another user — saved items are private and have no cross-user equivalent.",
|
|
1302
|
+
annotations: {
|
|
1303
|
+
title: "Get My Saved",
|
|
1304
|
+
readOnlyHint: true,
|
|
1305
|
+
openWorldHint: true
|
|
1306
|
+
},
|
|
1282
1307
|
parameters: z.object({
|
|
1283
|
-
limit: z.number().min(1).max(100).default(25).describe("
|
|
1284
|
-
after: z.string().optional().describe("
|
|
1308
|
+
limit: z.number().min(1).max(100).default(25).describe("How many saved items to return, 1–100 (default 25)."),
|
|
1309
|
+
after: z.string().optional().describe("Forward pagination cursor: the `after` value returned by a previous call. Omit for the first page.")
|
|
1285
1310
|
}),
|
|
1286
1311
|
execute: async (args) => {
|
|
1287
1312
|
return (await unwrapClient().getMySaved({
|
|
@@ -1294,14 +1319,19 @@ server.addTool({
|
|
|
1294
1319
|
});
|
|
1295
1320
|
server.addTool({
|
|
1296
1321
|
name: "get_user_posts",
|
|
1297
|
-
description: "Get
|
|
1322
|
+
description: "Get posts submitted by a specific user, with sort (new/hot/top) and time filter. Read-only; works anonymously. Returns a page of posts (title, subreddit, score, upvote ratio, comment count, permalink) plus an `after` cursor for paging. Use get_user_comments for their comments, or get_user_info for karma/profile stats. Do NOT use this to search a subreddit — use search_reddit or browse_subreddit.",
|
|
1323
|
+
annotations: {
|
|
1324
|
+
title: "Get User Posts",
|
|
1325
|
+
readOnlyHint: true,
|
|
1326
|
+
openWorldHint: true
|
|
1327
|
+
},
|
|
1298
1328
|
parameters: z.object({
|
|
1299
|
-
username: z.string().describe("The Reddit username
|
|
1329
|
+
username: z.string().describe("The author's Reddit username, without the u/ prefix (e.g. 'spez')."),
|
|
1300
1330
|
sort: z.enum([
|
|
1301
1331
|
"new",
|
|
1302
1332
|
"hot",
|
|
1303
1333
|
"top"
|
|
1304
|
-
]).default("new").describe("
|
|
1334
|
+
]).default("new").describe("Ordering: 'new' (most recent), 'hot' (currently active), or 'top' (highest score within `time_filter`). Default 'new'."),
|
|
1305
1335
|
time_filter: z.enum([
|
|
1306
1336
|
"hour",
|
|
1307
1337
|
"day",
|
|
@@ -1309,9 +1339,9 @@ server.addTool({
|
|
|
1309
1339
|
"month",
|
|
1310
1340
|
"year",
|
|
1311
1341
|
"all"
|
|
1312
|
-
]).default("all").describe("Time
|
|
1313
|
-
limit: z.number().min(1).max(100).default(10).describe("
|
|
1314
|
-
after: z.string().optional().describe("
|
|
1342
|
+
]).default("all").describe("Time window for scoring; only applies when sort='top'. Ignored for 'new'/'hot'. Default 'all'."),
|
|
1343
|
+
limit: z.number().min(1).max(100).default(10).describe("How many posts to return, 1–100 (default 10)."),
|
|
1344
|
+
after: z.string().optional().describe("Forward pagination cursor: the `after` value from a previous call. Omit for the first page.")
|
|
1315
1345
|
}),
|
|
1316
1346
|
execute: async (args) => {
|
|
1317
1347
|
return (await unwrapClient().getUserPosts(args.username, {
|
|
@@ -1341,14 +1371,19 @@ ${postSummaries}${nextPageHint(page.after)}`;
|
|
|
1341
1371
|
});
|
|
1342
1372
|
server.addTool({
|
|
1343
1373
|
name: "get_user_comments",
|
|
1344
|
-
description: "Get
|
|
1374
|
+
description: "Get comments made by a specific user, with sort (new/hot/top) and time filter. Read-only; works anonymously. Returns a page of comments (subreddit, parent post title, body excerpt, score, permalink) plus an `after` cursor. Use get_user_posts for their submissions, or get_user_info for karma/profile stats. Do NOT use this to read one post's thread — use get_post_comments.",
|
|
1375
|
+
annotations: {
|
|
1376
|
+
title: "Get User Comments",
|
|
1377
|
+
readOnlyHint: true,
|
|
1378
|
+
openWorldHint: true
|
|
1379
|
+
},
|
|
1345
1380
|
parameters: z.object({
|
|
1346
|
-
username: z.string().describe("The Reddit username
|
|
1381
|
+
username: z.string().describe("The author's Reddit username, without the u/ prefix (e.g. 'spez')."),
|
|
1347
1382
|
sort: z.enum([
|
|
1348
1383
|
"new",
|
|
1349
1384
|
"hot",
|
|
1350
1385
|
"top"
|
|
1351
|
-
]).default("new").describe("
|
|
1386
|
+
]).default("new").describe("Ordering: 'new' (most recent), 'hot' (currently active), or 'top' (highest score within `time_filter`). Default 'new'."),
|
|
1352
1387
|
time_filter: z.enum([
|
|
1353
1388
|
"hour",
|
|
1354
1389
|
"day",
|
|
@@ -1356,9 +1391,9 @@ server.addTool({
|
|
|
1356
1391
|
"month",
|
|
1357
1392
|
"year",
|
|
1358
1393
|
"all"
|
|
1359
|
-
]).default("all").describe("Time
|
|
1360
|
-
limit: z.number().min(1).max(100).default(10).describe("
|
|
1361
|
-
after: z.string().optional().describe("
|
|
1394
|
+
]).default("all").describe("Time window for scoring; only applies when sort='top'. Ignored for 'new'/'hot'. Default 'all'."),
|
|
1395
|
+
limit: z.number().min(1).max(100).default(10).describe("How many comments to return, 1–100 (default 10)."),
|
|
1396
|
+
after: z.string().optional().describe("Forward pagination cursor: the `after` value from a previous call. Omit for the first page.")
|
|
1362
1397
|
}),
|
|
1363
1398
|
execute: async (args) => {
|
|
1364
1399
|
return (await unwrapClient().getUserComments(args.username, {
|
|
@@ -1391,10 +1426,15 @@ ${commentSummaries}${nextPageHint(page.after)}`;
|
|
|
1391
1426
|
});
|
|
1392
1427
|
server.addTool({
|
|
1393
1428
|
name: "get_reddit_post",
|
|
1394
|
-
description: "Get
|
|
1429
|
+
description: "Get a single post by subreddit + post id: title, author, self-text or link content, score, upvote ratio, comment count, flair/flags, and an engagement analysis. Read-only; works anonymously. Returns the post only — use get_post_comments for its comment thread. Do NOT use this to list a subreddit's posts (use browse_subreddit / get_top_posts) or to find posts by keyword (use search_reddit).",
|
|
1430
|
+
annotations: {
|
|
1431
|
+
title: "Get Reddit Post",
|
|
1432
|
+
readOnlyHint: true,
|
|
1433
|
+
openWorldHint: true
|
|
1434
|
+
},
|
|
1395
1435
|
parameters: z.object({
|
|
1396
|
-
subreddit: z.string().describe("The subreddit
|
|
1397
|
-
post_id: z.string().describe("
|
|
1436
|
+
subreddit: z.string().describe("The subreddit the post lives in, without the r/ prefix (e.g. 'programming')."),
|
|
1437
|
+
post_id: z.string().describe("Base36 post id — the segment after /comments/ in a permalink like reddit.com/r/<sub>/comments/<post_id>/... (e.g. '1abc23'). With or without a t3_ prefix.")
|
|
1398
1438
|
}),
|
|
1399
1439
|
execute: async (args) => {
|
|
1400
1440
|
return (await unwrapClient().getPost(args.post_id, args.subreddit)).fold((err) => {
|
|
@@ -1435,9 +1475,14 @@ ${formattedPost.bestTimeToEngage}`;
|
|
|
1435
1475
|
});
|
|
1436
1476
|
server.addTool({
|
|
1437
1477
|
name: "get_top_posts",
|
|
1438
|
-
description: "Get top posts from a subreddit or from the
|
|
1478
|
+
description: "Get the top-scoring posts from a subreddit — or from the authenticated home feed if no subreddit is given — within a time window (hour…all). Read-only; works anonymously. Returns a page of posts (title, author, score, upvote ratio, comments, link) plus an `after` cursor. This is a shortcut for the 'top' sort; use browse_subreddit for hot/new/rising/controversial, or search_reddit to find posts by keyword.",
|
|
1479
|
+
annotations: {
|
|
1480
|
+
title: "Get Top Posts",
|
|
1481
|
+
readOnlyHint: true,
|
|
1482
|
+
openWorldHint: true
|
|
1483
|
+
},
|
|
1439
1484
|
parameters: z.object({
|
|
1440
|
-
subreddit: z.string().optional().describe("
|
|
1485
|
+
subreddit: z.string().optional().describe("Subreddit to read, without the r/ prefix (e.g. 'science'). Omit to use the authenticated home feed (requires credentials)."),
|
|
1441
1486
|
time_filter: z.enum([
|
|
1442
1487
|
"hour",
|
|
1443
1488
|
"day",
|
|
@@ -1445,9 +1490,9 @@ server.addTool({
|
|
|
1445
1490
|
"month",
|
|
1446
1491
|
"year",
|
|
1447
1492
|
"all"
|
|
1448
|
-
]).default("week").describe("Time
|
|
1449
|
-
limit: z.number().min(1).max(100).default(10).describe("
|
|
1450
|
-
after: z.string().optional().describe("
|
|
1493
|
+
]).default("week").describe("Time window the 'top' ranking is computed over (e.g. 'day' = top today). Default 'week'."),
|
|
1494
|
+
limit: z.number().min(1).max(100).default(10).describe("How many posts to return, 1–100 (default 10)."),
|
|
1495
|
+
after: z.string().optional().describe("Forward pagination cursor: the `after` value from a previous call. Omit for the first page.")
|
|
1451
1496
|
}),
|
|
1452
1497
|
execute: async (args) => {
|
|
1453
1498
|
return (await unwrapClient().getTopPosts(args.subreddit ?? "", args.time_filter, args.limit, args.after)).fold((err) => {
|
|
@@ -1469,16 +1514,21 @@ ${postSummaries}${nextPageHint(page.after)}`;
|
|
|
1469
1514
|
});
|
|
1470
1515
|
server.addTool({
|
|
1471
1516
|
name: "browse_subreddit",
|
|
1472
|
-
description: "Browse
|
|
1517
|
+
description: "Browse a subreddit — or the authenticated home feed when no subreddit is given — by sort order: hot, new, top, rising, or controversial. Read-only; works anonymously. `time_filter` applies only to the top and controversial sorts. Returns a page of posts (title, author, score, upvote ratio, comments, link) plus an `after` cursor. Use get_top_posts as a shortcut for the top sort, or search_reddit to find posts by keyword rather than by feed order.",
|
|
1518
|
+
annotations: {
|
|
1519
|
+
title: "Browse Subreddit",
|
|
1520
|
+
readOnlyHint: true,
|
|
1521
|
+
openWorldHint: true
|
|
1522
|
+
},
|
|
1473
1523
|
parameters: z.object({
|
|
1474
|
-
subreddit: z.string().optional().describe("
|
|
1524
|
+
subreddit: z.string().optional().describe("Subreddit to browse, without the r/ prefix (e.g. 'news'). Omit to use the authenticated home feed (requires credentials)."),
|
|
1475
1525
|
sort: z.enum([
|
|
1476
1526
|
"hot",
|
|
1477
1527
|
"new",
|
|
1478
1528
|
"top",
|
|
1479
1529
|
"rising",
|
|
1480
1530
|
"controversial"
|
|
1481
|
-
]).default("hot").describe("
|
|
1531
|
+
]).default("hot").describe("Feed ordering: 'hot' (default), 'new', 'rising', 'top', or 'controversial'. 'top'/'controversial' honor `time_filter`."),
|
|
1482
1532
|
time_filter: z.enum([
|
|
1483
1533
|
"hour",
|
|
1484
1534
|
"day",
|
|
@@ -1486,9 +1536,9 @@ server.addTool({
|
|
|
1486
1536
|
"month",
|
|
1487
1537
|
"year",
|
|
1488
1538
|
"all"
|
|
1489
|
-
]).default("week").describe("Time
|
|
1490
|
-
limit: z.number().min(1).max(100).default(10).describe("
|
|
1491
|
-
after: z.string().optional().describe("
|
|
1539
|
+
]).default("week").describe("Time window; only applies to sort='top' or 'controversial'. Ignored otherwise. Default 'week'."),
|
|
1540
|
+
limit: z.number().min(1).max(100).default(10).describe("How many posts to return, 1–100 (default 10)."),
|
|
1541
|
+
after: z.string().optional().describe("Forward pagination cursor: the `after` value from a previous call. Omit for the first page.")
|
|
1492
1542
|
}),
|
|
1493
1543
|
execute: async (args) => {
|
|
1494
1544
|
return (await unwrapClient().browseSubreddit(args.subreddit ?? "", args.sort, args.time_filter, args.limit, args.after)).fold((err) => {
|
|
@@ -1513,8 +1563,13 @@ ${postSummaries}${nextPageHint(page.after)}`;
|
|
|
1513
1563
|
});
|
|
1514
1564
|
server.addTool({
|
|
1515
1565
|
name: "get_subreddit_info",
|
|
1516
|
-
description: "Get
|
|
1517
|
-
|
|
1566
|
+
description: "Get a subreddit's profile: title, description, subscriber and active-user counts, creation date, flags, wiki/link URLs, plus a community analysis and posting tips. Read-only; works anonymously. Returns metadata about the community itself — use browse_subreddit / get_top_posts for its posts, or get_subreddit_rules for its posting rules. Do NOT use this to find subreddits by topic — use search_reddit with type='sr'.",
|
|
1567
|
+
annotations: {
|
|
1568
|
+
title: "Get Subreddit Info",
|
|
1569
|
+
readOnlyHint: true,
|
|
1570
|
+
openWorldHint: true
|
|
1571
|
+
},
|
|
1572
|
+
parameters: z.object({ subreddit_name: z.string().describe("The subreddit name, without the r/ prefix (e.g. 'askscience').") }),
|
|
1518
1573
|
execute: async (args) => {
|
|
1519
1574
|
return (await unwrapClient().getSubredditInfo(args.subreddit_name)).fold((err) => {
|
|
1520
1575
|
throw new Error(`Failed to get subreddit info: ${err.message}`);
|
|
@@ -1552,8 +1607,13 @@ ${formattedSubreddit.description.full}
|
|
|
1552
1607
|
});
|
|
1553
1608
|
server.addTool({
|
|
1554
1609
|
name: "get_subreddit_rules",
|
|
1555
|
-
description: "Get a subreddit's posting rules
|
|
1556
|
-
|
|
1610
|
+
description: "Get a subreddit's posting rules (each rule's name, what it applies to, and its description). Read-only; works anonymously. Returns the rules list, or a note when the subreddit lists none. Call this before create_post to check requirements and avoid auto-removal. For available post flairs use get_post_flairs instead.",
|
|
1611
|
+
annotations: {
|
|
1612
|
+
title: "Get Subreddit Rules",
|
|
1613
|
+
readOnlyHint: true,
|
|
1614
|
+
openWorldHint: true
|
|
1615
|
+
},
|
|
1616
|
+
parameters: z.object({ subreddit_name: z.string().describe("The subreddit name, without the r/ prefix (e.g. 'AskReddit').") }),
|
|
1557
1617
|
execute: async (args) => {
|
|
1558
1618
|
return (await unwrapClient().getSubredditRules(args.subreddit_name)).fold((err) => {
|
|
1559
1619
|
throw new Error(`Failed to get subreddit rules: ${err.message}`);
|
|
@@ -1572,8 +1632,13 @@ ${ruleList}`;
|
|
|
1572
1632
|
});
|
|
1573
1633
|
server.addTool({
|
|
1574
1634
|
name: "get_post_flairs",
|
|
1575
|
-
description: "List
|
|
1576
|
-
|
|
1635
|
+
description: "List a subreddit's selectable link flairs (flair text + flair_id) for use with create_post. Read-only, but requires user credentials; many subreddits expose flairs only to members, so this can 403 or return empty anonymously. Pass a returned flair_id (and flair_text for text-editable flairs) to create_post. For the subreddit's posting rules use get_subreddit_rules instead.",
|
|
1636
|
+
annotations: {
|
|
1637
|
+
title: "Get Post Flairs",
|
|
1638
|
+
readOnlyHint: true,
|
|
1639
|
+
openWorldHint: true
|
|
1640
|
+
},
|
|
1641
|
+
parameters: z.object({ subreddit_name: z.string().describe("The subreddit name, without the r/ prefix (e.g. 'gadgets').") }),
|
|
1577
1642
|
execute: async (args) => {
|
|
1578
1643
|
return (await unwrapClient().getPostFlairs(args.subreddit_name)).fold((err) => {
|
|
1579
1644
|
throw new Error(`Failed to get post flairs: ${err.message}`);
|
|
@@ -1593,7 +1658,12 @@ Pass the desired \`flair_id\` to \`create_post\`.`;
|
|
|
1593
1658
|
});
|
|
1594
1659
|
server.addTool({
|
|
1595
1660
|
name: "get_trending_subreddits",
|
|
1596
|
-
description: "Get a list of
|
|
1661
|
+
description: "Get the subreddits Reddit is currently featuring as trending/popular. Read-only, no parameters; works anonymously. Returns a list of subreddit names that changes through the day (cached briefly server-side). To find subreddits by keyword instead of by trend, use search_reddit with type='sr'.",
|
|
1662
|
+
annotations: {
|
|
1663
|
+
title: "Get Trending Subreddits",
|
|
1664
|
+
readOnlyHint: true,
|
|
1665
|
+
openWorldHint: true
|
|
1666
|
+
},
|
|
1597
1667
|
parameters: z.object({}),
|
|
1598
1668
|
execute: async () => {
|
|
1599
1669
|
return (await unwrapClient().getTrendingSubreddits()).fold((err) => {
|
|
@@ -1605,10 +1675,15 @@ ${trendingSubreddits.map((subreddit, index) => `${index + 1}. r/${subreddit}`).j
|
|
|
1605
1675
|
});
|
|
1606
1676
|
server.addTool({
|
|
1607
1677
|
name: "search_reddit",
|
|
1608
|
-
description: "Search Reddit for posts and content
|
|
1678
|
+
description: "Search Reddit for posts — or subreddits/users via `type` — optionally scoped to one subreddit, with sort and time filters. Read-only; works anonymously. Returns a page of results (title, subreddit, author, score, comments, link) plus an `after` cursor for paging. Use this to find content by keyword; use browse_subreddit / get_top_posts to list a known subreddit's feed instead.",
|
|
1679
|
+
annotations: {
|
|
1680
|
+
title: "Search Reddit",
|
|
1681
|
+
readOnlyHint: true,
|
|
1682
|
+
openWorldHint: true
|
|
1683
|
+
},
|
|
1609
1684
|
parameters: z.object({
|
|
1610
|
-
query: z.string().describe("Search
|
|
1611
|
-
subreddit: z.string().optional().describe("
|
|
1685
|
+
query: z.string().describe("Search terms; supports Reddit operators (quotes for exact phrases, author:name, self:yes). Must be non-empty."),
|
|
1686
|
+
subreddit: z.string().optional().describe("Restrict results to this subreddit, without the r/ prefix (e.g. 'python'). Omit to search all of Reddit."),
|
|
1612
1687
|
sort: z.enum([
|
|
1613
1688
|
"relevance",
|
|
1614
1689
|
"hot",
|
|
@@ -1623,14 +1698,14 @@ server.addTool({
|
|
|
1623
1698
|
"month",
|
|
1624
1699
|
"year",
|
|
1625
1700
|
"all"
|
|
1626
|
-
]).default("all").describe("
|
|
1627
|
-
limit: z.number().min(1).max(100).default(10).describe("
|
|
1701
|
+
]).default("all").describe("Restrict to results from this recent window (e.g. 'week'). Default 'all' (no time limit)."),
|
|
1702
|
+
limit: z.number().min(1).max(100).default(10).describe("How many results to return, 1–100 (default 10)."),
|
|
1628
1703
|
type: z.enum([
|
|
1629
1704
|
"link",
|
|
1630
1705
|
"sr",
|
|
1631
1706
|
"user"
|
|
1632
|
-
]).default("link").describe("
|
|
1633
|
-
after: z.string().optional().describe("
|
|
1707
|
+
]).default("link").describe("What to search for: 'link' = posts (default), 'sr' = subreddits, 'user' = users."),
|
|
1708
|
+
after: z.string().optional().describe("Forward pagination cursor: the `after` value from a previous call. Omit for the first page.")
|
|
1634
1709
|
}),
|
|
1635
1710
|
execute: async (args) => {
|
|
1636
1711
|
const client = unwrapClient();
|
|
@@ -1671,14 +1746,21 @@ ${searchResults}${nextPageHint(page.after)}`;
|
|
|
1671
1746
|
});
|
|
1672
1747
|
server.addTool({
|
|
1673
1748
|
name: "create_post",
|
|
1674
|
-
description: "Create a new post in a subreddit
|
|
1749
|
+
description: "Create a new text or link post in a subreddit. Mutating and NOT idempotent — each call publishes a separate post. Requires REDDIT_USERNAME and REDDIT_PASSWORD; fails without them. Returns the new post's id and URL. Check get_subreddit_rules and get_post_flairs first, since many subreddits require a flair or reject certain content. WARNING: rapid posting or duplicate content may trigger Reddit's spam detection and account bans — enable REDDIT_SAFE_MODE=standard for rate limiting and duplicate detection.",
|
|
1750
|
+
annotations: {
|
|
1751
|
+
title: "Create Post",
|
|
1752
|
+
readOnlyHint: false,
|
|
1753
|
+
destructiveHint: false,
|
|
1754
|
+
idempotentHint: false,
|
|
1755
|
+
openWorldHint: true
|
|
1756
|
+
},
|
|
1675
1757
|
parameters: z.object({
|
|
1676
|
-
subreddit: z.string().describe("
|
|
1677
|
-
title: z.string().describe("
|
|
1678
|
-
content: z.string().describe("
|
|
1679
|
-
is_self: z.boolean().default(true).describe("
|
|
1680
|
-
flair_id: z.string().optional().describe("Link flair template id
|
|
1681
|
-
flair_text: z.string().optional().describe("Custom flair text, only for flairs whose template is text-editable")
|
|
1758
|
+
subreddit: z.string().describe("Target subreddit, without the r/ prefix (e.g. 'test')."),
|
|
1759
|
+
title: z.string().describe("Post title (cannot be edited after creation)."),
|
|
1760
|
+
content: z.string().describe("For a self post (is_self=true): the body text, Reddit markdown supported. For a link post (is_self=false): the destination URL."),
|
|
1761
|
+
is_self: z.boolean().default(true).describe("true = text/self post using `content` as the body (default); false = link post using `content` as the URL."),
|
|
1762
|
+
flair_id: z.string().optional().describe("Link flair template id from get_post_flairs; many subreddits require one or the post is auto-removed."),
|
|
1763
|
+
flair_text: z.string().optional().describe("Custom flair text, allowed only for flairs whose template is text-editable.")
|
|
1682
1764
|
}),
|
|
1683
1765
|
execute: async (args) => {
|
|
1684
1766
|
const client = unwrapClient();
|
|
@@ -1701,10 +1783,17 @@ Your post has been successfully submitted to r/${formattedPost.subreddit}.`;
|
|
|
1701
1783
|
});
|
|
1702
1784
|
server.addTool({
|
|
1703
1785
|
name: "reply_to_post",
|
|
1704
|
-
description: "Post a reply to an existing
|
|
1786
|
+
description: "Post a reply to an existing post or comment. Mutating and NOT idempotent — each call adds a new comment. Requires REDDIT_USERNAME and REDDIT_PASSWORD. The parent is identified by its thing id — t3_ for a post, t1_ for a comment — so this creates both top-level and nested replies. Returns the new comment's id. Use edit_comment to change a reply you already posted. WARNING: rapid or duplicate replies may trigger Reddit's spam detection; enable REDDIT_SAFE_MODE=standard for rate limiting and duplicate detection.",
|
|
1787
|
+
annotations: {
|
|
1788
|
+
title: "Reply to Post or Comment",
|
|
1789
|
+
readOnlyHint: false,
|
|
1790
|
+
destructiveHint: false,
|
|
1791
|
+
idempotentHint: false,
|
|
1792
|
+
openWorldHint: true
|
|
1793
|
+
},
|
|
1705
1794
|
parameters: z.object({
|
|
1706
|
-
post_id: z.string().describe("
|
|
1707
|
-
content: z.string().describe("
|
|
1795
|
+
post_id: z.string().describe("Parent thing id to reply under: t3_<id> for a post (creates a top-level comment) or t1_<id> for a comment (creates a nested reply)."),
|
|
1796
|
+
content: z.string().describe("Reply body text; Reddit markdown supported.")
|
|
1708
1797
|
}),
|
|
1709
1798
|
execute: async (args) => {
|
|
1710
1799
|
const client = unwrapClient();
|
|
@@ -1723,8 +1812,15 @@ Your reply has been successfully posted.`);
|
|
|
1723
1812
|
});
|
|
1724
1813
|
server.addTool({
|
|
1725
1814
|
name: "delete_post",
|
|
1726
|
-
description: "
|
|
1727
|
-
|
|
1815
|
+
description: "Permanently delete one of your own posts. Mutating and destructive but idempotent — deleting an already-deleted post is a no-op. Requires REDDIT_USERNAME and REDDIT_PASSWORD, and only works on posts authored by the authenticated account. Only affects the post you name; use delete_comment for comments. WARNING: this cannot be undone — the content is removed, though the post id remains.",
|
|
1816
|
+
annotations: {
|
|
1817
|
+
title: "Delete Post",
|
|
1818
|
+
readOnlyHint: false,
|
|
1819
|
+
destructiveHint: true,
|
|
1820
|
+
idempotentHint: true,
|
|
1821
|
+
openWorldHint: true
|
|
1822
|
+
},
|
|
1823
|
+
parameters: z.object({ thing_id: z.string().describe("The post to delete: a full thing id 't3_<id>' or just the base36 post id '<id>' (the 't3_' prefix is added automatically). Must be a post you authored.") }),
|
|
1728
1824
|
execute: async (args) => {
|
|
1729
1825
|
const client = unwrapClient();
|
|
1730
1826
|
if (process.env.REDDIT_USERNAME === void 0 || process.env.REDDIT_PASSWORD === void 0) throw new Error("User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.");
|
|
@@ -1739,8 +1835,15 @@ The post ${args.thing_id} has been permanently deleted from Reddit.
|
|
|
1739
1835
|
});
|
|
1740
1836
|
server.addTool({
|
|
1741
1837
|
name: "delete_comment",
|
|
1742
|
-
description: "
|
|
1743
|
-
|
|
1838
|
+
description: "Permanently delete one of your own comments. Mutating and destructive but idempotent — deleting an already-deleted comment is a no-op. Requires REDDIT_USERNAME and REDDIT_PASSWORD, and only works on comments authored by the authenticated account. Only affects the comment you name; use delete_post for posts. WARNING: this cannot be undone — the content is removed, though the comment id remains.",
|
|
1839
|
+
annotations: {
|
|
1840
|
+
title: "Delete Comment",
|
|
1841
|
+
readOnlyHint: false,
|
|
1842
|
+
destructiveHint: true,
|
|
1843
|
+
idempotentHint: true,
|
|
1844
|
+
openWorldHint: true
|
|
1845
|
+
},
|
|
1846
|
+
parameters: z.object({ thing_id: z.string().describe("The comment to delete: a full thing id 't1_<id>' or just the base36 comment id '<id>' (the 't1_' prefix is added automatically). Must be a comment you authored.") }),
|
|
1744
1847
|
execute: async (args) => {
|
|
1745
1848
|
const client = unwrapClient();
|
|
1746
1849
|
if (process.env.REDDIT_USERNAME === void 0 || process.env.REDDIT_PASSWORD === void 0) throw new Error("User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.");
|
|
@@ -1755,10 +1858,17 @@ The comment ${args.thing_id} has been permanently deleted from Reddit.
|
|
|
1755
1858
|
});
|
|
1756
1859
|
server.addTool({
|
|
1757
1860
|
name: "edit_post",
|
|
1758
|
-
description: "
|
|
1861
|
+
description: "Replace the body text of one of your own self-text posts. Mutating and idempotent (same text → same result); it overwrites the previous body. Requires REDDIT_USERNAME and REDDIT_PASSWORD, and works only on self posts you authored — titles and link posts cannot be edited. Adds an \"edited\" marker. Use create_post to make a new post, or edit_comment for comments. WARNING: rapid edits may trigger spam detection; enable REDDIT_SAFE_MODE for protection.",
|
|
1862
|
+
annotations: {
|
|
1863
|
+
title: "Edit Post",
|
|
1864
|
+
readOnlyHint: false,
|
|
1865
|
+
destructiveHint: true,
|
|
1866
|
+
idempotentHint: true,
|
|
1867
|
+
openWorldHint: true
|
|
1868
|
+
},
|
|
1759
1869
|
parameters: z.object({
|
|
1760
|
-
thing_id: z.string().describe("The full
|
|
1761
|
-
new_text: z.string().describe("
|
|
1870
|
+
thing_id: z.string().describe("The post to edit: a full thing id 't3_<id>' or just the base36 post id '<id>' (the 't3_' prefix is added automatically). Must be a self-text post you authored."),
|
|
1871
|
+
new_text: z.string().describe("Replacement body text; fully overwrites the current body. Reddit markdown supported.")
|
|
1762
1872
|
}),
|
|
1763
1873
|
execute: async (args) => {
|
|
1764
1874
|
const client = unwrapClient();
|
|
@@ -1778,10 +1888,17 @@ The post ${args.thing_id} has been updated with your new content.
|
|
|
1778
1888
|
});
|
|
1779
1889
|
server.addTool({
|
|
1780
1890
|
name: "edit_comment",
|
|
1781
|
-
description: "
|
|
1891
|
+
description: "Replace the text of one of your own comments. Mutating and idempotent (same text → same result); it overwrites the previous content. Requires REDDIT_USERNAME and REDDIT_PASSWORD, and works only on comments you authored. Adds an \"edited\" marker. Use reply_to_post to add a new comment, or edit_post for posts. WARNING: rapid edits may trigger spam detection; enable REDDIT_SAFE_MODE for protection.",
|
|
1892
|
+
annotations: {
|
|
1893
|
+
title: "Edit Comment",
|
|
1894
|
+
readOnlyHint: false,
|
|
1895
|
+
destructiveHint: true,
|
|
1896
|
+
idempotentHint: true,
|
|
1897
|
+
openWorldHint: true
|
|
1898
|
+
},
|
|
1782
1899
|
parameters: z.object({
|
|
1783
|
-
thing_id: z.string().describe("The full
|
|
1784
|
-
new_text: z.string().describe("
|
|
1900
|
+
thing_id: z.string().describe("The comment to edit: a full thing id 't1_<id>' or just the base36 comment id '<id>' (the 't1_' prefix is added automatically). Must be a comment you authored."),
|
|
1901
|
+
new_text: z.string().describe("Replacement comment text; fully overwrites the current content. Reddit markdown supported.")
|
|
1785
1902
|
}),
|
|
1786
1903
|
execute: async (args) => {
|
|
1787
1904
|
const client = unwrapClient();
|
|
@@ -1797,10 +1914,15 @@ The comment ${args.thing_id} has been updated with your new content.
|
|
|
1797
1914
|
});
|
|
1798
1915
|
server.addTool({
|
|
1799
1916
|
name: "get_post_comments",
|
|
1800
|
-
description: "Get
|
|
1917
|
+
description: "Get the comment thread for a post (by post id + subreddit), sorted best/top/new/controversial/old/qa. Read-only; works anonymously. Returns the post header plus threaded comments (author, OP/edited badges, score, body, nesting depth) up to `limit`. Long threads are truncated with 'load more' stubs — expand those with get_more_comments. Use get_reddit_post for just the post body, not the thread.",
|
|
1918
|
+
annotations: {
|
|
1919
|
+
title: "Get Post Comments",
|
|
1920
|
+
readOnlyHint: true,
|
|
1921
|
+
openWorldHint: true
|
|
1922
|
+
},
|
|
1801
1923
|
parameters: z.object({
|
|
1802
|
-
post_id: z.string().describe("
|
|
1803
|
-
subreddit: z.string().describe("The subreddit
|
|
1924
|
+
post_id: z.string().describe("Base36 post id — the segment after /comments/ in a permalink (e.g. '1abc23'). With or without a t3_ prefix."),
|
|
1925
|
+
subreddit: z.string().describe("The subreddit the post lives in, without the r/ prefix (e.g. 'movies')."),
|
|
1804
1926
|
sort: z.enum([
|
|
1805
1927
|
"best",
|
|
1806
1928
|
"top",
|
|
@@ -1808,8 +1930,8 @@ server.addTool({
|
|
|
1808
1930
|
"controversial",
|
|
1809
1931
|
"old",
|
|
1810
1932
|
"qa"
|
|
1811
|
-
]).default("best").describe("Comment
|
|
1812
|
-
limit: z.number().min(1).max(500).default(100).describe("Maximum
|
|
1933
|
+
]).default("best").describe("Comment ordering: 'best' (default), 'top', 'new', 'controversial', 'old', or 'qa' (Q&A)."),
|
|
1934
|
+
limit: z.number().min(1).max(500).default(100).describe("Maximum comments to return, 1–500 (default 100). Deeply nested replies may still be truncated as 'load more' stubs.")
|
|
1813
1935
|
}),
|
|
1814
1936
|
execute: async (args) => {
|
|
1815
1937
|
const client = unwrapClient();
|
|
@@ -1845,10 +1967,15 @@ ${comment.body}
|
|
|
1845
1967
|
});
|
|
1846
1968
|
server.addTool({
|
|
1847
1969
|
name: "get_more_comments",
|
|
1848
|
-
description: "Expand truncated 'load more comments' stubs in a thread. Pass the post's link id and the comment ids from a 'more' node (
|
|
1970
|
+
description: "Expand truncated 'load more comments' stubs in a thread. Read-only; works anonymously. Pass the post's link id and the comment ids from a 'more' node (surfaced by get_post_comments) to fetch those hidden comments; returns the expanded comments (author, body excerpt, score, link). Call get_post_comments first to obtain the thread and its 'more' node ids — do NOT invent ids.",
|
|
1971
|
+
annotations: {
|
|
1972
|
+
title: "Get More Comments",
|
|
1973
|
+
readOnlyHint: true,
|
|
1974
|
+
openWorldHint: true
|
|
1975
|
+
},
|
|
1849
1976
|
parameters: z.object({
|
|
1850
|
-
link_id: z.string().describe("The post
|
|
1851
|
-
comment_ids: z.array(z.string()).min(1).describe("
|
|
1977
|
+
link_id: z.string().describe("The parent post's link id (base36, with or without the t3_ prefix) that the stub belongs to."),
|
|
1978
|
+
comment_ids: z.array(z.string()).min(1).describe("Base36 comment ids to expand, taken from a 'more' node returned by get_post_comments (not arbitrary ids).")
|
|
1852
1979
|
}),
|
|
1853
1980
|
execute: async (args) => {
|
|
1854
1981
|
return (await unwrapClient().getMoreComments(args.link_id, args.comment_ids)).fold((err) => {
|