@prmichaelsen/reddit-mcp 0.1.0 → 0.1.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.
Files changed (58) hide show
  1. package/.claude/settings.local.json +4 -1
  2. package/README.md +253 -27
  3. package/agent/progress.yaml +29 -30
  4. package/dist/factory.js +1206 -0
  5. package/dist/factory.js.map +4 -4
  6. package/dist/index.js +1206 -0
  7. package/dist/index.js.map +4 -4
  8. package/dist/server.d.ts.map +1 -1
  9. package/dist/server.js +1206 -0
  10. package/dist/server.js.map +4 -4
  11. package/dist/tools/account.d.ts +4 -0
  12. package/dist/tools/account.d.ts.map +1 -0
  13. package/dist/tools/comments.d.ts +4 -0
  14. package/dist/tools/comments.d.ts.map +1 -0
  15. package/dist/tools/flair.d.ts +4 -0
  16. package/dist/tools/flair.d.ts.map +1 -0
  17. package/dist/tools/messages.d.ts +4 -0
  18. package/dist/tools/messages.d.ts.map +1 -0
  19. package/dist/tools/moderation.d.ts +4 -0
  20. package/dist/tools/moderation.d.ts.map +1 -0
  21. package/dist/tools/multireddits.d.ts +4 -0
  22. package/dist/tools/multireddits.d.ts.map +1 -0
  23. package/dist/tools/posts.d.ts +4 -0
  24. package/dist/tools/posts.d.ts.map +1 -0
  25. package/dist/tools/subreddits.d.ts +4 -0
  26. package/dist/tools/subreddits.d.ts.map +1 -0
  27. package/dist/tools/users.d.ts +4 -0
  28. package/dist/tools/users.d.ts.map +1 -0
  29. package/dist/tools/voting.d.ts +4 -0
  30. package/dist/tools/voting.d.ts.map +1 -0
  31. package/dist/tools/wiki.d.ts +4 -0
  32. package/dist/tools/wiki.d.ts.map +1 -0
  33. package/package.json +1 -1
  34. package/src/server.ts +22 -0
  35. package/src/tools/account.ts +84 -0
  36. package/src/tools/comments.ts +73 -0
  37. package/src/tools/flair.ts +79 -0
  38. package/src/tools/messages.ts +126 -0
  39. package/src/tools/moderation.ts +292 -0
  40. package/src/tools/multireddits.ts +152 -0
  41. package/src/tools/posts.ts +177 -0
  42. package/src/tools/subreddits.ts +137 -0
  43. package/src/tools/users.ts +181 -0
  44. package/src/tools/voting.ts +90 -0
  45. package/src/tools/wiki.ts +118 -0
  46. package/tests/fixtures/reddit-responses.ts +159 -0
  47. package/tests/unit/account.test.ts +95 -0
  48. package/tests/unit/comments.test.ts +92 -0
  49. package/tests/unit/flair.test.ts +101 -0
  50. package/tests/unit/messages.test.ts +106 -0
  51. package/tests/unit/moderation.test.ts +243 -0
  52. package/tests/unit/multireddits.test.ts +136 -0
  53. package/tests/unit/posts.test.ts +155 -0
  54. package/tests/unit/subreddits.test.ts +125 -0
  55. package/tests/unit/transport.test.ts +13 -0
  56. package/tests/unit/users.test.ts +124 -0
  57. package/tests/unit/voting.test.ts +110 -0
  58. package/tests/unit/wiki.test.ts +116 -0
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { RedditClient } from "../client/reddit.js";
3
+ export declare function registerAccountTools(server: McpServer, client: RedditClient): void;
4
+ //# sourceMappingURL=account.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account.d.ts","sourceRoot":"","sources":["../../src/tools/account.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,YAAY,GACnB,IAAI,CA4EN"}
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { RedditClient } from "../client/reddit.js";
3
+ export declare function registerCommentTools(server: McpServer, client: RedditClient): void;
4
+ //# sourceMappingURL=comments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comments.d.ts","sourceRoot":"","sources":["../../src/tools/comments.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAcxD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,YAAY,GACnB,IAAI,CAqDN"}
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { RedditClient } from "../client/reddit.js";
3
+ export declare function registerFlairTools(server: McpServer, client: RedditClient): void;
4
+ //# sourceMappingURL=flair.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flair.d.ts","sourceRoot":"","sources":["../../src/tools/flair.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAcxD,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,YAAY,GACnB,IAAI,CA2DN"}
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { RedditClient } from "../client/reddit.js";
3
+ export declare function registerMessageTools(server: McpServer, client: RedditClient): void;
4
+ //# sourceMappingURL=messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/tools/messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAyBxD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,YAAY,GACnB,IAAI,CA+FN"}
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { RedditClient } from "../client/reddit.js";
3
+ export declare function registerModerationTools(server: McpServer, client: RedditClient): void;
4
+ //# sourceMappingURL=moderation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"moderation.d.ts","sourceRoot":"","sources":["../../src/tools/moderation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAyBxD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,YAAY,GACnB,IAAI,CAqQN"}
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { RedditClient } from "../client/reddit.js";
3
+ export declare function registerMultiredditTools(server: McpServer, client: RedditClient): void;
4
+ //# sourceMappingURL=multireddits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multireddits.d.ts","sourceRoot":"","sources":["../../src/tools/multireddits.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,YAAY,GACnB,IAAI,CAgJN"}
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { RedditClient } from "../client/reddit.js";
3
+ export declare function registerPostTools(server: McpServer, client: RedditClient): void;
4
+ //# sourceMappingURL=posts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"posts.d.ts","sourceRoot":"","sources":["../../src/tools/posts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAcxD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,YAAY,GACnB,IAAI,CA6JN"}
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { RedditClient } from "../client/reddit.js";
3
+ export declare function registerSubredditTools(server: McpServer, client: RedditClient): void;
4
+ //# sourceMappingURL=subreddits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subreddits.d.ts","sourceRoot":"","sources":["../../src/tools/subreddits.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAyBxD,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,YAAY,GACnB,IAAI,CA0GN"}
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { RedditClient } from "../client/reddit.js";
3
+ export declare function registerUserTools(server: McpServer, client: RedditClient): void;
4
+ //# sourceMappingURL=users.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/tools/users.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAiCxD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,YAAY,GACnB,IAAI,CA8IN"}
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { RedditClient } from "../client/reddit.js";
3
+ export declare function registerVotingTools(server: McpServer, client: RedditClient): void;
4
+ //# sourceMappingURL=voting.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voting.d.ts","sourceRoot":"","sources":["../../src/tools/voting.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,YAAY,GACnB,IAAI,CAkFN"}
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { RedditClient } from "../client/reddit.js";
3
+ export declare function registerWikiTools(server: McpServer, client: RedditClient): void;
4
+ //# sourceMappingURL=wiki.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wiki.d.ts","sourceRoot":"","sources":["../../src/tools/wiki.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAyBxD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,YAAY,GACnB,IAAI,CAuFN"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prmichaelsen/reddit-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "MCP server wrapping the Reddit API for AI agents",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
package/src/server.ts CHANGED
@@ -3,6 +3,17 @@ import { RedditAuth } from "./auth/oauth.js";
3
3
  import { RedditClient } from "./client/reddit.js";
