@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 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 across social platforms |
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 on a platform |
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 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):
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
@@ -1,2 +1,3 @@
1
1
  export declare function apiGet(path: string): Promise<unknown>;
2
2
  export declare function apiPost(path: string, body: unknown): Promise<unknown>;
3
+ export declare function apiPut(path: string, body: unknown): Promise<unknown>;
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,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function register(server: McpServer): void;
@@ -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). Returns a search ID to retrieve results later.", {
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 data = await apiPost("/iq/keyword_search", body);
46
- return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
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,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function register(server: McpServer): void;
@@ -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. Returns a search ID to retrieve results later.", {
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 data = await apiPost("/iq/user_search", body);
41
- return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
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.0.0",
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
  }