threadfox-lite 0.1.1__tar.gz

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.
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "threadfox",
3
+ "owner": {
4
+ "name": "ThreadFox",
5
+ "email": "support@threadfox.vip"
6
+ },
7
+ "metadata": {
8
+ "description": "Reddit tools for Claude Code from ThreadFox."
9
+ },
10
+ "plugins": [
11
+ {
12
+ "name": "threadfox-lite",
13
+ "source": "./",
14
+ "description": "Free, read-only Reddit tools: subreddit rules before you post, post status after, and the reddit-rules-first skill.",
15
+ "version": "0.1.1",
16
+ "category": "marketing",
17
+ "homepage": "https://threadfox.vip"
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "threadfox-lite",
3
+ "version": "0.1.1",
4
+ "description": "Free, read-only Reddit tools: read a subreddit's rules before posting and check whether a post stayed up, plus the reddit-rules-first skill.",
5
+ "author": {
6
+ "name": "ThreadFox (AMF Indiana)",
7
+ "email": "support@threadfox.vip",
8
+ "url": "https://threadfox.vip"
9
+ },
10
+ "homepage": "https://threadfox.vip",
11
+ "repository": "https://github.com/amflimited/threadfox-lite",
12
+ "license": "MIT",
13
+ "keywords": ["reddit", "subreddit-rules", "marketing", "mcp", "skills"],
14
+ "mcpServers": {
15
+ "threadfox-lite": {
16
+ "command": "uv",
17
+ "args": ["run", "--directory", "${CLAUDE_PLUGIN_ROOT}", "threadfox-lite"]
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,100 @@
1
+ name: Publish
2
+
3
+ # Push a tag vX.Y.Z (matching pyproject.toml and manifest.json) to publish that version to PyPI
4
+ # (trusted publishing, no token), attach the Claude Desktop bundle to a GitHub release and update
5
+ # the entry in the official MCP Registry (GitHub OIDC, namespace io.github.amflimited).
6
+ # Each step skips what already exists, so a version uploaded to PyPI by hand, or a re-run, is fine.
7
+
8
+ on:
9
+ push:
10
+ tags: ["v*"]
11
+
12
+ jobs:
13
+ publish:
14
+ runs-on: ubuntu-latest
15
+ environment: pypi
16
+ permissions:
17
+ id-token: write
18
+ contents: write
19
+ steps:
20
+ - uses: actions/checkout@v5
21
+
22
+ - uses: astral-sh/setup-uv@v6
23
+
24
+ - name: Check the tag matches the package version
25
+ run: |
26
+ V="${GITHUB_REF_NAME#v}"
27
+ grep -q "^version = \"$V\"" pyproject.toml
28
+ grep -q "\"version\": \"$V\"" manifest.json
29
+ echo "VERSION=$V" >> "$GITHUB_ENV"
30
+
31
+ - name: Already on PyPI?
32
+ id: pypi
33
+ run: |
34
+ if curl -sf "https://pypi.org/pypi/threadfox-lite/$VERSION/json" > /dev/null; then echo "exists=true" >> "$GITHUB_OUTPUT"; else echo "exists=false" >> "$GITHUB_OUTPUT"; fi
35
+
36
+ - name: Test
37
+ run: uv run python -m unittest discover -s tests
38
+
39
+ - name: Build
40
+ if: steps.pypi.outputs.exists != 'true'
41
+ run: uv build
42
+
43
+ - name: Publish to PyPI
44
+ if: steps.pypi.outputs.exists != 'true'
45
+ uses: pypa/gh-action-pypi-publish@release/v1
46
+
47
+ - uses: actions/setup-node@v5
48
+ with:
49
+ node-version: "lts/*"
50
+
51
+ - name: Build the Claude Desktop bundle
52
+ run: npx -y @anthropic-ai/mcpb pack . "threadfox-lite-$VERSION.mcpb"
53
+
54
+ - name: GitHub release
55
+ env:
56
+ GH_TOKEN: ${{ github.token }}
57
+ run: |
58
+ if gh release view "$GITHUB_REF_NAME" > /dev/null 2>&1; then
59
+ rm -f "threadfox-lite-$VERSION.mcpb"
60
+ gh release download "$GITHUB_REF_NAME" --pattern "threadfox-lite-$VERSION.mcpb"
61
+ echo "SHA=$(sha256sum "threadfox-lite-$VERSION.mcpb" | cut -d' ' -f1)" >> "$GITHUB_ENV"
62
+ exit 0
63
+ fi
64
+ SHA=$(sha256sum "threadfox-lite-$VERSION.mcpb" | cut -d' ' -f1)
65
+ echo "SHA=$SHA" >> "$GITHUB_ENV"
66
+ gh release create "$GITHUB_REF_NAME" "threadfox-lite-$VERSION.mcpb" --title "ThreadFox Lite $VERSION" --notes "Free, read-only Reddit tools for Claude, Claude Code, Codex and any MCP client: subreddit_rules and post_status, plus the reddit-rules-first skill.
67
+
68
+ **Any MCP client:** \`uvx threadfox-lite\` (PyPI)
69
+ **Claude Code plugin:** \`/plugin marketplace add amflimited/threadfox-lite\`, then \`/plugin install threadfox-lite@threadfox\`
70
+ **Claude Desktop:** download threadfox-lite-$VERSION.mcpb below and open it (needs uv).
71
+
72
+ SHA-256 of the bundle: $SHA"
73
+
74
+ - name: server.json for this version
75
+ run: |
76
+ jq --arg v "$VERSION" --arg sha "$SHA" '
77
+ .version = $v
78
+ | .packages |= map(
79
+ if .registryType == "pypi" then .version = $v
80
+ elif .registryType == "mcpb" then .identifier = "https://github.com/amflimited/threadfox-lite/releases/download/v\($v)/threadfox-lite-\($v).mcpb" | .fileSha256 = $sha
81
+ else . end)' server.json > server.out.json
82
+ mv server.out.json server.json
83
+ cat server.json
84
+
85
+ - name: Wait for PyPI to serve the release
86
+ run: |
87
+ for i in $(seq 1 30); do
88
+ curl -sf "https://pypi.org/pypi/threadfox-lite/$VERSION/json" > /dev/null && exit 0
89
+ sleep 10
90
+ done
91
+ exit 1
92
+
93
+ - name: Publish to the MCP Registry
94
+ run: |
95
+ if curl -sf "https://registry.modelcontextprotocol.io/v0.1/servers/io.github.amflimited%2Fthreadfox-lite/versions/$VERSION" > /dev/null; then
96
+ echo "Registry already has $VERSION"; exit 0
97
+ fi
98
+ curl -sL "https://github.com/modelcontextprotocol/registry/releases/download/v1.8.1/mcp-publisher_linux_amd64.tar.gz" | tar xz mcp-publisher
99
+ ./mcp-publisher login github-oidc
100
+ ./mcp-publisher publish server.json
@@ -0,0 +1,3 @@
1
+ __pycache__/
2
+ .venv/
3
+ dist/
@@ -0,0 +1,12 @@
1
+ .git/
2
+ .github/
3
+ .venv/
4
+ dist/
5
+ tests/
6
+ __pycache__/
7
+ *.pyc
8
+ uv.lock
9
+ server.json
10
+ glama.json
11
+ LAUNCHGUIDE.md
12
+ *.mcpb
@@ -0,0 +1,41 @@
1
+ # ThreadFox
2
+
3
+ ## Tagline
4
+ Reddit tools for Claude Code and Codex: rules first, posts that fit, checks that they stayed up.
5
+
6
+ ## Description
7
+ ThreadFox gives your AI the Reddit job. Buying on MCP Marketplace gets you the full ThreadFox kit: your Claude Code or Codex reads each community's rules, writes a post that fits that community, publishes it from your own signed-in Chrome (no Reddit API keys, no exported cookies), answers the comments, and records every outcome in a ledger, stopping where a community removed you. It includes a 12-module course, 48 prompts and a one-command install for Windows, Mac and Linux.
8
+
9
+ This server (ThreadFox Lite) runs locally and gives every user two free, read-only tools: subreddit_rules and post_status. Your license key opens the full kit: set MCP_LICENSE_KEY, run threadfox_full_kit, or redeem the key at https://threadfox.vip/redeem for your private library.
10
+
11
+ Built from our own run: 978,227 recorded views from two days of posts (Sept 20-21 UTC), with no ad spend. That is our result, not a promise about yours. Made by AMF Indiana; not affiliated with Reddit, OpenAI or Anthropic.
12
+
13
+ ## Setup Requirements
14
+ MCP_LICENSE_KEY (optional): your MCP Marketplace license key. The two read-only tools work without it. Requires uv (uvx) and Python 3.10+.
15
+
16
+ ## Category
17
+ Marketing
18
+
19
+ ## Use Cases
20
+ Reddit marketing, Product launches, Community research, Content distribution, Social media automation
21
+
22
+ ## Features
23
+ - subreddit_rules: rules, size and description of any subreddit, with self-promotion rules flagged
24
+ - post_status: live, removed or deleted, with score and comment count
25
+ - Full kit with a license: publishing from your own Chrome, replies, outcome ledger, 12-module course, 48 prompts
26
+ - No Reddit API keys, no login for the free tools
27
+ - Works with Claude Code, Codex and other MCP clients
28
+
29
+ ## Getting Started
30
+ - "Read the rules of r/SaaS and tell me whether I can post my launch there."
31
+ - "Is https://www.reddit.com/r/SideProject/comments/abc123/my_post/ still up?"
32
+ - "How do I open the full ThreadFox kit?"
33
+ - Tool: subreddit_rules(subreddit)
34
+ - Tool: post_status(url)
35
+ - Tool: threadfox_full_kit()
36
+
37
+ ## Tags
38
+ reddit, marketing, claude-code, codex, mcp, community, launch, growth, social-media, ai-agents
39
+
40
+ ## Documentation URL
41
+ https://github.com/amflimited/threadfox-lite
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AMF Indiana
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,58 @@
1
+ Metadata-Version: 2.5
2
+ Name: threadfox-lite
3
+ Version: 0.1.1
4
+ Summary: Read a subreddit's rules and check whether a Reddit post is still up, from Claude Code, Codex or any MCP client. Free tools from ThreadFox.
5
+ Project-URL: Homepage, https://threadfox.vip
6
+ Project-URL: Source, https://github.com/amflimited/threadfox-lite
7
+ Author-email: "AMF Indiana (ThreadFox)" <support@threadfox.vip>
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Requires-Python: >=3.10
11
+ Requires-Dist: httpx>=0.27
12
+ Requires-Dist: mcp<2,>=1.2.0
13
+ Description-Content-Type: text/markdown
14
+
15
+ # ThreadFox Lite
16
+
17
+ Free, read-only Reddit tools for Claude Code, Codex and any MCP client, plus a `reddit-rules-first` agent skill.
18
+
19
+ - `subreddit_rules`: a subreddit's rules, size and description, with the self-promotion rules flagged. Call it before writing a post.
20
+ - `post_status`: whether a post is still live, removed by moderators or deleted, with score and comments. Check at 1, 6 and 24 hours.
21
+ - `threadfox_full_kit`: how to get the full ThreadFox kit (posting from your own Chrome, replies, an outcome ledger, a 12-module course).
22
+
23
+ No login, no posting, no Reddit API keys: each call is one request to Reddit's public JSON with a descriptive user agent.
24
+
25
+ ## Install
26
+
27
+ Claude Code plugin (the tools and the skill together):
28
+
29
+ /plugin marketplace add amflimited/threadfox-lite
30
+ /plugin install threadfox-lite@threadfox
31
+
32
+ Claude Code, the MCP server only:
33
+
34
+ claude mcp add threadfox-lite -- uvx threadfox-lite
35
+
36
+ Claude Desktop: download the `.mcpb` file from the [latest release](https://github.com/amflimited/threadfox-lite/releases/latest) and open it (Settings > Extensions). It needs [uv](https://docs.astral.sh/uv/) on your machine.
37
+
38
+ Codex (`~/.codex/config.toml`):
39
+
40
+ [mcp_servers.threadfox-lite]
41
+ command = "uvx"
42
+ args = ["threadfox-lite"]
43
+
44
+ Any other MCP client: run `uvx threadfox-lite` (the package is on [PyPI](https://pypi.org/project/threadfox-lite/)).
45
+
46
+ Bought ThreadFox on MCP Marketplace? Set `MCP_LICENSE_KEY` to your key, ask your AI to run `threadfox_full_kit`, or go straight to https://threadfox.vip/redeem.
47
+
48
+ ## The skill
49
+
50
+ `skills/reddit-rules-first/SKILL.md` works on its own in any agent that reads skills: rules first, one post per community, disclose, check what stayed up. Install it anywhere with `npx skills add amflimited/threadfox-lite`.
51
+
52
+ Also listed in the [official MCP Registry](https://registry.modelcontextprotocol.io/v0.1/servers?search=threadfox) as `io.github.amflimited/threadfox-lite`.
53
+
54
+ ## About
55
+
56
+ Made by AMF Indiana, the team behind ThreadFox (https://threadfox.vip). Built from our own run: 978,227 recorded views from two days of posts (Sept 20-21 UTC), with no ad spend. Not affiliated with Reddit, OpenAI or Anthropic. MIT licence.
57
+
58
+ <!-- mcp-name: io.github.amflimited/threadfox-lite -->
@@ -0,0 +1,44 @@
1
+ # ThreadFox Lite
2
+
3
+ Free, read-only Reddit tools for Claude Code, Codex and any MCP client, plus a `reddit-rules-first` agent skill.
4
+
5
+ - `subreddit_rules`: a subreddit's rules, size and description, with the self-promotion rules flagged. Call it before writing a post.
6
+ - `post_status`: whether a post is still live, removed by moderators or deleted, with score and comments. Check at 1, 6 and 24 hours.
7
+ - `threadfox_full_kit`: how to get the full ThreadFox kit (posting from your own Chrome, replies, an outcome ledger, a 12-module course).
8
+
9
+ No login, no posting, no Reddit API keys: each call is one request to Reddit's public JSON with a descriptive user agent.
10
+
11
+ ## Install
12
+
13
+ Claude Code plugin (the tools and the skill together):
14
+
15
+ /plugin marketplace add amflimited/threadfox-lite
16
+ /plugin install threadfox-lite@threadfox
17
+
18
+ Claude Code, the MCP server only:
19
+
20
+ claude mcp add threadfox-lite -- uvx threadfox-lite
21
+
22
+ Claude Desktop: download the `.mcpb` file from the [latest release](https://github.com/amflimited/threadfox-lite/releases/latest) and open it (Settings > Extensions). It needs [uv](https://docs.astral.sh/uv/) on your machine.
23
+
24
+ Codex (`~/.codex/config.toml`):
25
+
26
+ [mcp_servers.threadfox-lite]
27
+ command = "uvx"
28
+ args = ["threadfox-lite"]
29
+
30
+ Any other MCP client: run `uvx threadfox-lite` (the package is on [PyPI](https://pypi.org/project/threadfox-lite/)).
31
+
32
+ Bought ThreadFox on MCP Marketplace? Set `MCP_LICENSE_KEY` to your key, ask your AI to run `threadfox_full_kit`, or go straight to https://threadfox.vip/redeem.
33
+
34
+ ## The skill
35
+
36
+ `skills/reddit-rules-first/SKILL.md` works on its own in any agent that reads skills: rules first, one post per community, disclose, check what stayed up. Install it anywhere with `npx skills add amflimited/threadfox-lite`.
37
+
38
+ Also listed in the [official MCP Registry](https://registry.modelcontextprotocol.io/v0.1/servers?search=threadfox) as `io.github.amflimited/threadfox-lite`.
39
+
40
+ ## About
41
+
42
+ Made by AMF Indiana, the team behind ThreadFox (https://threadfox.vip). Built from our own run: 978,227 recorded views from two days of posts (Sept 20-21 UTC), with no ad spend. Not affiliated with Reddit, OpenAI or Anthropic. MIT licence.
43
+
44
+ <!-- mcp-name: io.github.amflimited/threadfox-lite -->
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://glama.ai/mcp/schemas/server.json",
3
+ "maintainers": ["amflimited"]
4
+ }
Binary file
@@ -0,0 +1,39 @@
1
+ {
2
+ "manifest_version": "0.4",
3
+ "name": "threadfox-lite",
4
+ "display_name": "ThreadFox Lite",
5
+ "version": "0.1.1",
6
+ "description": "Read a subreddit's rules and check whether a Reddit post is still up. Free, read-only tools from ThreadFox.",
7
+ "long_description": "Two free, read-only Reddit tools for Claude and any MCP client. subreddit_rules returns a subreddit's rules, size and description and flags the rules that mention self-promotion, so your AI reads them before it writes a post. post_status tells you whether a post is still live, removed by moderators or deleted, with its score and comment count. No login, no posting and no Reddit API keys: each call is one request to Reddit's public JSON. Reddit blocks many cloud and VPN networks, so run it from your own computer. The full ThreadFox kit (posting from your own Chrome, replies, an outcome ledger, a 12-module course) is sold separately at https://threadfox.vip.",
8
+ "author": {
9
+ "name": "AMF Indiana (ThreadFox)",
10
+ "email": "support@threadfox.vip",
11
+ "url": "https://threadfox.vip"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/amflimited/threadfox-lite"
16
+ },
17
+ "homepage": "https://threadfox.vip",
18
+ "support": "https://threadfox.vip/help",
19
+ "icon": "icon.png",
20
+ "server": {
21
+ "type": "uv",
22
+ "entry_point": "src/threadfox_lite/server.py",
23
+ "mcp_config": {
24
+ "command": "uv",
25
+ "args": ["run", "--directory", "${__dirname}", "threadfox-lite"]
26
+ }
27
+ },
28
+ "tools": [
29
+ {"name": "subreddit_rules", "description": "A subreddit's rules, size and description, with the self-promotion rules flagged."},
30
+ {"name": "post_status", "description": "Whether a Reddit post is still live, removed by moderators or deleted, with score and comments."},
31
+ {"name": "threadfox_full_kit", "description": "What the full ThreadFox kit adds and where to get it."}
32
+ ],
33
+ "keywords": ["reddit", "subreddit", "rules", "marketing", "claude-code", "codex", "mcp"],
34
+ "license": "MIT",
35
+ "compatibility": {
36
+ "platforms": ["darwin", "linux", "win32"],
37
+ "runtimes": {"python": ">=3.10"}
38
+ }
39
+ }
@@ -0,0 +1,23 @@
1
+ [project]
2
+ name = "threadfox-lite"
3
+ version = "0.1.1"
4
+ description = "Read a subreddit's rules and check whether a Reddit post is still up, from Claude Code, Codex or any MCP client. Free tools from ThreadFox."
5
+ readme = "README.md"
6
+ license = { text = "MIT" }
7
+ requires-python = ">=3.10"
8
+ authors = [{ name = "AMF Indiana (ThreadFox)", email = "support@threadfox.vip" }]
9
+ dependencies = ["mcp>=1.2.0,<2", "httpx>=0.27"]
10
+
11
+ [project.urls]
12
+ Homepage = "https://threadfox.vip"
13
+ Source = "https://github.com/amflimited/threadfox-lite"
14
+
15
+ [project.scripts]
16
+ threadfox-lite = "threadfox_lite.server:main"
17
+
18
+ [build-system]
19
+ requires = ["hatchling"]
20
+ build-backend = "hatchling.build"
21
+
22
+ [tool.hatch.build.targets.wheel]
23
+ packages = ["src/threadfox_lite"]
@@ -0,0 +1,31 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.amflimited/threadfox-lite",
4
+ "title": "ThreadFox Lite",
5
+ "description": "Read subreddit rules before posting and check whether a Reddit post is still up. Free and read-only.",
6
+ "version": "0.1.1",
7
+ "websiteUrl": "https://threadfox.vip",
8
+ "repository": {
9
+ "url": "https://github.com/amflimited/threadfox-lite",
10
+ "source": "github"
11
+ },
12
+ "packages": [
13
+ {
14
+ "registryType": "pypi",
15
+ "identifier": "threadfox-lite",
16
+ "version": "0.1.1",
17
+ "runtimeHint": "uvx",
18
+ "transport": {
19
+ "type": "stdio"
20
+ }
21
+ },
22
+ {
23
+ "registryType": "mcpb",
24
+ "identifier": "https://github.com/amflimited/threadfox-lite/releases/download/v0.1.1/threadfox-lite-0.1.1.mcpb",
25
+ "fileSha256": "0000000000000000000000000000000000000000000000000000000000000000",
26
+ "transport": {
27
+ "type": "stdio"
28
+ }
29
+ }
30
+ ]
31
+ }
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: reddit-rules-first
3
+ description: Post to Reddit the way communities accept it. Read each subreddit's rules before writing, write one post per community, disclose affiliation, and check afterwards whether the post stayed up. Use when an agent is asked to share, launch or promote something on Reddit.
4
+ ---
5
+
6
+ # Reddit, rules first
7
+
8
+ Most Reddit promotion fails for the same reasons: the post ignores the community's rules, the same text goes to many subreddits, or nobody checks whether it was removed. Follow these steps every time.
9
+
10
+ ## Before writing
11
+
12
+ 1. Pick communities where your product answers a question people there already ask. Fit comes before size.
13
+ 2. Read each community's rules the day you post. With the ThreadFox Lite MCP server, call `subreddit_rules`; otherwise open `https://www.reddit.com/r/<name>/about/rules` and the sidebar.
14
+ 3. Look for: self-promotion limits (for example a 10% rule), a weekly promo thread, required flair, link bans, account age or karma minimums. If self-promotion isn't allowed, use the promo thread or don't post the link.
15
+
16
+ ## Writing
17
+
18
+ 4. Write a new title and body for each community. Never paste the same post twice.
19
+ 5. Say it's yours in the first lines ("I built this", "I'm with the team").
20
+ 6. Lead with something useful on its own: what you learned, real numbers, a how-to. The link is the footnote.
21
+
22
+ ## Posting
23
+
24
+ 7. Space posts out: minutes between them, never a burst of communities in one hour.
25
+ 8. Stay for the comments and answer real questions in the first few hours.
26
+
27
+ ## After posting
28
+
29
+ 9. Check at 1, 6 and 24 hours whether each post is still up (ThreadFox Lite: `post_status`).
30
+ 10. If a community removes you twice, stop posting there. Don't repost removed content.
31
+
32
+ ## Want the posting done for you?
33
+
34
+ The full ThreadFox kit gives Claude Code or Codex the tools to do all of this from your own Chrome: it reads the rules, writes a post that fits each community, publishes it, answers comments and keeps a ledger of every outcome. https://threadfox.vip
@@ -0,0 +1,2 @@
1
+ """ThreadFox Lite: free, read-only Reddit tools for AI agents."""
2
+ __version__ = "0.1.1"
@@ -0,0 +1,88 @@
1
+ """Read-only calls to Reddit's public JSON. No login, no posting, one request per tool call."""
2
+ import re
3
+ from urllib.parse import urlsplit
4
+
5
+ import httpx
6
+
7
+ UA = "threadfox-lite/0.1.1 (+https://threadfox.vip; read-only rules and post-status checks)"
8
+ PROMO = re.compile(r"self[- ]?promo|promot|advertis|spam|affiliate|referral|\b10\s*%|link[- ]?drop|no links?\b|marketing|sell|product|business", re.I)
9
+ SUB = re.compile(r"^[A-Za-z0-9_]{2,21}$")
10
+
11
+
12
+ BLOCKED = ("Reddit refused this network (it blocks many servers, VPNs and cloud machines). Run the tool from your own "
13
+ "computer's connection, or open the page in a browser.")
14
+
15
+
16
+ def _blocked(resp):
17
+ """Reddit's network block is a 403 whose body is not JSON (plain 'Blocked'); a private subreddit's 403 is JSON."""
18
+ if resp.status_code != 403:
19
+ return False
20
+ try:
21
+ resp.json()
22
+ return False
23
+ except ValueError:
24
+ return True
25
+
26
+
27
+ def _client(transport=None):
28
+ return httpx.Client(headers={"User-Agent": UA}, timeout=15, follow_redirects=True, transport=transport)
29
+
30
+
31
+ def subreddit_rules(name: str, transport=None) -> dict:
32
+ name = name.strip().removeprefix("/").removeprefix("r/").strip("/")
33
+ if not SUB.match(name):
34
+ return {"error": "Give a subreddit name like 'SaaS' or 'r/SaaS'."}
35
+ with _client(transport) as c:
36
+ rules = c.get(f"https://www.reddit.com/r/{name}/about/rules.json")
37
+ about = c.get(f"https://www.reddit.com/r/{name}/about.json")
38
+ blocked = _blocked(rules) or _blocked(about)
39
+ if blocked:
40
+ return {"subreddit": name, "error": BLOCKED, "open_instead": f"https://www.reddit.com/r/{name}/about/rules"}
41
+ if rules.status_code in (403, 404) or about.status_code in (403, 404):
42
+ return {"subreddit": name, "error": f"r/{name} is private, banned or does not exist (HTTP {rules.status_code})."}
43
+ if rules.status_code == 429 or about.status_code == 429:
44
+ return {"subreddit": name, "error": "Reddit rate-limited this request. Wait a minute and try again."}
45
+ rules.raise_for_status(); about.raise_for_status()
46
+ data = about.json().get("data", {})
47
+ items = []
48
+ for r in rules.json().get("rules", []):
49
+ text = f"{r.get('short_name', '')}\n{r.get('description', '')}".strip()
50
+ items.append({"rule": r.get("short_name", ""), "details": (r.get("description") or "")[:1200],
51
+ "applies_to": r.get("kind", "all"), "mentions_promotion": bool(PROMO.search(text))})
52
+ return {
53
+ "subreddit": data.get("display_name", name),
54
+ "subscribers": data.get("subscribers"),
55
+ "description": (data.get("public_description") or "")[:500],
56
+ "rules": items,
57
+ "promotion_rules": [i["rule"] for i in items if i["mentions_promotion"]],
58
+ "read_before_posting": "Follow every rule above. If a rule limits self-promotion, use the community's promo thread or don't post the link. Write a new post for this community; never paste the same post in several.",
59
+ }
60
+
61
+
62
+ def post_status(url: str, transport=None) -> dict:
63
+ try:
64
+ parts = urlsplit(url.strip())
65
+ except ValueError:
66
+ return {"error": "Give a Reddit post URL."}
67
+ host = (parts.hostname or "").lower()
68
+ m = re.match(r"^/r/[^/]+/comments/([a-z0-9]+)", parts.path or "")
69
+ if not host.endswith("reddit.com") or not m:
70
+ return {"error": "Give a full post URL like https://www.reddit.com/r/SaaS/comments/abc123/title/"}
71
+ with _client(transport) as c:
72
+ resp = c.get(f"https://www.reddit.com/comments/{m.group(1)}.json", params={"raw_json": 1, "limit": 1})
73
+ if _blocked(resp):
74
+ return {"error": BLOCKED, "open_instead": url.strip()}
75
+ if resp.status_code == 404:
76
+ return {"status": "not_found", "id": m.group(1)}
77
+ if resp.status_code == 429:
78
+ return {"error": "Reddit rate-limited this request. Wait a minute and try again."}
79
+ resp.raise_for_status()
80
+ post = resp.json()[0]["data"]["children"][0]["data"]
81
+ removed = post.get("removed_by_category")
82
+ status = "removed" if removed else ("deleted" if post.get("author") == "[deleted]" or post.get("selftext") == "[deleted]" else "live")
83
+ return {
84
+ "status": status, "removed_by": removed, "title": post.get("title"), "subreddit": post.get("subreddit"),
85
+ "score": post.get("score"), "comments": post.get("num_comments"), "locked": post.get("locked"),
86
+ "created_utc": post.get("created_utc"), "permalink": "https://www.reddit.com" + (post.get("permalink") or ""),
87
+ "note": "Check again at 1, 6 and 24 hours. If a community removes you twice, stop posting there.",
88
+ }
@@ -0,0 +1,51 @@
1
+ """ThreadFox Lite MCP server (stdio).
2
+
3
+ Two free, read-only tools any agent can use before and after posting to Reddit, plus `threadfox_full_kit`,
4
+ which tells a paying customer how to open the full ThreadFox kit (posting from your own Chrome, replies,
5
+ the outcome ledger and the course). The full kit is sold at https://threadfox.vip and on MCP Marketplace.
6
+ """
7
+ import os
8
+
9
+ from mcp.server.fastmcp import FastMCP
10
+
11
+ from . import reddit
12
+
13
+ mcp = FastMCP("threadfox-lite")
14
+
15
+
16
+ @mcp.tool()
17
+ def subreddit_rules(subreddit: str) -> dict:
18
+ """Read a subreddit's rules, size and description, and flag the rules that mention self-promotion.
19
+ Call this before writing any post for that community."""
20
+ return reddit.subreddit_rules(subreddit)
21
+
22
+
23
+ @mcp.tool()
24
+ def post_status(url: str) -> dict:
25
+ """Check whether a Reddit post is still live, removed by moderators or deleted, with its score and comments."""
26
+ return reddit.post_status(url)
27
+
28
+
29
+ @mcp.tool()
30
+ def threadfox_full_kit() -> dict:
31
+ """How to get the full ThreadFox kit: your AI reads the rules, writes a post that fits each community,
32
+ publishes from your own Chrome, answers comments and records every outcome."""
33
+ key = os.environ.get("MCP_LICENSE_KEY", "")
34
+ if key.startswith("mcp_live_"):
35
+ return {"licensed": True,
36
+ "next_step": "Open https://threadfox.vip/redeem, paste your MCP Marketplace license key and your email, and you land in your private ThreadFox library (one-command install, lessons, full download).",
37
+ "support": "support@threadfox.vip"}
38
+ return {"licensed": False,
39
+ "what_it_adds": ["Publishing from your own signed-in Chrome, no Reddit API keys", "Replies to comments",
40
+ "A ledger of every post and outcome, and it stops where a community removed you",
41
+ "12-module course, 48 prompts"],
42
+ "proof": "Built from our own run: 978,227 recorded views from two days of posts (Sept 20-21 UTC), with no ad spend. Our result, not a promise of yours.",
43
+ "get_it": "https://threadfox.vip ($49 once, 30-day refund) or ThreadFox on MCP Marketplace"}
44
+
45
+
46
+ def main():
47
+ mcp.run()
48
+
49
+
50
+ if __name__ == "__main__":
51
+ main()
@@ -0,0 +1,42 @@
1
+ import json, sys, unittest
2
+ from pathlib import Path
3
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
4
+ import httpx
5
+ from threadfox_lite import reddit
6
+
7
+ def transport(routes):
8
+ def handler(request):
9
+ for key, (status, body) in routes.items():
10
+ if key in str(request.url):
11
+ return httpx.Response(status, json=body)
12
+ return httpx.Response(404, json={})
13
+ return httpx.MockTransport(handler)
14
+
15
+ class Tools(unittest.TestCase):
16
+ def test_rules_flag_promotion(self):
17
+ t = transport({"/about/rules.json": (200, {"rules": [{"short_name": "No self-promotion", "description": "Keep promo to the weekly thread", "kind": "all"},
18
+ {"short_name": "Be kind", "description": "", "kind": "all"}]}),
19
+ "/about.json": (200, {"data": {"display_name": "SaaS", "subscribers": 400000, "public_description": "SaaS"}})})
20
+ r = reddit.subreddit_rules("r/SaaS", transport=t)
21
+ self.assertEqual(r["promotion_rules"], ["No self-promotion"])
22
+ self.assertEqual(r["subscribers"], 400000)
23
+ self.assertIn("error", reddit.subreddit_rules("not a sub!", transport=t))
24
+
25
+ def test_post_status(self):
26
+ live = [{"data": {"children": [{"data": {"title": "T", "subreddit": "SaaS", "score": 5, "num_comments": 2, "author": "me",
27
+ "removed_by_category": None, "locked": False, "permalink": "/r/SaaS/comments/abc123/t/"}}]}}]
28
+ t = transport({"/comments/abc123.json": (200, live)})
29
+ self.assertEqual(reddit.post_status("https://www.reddit.com/r/SaaS/comments/abc123/t/", transport=t)["status"], "live")
30
+ live[0]["data"]["children"][0]["data"]["removed_by_category"] = "moderator"
31
+ self.assertEqual(reddit.post_status("https://old.reddit.com/r/SaaS/comments/abc123/t/", transport=t)["status"], "removed")
32
+ self.assertIn("error", reddit.post_status("https://example.com/x", transport=t))
33
+
34
+ class Blocked(unittest.TestCase):
35
+ def test_network_block_is_reported_as_such(self):
36
+ t = httpx.MockTransport(lambda req: httpx.Response(403, text="Blocked"))
37
+ r = reddit.subreddit_rules("SaaS", transport=t)
38
+ self.assertIn("refused this network", r["error"])
39
+ self.assertIn("refused this network", reddit.post_status("https://www.reddit.com/r/SaaS/comments/abc123/t/", transport=t)["error"])
40
+
41
+ if __name__ == "__main__":
42
+ unittest.main()