@rolli/mcp 1.0.0 → 1.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.
- package/README.md +13 -31
- package/build/api.d.ts +1 -0
- package/build/api.js +12 -0
- package/build/index.js +4 -0
- package/build/tools/integration-setup.d.ts +2 -0
- package/build/tools/integration-setup.js +24 -0
- package/build/tools/keyword-search.js +21 -3
- package/build/tools/usage.d.ts +2 -0
- package/build/tools/usage.js +19 -0
- package/build/tools/user-search.js +21 -3
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -7,49 +7,25 @@ MCP server for [Rolli IQ](https://rolli.ai) — social media search and analytic
|
|
|
7
7
|
| Tool | Description |
|
|
8
8
|
|------|-------------|
|
|
9
9
|
| `list_keyword_searches` | List all keyword searches, filtered by status |
|
|
10
|
-
| `keyword_search` | Create a keyword/hashtag search
|
|
10
|
+
| `keyword_search` | Create a keyword/hashtag search and return results when complete |
|
|
11
11
|
| `get_keyword_search` | Get keyword search results (status, analytics, posts) |
|
|
12
12
|
| `list_user_searches` | List all user searches, filtered by status |
|
|
13
|
-
| `user_search` | Create a user profile search
|
|
13
|
+
| `user_search` | Create a user profile search and return results when complete |
|
|
14
14
|
| `get_user_search` | Get user search results (profile, metrics, content analysis) |
|
|
15
15
|
| `get_topic_tree` | Get conversation topic tree for a keyword search |
|
|
16
16
|
| `get_keyword_search_posts` | Get raw posts from a keyword search |
|
|
17
17
|
| `get_user_search_posts` | Get raw posts from a user search |
|
|
18
|
+
| `get_integration_setup` | Get current integration settings (webhook URL, name) |
|
|
19
|
+
| `update_integration_setup` | Set the webhook URL for search completion notifications |
|
|
20
|
+
| `get_usage` | Get API usage data and per-user breakdowns |
|
|
18
21
|
|
|
19
22
|
## Setup
|
|
20
23
|
|
|
21
24
|
You need a Rolli account with API access. Get your API token and user ID from [rolli.ai](https://rolli.ai).
|
|
22
25
|
|
|
23
|
-
### Claude Desktop
|
|
26
|
+
### Claude Desktop / Claude Code / VS Code / Cursor / Windsurf
|
|
24
27
|
|
|
25
|
-
Add to your
|
|
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):
|
|
28
|
+
Add to your MCP config (`claude_desktop_config.json`, `.vscode/mcp.json`, or equivalent):
|
|
53
29
|
|
|
54
30
|
```json
|
|
55
31
|
{
|
|
@@ -81,6 +57,12 @@ This server includes a `smithery.yaml` for deployment via [Smithery](https://smi
|
|
|
81
57
|
**Get topic breakdown:**
|
|
82
58
|
> "Show me the topic tree for my keyword search #123"
|
|
83
59
|
|
|
60
|
+
**Check API usage:**
|
|
61
|
+
> "How many searches have I used this month?"
|
|
62
|
+
|
|
63
|
+
**Set up a webhook:**
|
|
64
|
+
> "Set my webhook URL to https://myapp.com/rolli-callback"
|
|
65
|
+
|
|
84
66
|
## License
|
|
85
67
|
|
|
86
68
|
MIT
|
package/build/api.d.ts
CHANGED
package/build/api.js
CHANGED
|
@@ -31,3 +31,15 @@ export async function apiPost(path, body) {
|
|
|
31
31
|
}
|
|
32
32
|
return res.json();
|
|
33
33
|
}
|
|
34
|
+
export async function apiPut(path, body) {
|
|
35
|
+
const res = await fetch(`${BASE_URL}${path}`, {
|
|
36
|
+
method: "PUT",
|
|
37
|
+
headers,
|
|
38
|
+
body: JSON.stringify(body),
|
|
39
|
+
});
|
|
40
|
+
if (!res.ok) {
|
|
41
|
+
const text = await res.text();
|
|
42
|
+
throw new Error(`API error ${res.status}: ${text}`);
|
|
43
|
+
}
|
|
44
|
+
return res.json();
|
|
45
|
+
}
|
package/build/index.js
CHANGED
|
@@ -5,6 +5,8 @@ import { register as registerKeywordSearch } from "./tools/keyword-search.js";
|
|
|
5
5
|
import { register as registerUserSearch } from "./tools/user-search.js";
|
|
6
6
|
import { register as registerTopicTree } from "./tools/topic-tree.js";
|
|
7
7
|
import { register as registerPosts } from "./tools/posts.js";
|
|
8
|
+
import { register as registerIntegrationSetup } from "./tools/integration-setup.js";
|
|
9
|
+
import { register as registerUsage } from "./tools/usage.js";
|
|
8
10
|
const server = new McpServer({
|
|
9
11
|
name: "rolli-mcp",
|
|
10
12
|
version: "1.0.0",
|
|
@@ -13,6 +15,8 @@ registerKeywordSearch(server);
|
|
|
13
15
|
registerUserSearch(server);
|
|
14
16
|
registerTopicTree(server);
|
|
15
17
|
registerPosts(server);
|
|
18
|
+
registerIntegrationSetup(server);
|
|
19
|
+
registerUsage(server);
|
|
16
20
|
const transport = new StdioServerTransport();
|
|
17
21
|
await server.connect(transport);
|
|
18
22
|
console.error("Rolli MCP server running on stdio");
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { apiGet, apiPut } from "../api.js";
|
|
3
|
+
export function register(server) {
|
|
4
|
+
server.tool("get_integration_setup", "Get the current integration settings (webhook return URL and integration name).", {}, async () => {
|
|
5
|
+
try {
|
|
6
|
+
const data = await apiGet("/setup");
|
|
7
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
8
|
+
}
|
|
9
|
+
catch (e) {
|
|
10
|
+
return { content: [{ type: "text", text: String(e) }], isError: true };
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
server.tool("update_integration_setup", "Update integration configuration. Set the webhook URL that receives notifications when a search completes.", {
|
|
14
|
+
return_url: z.string().describe("URL that will receive webhook notifications when a search completes"),
|
|
15
|
+
}, async (params) => {
|
|
16
|
+
try {
|
|
17
|
+
const data = await apiPut("/setup", { return_url: params.return_url });
|
|
18
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
return { content: [{ type: "text", text: String(e) }], isError: true };
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { apiPost, apiGet } from "../api.js";
|
|
3
|
+
const POLL_INTERVAL_MS = 5_000;
|
|
4
|
+
const MAX_POLL_MS = 10 * 60 * 1_000; // 10 minutes
|
|
5
|
+
function sleep(ms) {
|
|
6
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
+
}
|
|
3
8
|
export function register(server) {
|
|
4
9
|
server.tool("list_keyword_searches", "List all keyword searches. Returns a paginated list filtered by status.", {
|
|
5
10
|
show: z
|
|
@@ -22,7 +27,7 @@ export function register(server) {
|
|
|
22
27
|
return { content: [{ type: "text", text: String(e) }], isError: true };
|
|
23
28
|
}
|
|
24
29
|
});
|
|
25
|
-
server.tool("keyword_search", "Create a keyword/hashtag search across social media platforms (X, Reddit, Bluesky, YouTube, LinkedIn, Facebook, Instagram, Weibo).
|
|
30
|
+
server.tool("keyword_search", "Create a keyword/hashtag search across social media platforms (X, Reddit, Bluesky, YouTube, LinkedIn, Facebook, Instagram, Weibo). Polls until the search is complete and returns the full results.", {
|
|
26
31
|
query: z.string().describe("Search query (keyword or hashtag)"),
|
|
27
32
|
platforms: z
|
|
28
33
|
.array(z.enum(["twitter", "reddit", "bluesky", "youtube", "linkedin", "facebook", "instagram", "weibo"]))
|
|
@@ -42,8 +47,21 @@ export function register(server) {
|
|
|
42
47
|
body.end_date = params.end_date;
|
|
43
48
|
if (params.max_post !== undefined)
|
|
44
49
|
body.max_post = params.max_post;
|
|
45
|
-
const
|
|
46
|
-
|
|
50
|
+
const createResult = await apiPost("/iq/keyword_search", body);
|
|
51
|
+
const searchId = createResult.keyword_search?.id ?? createResult.id;
|
|
52
|
+
if (searchId == null) {
|
|
53
|
+
return { content: [{ type: "text", text: JSON.stringify(createResult, null, 2) }] };
|
|
54
|
+
}
|
|
55
|
+
const startTime = Date.now();
|
|
56
|
+
while (Date.now() - startTime < MAX_POLL_MS) {
|
|
57
|
+
await sleep(POLL_INTERVAL_MS);
|
|
58
|
+
const data = await apiGet(`/iq/keyword_search/${searchId}`);
|
|
59
|
+
const status = data.keyword_search?.status ?? data.status;
|
|
60
|
+
if (status === "finished" || status === "failed") {
|
|
61
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { content: [{ type: "text", text: `Search ${searchId} timed out after 10 minutes. Use get_keyword_search to check status.` }], isError: true };
|
|
47
65
|
}
|
|
48
66
|
catch (e) {
|
|
49
67
|
return { content: [{ type: "text", text: String(e) }], isError: true };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { apiGet } from "../api.js";
|
|
3
|
+
export function register(server) {
|
|
4
|
+
server.tool("get_usage", "Get API usage data. Returns search counts and per-user breakdowns. Optionally filter by month.", {
|
|
5
|
+
month: z
|
|
6
|
+
.string()
|
|
7
|
+
.optional()
|
|
8
|
+
.describe("Month to query (YYYY-MM format). Omit to get the last 12 months."),
|
|
9
|
+
}, async (params) => {
|
|
10
|
+
try {
|
|
11
|
+
const query = params.month ? `?month=${params.month}` : "";
|
|
12
|
+
const data = await apiGet(`/usage${query}`);
|
|
13
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
14
|
+
}
|
|
15
|
+
catch (e) {
|
|
16
|
+
return { content: [{ type: "text", text: String(e) }], isError: true };
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { apiPost, apiGet } from "../api.js";
|
|
3
|
+
const POLL_INTERVAL_MS = 5_000;
|
|
4
|
+
const MAX_POLL_MS = 10 * 60 * 1_000; // 10 minutes
|
|
5
|
+
function sleep(ms) {
|
|
6
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
+
}
|
|
3
8
|
export function register(server) {
|
|
4
9
|
server.tool("list_user_searches", "List all user searches. Returns a paginated list filtered by status.", {
|
|
5
10
|
show: z
|
|
@@ -22,7 +27,7 @@ export function register(server) {
|
|
|
22
27
|
return { content: [{ type: "text", text: String(e) }], isError: true };
|
|
23
28
|
}
|
|
24
29
|
});
|
|
25
|
-
server.tool("user_search", "Create a user profile search on a social media platform.
|
|
30
|
+
server.tool("user_search", "Create a user profile search on a social media platform. Polls until the search is complete and returns the full results.", {
|
|
26
31
|
query: z.string().describe("Username or profile URL to search"),
|
|
27
32
|
platform: z.enum(["twitter", "facebook", "instagram"]).describe("Platform to search"),
|
|
28
33
|
start_date: z.string().optional().describe("Start date (YYYY-MM-DD)"),
|
|
@@ -37,8 +42,21 @@ export function register(server) {
|
|
|
37
42
|
body.start_date = params.start_date;
|
|
38
43
|
if (params.end_date)
|
|
39
44
|
body.end_date = params.end_date;
|
|
40
|
-
const
|
|
41
|
-
|
|
45
|
+
const createResult = await apiPost("/iq/user_search", body);
|
|
46
|
+
const searchId = createResult.user_search?.id ?? createResult.id;
|
|
47
|
+
if (searchId == null) {
|
|
48
|
+
return { content: [{ type: "text", text: JSON.stringify(createResult, null, 2) }] };
|
|
49
|
+
}
|
|
50
|
+
const startTime = Date.now();
|
|
51
|
+
while (Date.now() - startTime < MAX_POLL_MS) {
|
|
52
|
+
await sleep(POLL_INTERVAL_MS);
|
|
53
|
+
const data = await apiGet(`/iq/user_search/${searchId}`);
|
|
54
|
+
const status = data.user_search?.status ?? data.status;
|
|
55
|
+
if (status === "finished" || status === "failed") {
|
|
56
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { content: [{ type: "text", text: `Search ${searchId} timed out after 10 minutes. Use get_user_search to check status.` }], isError: true };
|
|
42
60
|
}
|
|
43
61
|
catch (e) {
|
|
44
62
|
return { content: [{ type: "text", text: String(e) }], isError: true };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rolli/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "MCP server for Rolli IQ — social media search and analytics across X, Reddit, Bluesky, YouTube, LinkedIn, Facebook, Instagram, and Weibo",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "tsc",
|
|
15
|
-
"prepare": "tsc"
|
|
15
|
+
"prepare": "tsc",
|
|
16
|
+
"test": "vitest run"
|
|
16
17
|
},
|
|
17
18
|
"keywords": [
|
|
18
19
|
"mcp",
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
35
|
"@types/node": "^22.15.3",
|
|
35
|
-
"typescript": "^5.8.3"
|
|
36
|
+
"typescript": "^5.8.3",
|
|
37
|
+
"vitest": "^4.0.18"
|
|
36
38
|
}
|
|
37
39
|
}
|