@daanrongen/reddit-mcp 1.0.2 → 1.1.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/dist/main.js +212 -265
- package/package.json +2 -2
package/dist/main.js
CHANGED
|
@@ -27263,13 +27263,15 @@ class RedditNotFoundError extends exports_Data.TaggedError("RedditNotFoundError"
|
|
|
27263
27263
|
class RedditAuth extends exports_Context.Tag("RedditAuth")() {
|
|
27264
27264
|
}
|
|
27265
27265
|
|
|
27266
|
-
// src/infra/
|
|
27267
|
-
var REDDIT_TOKEN_URL = "https://www.reddit.com/api/v1/access_token";
|
|
27268
|
-
var REFRESH_BUFFER_MS = 5 * 60 * 1000;
|
|
27266
|
+
// src/infra/utils.ts
|
|
27269
27267
|
var buildUserAgent = (username) => {
|
|
27270
27268
|
const user = exports_Option.getOrElse(username, () => "your-username");
|
|
27271
27269
|
return `reddit-mcp/1.0 by ${user}`;
|
|
27272
27270
|
};
|
|
27271
|
+
|
|
27272
|
+
// src/infra/RedditAuthLive.ts
|
|
27273
|
+
var REDDIT_TOKEN_URL = "https://www.reddit.com/api/v1/access_token";
|
|
27274
|
+
var REFRESH_BUFFER_MS = 5 * 60 * 1000;
|
|
27273
27275
|
var fetchToken = (clientId, clientSecret, grantParams, userAgent) => exports_Effect.tryPromise({
|
|
27274
27276
|
try: async () => {
|
|
27275
27277
|
const credentials = btoa(`${clientId}:${clientSecret}`);
|
|
@@ -27338,14 +27340,10 @@ class RedditClient extends exports_Context.Tag("RedditClient")() {
|
|
|
27338
27340
|
|
|
27339
27341
|
// src/infra/RedditClientLive.ts
|
|
27340
27342
|
var REDDIT_API_BASE = "https://oauth.reddit.com";
|
|
27341
|
-
var buildUserAgent2 = (username) => {
|
|
27342
|
-
const user = exports_Option.getOrElse(username, () => "your-username");
|
|
27343
|
-
return `reddit-mcp/1.0 by ${user}`;
|
|
27344
|
-
};
|
|
27345
27343
|
var RedditClientLive = exports_Layer.effect(RedditClient, exports_Effect.gen(function* () {
|
|
27346
27344
|
const auth = yield* RedditAuth;
|
|
27347
27345
|
const usernameOption = yield* exports_Effect.orDie(RedditUsernameConfig);
|
|
27348
|
-
const userAgent =
|
|
27346
|
+
const userAgent = buildUserAgent(usernameOption);
|
|
27349
27347
|
const makeRequest = (method, path, params, body) => exports_Effect.gen(function* () {
|
|
27350
27348
|
const token = yield* auth.getAccessToken();
|
|
27351
27349
|
const url3 = new URL(`${REDDIT_API_BASE}${path}`);
|
|
@@ -34991,6 +34989,11 @@ var EMPTY_COMPLETION_RESULT = {
|
|
|
34991
34989
|
}
|
|
34992
34990
|
};
|
|
34993
34991
|
|
|
34992
|
+
// src/mcp/types.ts
|
|
34993
|
+
var buildPaginationString = (after3) => after3 ? `
|
|
34994
|
+
|
|
34995
|
+
More results available. Use after="${after3}" to fetch the next page.` : "";
|
|
34996
|
+
|
|
34994
34997
|
// src/mcp/utils.ts
|
|
34995
34998
|
var formatSuccess = (data) => ({
|
|
34996
34999
|
content: [
|
|
@@ -35010,6 +35013,12 @@ var formatError2 = (cause3) => ({
|
|
|
35010
35013
|
isError: true
|
|
35011
35014
|
});
|
|
35012
35015
|
var WRITE_AUTH_ERROR = "Write operations require REDDIT_REFRESH_TOKEN. " + "See README for setup instructions on obtaining a refresh token via the OAuth2 authorization code flow.";
|
|
35016
|
+
var runTool = async (runtime4, effect2) => {
|
|
35017
|
+
const exit3 = await runtime4.runPromiseExit(effect2);
|
|
35018
|
+
if (exit3._tag === "Failure")
|
|
35019
|
+
return formatError2(exit3.cause);
|
|
35020
|
+
return formatSuccess(exit3.value);
|
|
35021
|
+
};
|
|
35013
35022
|
|
|
35014
35023
|
// src/mcp/tools/browse.ts
|
|
35015
35024
|
var formatPost = (post) => {
|
|
@@ -35061,27 +35070,24 @@ var registerBrowseTools = (server, runtime4) => {
|
|
|
35061
35070
|
destructiveHint: false,
|
|
35062
35071
|
idempotentHint: false,
|
|
35063
35072
|
openWorldHint: true
|
|
35064
|
-
}, async ({ subreddit, feed, t, limit, after: after3 }) => {
|
|
35065
|
-
const
|
|
35066
|
-
|
|
35067
|
-
|
|
35068
|
-
|
|
35069
|
-
|
|
35070
|
-
|
|
35071
|
-
|
|
35072
|
-
|
|
35073
|
-
|
|
35074
|
-
|
|
35075
|
-
|
|
35073
|
+
}, async ({ subreddit, feed, t, limit, after: after3 }) => runTool(runtime4, exports_Effect.gen(function* () {
|
|
35074
|
+
const client = yield* RedditClient;
|
|
35075
|
+
const feedPath = feed ?? "hot";
|
|
35076
|
+
const data = yield* client.get(`/r/${subreddit}/${feedPath}`, {
|
|
35077
|
+
t: t ?? "day",
|
|
35078
|
+
limit: limit ?? 25,
|
|
35079
|
+
after: after3
|
|
35080
|
+
});
|
|
35081
|
+
const posts = (data.data?.children ?? []).map((c) => c.data ?? {});
|
|
35082
|
+
if (!posts.length)
|
|
35083
|
+
return `No posts found in r/${subreddit}.`;
|
|
35084
|
+
const pagination = buildPaginationString(data.data?.after);
|
|
35085
|
+
return `r/${subreddit} \u2014 ${feedPath} posts:
|
|
35076
35086
|
|
|
35077
35087
|
${posts.map(formatPost).join(`
|
|
35078
35088
|
|
|
35079
35089
|
`)}` + pagination;
|
|
35080
|
-
|
|
35081
|
-
if (result._tag === "Failure")
|
|
35082
|
-
return formatError2(result.cause);
|
|
35083
|
-
return formatSuccess(result.value);
|
|
35084
|
-
});
|
|
35090
|
+
})));
|
|
35085
35091
|
server.tool("get_post", "Fetch a single Reddit post by ID or permalink URL, including the top-level comments.", {
|
|
35086
35092
|
id: exports_external.string().optional().describe("Reddit post ID (e.g. 'abc123') \u2014 provide either this or 'url', not both"),
|
|
35087
35093
|
url: exports_external.string().optional().describe("Full Reddit permalink URL (e.g. 'https://reddit.com/r/programming/comments/abc123/...') \u2014 provide either this or 'id'"),
|
|
@@ -35094,9 +35100,13 @@ ${posts.map(formatPost).join(`
|
|
|
35094
35100
|
openWorldHint: true
|
|
35095
35101
|
}, async ({ id, url: url3, comment_limit }) => {
|
|
35096
35102
|
if (!id && !url3) {
|
|
35097
|
-
return
|
|
35103
|
+
return {
|
|
35104
|
+
content: [
|
|
35105
|
+
{ type: "text", text: "Provide either 'id' or 'url' to fetch a post." }
|
|
35106
|
+
]
|
|
35107
|
+
};
|
|
35098
35108
|
}
|
|
35099
|
-
|
|
35109
|
+
return runTool(runtime4, exports_Effect.gen(function* () {
|
|
35100
35110
|
const client = yield* RedditClient;
|
|
35101
35111
|
let path;
|
|
35102
35112
|
if (url3) {
|
|
@@ -35109,7 +35119,6 @@ ${posts.map(formatPost).join(`
|
|
|
35109
35119
|
return `Post with ID "${id}" not found.`;
|
|
35110
35120
|
}
|
|
35111
35121
|
path = post2.permalink?.replace(/\/$/, "") ?? `/r/${post2.subreddit}/comments/${id}`;
|
|
35112
|
-
return formatPostWithComments(post2, [], comment_limit ?? 10);
|
|
35113
35122
|
}
|
|
35114
35123
|
const listingData = yield* client.get(path, { limit: comment_limit ?? 10 });
|
|
35115
35124
|
const post = listingData[0]?.data?.children?.[0]?.data ?? {};
|
|
@@ -35117,9 +35126,6 @@ ${posts.map(formatPost).join(`
|
|
|
35117
35126
|
const comments = commentChildren.filter((c) => c.kind === "t1").map((c) => c.data ?? {});
|
|
35118
35127
|
return formatPostWithComments(post, comments, comment_limit ?? 10);
|
|
35119
35128
|
}));
|
|
35120
|
-
if (result._tag === "Failure")
|
|
35121
|
-
return formatError2(result.cause);
|
|
35122
|
-
return formatSuccess(result.value);
|
|
35123
35129
|
});
|
|
35124
35130
|
server.tool("get_comments", "Fetch comments for a Reddit post. Supports sorting and depth control.", {
|
|
35125
35131
|
subreddit: exports_external.string().min(1).describe("Subreddit name without r/ prefix"),
|
|
@@ -35133,31 +35139,26 @@ ${posts.map(formatPost).join(`
|
|
|
35133
35139
|
destructiveHint: false,
|
|
35134
35140
|
idempotentHint: true,
|
|
35135
35141
|
openWorldHint: true
|
|
35136
|
-
}, async ({ subreddit, post_id, sort: sort2, limit, depth }) => {
|
|
35137
|
-
const
|
|
35138
|
-
|
|
35139
|
-
|
|
35140
|
-
|
|
35141
|
-
|
|
35142
|
-
|
|
35143
|
-
|
|
35144
|
-
|
|
35145
|
-
|
|
35146
|
-
|
|
35147
|
-
|
|
35148
|
-
const formatted = comments.map((c) => formatComment(c)).filter(Boolean).join(`
|
|
35142
|
+
}, async ({ subreddit, post_id, sort: sort2, limit, depth }) => runTool(runtime4, exports_Effect.gen(function* () {
|
|
35143
|
+
const client = yield* RedditClient;
|
|
35144
|
+
const data = yield* client.get(`/r/${subreddit}/comments/${post_id}`, {
|
|
35145
|
+
sort: sort2 ?? "confidence",
|
|
35146
|
+
limit: limit ?? 25,
|
|
35147
|
+
depth: depth ?? 3
|
|
35148
|
+
});
|
|
35149
|
+
const commentChildren = data[1]?.data?.children ?? [];
|
|
35150
|
+
const comments = commentChildren.filter((c) => c.kind === "t1").map((c) => c.data ?? {});
|
|
35151
|
+
if (!comments.length)
|
|
35152
|
+
return "No comments found for this post.";
|
|
35153
|
+
const formatted = comments.map((c) => formatComment(c)).filter(Boolean).join(`
|
|
35149
35154
|
|
|
35150
35155
|
---
|
|
35151
35156
|
|
|
35152
35157
|
`);
|
|
35153
|
-
|
|
35158
|
+
return `Comments for r/${subreddit}/comments/${post_id} (sort: ${sort2 ?? "confidence"}):
|
|
35154
35159
|
|
|
35155
35160
|
${formatted}`;
|
|
35156
|
-
|
|
35157
|
-
if (result._tag === "Failure")
|
|
35158
|
-
return formatError2(result.cause);
|
|
35159
|
-
return formatSuccess(result.value);
|
|
35160
|
-
});
|
|
35161
|
+
})));
|
|
35161
35162
|
server.tool("get_subreddit_info", "Fetch metadata for a subreddit \u2014 subscriber count, description, rules, and settings.", {
|
|
35162
35163
|
subreddit: exports_external.string().min(1).describe("Subreddit name without r/ prefix")
|
|
35163
35164
|
}, {
|
|
@@ -35166,41 +35167,36 @@ ${formatted}`;
|
|
|
35166
35167
|
destructiveHint: false,
|
|
35167
35168
|
idempotentHint: true,
|
|
35168
35169
|
openWorldHint: true
|
|
35169
|
-
}, async ({ subreddit }) => {
|
|
35170
|
-
const
|
|
35171
|
-
|
|
35172
|
-
|
|
35173
|
-
|
|
35174
|
-
|
|
35175
|
-
|
|
35176
|
-
|
|
35177
|
-
|
|
35178
|
-
|
|
35179
|
-
|
|
35180
|
-
|
|
35181
|
-
|
|
35182
|
-
|
|
35183
|
-
|
|
35184
|
-
|
|
35185
|
-
|
|
35186
|
-
|
|
35187
|
-
|
|
35188
|
-
|
|
35189
|
-
|
|
35190
|
-
|
|
35191
|
-
|
|
35192
|
-
|
|
35193
|
-
|
|
35194
|
-
|
|
35195
|
-
|
|
35196
|
-
|
|
35197
|
-
return lines.join(`
|
|
35170
|
+
}, async ({ subreddit }) => runTool(runtime4, exports_Effect.gen(function* () {
|
|
35171
|
+
const client = yield* RedditClient;
|
|
35172
|
+
const [aboutData, rulesData] = yield* exports_Effect.all([
|
|
35173
|
+
client.get(`/r/${subreddit}/about`),
|
|
35174
|
+
client.get(`/r/${subreddit}/about/rules`)
|
|
35175
|
+
], { concurrency: "unbounded" });
|
|
35176
|
+
const about = aboutData.data ?? {};
|
|
35177
|
+
const rules = rulesData.rules ?? [];
|
|
35178
|
+
const lines = [
|
|
35179
|
+
`r/${about.display_name ?? subreddit}`,
|
|
35180
|
+
`Title: ${about.title ?? "?"}`,
|
|
35181
|
+
`Subscribers: ${about.subscribers?.toLocaleString() ?? "?"}`,
|
|
35182
|
+
`Active users: ${about.active_user_count?.toLocaleString() ?? "?"}`,
|
|
35183
|
+
`NSFW: ${about.over18 ? "yes" : "no"}`,
|
|
35184
|
+
``,
|
|
35185
|
+
`Description:`,
|
|
35186
|
+
about.public_description ?? "(no description)"
|
|
35187
|
+
];
|
|
35188
|
+
if (rules.length) {
|
|
35189
|
+
lines.push("", "Rules:");
|
|
35190
|
+
for (const rule of rules) {
|
|
35191
|
+
lines.push(` \u2022 ${rule.short_name ?? "Rule"}: ${rule.description ?? ""}`);
|
|
35192
|
+
}
|
|
35193
|
+
}
|
|
35194
|
+
if (about.submit_text) {
|
|
35195
|
+
lines.push("", "Submission guidelines:", about.submit_text);
|
|
35196
|
+
}
|
|
35197
|
+
return lines.join(`
|
|
35198
35198
|
`);
|
|
35199
|
-
|
|
35200
|
-
if (result._tag === "Failure")
|
|
35201
|
-
return formatError2(result.cause);
|
|
35202
|
-
return formatSuccess(result.value);
|
|
35203
|
-
});
|
|
35199
|
+
})));
|
|
35204
35200
|
};
|
|
35205
35201
|
var formatPostWithComments = (post, comments, commentLimit) => {
|
|
35206
35202
|
const postLines = [
|
|
@@ -35259,31 +35255,26 @@ var registerSearchTools = (server, runtime4) => {
|
|
|
35259
35255
|
destructiveHint: false,
|
|
35260
35256
|
idempotentHint: true,
|
|
35261
35257
|
openWorldHint: true
|
|
35262
|
-
}, async ({ q, subreddit, sort: sort2, t, limit }) => {
|
|
35263
|
-
const
|
|
35264
|
-
|
|
35265
|
-
|
|
35266
|
-
|
|
35267
|
-
|
|
35268
|
-
|
|
35269
|
-
|
|
35270
|
-
|
|
35271
|
-
|
|
35272
|
-
|
|
35273
|
-
|
|
35274
|
-
|
|
35275
|
-
|
|
35276
|
-
|
|
35277
|
-
return `Search results for "${q}"${subreddit ? ` in r/${subreddit}` : ""}:
|
|
35258
|
+
}, async ({ q, subreddit, sort: sort2, t, limit }) => runTool(runtime4, exports_Effect.gen(function* () {
|
|
35259
|
+
const client = yield* RedditClient;
|
|
35260
|
+
const path = subreddit ? `/r/${subreddit}/search` : "/search";
|
|
35261
|
+
const data = yield* client.get(path, {
|
|
35262
|
+
q,
|
|
35263
|
+
sort: sort2 ?? "relevance",
|
|
35264
|
+
t: t ?? "all",
|
|
35265
|
+
limit: limit ?? 25,
|
|
35266
|
+
restrict_sr: subreddit ? "true" : undefined,
|
|
35267
|
+
type: "link"
|
|
35268
|
+
});
|
|
35269
|
+
const posts = extractPosts(data);
|
|
35270
|
+
if (!posts.length)
|
|
35271
|
+
return `No posts found for query: "${q}"`;
|
|
35272
|
+
return `Search results for "${q}"${subreddit ? ` in r/${subreddit}` : ""}:
|
|
35278
35273
|
|
|
35279
35274
|
${posts.map(formatPost2).join(`
|
|
35280
35275
|
|
|
35281
35276
|
`)}`;
|
|
35282
|
-
|
|
35283
|
-
if (result._tag === "Failure")
|
|
35284
|
-
return formatError2(result.cause);
|
|
35285
|
-
return formatSuccess(result.value);
|
|
35286
|
-
});
|
|
35277
|
+
})));
|
|
35287
35278
|
server.tool("search_subreddits", "Search for subreddits by topic or keyword. Returns matching subreddits with subscriber counts and descriptions.", {
|
|
35288
35279
|
q: exports_external.string().min(1).describe("Topic or keyword to search subreddits for"),
|
|
35289
35280
|
limit: exports_external.number().int().min(1).max(100).optional().describe("Number of results to return (1-100, default: 25)")
|
|
@@ -35293,30 +35284,25 @@ ${posts.map(formatPost2).join(`
|
|
|
35293
35284
|
destructiveHint: false,
|
|
35294
35285
|
idempotentHint: true,
|
|
35295
35286
|
openWorldHint: true
|
|
35296
|
-
}, async ({ q, limit }) => {
|
|
35297
|
-
const
|
|
35298
|
-
|
|
35299
|
-
|
|
35300
|
-
|
|
35301
|
-
|
|
35302
|
-
|
|
35303
|
-
const
|
|
35304
|
-
|
|
35305
|
-
|
|
35306
|
-
const desc = s.public_description ? `
|
|
35287
|
+
}, async ({ q, limit }) => runTool(runtime4, exports_Effect.gen(function* () {
|
|
35288
|
+
const client = yield* RedditClient;
|
|
35289
|
+
const data = yield* client.get("/subreddits/search", { q, limit: limit ?? 25 });
|
|
35290
|
+
const subs = (data.data?.children ?? []).map((c) => c.data ?? {});
|
|
35291
|
+
if (!subs.length)
|
|
35292
|
+
return `No subreddits found for: "${q}"`;
|
|
35293
|
+
const lines = subs.map((s) => {
|
|
35294
|
+
const name = s.display_name ?? "?";
|
|
35295
|
+
const subscribers = s.subscribers?.toLocaleString() ?? "?";
|
|
35296
|
+
const desc = s.public_description ? `
|
|
35307
35297
|
${s.public_description.slice(0, 150)}${(s.public_description?.length ?? 0) > 150 ? "\u2026" : ""}` : "";
|
|
35308
|
-
|
|
35309
|
-
|
|
35310
|
-
|
|
35298
|
+
return `r/${name} (${subscribers} subscribers)${desc}`;
|
|
35299
|
+
});
|
|
35300
|
+
return `Subreddits matching "${q}":
|
|
35311
35301
|
|
|
35312
35302
|
${lines.join(`
|
|
35313
35303
|
|
|
35314
35304
|
`)}`;
|
|
35315
|
-
|
|
35316
|
-
if (result._tag === "Failure")
|
|
35317
|
-
return formatError2(result.cause);
|
|
35318
|
-
return formatSuccess(result.value);
|
|
35319
|
-
});
|
|
35305
|
+
})));
|
|
35320
35306
|
};
|
|
35321
35307
|
|
|
35322
35308
|
// src/mcp/tools/user.ts
|
|
@@ -35356,19 +35342,14 @@ var registerUserTools = (server, runtime4) => {
|
|
|
35356
35342
|
destructiveHint: false,
|
|
35357
35343
|
idempotentHint: true,
|
|
35358
35344
|
openWorldHint: true
|
|
35359
|
-
}, async ({ username }) => {
|
|
35360
|
-
const
|
|
35361
|
-
|
|
35362
|
-
|
|
35363
|
-
|
|
35364
|
-
|
|
35365
|
-
|
|
35366
|
-
|
|
35367
|
-
}));
|
|
35368
|
-
if (result._tag === "Failure")
|
|
35369
|
-
return formatError2(result.cause);
|
|
35370
|
-
return formatSuccess(result.value);
|
|
35371
|
-
});
|
|
35345
|
+
}, async ({ username }) => runTool(runtime4, exports_Effect.gen(function* () {
|
|
35346
|
+
const client = yield* RedditClient;
|
|
35347
|
+
const data = yield* client.get(`/user/${username}/about`);
|
|
35348
|
+
const user = data.data;
|
|
35349
|
+
if (!user)
|
|
35350
|
+
return `User u/${username} not found.`;
|
|
35351
|
+
return formatUser(user);
|
|
35352
|
+
})));
|
|
35372
35353
|
server.tool("get_user_posts", "Fetch a Reddit user's submitted posts.", {
|
|
35373
35354
|
username: exports_external.string().min(1).describe("Reddit username without u/ prefix"),
|
|
35374
35355
|
sort: exports_external.enum(["hot", "new", "top", "controversial"]).optional().describe("Sort order (default: new)"),
|
|
@@ -35381,31 +35362,24 @@ var registerUserTools = (server, runtime4) => {
|
|
|
35381
35362
|
destructiveHint: false,
|
|
35382
35363
|
idempotentHint: false,
|
|
35383
35364
|
openWorldHint: true
|
|
35384
|
-
}, async ({ username, sort: sort2, t, limit, after: after3 }) => {
|
|
35385
|
-
const
|
|
35386
|
-
|
|
35387
|
-
|
|
35388
|
-
|
|
35389
|
-
|
|
35390
|
-
|
|
35391
|
-
|
|
35392
|
-
|
|
35393
|
-
|
|
35394
|
-
|
|
35395
|
-
|
|
35396
|
-
|
|
35397
|
-
|
|
35398
|
-
More results available. Use after="${data.data.after}" to fetch the next page.` : "";
|
|
35399
|
-
return `Posts by u/${username}:
|
|
35365
|
+
}, async ({ username, sort: sort2, t, limit, after: after3 }) => runTool(runtime4, exports_Effect.gen(function* () {
|
|
35366
|
+
const client = yield* RedditClient;
|
|
35367
|
+
const data = yield* client.get(`/user/${username}/submitted`, {
|
|
35368
|
+
sort: sort2 ?? "new",
|
|
35369
|
+
t: t ?? "all",
|
|
35370
|
+
limit: limit ?? 25,
|
|
35371
|
+
after: after3
|
|
35372
|
+
});
|
|
35373
|
+
const posts = (data.data?.children ?? []).map((c) => c.data ?? {});
|
|
35374
|
+
if (!posts.length)
|
|
35375
|
+
return `u/${username} has no posts.`;
|
|
35376
|
+
const pagination = buildPaginationString(data.data?.after);
|
|
35377
|
+
return `Posts by u/${username}:
|
|
35400
35378
|
|
|
35401
35379
|
${posts.map(formatUserPost).join(`
|
|
35402
35380
|
|
|
35403
35381
|
`)}${pagination}`;
|
|
35404
|
-
|
|
35405
|
-
if (result._tag === "Failure")
|
|
35406
|
-
return formatError2(result.cause);
|
|
35407
|
-
return formatSuccess(result.value);
|
|
35408
|
-
});
|
|
35382
|
+
})));
|
|
35409
35383
|
server.tool("get_user_comments", "Fetch a Reddit user's comment history.", {
|
|
35410
35384
|
username: exports_external.string().min(1).describe("Reddit username without u/ prefix"),
|
|
35411
35385
|
sort: exports_external.enum(["hot", "new", "top", "controversial"]).optional().describe("Sort order (default: new)"),
|
|
@@ -35418,31 +35392,24 @@ ${posts.map(formatUserPost).join(`
|
|
|
35418
35392
|
destructiveHint: false,
|
|
35419
35393
|
idempotentHint: false,
|
|
35420
35394
|
openWorldHint: true
|
|
35421
|
-
}, async ({ username, sort: sort2, t, limit, after: after3 }) => {
|
|
35422
|
-
const
|
|
35423
|
-
|
|
35424
|
-
|
|
35425
|
-
|
|
35426
|
-
|
|
35427
|
-
|
|
35428
|
-
|
|
35429
|
-
|
|
35430
|
-
|
|
35431
|
-
|
|
35432
|
-
|
|
35433
|
-
|
|
35434
|
-
|
|
35435
|
-
More results available. Use after="${data.data.after}" to fetch the next page.` : "";
|
|
35436
|
-
return `Comments by u/${username}:
|
|
35395
|
+
}, async ({ username, sort: sort2, t, limit, after: after3 }) => runTool(runtime4, exports_Effect.gen(function* () {
|
|
35396
|
+
const client = yield* RedditClient;
|
|
35397
|
+
const data = yield* client.get(`/user/${username}/comments`, {
|
|
35398
|
+
sort: sort2 ?? "new",
|
|
35399
|
+
t: t ?? "all",
|
|
35400
|
+
limit: limit ?? 25,
|
|
35401
|
+
after: after3
|
|
35402
|
+
});
|
|
35403
|
+
const comments = (data.data?.children ?? []).map((c) => c.data ?? {});
|
|
35404
|
+
if (!comments.length)
|
|
35405
|
+
return `u/${username} has no comments.`;
|
|
35406
|
+
const pagination = buildPaginationString(data.data?.after);
|
|
35407
|
+
return `Comments by u/${username}:
|
|
35437
35408
|
|
|
35438
35409
|
${comments.map(formatUserComment).join(`
|
|
35439
35410
|
|
|
35440
35411
|
`)}` + pagination;
|
|
35441
|
-
|
|
35442
|
-
if (result._tag === "Failure")
|
|
35443
|
-
return formatError2(result.cause);
|
|
35444
|
-
return formatSuccess(result.value);
|
|
35445
|
-
});
|
|
35412
|
+
})));
|
|
35446
35413
|
};
|
|
35447
35414
|
|
|
35448
35415
|
// src/mcp/tools/write.ts
|
|
@@ -35467,43 +35434,38 @@ var registerWriteTools = (server, runtime4) => {
|
|
|
35467
35434
|
destructiveHint: false,
|
|
35468
35435
|
idempotentHint: false,
|
|
35469
35436
|
openWorldHint: true
|
|
35470
|
-
}, async ({ subreddit, title, kind, text, url: url3, nsfw, spoiler, flair_id, flair_text }) => {
|
|
35471
|
-
const
|
|
35472
|
-
|
|
35473
|
-
|
|
35474
|
-
|
|
35475
|
-
|
|
35476
|
-
|
|
35477
|
-
|
|
35478
|
-
|
|
35479
|
-
|
|
35480
|
-
|
|
35481
|
-
|
|
35482
|
-
|
|
35483
|
-
|
|
35484
|
-
|
|
35485
|
-
|
|
35486
|
-
|
|
35487
|
-
|
|
35488
|
-
|
|
35489
|
-
|
|
35490
|
-
|
|
35491
|
-
|
|
35492
|
-
|
|
35493
|
-
|
|
35494
|
-
|
|
35495
|
-
|
|
35496
|
-
|
|
35497
|
-
|
|
35498
|
-
|
|
35499
|
-
return `Post submitted successfully!
|
|
35437
|
+
}, async ({ subreddit, title, kind, text, url: url3, nsfw, spoiler, flair_id, flair_text }) => runTool(runtime4, exports_Effect.gen(function* () {
|
|
35438
|
+
const hasToken = yield* requireRefreshToken;
|
|
35439
|
+
if (!hasToken)
|
|
35440
|
+
return WRITE_AUTH_ERROR;
|
|
35441
|
+
const client = yield* RedditClient;
|
|
35442
|
+
const body = {
|
|
35443
|
+
sr: subreddit,
|
|
35444
|
+
title,
|
|
35445
|
+
kind,
|
|
35446
|
+
resubmit: true,
|
|
35447
|
+
nsfw: nsfw ?? false,
|
|
35448
|
+
spoiler: spoiler ?? false
|
|
35449
|
+
};
|
|
35450
|
+
if (kind === "self" && text)
|
|
35451
|
+
body.text = text;
|
|
35452
|
+
if (kind === "link" && url3)
|
|
35453
|
+
body.url = url3;
|
|
35454
|
+
if (flair_id)
|
|
35455
|
+
body.flair_id = flair_id;
|
|
35456
|
+
if (flair_text)
|
|
35457
|
+
body.flair_text = flair_text;
|
|
35458
|
+
const data = yield* client.post("/api/submit", body);
|
|
35459
|
+
const errors4 = data.json?.errors ?? [];
|
|
35460
|
+
if (errors4.length > 0) {
|
|
35461
|
+
return `Post submission failed. Errors: ${JSON.stringify(errors4)}`;
|
|
35462
|
+
}
|
|
35463
|
+
const postUrl = data.json?.data?.url ?? "";
|
|
35464
|
+
const postId = data.json?.data?.id ?? "";
|
|
35465
|
+
return `Post submitted successfully!
|
|
35500
35466
|
ID: ${postId}
|
|
35501
35467
|
URL: ${postUrl}`;
|
|
35502
|
-
|
|
35503
|
-
if (result._tag === "Failure")
|
|
35504
|
-
return formatError2(result.cause);
|
|
35505
|
-
return formatSuccess(result.value);
|
|
35506
|
-
});
|
|
35468
|
+
})));
|
|
35507
35469
|
server.tool("submit_comment", "Reply to a Reddit post or comment. Requires REDDIT_REFRESH_TOKEN to be configured.", {
|
|
35508
35470
|
parent_id: exports_external.string().min(1).describe("Fullname of the parent \u2014 a post ID prefixed with t3_ (e.g. 't3_abc123') or a comment ID prefixed with t1_ (e.g. 't1_xyz789')"),
|
|
35509
35471
|
text: exports_external.string().min(1).describe("Comment body in Markdown")
|
|
@@ -35513,31 +35475,26 @@ URL: ${postUrl}`;
|
|
|
35513
35475
|
destructiveHint: false,
|
|
35514
35476
|
idempotentHint: false,
|
|
35515
35477
|
openWorldHint: true
|
|
35516
|
-
}, async ({ parent_id, text }) => {
|
|
35517
|
-
const
|
|
35518
|
-
|
|
35519
|
-
|
|
35520
|
-
|
|
35521
|
-
|
|
35522
|
-
|
|
35523
|
-
|
|
35524
|
-
|
|
35525
|
-
|
|
35526
|
-
|
|
35527
|
-
|
|
35528
|
-
|
|
35529
|
-
|
|
35530
|
-
|
|
35531
|
-
|
|
35532
|
-
|
|
35533
|
-
return `Comment submitted successfully!
|
|
35478
|
+
}, async ({ parent_id, text }) => runTool(runtime4, exports_Effect.gen(function* () {
|
|
35479
|
+
const hasToken = yield* requireRefreshToken;
|
|
35480
|
+
if (!hasToken)
|
|
35481
|
+
return WRITE_AUTH_ERROR;
|
|
35482
|
+
const client = yield* RedditClient;
|
|
35483
|
+
const data = yield* client.post("/api/comment", {
|
|
35484
|
+
parent: parent_id,
|
|
35485
|
+
text
|
|
35486
|
+
});
|
|
35487
|
+
const errors4 = data.json?.errors ?? [];
|
|
35488
|
+
if (errors4.length > 0) {
|
|
35489
|
+
return `Comment submission failed. Errors: ${JSON.stringify(errors4)}`;
|
|
35490
|
+
}
|
|
35491
|
+
const comment = data.json?.data?.things?.[0]?.data;
|
|
35492
|
+
const commentId = comment?.id ?? "";
|
|
35493
|
+
const permalink = comment?.permalink ?? "";
|
|
35494
|
+
return `Comment submitted successfully!
|
|
35534
35495
|
ID: ${commentId}${permalink ? `
|
|
35535
35496
|
URL: https://reddit.com${permalink}` : ""}`;
|
|
35536
|
-
|
|
35537
|
-
if (result._tag === "Failure")
|
|
35538
|
-
return formatError2(result.cause);
|
|
35539
|
-
return formatSuccess(result.value);
|
|
35540
|
-
});
|
|
35497
|
+
})));
|
|
35541
35498
|
server.tool("vote", "Vote on a Reddit post or comment. Requires REDDIT_REFRESH_TOKEN to be configured.", {
|
|
35542
35499
|
id: exports_external.string().min(1).describe("Fullname of the thing to vote on \u2014 post ID prefixed with t3_ (e.g. 't3_abc123') or comment ID prefixed with t1_ (e.g. 't1_xyz789')"),
|
|
35543
35500
|
dir: exports_external.union([exports_external.literal(1), exports_external.literal(0), exports_external.literal(-1)]).describe("Vote direction: 1 = upvote, 0 = remove vote, -1 = downvote")
|
|
@@ -35547,20 +35504,15 @@ URL: https://reddit.com${permalink}` : ""}`;
|
|
|
35547
35504
|
destructiveHint: false,
|
|
35548
35505
|
idempotentHint: true,
|
|
35549
35506
|
openWorldHint: true
|
|
35550
|
-
}, async ({ id, dir }) => {
|
|
35551
|
-
const
|
|
35552
|
-
|
|
35553
|
-
|
|
35554
|
-
|
|
35555
|
-
|
|
35556
|
-
|
|
35557
|
-
|
|
35558
|
-
|
|
35559
|
-
}));
|
|
35560
|
-
if (result._tag === "Failure")
|
|
35561
|
-
return formatError2(result.cause);
|
|
35562
|
-
return formatSuccess(result.value);
|
|
35563
|
-
});
|
|
35507
|
+
}, async ({ id, dir }) => runTool(runtime4, exports_Effect.gen(function* () {
|
|
35508
|
+
const hasToken = yield* requireRefreshToken;
|
|
35509
|
+
if (!hasToken)
|
|
35510
|
+
return WRITE_AUTH_ERROR;
|
|
35511
|
+
const client = yield* RedditClient;
|
|
35512
|
+
yield* client.post("/api/vote", { id, dir });
|
|
35513
|
+
const label = dir === 1 ? "Upvoted" : dir === -1 ? "Downvoted" : "Vote removed from";
|
|
35514
|
+
return `${label} ${id}`;
|
|
35515
|
+
})));
|
|
35564
35516
|
server.tool("save_post", "Save or unsave a Reddit post or comment. Requires REDDIT_REFRESH_TOKEN to be configured.", {
|
|
35565
35517
|
id: exports_external.string().min(1).describe("Fullname of the post or comment \u2014 e.g. 't3_abc123' for a post, 't1_xyz789' for a comment"),
|
|
35566
35518
|
save: exports_external.boolean().describe("true to save, false to unsave")
|
|
@@ -35570,20 +35522,15 @@ URL: https://reddit.com${permalink}` : ""}`;
|
|
|
35570
35522
|
destructiveHint: false,
|
|
35571
35523
|
idempotentHint: true,
|
|
35572
35524
|
openWorldHint: true
|
|
35573
|
-
}, async ({ id, save }) => {
|
|
35574
|
-
const
|
|
35575
|
-
|
|
35576
|
-
|
|
35577
|
-
|
|
35578
|
-
|
|
35579
|
-
|
|
35580
|
-
|
|
35581
|
-
|
|
35582
|
-
}));
|
|
35583
|
-
if (result._tag === "Failure")
|
|
35584
|
-
return formatError2(result.cause);
|
|
35585
|
-
return formatSuccess(result.value);
|
|
35586
|
-
});
|
|
35525
|
+
}, async ({ id, save }) => runTool(runtime4, exports_Effect.gen(function* () {
|
|
35526
|
+
const hasToken = yield* requireRefreshToken;
|
|
35527
|
+
if (!hasToken)
|
|
35528
|
+
return WRITE_AUTH_ERROR;
|
|
35529
|
+
const client = yield* RedditClient;
|
|
35530
|
+
const path = save ? "/api/save" : "/api/unsave";
|
|
35531
|
+
yield* client.post(path, { id });
|
|
35532
|
+
return `${save ? "Saved" : "Unsaved"} ${id}`;
|
|
35533
|
+
})));
|
|
35587
35534
|
};
|
|
35588
35535
|
|
|
35589
35536
|
// src/mcp/server.ts
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daanrongen/reddit-mcp",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "MCP server for Reddit
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "MCP server for Reddit \u2014 search posts, browse subreddits, read comments, and write with OAuth2 user auth over stdio",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"reddit-mcp": "./dist/main.js"
|