@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.
Files changed (3) hide show
  1. package/README.md +48 -37
  2. package/dist/main.js +21 -6
  3. 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
- npx -y @daanrongen/reddit-mcp
8
+ bunx @daanrongen/reddit-mcp
9
9
  ```
10
10
 
11
- ## Tools (13 total)
11
+ ## Tools
12
12
 
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) |
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
- ## Setup
20
+ ## Configuration
21
21
 
22
- ### Reddit OAuth2 app
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
- 1. Go to [https://www.reddit.com/prefs/apps](https://www.reddit.com/prefs/apps)
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
- ### Environment variables
31
+ ### OAuth2 setup
30
32
 
31
- | Variable | Required | Description |
32
- | ----------------------- | -------------- | -------------------------------------- |
33
- | `REDDIT_CLIENT_ID` | Yes | OAuth2 app client ID |
34
- | `REDDIT_CLIENT_SECRET` | Yes | OAuth2 app client secret |
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
- Read-only tools (`search_posts`, `browse`, `user` tools) work with just `REDDIT_CLIENT_ID` and `REDDIT_CLIENT_SECRET` using app-only auth. Write tools require a valid refresh token.
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": "npx",
50
- "args": ["-y", "@daanrongen/reddit-mcp"],
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
- Or via the CLI:
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
- -- npx -y @daanrongen/reddit-mcp
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 # Effect Config — REDDIT_CLIENT_ID, REDDIT_CLIENT_SECRET, …
96
- ├── main.ts # Entry point — ManagedRuntime + StdioServerTransport
97
+ ├── config.ts # Effect Config — REDDIT_CLIENT_ID, REDDIT_CLIENT_SECRET, …
98
+ ├── main.ts # Entry point — ManagedRuntime + StdioServerTransport
97
99
  ├── domain/
98
- │ ├── RedditClient.ts # Context.Tag service interface
99
- │ ├── errors.ts # RedditError, AuthError
100
- └── models.ts # Schema.Class models (Post, Comment, Subreddit, …)
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
- │ ├── RedditClientLive.ts # Layer.scoped — OAuth2 token management + Reddit API
103
- └── RedditClientTest.ts # In-memory test adapter
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 # McpServer wired to ManagedRuntime
106
- ├── utils.ts # formatSuccess, formatError
107
- └── tools/ # search.ts, browse.ts, user.ts, write.ts
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: union([number2(), _null3()]).optional(),
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["refresh_token"] ? exports_Option.some(grantParams["refresh_token"]) : exports_Option.none()
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.delete(request2.id);
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
- `)}` + pagination;
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.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 dist/main.js",
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.10.2",
46
- "effect": "^3.13.0",
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.8",
49
+ "@biomejs/biome": "^2.4.12",
51
50
  "bun-types": "latest",
52
- "lefthook": "^2.1.4",
51
+ "lefthook": "^2.1.5",
53
52
  "typescript": "^5.8.3"
54
53
  }
55
54
  }