@rolli/mcp 1.0.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.
- package/README.md +86 -0
- package/build/api.d.ts +2 -0
- package/build/api.js +33 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +18 -0
- package/build/tools/keyword-search.d.ts +2 -0
- package/build/tools/keyword-search.js +63 -0
- package/build/tools/posts.d.ts +2 -0
- package/build/tools/posts.js +31 -0
- package/build/tools/topic-tree.d.ts +2 -0
- package/build/tools/topic-tree.js +20 -0
- package/build/tools/user-search.d.ts +2 -0
- package/build/tools/user-search.js +58 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# rolli-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for [Rolli IQ](https://rolli.ai) — social media search and analytics across X, Reddit, Bluesky, YouTube, LinkedIn, Facebook, Instagram, and Weibo.
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
| Tool | Description |
|
|
8
|
+
|------|-------------|
|
|
9
|
+
| `list_keyword_searches` | List all keyword searches, filtered by status |
|
|
10
|
+
| `keyword_search` | Create a keyword/hashtag search across social platforms |
|
|
11
|
+
| `get_keyword_search` | Get keyword search results (status, analytics, posts) |
|
|
12
|
+
| `list_user_searches` | List all user searches, filtered by status |
|
|
13
|
+
| `user_search` | Create a user profile search on a platform |
|
|
14
|
+
| `get_user_search` | Get user search results (profile, metrics, content analysis) |
|
|
15
|
+
| `get_topic_tree` | Get conversation topic tree for a keyword search |
|
|
16
|
+
| `get_keyword_search_posts` | Get raw posts from a keyword search |
|
|
17
|
+
| `get_user_search_posts` | Get raw posts from a user search |
|
|
18
|
+
|
|
19
|
+
## Setup
|
|
20
|
+
|
|
21
|
+
You need a Rolli account with API access. Get your API token and user ID from [rolli.ai](https://rolli.ai).
|
|
22
|
+
|
|
23
|
+
### Claude Desktop
|
|
24
|
+
|
|
25
|
+
Add to your Claude Desktop config (`claude_desktop_config.json`):
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"mcpServers": {
|
|
30
|
+
"rolli": {
|
|
31
|
+
"command": "npx",
|
|
32
|
+
"args": ["-y", "@rolli/mcp"],
|
|
33
|
+
"env": {
|
|
34
|
+
"ROLLI_API_TOKEN": "your_token",
|
|
35
|
+
"ROLLI_USER_ID": "your_user_id"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Claude Code
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
claude mcp add rolli -- npx -y @rolli/mcp
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then set the environment variables `ROLLI_API_TOKEN` and `ROLLI_USER_ID`.
|
|
49
|
+
|
|
50
|
+
### VS Code / Cursor / Windsurf
|
|
51
|
+
|
|
52
|
+
Add to your MCP settings (`.vscode/mcp.json` or equivalent):
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"mcpServers": {
|
|
57
|
+
"rolli": {
|
|
58
|
+
"command": "npx",
|
|
59
|
+
"args": ["-y", "@rolli/mcp"],
|
|
60
|
+
"env": {
|
|
61
|
+
"ROLLI_API_TOKEN": "your_token",
|
|
62
|
+
"ROLLI_USER_ID": "your_user_id"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Smithery
|
|
70
|
+
|
|
71
|
+
This server includes a `smithery.yaml` for deployment via [Smithery](https://smithery.ai). It will prompt for your API token and user ID during setup.
|
|
72
|
+
|
|
73
|
+
## Usage Examples
|
|
74
|
+
|
|
75
|
+
**Search for a keyword across social media:**
|
|
76
|
+
> "Search for posts about 'artificial intelligence' on Twitter and Reddit from the last week"
|
|
77
|
+
|
|
78
|
+
**Analyze a user profile:**
|
|
79
|
+
> "Look up @elonmusk on Twitter and analyze their recent posts"
|
|
80
|
+
|
|
81
|
+
**Get topic breakdown:**
|
|
82
|
+
> "Show me the topic tree for my keyword search #123"
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT
|
package/build/api.d.ts
ADDED
package/build/api.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const BASE_URL = "https://rolli.ai/api";
|
|
2
|
+
const ROLLI_API_TOKEN = process.env.ROLLI_API_TOKEN;
|
|
3
|
+
const ROLLI_USER_ID = process.env.ROLLI_USER_ID;
|
|
4
|
+
if (!ROLLI_API_TOKEN || !ROLLI_USER_ID) {
|
|
5
|
+
console.error("Error: ROLLI_API_TOKEN and ROLLI_USER_ID environment variables are required.\n" +
|
|
6
|
+
"Set them in your MCP client configuration.");
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
const headers = {
|
|
10
|
+
"X-ROLLI-TOKEN": ROLLI_API_TOKEN,
|
|
11
|
+
"X-ROLLI-USER-ID": ROLLI_USER_ID,
|
|
12
|
+
"Content-Type": "application/json",
|
|
13
|
+
};
|
|
14
|
+
export async function apiGet(path) {
|
|
15
|
+
const res = await fetch(`${BASE_URL}${path}`, { headers });
|
|
16
|
+
if (!res.ok) {
|
|
17
|
+
const text = await res.text();
|
|
18
|
+
throw new Error(`API error ${res.status}: ${text}`);
|
|
19
|
+
}
|
|
20
|
+
return res.json();
|
|
21
|
+
}
|
|
22
|
+
export async function apiPost(path, body) {
|
|
23
|
+
const res = await fetch(`${BASE_URL}${path}`, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers,
|
|
26
|
+
body: JSON.stringify(body),
|
|
27
|
+
});
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
const text = await res.text();
|
|
30
|
+
throw new Error(`API error ${res.status}: ${text}`);
|
|
31
|
+
}
|
|
32
|
+
return res.json();
|
|
33
|
+
}
|
package/build/index.d.ts
ADDED
package/build/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { register as registerKeywordSearch } from "./tools/keyword-search.js";
|
|
5
|
+
import { register as registerUserSearch } from "./tools/user-search.js";
|
|
6
|
+
import { register as registerTopicTree } from "./tools/topic-tree.js";
|
|
7
|
+
import { register as registerPosts } from "./tools/posts.js";
|
|
8
|
+
const server = new McpServer({
|
|
9
|
+
name: "rolli-mcp",
|
|
10
|
+
version: "1.0.0",
|
|
11
|
+
});
|
|
12
|
+
registerKeywordSearch(server);
|
|
13
|
+
registerUserSearch(server);
|
|
14
|
+
registerTopicTree(server);
|
|
15
|
+
registerPosts(server);
|
|
16
|
+
const transport = new StdioServerTransport();
|
|
17
|
+
await server.connect(transport);
|
|
18
|
+
console.error("Rolli MCP server running on stdio");
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { apiPost, apiGet } from "../api.js";
|
|
3
|
+
export function register(server) {
|
|
4
|
+
server.tool("list_keyword_searches", "List all keyword searches. Returns a paginated list filtered by status.", {
|
|
5
|
+
show: z
|
|
6
|
+
.enum(["all", "started", "finished", "pending", "failed"])
|
|
7
|
+
.optional()
|
|
8
|
+
.describe("Filter by status (default: all)"),
|
|
9
|
+
page: z.number().optional().describe("Page number (100 results per page)"),
|
|
10
|
+
}, async (params) => {
|
|
11
|
+
try {
|
|
12
|
+
const queryParts = [];
|
|
13
|
+
if (params.show)
|
|
14
|
+
queryParts.push(`show=${params.show}`);
|
|
15
|
+
if (params.page !== undefined)
|
|
16
|
+
queryParts.push(`page=${params.page}`);
|
|
17
|
+
const query = queryParts.length ? `?${queryParts.join("&")}` : "";
|
|
18
|
+
const data = await apiGet(`/iq/keyword_search${query}`);
|
|
19
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
return { content: [{ type: "text", text: String(e) }], isError: true };
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
server.tool("keyword_search", "Create a keyword/hashtag search across social media platforms (X, Reddit, Bluesky, YouTube, LinkedIn, Facebook, Instagram, Weibo). Returns a search ID to retrieve results later.", {
|
|
26
|
+
query: z.string().describe("Search query (keyword or hashtag)"),
|
|
27
|
+
platforms: z
|
|
28
|
+
.array(z.enum(["twitter", "reddit", "bluesky", "youtube", "linkedin", "facebook", "instagram", "weibo"]))
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("Platforms to search (default: twitter, reddit, bluesky, youtube)"),
|
|
31
|
+
start_date: z.string().optional().describe("Start date (YYYY-MM-DD)"),
|
|
32
|
+
end_date: z.string().optional().describe("End date (YYYY-MM-DD)"),
|
|
33
|
+
max_post: z.number().optional().describe("Maximum number of posts to retrieve (default: 100)"),
|
|
34
|
+
}, async (params) => {
|
|
35
|
+
try {
|
|
36
|
+
const body = { query: params.query };
|
|
37
|
+
if (params.platforms)
|
|
38
|
+
body.platforms = params.platforms;
|
|
39
|
+
if (params.start_date)
|
|
40
|
+
body.start_date = params.start_date;
|
|
41
|
+
if (params.end_date)
|
|
42
|
+
body.end_date = params.end_date;
|
|
43
|
+
if (params.max_post !== undefined)
|
|
44
|
+
body.max_post = params.max_post;
|
|
45
|
+
const data = await apiPost("/iq/keyword_search", body);
|
|
46
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
return { content: [{ type: "text", text: String(e) }], isError: true };
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
server.tool("get_keyword_search", "Get results for a keyword search by ID. Returns search status, analytics summary, and posts.", {
|
|
53
|
+
id: z.number().describe("Keyword search ID"),
|
|
54
|
+
}, async (params) => {
|
|
55
|
+
try {
|
|
56
|
+
const data = await apiGet(`/iq/keyword_search/${params.id}`);
|
|
57
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
return { content: [{ type: "text", text: String(e) }], isError: true };
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { apiGet } from "../api.js";
|
|
3
|
+
export function register(server) {
|
|
4
|
+
server.tool("get_keyword_search_posts", "Get raw posts from a keyword search. Returns the actual social media posts matching the search query.", {
|
|
5
|
+
search_id: z.number().describe("Keyword search ID"),
|
|
6
|
+
platform: z
|
|
7
|
+
.enum(["all", "twitter", "reddit", "bluesky", "youtube", "instagram", "facebook", "weibo", "linkedin"])
|
|
8
|
+
.optional()
|
|
9
|
+
.describe("Filter by platform (default: all)"),
|
|
10
|
+
}, async (params) => {
|
|
11
|
+
try {
|
|
12
|
+
const query = params.platform ? `?platform=${params.platform}` : "";
|
|
13
|
+
const data = await apiGet(`/iq/keyword_search/${params.search_id}/posts_data${query}`);
|
|
14
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
return { content: [{ type: "text", text: String(e) }], isError: true };
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
server.tool("get_user_search_posts", "Get raw posts from a user search. Returns the actual social media posts from the searched user profile.", {
|
|
21
|
+
search_id: z.number().describe("User search ID"),
|
|
22
|
+
}, async (params) => {
|
|
23
|
+
try {
|
|
24
|
+
const data = await apiGet(`/iq/user_search/${params.search_id}/posts_data`);
|
|
25
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
return { content: [{ type: "text", text: String(e) }], isError: true };
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { apiGet } from "../api.js";
|
|
3
|
+
export function register(server) {
|
|
4
|
+
server.tool("get_topic_tree", "Get the conversation topic tree for a keyword search. Shows how topics and subtopics are distributed across the search results.", {
|
|
5
|
+
search_id: z.number().describe("Keyword search ID"),
|
|
6
|
+
platform: z
|
|
7
|
+
.enum(["twitter", "bluesky", "youtube"])
|
|
8
|
+
.optional()
|
|
9
|
+
.describe("Filter by platform"),
|
|
10
|
+
}, async (params) => {
|
|
11
|
+
try {
|
|
12
|
+
const query = params.platform ? `?platform=${params.platform}` : "";
|
|
13
|
+
const data = await apiGet(`/iq/keyword_search/${params.search_id}/topic_tree${query}`);
|
|
14
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
return { content: [{ type: "text", text: String(e) }], isError: true };
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { apiPost, apiGet } from "../api.js";
|
|
3
|
+
export function register(server) {
|
|
4
|
+
server.tool("list_user_searches", "List all user searches. Returns a paginated list filtered by status.", {
|
|
5
|
+
show: z
|
|
6
|
+
.enum(["all", "started", "finished", "pending", "failed"])
|
|
7
|
+
.optional()
|
|
8
|
+
.describe("Filter by status (default: all)"),
|
|
9
|
+
page: z.number().optional().describe("Page number (100 results per page)"),
|
|
10
|
+
}, async (params) => {
|
|
11
|
+
try {
|
|
12
|
+
const queryParts = [];
|
|
13
|
+
if (params.show)
|
|
14
|
+
queryParts.push(`show=${params.show}`);
|
|
15
|
+
if (params.page !== undefined)
|
|
16
|
+
queryParts.push(`page=${params.page}`);
|
|
17
|
+
const query = queryParts.length ? `?${queryParts.join("&")}` : "";
|
|
18
|
+
const data = await apiGet(`/iq/user_search${query}`);
|
|
19
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
return { content: [{ type: "text", text: String(e) }], isError: true };
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
server.tool("user_search", "Create a user profile search on a social media platform. Returns a search ID to retrieve results later.", {
|
|
26
|
+
query: z.string().describe("Username or profile URL to search"),
|
|
27
|
+
platform: z.enum(["twitter", "facebook", "instagram"]).describe("Platform to search"),
|
|
28
|
+
start_date: z.string().optional().describe("Start date (YYYY-MM-DD)"),
|
|
29
|
+
end_date: z.string().optional().describe("End date (YYYY-MM-DD)"),
|
|
30
|
+
}, async (params) => {
|
|
31
|
+
try {
|
|
32
|
+
const body = {
|
|
33
|
+
query: params.query,
|
|
34
|
+
platform: params.platform,
|
|
35
|
+
};
|
|
36
|
+
if (params.start_date)
|
|
37
|
+
body.start_date = params.start_date;
|
|
38
|
+
if (params.end_date)
|
|
39
|
+
body.end_date = params.end_date;
|
|
40
|
+
const data = await apiPost("/iq/user_search", body);
|
|
41
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
return { content: [{ type: "text", text: String(e) }], isError: true };
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
server.tool("get_user_search", "Get results for a user search by ID. Returns profile info, metrics, and content analysis.", {
|
|
48
|
+
id: z.number().describe("User search ID"),
|
|
49
|
+
}, async (params) => {
|
|
50
|
+
try {
|
|
51
|
+
const data = await apiGet(`/iq/user_search/${params.id}`);
|
|
52
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
return { content: [{ type: "text", text: String(e) }], isError: true };
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rolli/mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for Rolli IQ — social media search and analytics across X, Reddit, Bluesky, YouTube, LinkedIn, Facebook, Instagram, and Weibo",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "build/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"rolli-mcp": "build/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"build"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"prepare": "tsc"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mcp",
|
|
19
|
+
"rolli",
|
|
20
|
+
"social-media",
|
|
21
|
+
"analytics",
|
|
22
|
+
"search",
|
|
23
|
+
"model-context-protocol"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
31
|
+
"zod": "^3.24.4"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^22.15.3",
|
|
35
|
+
"typescript": "^5.8.3"
|
|
36
|
+
}
|
|
37
|
+
}
|