@daanrongen/reddit-mcp 1.0.0 → 1.0.2
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 +48 -37
- package/dist/main.js +21 -6
- package/package.json +6 -7
package/README.md
CHANGED
|
@@ -5,37 +5,37 @@ MCP server for [Reddit](https://www.reddit.com/) — search posts, browse subred
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
8
|
+
bunx @daanrongen/reddit-mcp
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
## Tools
|
|
11
|
+
## Tools
|
|
12
12
|
|
|
13
|
-
| Domain | Tools
|
|
14
|
-
| ---------- |
|
|
15
|
-
| **Search** | `search_posts`, `search_subreddits`
|
|
16
|
-
| **Browse** | `get_subreddit_posts`, `get_post`, `get_comments`, `get_subreddit_info`
|
|
17
|
-
| **User** | `get_user_profile`, `get_user_posts`, `get_user_comments`
|
|
18
|
-
| **Write** | `submit_post`, `submit_comment`, `vote`, `save_post`
|
|
13
|
+
| Domain | Tools | Coverage |
|
|
14
|
+
| ---------- | ----------------------------------------------------------------------- | ------------------------------------------ |
|
|
15
|
+
| **Search** | `search_posts`, `search_subreddits` | Full-text post and subreddit search |
|
|
16
|
+
| **Browse** | `get_subreddit_posts`, `get_post`, `get_comments`, `get_subreddit_info` | Read posts, comments, and subreddit info |
|
|
17
|
+
| **User** | `get_user_profile`, `get_user_posts`, `get_user_comments` | User profiles and activity history |
|
|
18
|
+
| **Write** | `submit_post`, `submit_comment`, `vote`, `save_post` | Post, comment, vote, and save (OAuth only) |
|
|
19
19
|
|
|
20
|
-
##
|
|
20
|
+
## Configuration
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
| Variable | Required | Description |
|
|
23
|
+
| ---------------------- | ---------------- | ----------------------------------- |
|
|
24
|
+
| `REDDIT_CLIENT_ID` | Yes | OAuth2 app client ID |
|
|
25
|
+
| `REDDIT_CLIENT_SECRET` | Yes | OAuth2 app client secret |
|
|
26
|
+
| `REDDIT_REFRESH_TOKEN` | For write tools | Refresh token for user-level access |
|
|
27
|
+
| `REDDIT_USERNAME` | For write tools | Reddit username |
|
|
23
28
|
|
|
24
|
-
|
|
25
|
-
2. Create a **script** app (for personal use) or **web app** (for user auth)
|
|
26
|
-
3. Note the **client ID** (under the app name) and **client secret**
|
|
27
|
-
4. For write access, obtain a refresh token via the OAuth2 flow
|
|
29
|
+
Read-only tools (`search_posts`, browse, and user tools) work with just `REDDIT_CLIENT_ID` and `REDDIT_CLIENT_SECRET` using app-only auth. Write tools require a valid refresh token.
|
|
28
30
|
|
|
29
|
-
###
|
|
31
|
+
### OAuth2 setup
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
| `REDDIT_REFRESH_TOKEN` | For write tools | Refresh token for user-level access |
|
|
36
|
-
| `REDDIT_USERNAME` | For write tools | Reddit username |
|
|
33
|
+
1. Go to [https://www.reddit.com/prefs/apps](https://www.reddit.com/prefs/apps)
|
|
34
|
+
2. Create a **script** app (for personal use) or **web app** (for user auth)
|
|
35
|
+
3. Note the **client ID** (shown under the app name) and **client secret**
|
|
36
|
+
4. For write access, run the OAuth2 authorization flow to obtain a refresh token
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
## Setup
|
|
39
39
|
|
|
40
40
|
### Claude Desktop
|
|
41
41
|
|
|
@@ -46,8 +46,8 @@ Edit `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
|
46
46
|
"mcpServers": {
|
|
47
47
|
"reddit": {
|
|
48
48
|
"type": "stdio",
|
|
49
|
-
"command": "
|
|
50
|
-
"args": ["
|
|
49
|
+
"command": "bunx",
|
|
50
|
+
"args": ["@daanrongen/reddit-mcp"],
|
|
51
51
|
"env": {
|
|
52
52
|
"REDDIT_CLIENT_ID": "your-client-id",
|
|
53
53
|
"REDDIT_CLIENT_SECRET": "your-client-secret",
|
|
@@ -59,13 +59,13 @@ Edit `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
|
59
59
|
}
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
### Claude Code CLI
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
65
|
claude mcp add reddit \
|
|
66
66
|
-e REDDIT_CLIENT_ID=your-client-id \
|
|
67
67
|
-e REDDIT_CLIENT_SECRET=your-client-secret \
|
|
68
|
-
--
|
|
68
|
+
-- bunx @daanrongen/reddit-mcp
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
## Development
|
|
@@ -74,8 +74,10 @@ claude mcp add reddit \
|
|
|
74
74
|
bun install
|
|
75
75
|
bun run dev # run with --watch
|
|
76
76
|
bun test # run test suite
|
|
77
|
+
bun run typecheck # type-check without emit
|
|
78
|
+
bun run lint # biome lint
|
|
79
|
+
bun run format # biome format
|
|
77
80
|
bun run build # bundle to dist/main.js
|
|
78
|
-
bun run inspect # open MCP Inspector in browser
|
|
79
81
|
```
|
|
80
82
|
|
|
81
83
|
## Inspecting locally
|
|
@@ -92,17 +94,26 @@ This opens the Inspector UI in your browser where you can call any tool interact
|
|
|
92
94
|
|
|
93
95
|
```
|
|
94
96
|
src/
|
|
95
|
-
├── config.ts
|
|
96
|
-
├── main.ts
|
|
97
|
+
├── config.ts # Effect Config — REDDIT_CLIENT_ID, REDDIT_CLIENT_SECRET, …
|
|
98
|
+
├── main.ts # Entry point — ManagedRuntime + StdioServerTransport
|
|
97
99
|
├── domain/
|
|
98
|
-
│ ├──
|
|
99
|
-
│ ├──
|
|
100
|
-
│
|
|
100
|
+
│ ├── auth.test.ts # RedditAuth unit tests
|
|
101
|
+
│ ├── client.test.ts # RedditClient unit tests
|
|
102
|
+
│ ├── tools.test.ts # Tool-level data shape tests
|
|
103
|
+
│ ├── errors.ts # RedditError, AuthError
|
|
104
|
+
│ ├── models.ts # Schema.Class models (Post, Comment, Subreddit, …)
|
|
105
|
+
│ ├── RedditAuth.ts # Context.Tag auth service interface
|
|
106
|
+
│ └── RedditClient.ts # Context.Tag client service interface
|
|
101
107
|
├── infra/
|
|
102
|
-
│ ├──
|
|
103
|
-
│
|
|
108
|
+
│ ├── RedditAuthLive.ts # OAuth2 token management
|
|
109
|
+
│ ├── RedditClientLive.ts # Layer.scoped — Reddit API adapter
|
|
110
|
+
│ └── RedditClientTest.ts # In-memory test adapter
|
|
104
111
|
└── mcp/
|
|
105
|
-
├── server.ts
|
|
106
|
-
├── utils.ts
|
|
107
|
-
└── tools/
|
|
112
|
+
├── server.ts # McpServer wired to ManagedRuntime
|
|
113
|
+
├── utils.ts # formatSuccess, formatError
|
|
114
|
+
└── tools/ # search.ts, browse.ts, user.ts, write.ts
|
|
108
115
|
```
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
|
|
119
|
+
MIT
|
package/dist/main.js
CHANGED
|
@@ -10984,7 +10984,7 @@ var AssertObjectSchema = custom((v) => v !== null && (typeof v === "object" || t
|
|
|
10984
10984
|
var ProgressTokenSchema = union([string2(), number2().int()]);
|
|
10985
10985
|
var CursorSchema = string2();
|
|
10986
10986
|
var TaskCreationParamsSchema = looseObject({
|
|
10987
|
-
ttl:
|
|
10987
|
+
ttl: number2().optional(),
|
|
10988
10988
|
pollInterval: number2().optional()
|
|
10989
10989
|
});
|
|
10990
10990
|
var TaskMetadataSchema = object({
|
|
@@ -11138,7 +11138,8 @@ var ClientCapabilitiesSchema = object({
|
|
|
11138
11138
|
roots: object({
|
|
11139
11139
|
listChanged: boolean2().optional()
|
|
11140
11140
|
}).optional(),
|
|
11141
|
-
tasks: ClientTasksCapabilitySchema.optional()
|
|
11141
|
+
tasks: ClientTasksCapabilitySchema.optional(),
|
|
11142
|
+
extensions: record(string2(), AssertObjectSchema).optional()
|
|
11142
11143
|
});
|
|
11143
11144
|
var InitializeRequestParamsSchema = BaseRequestParamsSchema.extend({
|
|
11144
11145
|
protocolVersion: string2(),
|
|
@@ -11163,7 +11164,8 @@ var ServerCapabilitiesSchema = object({
|
|
|
11163
11164
|
tools: object({
|
|
11164
11165
|
listChanged: boolean2().optional()
|
|
11165
11166
|
}).optional(),
|
|
11166
|
-
tasks: ServerTasksCapabilitySchema.optional()
|
|
11167
|
+
tasks: ServerTasksCapabilitySchema.optional(),
|
|
11168
|
+
extensions: record(string2(), AssertObjectSchema).optional()
|
|
11167
11169
|
});
|
|
11168
11170
|
var InitializeResultSchema = ResultSchema.extend({
|
|
11169
11171
|
protocolVersion: string2(),
|
|
@@ -11278,6 +11280,7 @@ var ResourceSchema = object({
|
|
|
11278
11280
|
uri: string2(),
|
|
11279
11281
|
description: optional(string2()),
|
|
11280
11282
|
mimeType: optional(string2()),
|
|
11283
|
+
size: optional(number2()),
|
|
11281
11284
|
annotations: AnnotationsSchema.optional(),
|
|
11282
11285
|
_meta: optional(looseObject({}))
|
|
11283
11286
|
});
|
|
@@ -27292,7 +27295,7 @@ var fetchToken = (clientId, clientSecret, grantParams, userAgent) => exports_Eff
|
|
|
27292
27295
|
accessToken: json.access_token,
|
|
27293
27296
|
expiresAt: Date.now() + (json.expires_in ?? 3600) * 1000,
|
|
27294
27297
|
scope: json.scope ?? "",
|
|
27295
|
-
refreshToken: grantParams
|
|
27298
|
+
refreshToken: grantParams.refresh_token ? exports_Option.some(grantParams.refresh_token) : exports_Option.none()
|
|
27296
27299
|
};
|
|
27297
27300
|
},
|
|
27298
27301
|
catch: (e) => new RedditAuthError({
|
|
@@ -33053,6 +33056,10 @@ class Protocol {
|
|
|
33053
33056
|
this._progressHandlers.clear();
|
|
33054
33057
|
this._taskProgressTokens.clear();
|
|
33055
33058
|
this._pendingDebouncedNotifications.clear();
|
|
33059
|
+
for (const info of this._timeoutInfo.values()) {
|
|
33060
|
+
clearTimeout(info.timeoutId);
|
|
33061
|
+
}
|
|
33062
|
+
this._timeoutInfo.clear();
|
|
33056
33063
|
for (const controller of this._requestHandlerAbortControllers.values()) {
|
|
33057
33064
|
controller.abort();
|
|
33058
33065
|
}
|
|
@@ -33183,7 +33190,9 @@ class Protocol {
|
|
|
33183
33190
|
await capturedTransport?.send(errorResponse);
|
|
33184
33191
|
}
|
|
33185
33192
|
}).catch((error2) => this._onerror(new Error(`Failed to send response: ${error2}`))).finally(() => {
|
|
33186
|
-
this._requestHandlerAbortControllers.
|
|
33193
|
+
if (this._requestHandlerAbortControllers.get(request2.id) === abortController) {
|
|
33194
|
+
this._requestHandlerAbortControllers.delete(request2.id);
|
|
33195
|
+
}
|
|
33187
33196
|
});
|
|
33188
33197
|
}
|
|
33189
33198
|
_onprogress(notification) {
|
|
@@ -34840,6 +34849,9 @@ class McpServer {
|
|
|
34840
34849
|
annotations = rest.shift();
|
|
34841
34850
|
}
|
|
34842
34851
|
} else if (typeof firstArg === "object" && firstArg !== null) {
|
|
34852
|
+
if (Object.values(firstArg).some((v) => typeof v === "object" && v !== null)) {
|
|
34853
|
+
throw new Error(`Tool ${name} expected a Zod schema or ToolAnnotations, but received an unrecognized object`);
|
|
34854
|
+
}
|
|
34843
34855
|
annotations = rest.shift();
|
|
34844
34856
|
}
|
|
34845
34857
|
}
|
|
@@ -34932,6 +34944,9 @@ function getZodSchemaObject(schema) {
|
|
|
34932
34944
|
if (isZodRawShapeCompat(schema)) {
|
|
34933
34945
|
return objectFromShape(schema);
|
|
34934
34946
|
}
|
|
34947
|
+
if (!isZodSchemaInstance(schema)) {
|
|
34948
|
+
throw new Error("inputSchema must be a Zod schema or raw shape, received an unrecognized object");
|
|
34949
|
+
}
|
|
34935
34950
|
return schema;
|
|
34936
34951
|
}
|
|
34937
34952
|
function promptArgumentsFromSchema(schema) {
|
|
@@ -35385,7 +35400,7 @@ More results available. Use after="${data.data.after}" to fetch the next page.`
|
|
|
35385
35400
|
|
|
35386
35401
|
${posts.map(formatUserPost).join(`
|
|
35387
35402
|
|
|
35388
|
-
`)}
|
|
35403
|
+
`)}${pagination}`;
|
|
35389
35404
|
}));
|
|
35390
35405
|
if (result._tag === "Failure")
|
|
35391
35406
|
return formatError2(result.cause);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daanrongen/reddit-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "MCP server for Reddit — search posts, browse subreddits, read comments, and write with OAuth2 user auth over stdio",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,9 +11,8 @@
|
|
|
11
11
|
"dist"
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
|
-
"start": "bun run src/main.ts",
|
|
15
14
|
"dev": "bun --watch src/main.ts",
|
|
16
|
-
"inspect": "DANGEROUSLY_OMIT_AUTH=true mcp-inspector bun
|
|
15
|
+
"inspect": "DANGEROUSLY_OMIT_AUTH=true mcp-inspector bun src/main.ts",
|
|
17
16
|
"build": "bun build src/main.ts --outfile dist/main.js --target bun",
|
|
18
17
|
"typecheck": "tsc -p tsconfig.check.json",
|
|
19
18
|
"test": "bun test",
|
|
@@ -42,14 +41,14 @@
|
|
|
42
41
|
"access": "public"
|
|
43
42
|
},
|
|
44
43
|
"dependencies": {
|
|
45
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
46
|
-
"effect": "^3.
|
|
44
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
45
|
+
"effect": "^3.21.0",
|
|
47
46
|
"zod": "^3.24.1"
|
|
48
47
|
},
|
|
49
48
|
"devDependencies": {
|
|
50
|
-
"@biomejs/biome": "^2.4.
|
|
49
|
+
"@biomejs/biome": "^2.4.12",
|
|
51
50
|
"bun-types": "latest",
|
|
52
|
-
"lefthook": "^2.1.
|
|
51
|
+
"lefthook": "^2.1.5",
|
|
53
52
|
"typescript": "^5.8.3"
|
|
54
53
|
}
|
|
55
54
|
}
|