redditapis-mcp 0.5.1 → 0.5.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.2 (2026-09-06)
4
+
5
+ ### Added
6
+
7
+ - **`reddit_post_visibility`.** Is a post still publicly visible, or did it quietly stop being so? A removed Reddit post still returns when you fetch it by id, with its title and score, so asking the post does not answer the question. This fetches the post and then one page of its author's submitted listing and compares them, returning a verdict of `live`, `not_visible` or `undecidable` with a plain-language reason and a `confident` flag. It never says WHY a post is not visible: a moderator removal, an admin removal, a spam filter and an author who has hidden their post history are indistinguishable from outside, and only one of them is a removal. `undecidable` is a real answer, not a failure. Two upstream calls, billed as one request at $0.004.
8
+
3
9
  ## 0.5.1 (2026-09-06)
4
10
 
5
11
  ### Fixed
package/README.md CHANGED
@@ -139,6 +139,7 @@ A few conventions across the catalog:
139
139
 
140
140
  | Tool | Endpoint | What it does |
141
141
  |---|---|---|
142
+ | `reddit_post_visibility` | `GET /api/reddit/post/{id}/visibility` | Is a post still publicly visible, or did it quietly stop being so? Fetches the post and one page of its author's listing and compares. Returns `live`, `not_visible` or `undecidable` with a reason, and never claims to know WHY. Two upstream calls, $0.004. |
142
143
  | `reddit_post` | `GET /api/reddit/post/{id}` | A single post by its base-36 `id` (no `t3_` prefix): title, author, score, text, permalink, subreddit, url. |
143
144
  | `reddit_post_comments` | `GET /api/reddit/comments` | A post plus its full threaded comment tree, fetched by `permalink`. |
144
145
  | `reddit_by_id` | `GET /api/reddit/by_id/{fullnames}` | Bulk-hydrate up to 100 posts in one call from a comma-separated list of `t3_` fullnames. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "redditapis-mcp",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "mcpName": "io.github.redditapis/redditapis-mcp",
5
5
  "description": "Official MCP server for redditapis.com. Search Reddit posts, comments, communities, users and media; read subreddits, comment trees, top posts and profiles; create and manage monitors and webhooks. Native tools for Claude, Cursor and any MCP client.",
6
6
  "repository": {
@@ -26,7 +26,7 @@
26
26
  "scripts": {
27
27
  "start": "node src/index.js",
28
28
  "check": "node --check src/index.js && node --check src/tools.js && node --check src/feedback.js",
29
- "test": "node test/tools.test.mjs && node test/feedback.test.mjs && node test/index-lasterror.test.mjs",
29
+ "test": "node test/tools.test.mjs && node test/feedback.test.mjs && node test/index-lasterror.test.mjs && node test/scannable-without-key.test.mjs",
30
30
  "prepublishOnly": "node scripts/prepublish-tenant-check.mjs"
31
31
  },
32
32
  "dependencies": {
package/src/index.js CHANGED
@@ -39,11 +39,25 @@ const REQUEST_TIMEOUT_MS = Number(process.env.REDDITAPIS_TIMEOUT_MS || 30000);
39
39
  // User-Agent and MCP server handshake understated its own version for months.
40
40
  const { version: VERSION } = createRequire(import.meta.url)("../package.json");
41
41
 
42
+ // WARN AND CONTINUE, NEVER exit. A registry scanner spawns the server and calls
43
+ // tools/list to enumerate what it offers, and tools/list is the DISCOVERY step
44
+ // in the MCP spec: it comes before tool selection and before any invocation, so
45
+ // it needs no credential. Exiting here answers that scan with nothing.
46
+ //
47
+ // This is not a hypothesis. Measured 2026-09-06 by speaking raw MCP stdio to
48
+ // the published redditapis-mcp@0.5.2 with an empty env: initialize was never
49
+ // answered and tools/list returned nothing, because this line fired first. A
50
+ // scanner that behaves this way reports the server as uninspectable rather than
51
+ // as having no tools, so the listing carries no catalog at all.
52
+ //
53
+ // NOT a security relaxation. This is a stdio server: whoever spawns it already
54
+ // has local execution, so there is no unauthenticated party to expose anything
55
+ // to. A real tool CALL with no key still fails clearly, at the point of the
56
+ // call, exactly as it already does for a WRONG key (see the 401 branch below).
42
57
  if (!API_KEY) {
43
58
  console.error(
44
- "[reddit-mcp] Missing REDDITAPIS_KEY. Get a key at https://www.redditapis.com and set it in your MCP client config.",
59
+ "[reddit-mcp] Missing REDDITAPIS_KEY. Get a key at https://www.redditapis.com and set it in your MCP client config. Tools are registered but every call will fail until it is set.",
45
60
  );
46
- process.exit(1);
47
61
  }
48
62
 
49
63
  // The last tool call that failed, so a feedback draft can carry the endpoint,
@@ -90,6 +104,20 @@ export function hintFor(status, path) {
90
104
  }
91
105
 
92
106
  async function callEndpoint(pathTemplate, args, method = "GET", tool = null) {
107
+ // The credential check MOVED HERE from startup, so listing tools needs no key
108
+ // and calling one still cannot silently proceed without it. Without this the
109
+ // relaxation above would send an Authorization header reading "Bearer
110
+ // undefined" and the caller would read a 401 about an INVALID key when the
111
+ // real answer is that no key was ever set.
112
+ if (!API_KEY) {
113
+ return {
114
+ isError: true,
115
+ content: [{
116
+ type: "text",
117
+ text: "Missing REDDITAPIS_KEY (no API key is set; get one at https://www.redditapis.com and set it in your MCP client config).",
118
+ }],
119
+ };
120
+ }
93
121
  const { path, rest: pathRest } = buildPath(pathTemplate, args);
94
122
  // A tool that declares `sessionHeaders` has its Reddit session args lifted
95
123
  // out of the query and onto headers. For every other tool this is the
package/src/tools.js CHANGED
@@ -290,6 +290,21 @@ export const TOOLS = [
290
290
  sort_type: z.enum(["score", "num_comments", "created"]).optional().describe("Re-sort the filtered page (descending) by this field."),
291
291
  },
292
292
  },
293
+ {
294
+ name: "reddit_post_visibility",
295
+ path: "/api/reddit/post/:id/visibility",
296
+ description:
297
+ "Is a post still publicly visible, or did it quietly stop being so? A removed Reddit post still " +
298
+ "returns when you fetch it by id, so asking the post does not answer this. This fetches the post " +
299
+ "and then one page of its author's submitted listing and compares them. Returns a verdict of live, " +
300
+ "not_visible or undecidable, a plain-language reason, and a confident flag. It deliberately never " +
301
+ "says WHY a post is not visible: a moderator removal, an admin removal, a spam filter and an author " +
302
+ "who has hidden their history are indistinguishable from outside. undecidable is a real answer, not " +
303
+ "a failure. Two upstream calls, billed as one $0.004 dual read.",
304
+ shape: {
305
+ id: z.string().min(1).describe("Reddit post id, base36, with or without the t3_ prefix"),
306
+ },
307
+ },
293
308
  {
294
309
  name: "reddit_post_comments",
295
310
  path: "/api/reddit/comments",