viralhunt-mcp 0.1.0

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 +52 -0
  2. package/index.js +127 -0
  3. package/package.json +32 -0
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # ViralHunt MCP Server
2
+
3
+ An [MCP](https://modelcontextprotocol.io) server for **[ViralHunt.io](https://viralhunt.io)** — a
4
+ trending-content radar + cross-network social scheduler. It gives any MCP client the full agent
5
+ loop: **find what's trending → publish/schedule → verify → edit/cancel**.
6
+
7
+ A BuzzSumo alternative with an API agents can drive end to end.
8
+
9
+ ## Tools
10
+
11
+ | Tool | What it does |
12
+ |------|--------------|
13
+ | `viralhunt_trending` | Find viral posts on TikTok / Instagram / X / Facebook / Pinterest / RSS, ranked by viral score |
14
+ | `viralhunt_targets` | List your projects/brands and the connected accounts you can post to |
15
+ | `viralhunt_schedule` | Publish now or schedule a post (text + media) to your accounts |
16
+ | `viralhunt_get_post` | Get a post's status + per-network permalinks |
17
+ | `viralhunt_update_post` | Edit a still-scheduled post (body / media / networks / time) |
18
+ | `viralhunt_cancel_post` | Cancel the not-yet-published targets of a scheduled post |
19
+
20
+ ## Get a token
21
+
22
+ 1. Sign up at **https://viralhunt.io** (free 7-day trial, no card).
23
+ 2. **Account → API Access** → create a personal token (`vhk_…`).
24
+
25
+ ## Configure
26
+
27
+ Set two environment variables:
28
+
29
+ - `VIRALHUNT_API_KEY` — your `vhk_…` token (required)
30
+ - `VIRALHUNT_BASE_URL` — optional, defaults to `https://viralhunt.io/tool/api/v1`
31
+
32
+ ### Claude Desktop / Cline (`mcp` config)
33
+
34
+ ```json
35
+ {
36
+ "mcpServers": {
37
+ "viralhunt": {
38
+ "command": "npx",
39
+ "args": ["-y", "viralhunt-mcp"],
40
+ "env": { "VIRALHUNT_API_KEY": "vhk_your_token_here" }
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ ## Run locally
47
+
48
+ ```bash
49
+ VIRALHUNT_API_KEY=vhk_... npx viralhunt-mcp
50
+ ```
51
+
52
+ Requires Node ≥ 18. Docs: **https://viralhunt.io/api** · License: MIT.
package/index.js ADDED
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ViralHunt MCP server — exposes the ViralHunt.io public API v1 as MCP tools so any MCP client
4
+ * (Claude Desktop, Cline, etc.) can run the full loop: find trending content → publish/schedule →
5
+ * verify → edit/cancel.
6
+ *
7
+ * Config via environment:
8
+ * VIRALHUNT_API_KEY required — a personal token (vhk_…) from viralhunt.io → Account → API Access
9
+ * VIRALHUNT_BASE_URL optional — defaults to https://viralhunt.io/tool/api/v1
10
+ *
11
+ * No token is stored here; it's read from the env and sent as a Bearer header per request.
12
+ */
13
+
14
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
15
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
16
+ import { z } from "zod";
17
+
18
+ const API_KEY = process.env.VIRALHUNT_API_KEY || "";
19
+ const BASE_URL = (process.env.VIRALHUNT_BASE_URL || "https://viralhunt.io/tool/api/v1").replace(/\/+$/, "");
20
+
21
+ /** Call the ViralHunt API. Returns parsed JSON; throws with the API's error message on failure. */
22
+ async function vh(path, { method = "GET", query = null, body = null } = {}) {
23
+ if (!API_KEY) throw new Error("VIRALHUNT_API_KEY is not set. Get a token at viralhunt.io → Account → API Access.");
24
+ let url = BASE_URL + path;
25
+ if (query) {
26
+ const qs = new URLSearchParams(Object.entries(query).filter(([, v]) => v !== undefined && v !== null && v !== "")).toString();
27
+ if (qs) url += (url.includes("?") ? "&" : "?") + qs;
28
+ }
29
+ const opts = { method, headers: { Authorization: `Bearer ${API_KEY}`, Accept: "application/json" } };
30
+ if (body !== null) { opts.headers["Content-Type"] = "application/json"; opts.body = JSON.stringify(body); }
31
+
32
+ const res = await fetch(url, opts);
33
+ const text = await res.text();
34
+ let json;
35
+ try { json = JSON.parse(text); } catch { throw new Error(`ViralHunt returned non-JSON (HTTP ${res.status}): ${text.slice(0, 200)}`); }
36
+ if (!res.ok || json?.success === false) {
37
+ const e = json?.error || {};
38
+ throw new Error(`ViralHunt API ${res.status} ${e.code || ""}: ${e.message || text.slice(0, 200)}`.trim());
39
+ }
40
+ return json.data ?? json;
41
+ }
42
+
43
+ /** Wrap a handler so any error is returned as a clean MCP error result (isError). */
44
+ function tool(server, name, description, schema, handler) {
45
+ server.tool(name, description, schema, async (args) => {
46
+ try {
47
+ const data = await handler(args);
48
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
49
+ } catch (err) {
50
+ return { content: [{ type: "text", text: String(err?.message || err) }], isError: true };
51
+ }
52
+ });
53
+ }
54
+
55
+ const server = new McpServer({ name: "viralhunt", version: "0.1.0" });
56
+
57
+ // ── Discover trending content ──
58
+ tool(server, "viralhunt_trending",
59
+ "Find trending/viral posts on a social network, ranked by viral score (engagement velocity).",
60
+ {
61
+ source: z.enum(["tiktok", "instagram", "x", "facebook", "pinterest", "rss"]).describe("Which network to pull trending from."),
62
+ sort: z.string().optional().describe("viral (default), most_liked, most_viewed, most_commented, newest, …"),
63
+ time_range: z.enum(["24h", "7d", "30d", "3m", "all"]).optional().describe("Time window (default 7d)."),
64
+ keyword: z.string().optional().describe("Filter by keyword/niche."),
65
+ per_page: z.number().int().min(1).max(100).optional().describe("How many results (max 100)."),
66
+ },
67
+ (a) => vh("/trending.php", { query: { source: a.source, sort: a.sort || "viral", time_range: a.time_range || "7d", keyword: a.keyword, per_page: a.per_page || 20 } })
68
+ );
69
+
70
+ // ── Where can I publish (projects + connected accounts) ──
71
+ tool(server, "viralhunt_targets",
72
+ "List the user's projects/brands and the connected social accounts you can publish to in each.",
73
+ { project: z.string().optional().describe("Project name to scope the accounts to (optional).") },
74
+ (a) => vh("/schedule.php", { query: { action: "targets", project: a.project } })
75
+ );
76
+
77
+ // ── Publish now or schedule ──
78
+ tool(server, "viralhunt_schedule",
79
+ "Publish now or schedule a post (text + media URLs) to a project's connected accounts. Verify content before posting — never publish fake news, copyrighted media, or spam.",
80
+ {
81
+ project: z.string().optional().describe("Project name — REQUIRED if the org has more than one project."),
82
+ project_id: z.number().int().optional().describe("Project id — alternative to project name."),
83
+ body: z.string().optional().describe("Caption/text. Optional if media is provided."),
84
+ media: z.array(z.string()).optional().describe("Public image/video URLs."),
85
+ networks: z.array(z.string()).optional().describe('Post to every account on these networks, e.g. ["instagram","facebook"].'),
86
+ target_account_ids: z.array(z.number().int()).optional().describe("Exact account ids (from viralhunt_targets). Omit = all accounts in the project."),
87
+ scheduled_at: z.string().optional().describe("ISO-8601 UTC, e.g. 2026-08-01T15:30:00Z. Omit = publish now."),
88
+ first_comment: z.string().optional().describe("Optional first comment (link-in-comments)."),
89
+ },
90
+ (a) => vh("/schedule.php", { method: "POST", query: { action: "create" }, body: {
91
+ project: a.project, project_id: a.project_id, body: a.body, media: a.media,
92
+ networks: a.networks, target_account_ids: a.target_account_ids, scheduled_at: a.scheduled_at, first_comment: a.first_comment,
93
+ } })
94
+ );
95
+
96
+ // ── Check status ──
97
+ tool(server, "viralhunt_get_post",
98
+ "Get the current status and per-network results (incl. permalinks) of a post you created.",
99
+ { id: z.number().int().describe("The post id returned by viralhunt_schedule.") },
100
+ (a) => vh("/schedule.php", { query: { action: "get", id: a.id } })
101
+ );
102
+
103
+ // ── Edit a scheduled post ──
104
+ tool(server, "viralhunt_update_post",
105
+ "Edit a still-scheduled post. Only works while status is 'scheduled' and >5 min before publish. Only the fields you send change; the project cannot be changed. Check status with viralhunt_get_post first; on a 409 error re-fetch instead of retrying.",
106
+ {
107
+ id: z.number().int().describe("The post id."),
108
+ body: z.string().optional().describe("New caption."),
109
+ media: z.array(z.string()).optional().describe("New media URLs (replaces the set)."),
110
+ networks: z.array(z.string()).optional().describe("New network set (adds/removes targets)."),
111
+ target_account_ids: z.array(z.number().int()).optional().describe("New exact account-id set."),
112
+ scheduled_at: z.string().optional().describe("New ISO-8601 UTC time (must be in the future)."),
113
+ },
114
+ (a) => vh("/schedule.php", { method: "POST", query: { action: "update" }, body: {
115
+ id: a.id, body: a.body, media: a.media, networks: a.networks, target_account_ids: a.target_account_ids, scheduled_at: a.scheduled_at,
116
+ } })
117
+ );
118
+
119
+ // ── Cancel a scheduled post ──
120
+ tool(server, "viralhunt_cancel_post",
121
+ "Cancel the not-yet-published targets of a scheduled post. If everything already published you'll get a 409 — delete it from the platform instead.",
122
+ { id: z.number().int().describe("The post id.") },
123
+ (a) => vh("/schedule.php", { method: "POST", query: { action: "cancel" }, body: { id: a.id } })
124
+ );
125
+
126
+ await server.connect(new StdioServerTransport());
127
+ console.error("viralhunt-mcp running (base: " + BASE_URL + ")");
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "viralhunt-mcp",
3
+ "mcpName": "io.github.rodvan/viralhunt-mcp",
4
+ "version": "0.1.0",
5
+ "description": "MCP server for ViralHunt.io — find trending/viral content across TikTok, Instagram, X, Facebook, Pinterest and Reddit, and schedule/publish/edit posts on your connected social accounts.",
6
+ "type": "module",
7
+ "bin": {
8
+ "viralhunt-mcp": "index.js"
9
+ },
10
+ "files": [
11
+ "index.js",
12
+ "README.md"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18"
16
+ },
17
+ "keywords": [
18
+ "mcp",
19
+ "model-context-protocol",
20
+ "social-media",
21
+ "trending",
22
+ "scheduler",
23
+ "viralhunt",
24
+ "buzzsumo-alternative"
25
+ ],
26
+ "homepage": "https://viralhunt.io",
27
+ "license": "MIT",
28
+ "dependencies": {
29
+ "@modelcontextprotocol/sdk": "^1.0.4",
30
+ "zod": "^3.23.8"
31
+ }
32
+ }