4
4
  import { registerListingTools } from "./tools/listings.js";
5
5
  import { registerSearchTools } from "./tools/search.js";
6
+ import { registerPostTools } from "./tools/posts.js";
7
+ import { registerCommentTools } from "./tools/comments.js";
8
+ import { registerVotingTools } from "./tools/voting.js";
9
+ import { registerAccountTools } from "./tools/account.js";
10
+ import { registerUserTools } from "./tools/users.js";
11
+ import { registerMessageTools } from "./tools/messages.js";
12
+ import { registerSubredditTools } from "./tools/subreddits.js";
13
+ import { registerFlairTools } from "./tools/flair.js";
14
+ import { registerModerationTools } from "./tools/moderation.js";
15
+ import { registerMultiredditTools } from "./tools/multireddits.js";
16
+ import { registerWikiTools } from "./tools/wiki.js";
6
17
 
7
18
  export function createServer(auth?: RedditAuth): McpServer {
8
19
  const server = new McpServer({
@@ -33,4 +44,15 @@ export function createServerWithToken(accessToken: string): McpServer {
33
44
  function registerAllTools(server: McpServer, client: RedditClient): void {
34
45
  registerListingTools(server, client);
35
46
  registerSearchTools(server, client);
47
+ registerPostTools(server, client);
48
+ registerCommentTools(server, client);
49
+ registerVotingTools(server, client);
50
+ registerAccountTools(server, client);
51
+ registerUserTools(server, client);
52
+ registerMessageTools(server, client);
53
+ registerSubredditTools(server, client);
54
+ registerFlairTools(server, client);
55
+ registerModerationTools(server, client);
56
+ registerMultiredditTools(server, client);
57
+ registerWikiTools(server, client);
36
58
  }
@@ -0,0 +1,84 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import type { RedditClient } from "../client/reddit.js";
4
+
5
+ export function registerAccountTools(
6
+ server: McpServer,
7
+ client: RedditClient,
8
+ ): void {
9
+ server.tool(
10
+ "reddit_me",
11
+ "Get the authenticated user's account info (username, karma, etc). Requires 'identity' scope.",
12
+ {},
13
+ async () => {
14
+ const data = await client.get("/api/v1/me");
15
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
16
+ },
17
+ );
18
+
19
+ server.tool(
20
+ "reddit_me_karma",
21
+ "Get the authenticated user's karma breakdown by subreddit. Requires 'mysubreddits' scope.",
22
+ {},
23
+ async () => {
24
+ const data = await client.get("/api/v1/me/karma");
25
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
26
+ },
27
+ );
28
+
29
+ server.tool(
30
+ "reddit_me_prefs",
31
+ "Get the authenticated user's preferences. Requires 'identity' scope.",
32
+ {},
33
+ async () => {
34
+ const data = await client.get("/api/v1/me/prefs");
35
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
36
+ },
37
+ );
38
+
39
+ server.tool(
40
+ "reddit_me_prefs_update",
41
+ "Update the authenticated user's preferences. Requires 'account' scope. Pass a JSON object with preference keys and values.",
42
+ {
43
+ prefs: z
44
+ .record(z.string(), z.unknown())
45
+ .describe(
46
+ "JSON object of preference key-value pairs to update (e.g. {\"over_18\": true, \"hide_downs\": false})",
47
+ ),
48
+ },
49
+ async (input) => {
50
+ const data = await client.patch("/api/v1/me/prefs", input.prefs);
51
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
52
+ },
53
+ );
54
+
55
+ server.tool(
56
+ "reddit_me_trophies",
57
+ "Get the authenticated user's trophies. Requires 'identity' scope.",
58
+ {},
59
+ async () => {
60
+ const data = await client.get("/api/v1/me/trophies");
61
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
62
+ },
63
+ );
64
+
65
+ server.tool(
66
+ "reddit_me_friends",
67
+ "Get the authenticated user's friends list. Requires 'mysubreddits' scope.",
68
+ {},
69
+ async () => {
70
+ const data = await client.get("/prefs/friends");
71
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
72
+ },
73
+ );
74
+
75
+ server.tool(
76
+ "reddit_me_blocked",
77
+ "Get the authenticated user's blocked users list. Requires 'mysubreddits' scope.",
78
+ {},
79
+ async () => {
80
+ const data = await client.get("/prefs/blocked");
81
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
82
+ },
83
+ );
84
+ }
@@ -0,0 +1,73 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import type { RedditClient } from "../client/reddit.js";
4
+
5
+ function buildParams(
6
+ input: Record<string, unknown>,
7
+ ): Record<string, string | undefined> {
8
+ const params: Record<string, string | undefined> = {};
9
+ for (const [key, value] of Object.entries(input)) {
10
+ if (value !== undefined && value !== null) {
11
+ params[key] = String(value);
12
+ }
13
+ }
14
+ return params;
15
+ }
16
+
17
+ export function registerCommentTools(
18
+ server: McpServer,
19
+ client: RedditClient,
20
+ ): void {
21
+ server.tool(
22
+ "reddit_comment",
23
+ "Post a comment or reply. Use a t3_ fullname as parent for a top-level comment on a post, or a t1_ fullname to reply to another comment. Requires 'submit' scope.",
24
+ {
25
+ parent: z
26
+ .string()
27
+ .describe(
28
+ "Fullname of the parent: t3_ for a post (top-level comment) or t1_ for a comment (reply)",
29
+ ),
30
+ text: z.string().describe("Comment body text (markdown)"),
31
+ },
32
+ async (input) => {
33
+ const data = await client.post("/api/comment", {
34
+ ...buildParams(input),
35
+ api_type: "json",
36
+ });
37
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
38
+ },
39
+ );
40
+
41
+ server.tool(
42
+ "reddit_more_children",
43
+ "Load more comments from collapsed 'load more' stubs in a comment thread. Requires 'read' scope.",
44
+ {
45
+ link_id: z
46
+ .string()
47
+ .describe("Fullname of the parent post (t3_)"),
48
+ children: z
49
+ .string()
50
+ .describe(
51
+ "Comma-separated list of comment IDs to expand (from the 'more' object's children array)",
52
+ ),
53
+ sort: z
54
+ .enum(["confidence", "top", "new", "controversial", "old", "qa"])
55
+ .optional()
56
+ .describe("Comment sort order"),
57
+ limit_children: z
58
+ .boolean()
59
+ .optional()
60
+ .describe("If true, only return the children requested (no deeper)"),
61
+ },
62
+ async (input) => {
63
+ const data = await client.get(
64
+ "/api/morechildren",
65
+ {
66
+ ...buildParams(input),
67
+ api_type: "json",
68
+ },
69
+ );
70
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
71
+ },
72
+ );
73
+ }
@@ -0,0 +1,79 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import type { RedditClient } from "../client/reddit.js";
4
+
5
+ function buildParams(
6
+ input: Record<string, unknown>,
7
+ ): Record<string, string | undefined> {
8
+ const params: Record<string, string | undefined> = {};
9
+ for (const [key, value] of Object.entries(input)) {
10
+ if (value !== undefined && value !== null) {
11
+ params[key] = String(value);
12
+ }
13
+ }
14
+ return params;
15
+ }
16
+
17
+ export function registerFlairTools(
18
+ server: McpServer,
19
+ client: RedditClient,
20
+ ): void {
21
+ server.tool(
22
+ "reddit_link_flair",
23
+ "Get available link (post) flair templates for a subreddit. Requires 'flair' scope.",
24
+ {
25
+ subreddit: z.string().describe("Subreddit name (without r/ prefix)"),
26
+ },
27
+ async (input) => {
28
+ const data = await client.get(
29
+ `/r/${input.subreddit}/api/link_flair_v2`,
30
+ );
31
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
32
+ },
33
+ );
34
+
35
+ server.tool(
36
+ "reddit_user_flair",
37
+ "Get available user flair templates for a subreddit. Requires 'flair' scope.",
38
+ {
39
+ subreddit: z.string().describe("Subreddit name (without r/ prefix)"),
40
+ },
41
+ async (input) => {
42
+ const data = await client.get(
43
+ `/r/${input.subreddit}/api/user_flair_v2`,
44
+ );
45
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
46
+ },
47
+ );
48
+
49
+ server.tool(
50
+ "reddit_select_flair",
51
+ "Set flair on a post or user in a subreddit. Provide 'link' for post flair or 'name' for user flair. Requires 'flair' scope.",
52
+ {
53
+ subreddit: z.string().describe("Subreddit name (without r/ prefix)"),
54
+ flair_template_id: z
55
+ .string()
56
+ .describe("Flair template ID (from link_flair or user_flair listing)"),
57
+ link: z
58
+ .string()
59
+ .optional()
60
+ .describe("Post fullname (t3_) to set link flair on"),
61
+ name: z
62
+ .string()
63
+ .optional()
64
+ .describe("Username to set user flair for"),
65
+ text: z
66
+ .string()
67
+ .optional()
68
+ .describe("Custom flair text (if template allows)"),
69
+ },
70
+ async (input) => {
71
+ const { subreddit, ...rest } = input;
72
+ const data = await client.post(
73
+ `/r/${subreddit}/api/selectflair`,
74
+ buildParams(rest),
75
+ );
76
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
77
+ },
78
+ );
79
+ }
@@ -0,0 +1,126 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import type { RedditClient } from "../client/reddit.js";
4
+
5
+ const paginationParams = {
6
+ limit: z
7
+ .number()
8
+ .min(1)
9
+ .max(100)
10
+ .optional()
11
+ .describe("Number of messages to return (1-100, default 25)"),
12
+ after: z.string().optional().describe("Fullname to paginate after"),
13
+ before: z.string().optional().describe("Fullname to paginate before"),
14
+ };
15
+
16
+ function buildParams(
17
+ input: Record<string, unknown>,
18
+ ): Record<string, string | undefined> {
19
+ const params: Record<string, string | undefined> = {};
20
+ for (const [key, value] of Object.entries(input)) {
21
+ if (value !== undefined && value !== null) {
22
+ params[key] = String(value);
23
+ }
24
+ }
25
+ return params;
26
+ }
27
+
28
+ export function registerMessageTools(
29
+ server: McpServer,
30
+ client: RedditClient,
31
+ ): void {
32
+ server.tool(
33
+ "reddit_inbox",
34
+ "Get the authenticated user's inbox messages. Requires 'privatemessages' scope.",
35
+ { ...paginationParams },
36
+ async (input) => {
37
+ const data = await client.get("/message/inbox", buildParams(input));
38
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
39
+ },
40
+ );
41
+
42
+ server.tool(
43
+ "reddit_unread",
44
+ "Get the authenticated user's unread messages. Requires 'privatemessages' scope.",
45
+ { ...paginationParams },
46
+ async (input) => {
47
+ const data = await client.get("/message/unread", buildParams(input));
48
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
49
+ },
50
+ );
51
+
52
+ server.tool(
53
+ "reddit_sent",
54
+ "Get the authenticated user's sent messages. Requires 'privatemessages' scope.",
55
+ { ...paginationParams },
56
+ async (input) => {
57
+ const data = await client.get("/message/sent", buildParams(input));
58
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
59
+ },
60
+ );
61
+
62
+ server.tool(
63
+ "reddit_compose",
64
+ "Send a private message to a user. Requires 'privatemessages' scope.",
65
+ {
66
+ to: z.string().describe("Recipient username (without /u/ prefix)"),
67
+ subject: z.string().describe("Message subject"),
68
+ text: z.string().describe("Message body (markdown)"),
69
+ },
70
+ async (input) => {
71
+ const data = await client.post("/api/compose", {
72
+ ...buildParams(input),
73
+ api_type: "json",
74
+ });
75
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
76
+ },
77
+ );
78
+
79
+ server.tool(
80
+ "reddit_read_message",
81
+ "Mark a message as read. Requires 'privatemessages' scope.",
82
+ {
83
+ id: z
84
+ .string()
85
+ .describe("Fullname of the message to mark as read (t4_)"),
86
+ },
87
+ async (input) => {
88
+ const data = await client.post("/api/read_message", {
89
+ id: input.id,
90
+ });
91
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
92
+ },
93
+ );
94
+
95
+ server.tool(
96
+ "reddit_unread_message",
97
+ "Mark a message as unread. Requires 'privatemessages' scope.",
98
+ {
99
+ id: z
100
+ .string()
101
+ .describe("Fullname of the message to mark as unread (t4_)"),
102
+ },
103
+ async (input) => {
104
+ const data = await client.post("/api/unread_message", {
105
+ id: input.id,
106
+ });
107
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
108
+ },
109
+ );
110
+
111
+ server.tool(
112
+ "reddit_del_msg",
113
+ "Delete a message from your inbox. Requires 'privatemessages' scope.",
114
+ {
115
+ id: z
116
+ .string()
117
+ .describe("Fullname of the message to delete (t4_)"),
118
+ },
119
+ async (input) => {
120
+ const data = await client.post("/api/del_msg", {
121
+ id: input.id,
122
+ });
123
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
124
+ },
125
+ );
126
+ }