outx-mcp-server 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/LICENSE +21 -0
- package/README.md +217 -0
- package/dist/client.d.ts +19 -0
- package/dist/client.js +91 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/engagement.d.ts +3 -0
- package/dist/tools/engagement.js +40 -0
- package/dist/tools/engagement.js.map +1 -0
- package/dist/tools/linkedin.d.ts +3 -0
- package/dist/tools/linkedin.js +57 -0
- package/dist/tools/linkedin.js.map +1 -0
- package/dist/tools/posts.d.ts +3 -0
- package/dist/tools/posts.js +32 -0
- package/dist/tools/posts.js.map +1 -0
- package/dist/tools/watchlists.d.ts +3 -0
- package/dist/tools/watchlists.js +177 -0
- package/dist/tools/watchlists.js.map +1 -0
- package/package.json +43 -0
- package/src/client.ts +105 -0
- package/src/index.ts +33 -0
- package/src/tools/engagement.ts +56 -0
- package/src/tools/linkedin.ts +98 -0
- package/src/tools/posts.ts +41 -0
- package/src/tools/watchlists.ts +246 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const fetchFreqEnum = z
|
|
3
|
+
.enum(["1", "3", "6", "12", "24", "48", "72"])
|
|
4
|
+
.describe("How often to scan for new posts (hours)");
|
|
5
|
+
export function registerWatchlistTools(server, client) {
|
|
6
|
+
// ── Keyword Watchlists ──────────────────────────────────
|
|
7
|
+
server.tool("create_keyword_watchlist", "Create a watchlist that monitors LinkedIn posts matching keywords. Posts appear after the first scan cycle.", {
|
|
8
|
+
keywords: z
|
|
9
|
+
.array(z.string())
|
|
10
|
+
.min(1)
|
|
11
|
+
.describe("Keywords to track (e.g. ['AI startup funding', 'series A'])"),
|
|
12
|
+
required_keywords: z
|
|
13
|
+
.array(z.string())
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("Posts MUST contain at least one of these words"),
|
|
16
|
+
exclude_keywords: z
|
|
17
|
+
.array(z.string())
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Exclude posts containing any of these words"),
|
|
20
|
+
name: z.string().optional().describe("Watchlist name"),
|
|
21
|
+
description: z.string().optional(),
|
|
22
|
+
labels: z.array(z.string()).optional().describe("Custom labels for categorization"),
|
|
23
|
+
fetchFreqInHours: fetchFreqEnum.optional(),
|
|
24
|
+
}, async (params) => {
|
|
25
|
+
const body = {
|
|
26
|
+
keywords: params.keywords.map((kw) => {
|
|
27
|
+
const obj = { keyword: kw };
|
|
28
|
+
if (params.required_keywords)
|
|
29
|
+
obj.required_keywords = params.required_keywords;
|
|
30
|
+
if (params.exclude_keywords)
|
|
31
|
+
obj.exclude_keywords = params.exclude_keywords;
|
|
32
|
+
return obj;
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
if (params.name)
|
|
36
|
+
body.name = params.name;
|
|
37
|
+
if (params.description)
|
|
38
|
+
body.description = params.description;
|
|
39
|
+
if (params.labels)
|
|
40
|
+
body.labels = params.labels;
|
|
41
|
+
if (params.fetchFreqInHours)
|
|
42
|
+
body.fetchFreqInHours = parseInt(params.fetchFreqInHours);
|
|
43
|
+
const result = await client.post("/api-keyword-watchlist", body);
|
|
44
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
45
|
+
});
|
|
46
|
+
server.tool("list_keyword_watchlists", "List all keyword watchlists for your account.", {}, async () => {
|
|
47
|
+
const result = await client.get("/api-keyword-watchlist");
|
|
48
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
49
|
+
});
|
|
50
|
+
server.tool("update_keyword_watchlist", "Update a keyword watchlist's name, frequency, or status.", {
|
|
51
|
+
id: z.string().describe("Watchlist ID"),
|
|
52
|
+
name: z.string().optional(),
|
|
53
|
+
fetchFreqInHours: fetchFreqEnum.optional(),
|
|
54
|
+
status: z.enum(["active", "paused"]).optional(),
|
|
55
|
+
}, async (params) => {
|
|
56
|
+
const body = { id: params.id };
|
|
57
|
+
if (params.name)
|
|
58
|
+
body.name = params.name;
|
|
59
|
+
if (params.fetchFreqInHours)
|
|
60
|
+
body.fetchFreqInHours = parseInt(params.fetchFreqInHours);
|
|
61
|
+
if (params.status)
|
|
62
|
+
body.status = params.status;
|
|
63
|
+
const result = await client.put("/api-keyword-watchlist", body);
|
|
64
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
65
|
+
});
|
|
66
|
+
server.tool("delete_keyword_watchlist", "Delete a keyword watchlist and all its tracked data.", {
|
|
67
|
+
id: z.string().describe("Watchlist ID to delete"),
|
|
68
|
+
}, async (params) => {
|
|
69
|
+
const result = await client.delete("/api-keyword-watchlist", {
|
|
70
|
+
id: params.id,
|
|
71
|
+
});
|
|
72
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
73
|
+
});
|
|
74
|
+
// ── People Watchlists ───────────────────────────────────
|
|
75
|
+
server.tool("create_people_watchlist", "Create a watchlist that monitors posts from specific LinkedIn profiles.", {
|
|
76
|
+
profiles: z
|
|
77
|
+
.array(z.string())
|
|
78
|
+
.min(1)
|
|
79
|
+
.describe("LinkedIn profile URLs or slugs to track"),
|
|
80
|
+
name: z.string().optional().describe("Watchlist name"),
|
|
81
|
+
description: z.string().optional(),
|
|
82
|
+
labels: z.array(z.string()).optional(),
|
|
83
|
+
fetchFreqInHours: fetchFreqEnum.optional(),
|
|
84
|
+
}, async (params) => {
|
|
85
|
+
const body = { profiles: params.profiles };
|
|
86
|
+
if (params.name)
|
|
87
|
+
body.name = params.name;
|
|
88
|
+
if (params.description)
|
|
89
|
+
body.description = params.description;
|
|
90
|
+
if (params.labels)
|
|
91
|
+
body.labels = params.labels;
|
|
92
|
+
if (params.fetchFreqInHours)
|
|
93
|
+
body.fetchFreqInHours = parseInt(params.fetchFreqInHours);
|
|
94
|
+
const result = await client.post("/api-people-watchlist", body);
|
|
95
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
96
|
+
});
|
|
97
|
+
server.tool("list_people_watchlists", "List all people watchlists for your account.", {}, async () => {
|
|
98
|
+
const result = await client.get("/api-people-watchlist");
|
|
99
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
100
|
+
});
|
|
101
|
+
server.tool("update_people_watchlist", "Update a people watchlist's name, frequency, or status.", {
|
|
102
|
+
id: z.string().describe("Watchlist ID"),
|
|
103
|
+
name: z.string().optional(),
|
|
104
|
+
fetchFreqInHours: fetchFreqEnum.optional(),
|
|
105
|
+
status: z.enum(["active", "paused"]).optional(),
|
|
106
|
+
}, async (params) => {
|
|
107
|
+
const body = { id: params.id };
|
|
108
|
+
if (params.name)
|
|
109
|
+
body.name = params.name;
|
|
110
|
+
if (params.fetchFreqInHours)
|
|
111
|
+
body.fetchFreqInHours = parseInt(params.fetchFreqInHours);
|
|
112
|
+
if (params.status)
|
|
113
|
+
body.status = params.status;
|
|
114
|
+
const result = await client.put("/api-people-watchlist", body);
|
|
115
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
116
|
+
});
|
|
117
|
+
server.tool("delete_people_watchlist", "Delete a people watchlist and all its tracked data.", {
|
|
118
|
+
id: z.string().describe("Watchlist ID to delete"),
|
|
119
|
+
}, async (params) => {
|
|
120
|
+
const result = await client.delete("/api-people-watchlist", {
|
|
121
|
+
id: params.id,
|
|
122
|
+
});
|
|
123
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
124
|
+
});
|
|
125
|
+
// ── Company Watchlists ──────────────────────────────────
|
|
126
|
+
server.tool("create_company_watchlist", "Create a watchlist that monitors posts from LinkedIn company pages.", {
|
|
127
|
+
companies: z
|
|
128
|
+
.array(z.string())
|
|
129
|
+
.min(1)
|
|
130
|
+
.describe("LinkedIn company URLs or slugs to track"),
|
|
131
|
+
name: z.string().optional().describe("Watchlist name"),
|
|
132
|
+
description: z.string().optional(),
|
|
133
|
+
labels: z.array(z.string()).optional(),
|
|
134
|
+
fetchFreqInHours: fetchFreqEnum.optional(),
|
|
135
|
+
}, async (params) => {
|
|
136
|
+
const body = { companies: params.companies };
|
|
137
|
+
if (params.name)
|
|
138
|
+
body.name = params.name;
|
|
139
|
+
if (params.description)
|
|
140
|
+
body.description = params.description;
|
|
141
|
+
if (params.labels)
|
|
142
|
+
body.labels = params.labels;
|
|
143
|
+
if (params.fetchFreqInHours)
|
|
144
|
+
body.fetchFreqInHours = parseInt(params.fetchFreqInHours);
|
|
145
|
+
const result = await client.post("/api-company-watchlist", body);
|
|
146
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
147
|
+
});
|
|
148
|
+
server.tool("list_company_watchlists", "List all company watchlists for your account.", {}, async () => {
|
|
149
|
+
const result = await client.get("/api-company-watchlist");
|
|
150
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
151
|
+
});
|
|
152
|
+
server.tool("update_company_watchlist", "Update a company watchlist's name, frequency, or status.", {
|
|
153
|
+
id: z.string().describe("Watchlist ID"),
|
|
154
|
+
name: z.string().optional(),
|
|
155
|
+
fetchFreqInHours: fetchFreqEnum.optional(),
|
|
156
|
+
status: z.enum(["active", "paused"]).optional(),
|
|
157
|
+
}, async (params) => {
|
|
158
|
+
const body = { id: params.id };
|
|
159
|
+
if (params.name)
|
|
160
|
+
body.name = params.name;
|
|
161
|
+
if (params.fetchFreqInHours)
|
|
162
|
+
body.fetchFreqInHours = parseInt(params.fetchFreqInHours);
|
|
163
|
+
if (params.status)
|
|
164
|
+
body.status = params.status;
|
|
165
|
+
const result = await client.put("/api-company-watchlist", body);
|
|
166
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
167
|
+
});
|
|
168
|
+
server.tool("delete_company_watchlist", "Delete a company watchlist and all its tracked data.", {
|
|
169
|
+
id: z.string().describe("Watchlist ID to delete"),
|
|
170
|
+
}, async (params) => {
|
|
171
|
+
const result = await client.delete("/api-company-watchlist", {
|
|
172
|
+
id: params.id,
|
|
173
|
+
});
|
|
174
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=watchlists.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watchlists.js","sourceRoot":"","sources":["../../src/tools/watchlists.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "outx-mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for OutX — LinkedIn social listening & data API",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"outx-mcp-server": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"dev": "tsx src/index.ts"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mcp",
|
|
17
|
+
"model-context-protocol",
|
|
18
|
+
"outx",
|
|
19
|
+
"linkedin",
|
|
20
|
+
"social-listening",
|
|
21
|
+
"linkedin-api",
|
|
22
|
+
"ai-agents"
|
|
23
|
+
],
|
|
24
|
+
"author": "OutX AI",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/outxai/outx-mcp-server.git"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://outx.ai",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
33
|
+
"zod": "^3.23.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^20.0.0",
|
|
37
|
+
"tsx": "^4.0.0",
|
|
38
|
+
"typescript": "^5.5.0"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18.0.0"
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OutX API client — shared HTTP wrapper with auth, error handling, and async task polling.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class OutxClient {
|
|
6
|
+
private baseUrl = "https://api.outx.ai";
|
|
7
|
+
private apiKey: string;
|
|
8
|
+
|
|
9
|
+
constructor(apiKey: string) {
|
|
10
|
+
this.apiKey = apiKey;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
private headers(): Record<string, string> {
|
|
14
|
+
return {
|
|
15
|
+
"x-api-key": this.apiKey,
|
|
16
|
+
"Content-Type": "application/json",
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private async handleResponse(res: Response): Promise<unknown> {
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
let message = `HTTP ${res.status}`;
|
|
23
|
+
try {
|
|
24
|
+
const body = await res.json();
|
|
25
|
+
if (body.error) message = body.error;
|
|
26
|
+
else if (body.message) message = body.message;
|
|
27
|
+
else message = JSON.stringify(body);
|
|
28
|
+
} catch {
|
|
29
|
+
message = await res.text().catch(() => message);
|
|
30
|
+
}
|
|
31
|
+
throw new Error(message);
|
|
32
|
+
}
|
|
33
|
+
return res.json();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async get(path: string, params?: Record<string, string>): Promise<unknown> {
|
|
37
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
38
|
+
if (params) {
|
|
39
|
+
for (const [k, v] of Object.entries(params)) {
|
|
40
|
+
if (v !== undefined && v !== "") url.searchParams.set(k, v);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const res = await fetch(url.toString(), { headers: this.headers() });
|
|
44
|
+
return this.handleResponse(res);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async post(path: string, body: unknown): Promise<unknown> {
|
|
48
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: this.headers(),
|
|
51
|
+
body: JSON.stringify(body),
|
|
52
|
+
});
|
|
53
|
+
return this.handleResponse(res);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async put(path: string, body: unknown): Promise<unknown> {
|
|
57
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
58
|
+
method: "PUT",
|
|
59
|
+
headers: this.headers(),
|
|
60
|
+
body: JSON.stringify(body),
|
|
61
|
+
});
|
|
62
|
+
return this.handleResponse(res);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async delete(path: string, body: unknown): Promise<unknown> {
|
|
66
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
67
|
+
method: "DELETE",
|
|
68
|
+
headers: this.headers(),
|
|
69
|
+
body: JSON.stringify(body),
|
|
70
|
+
});
|
|
71
|
+
return this.handleResponse(res);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Poll an async LinkedIn Data API task until it completes or fails.
|
|
76
|
+
* Returns the task_output on success.
|
|
77
|
+
*/
|
|
78
|
+
async pollTask(
|
|
79
|
+
taskId: string,
|
|
80
|
+
maxAttempts = 30,
|
|
81
|
+
delayMs = 5000
|
|
82
|
+
): Promise<unknown> {
|
|
83
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
84
|
+
const result = (await this.get("/linkedin-agent/get-task-status", {
|
|
85
|
+
api_agent_task_id: taskId,
|
|
86
|
+
})) as { data: { status: string; task_output?: unknown } };
|
|
87
|
+
|
|
88
|
+
const { status, task_output } = result.data;
|
|
89
|
+
|
|
90
|
+
if (status === "completed") return task_output;
|
|
91
|
+
if (status === "failed") {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`Task failed: ${task_output ? JSON.stringify(task_output) : "unknown error"}`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Task ${taskId} did not complete within ${(maxAttempts * delayMs) / 1000}s. ` +
|
|
102
|
+
`Use get_task_status to check manually.`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { OutxClient } from "./client.js";
|
|
6
|
+
import { registerWatchlistTools } from "./tools/watchlists.js";
|
|
7
|
+
import { registerPostTools } from "./tools/posts.js";
|
|
8
|
+
import { registerEngagementTools } from "./tools/engagement.js";
|
|
9
|
+
import { registerLinkedInTools } from "./tools/linkedin.js";
|
|
10
|
+
|
|
11
|
+
const apiKey = process.env.OUTX_API_KEY;
|
|
12
|
+
if (!apiKey) {
|
|
13
|
+
console.error(
|
|
14
|
+
"Error: OUTX_API_KEY environment variable is required.\n" +
|
|
15
|
+
"Get your key at https://mentions.outx.ai/api-doc"
|
|
16
|
+
);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const client = new OutxClient(apiKey);
|
|
21
|
+
|
|
22
|
+
const server = new McpServer({
|
|
23
|
+
name: "outx",
|
|
24
|
+
version: "1.0.0",
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
registerWatchlistTools(server, client);
|
|
28
|
+
registerPostTools(server, client);
|
|
29
|
+
registerEngagementTools(server, client);
|
|
30
|
+
registerLinkedInTools(server, client);
|
|
31
|
+
|
|
32
|
+
const transport = new StdioServerTransport();
|
|
33
|
+
await server.connect(transport);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { OutxClient } from "../client.js";
|
|
4
|
+
|
|
5
|
+
export function registerEngagementTools(
|
|
6
|
+
server: McpServer,
|
|
7
|
+
client: OutxClient
|
|
8
|
+
): void {
|
|
9
|
+
server.tool(
|
|
10
|
+
"like_post",
|
|
11
|
+
"Like a LinkedIn post from your watchlist. Requires the post ID from get_posts results.",
|
|
12
|
+
{
|
|
13
|
+
post_id: z.string().describe("Post ID from get_posts results"),
|
|
14
|
+
user_email: z.string().describe("Email of the LinkedIn account to like from"),
|
|
15
|
+
actor_type: z
|
|
16
|
+
.enum(["user", "company"])
|
|
17
|
+
.optional()
|
|
18
|
+
.describe("Like as user or company page (default: user)"),
|
|
19
|
+
},
|
|
20
|
+
async (params) => {
|
|
21
|
+
const body: Record<string, unknown> = {
|
|
22
|
+
post_id: params.post_id,
|
|
23
|
+
user_email: params.user_email,
|
|
24
|
+
};
|
|
25
|
+
if (params.actor_type) body.actor_type = params.actor_type;
|
|
26
|
+
|
|
27
|
+
const result = await client.post("/api-like", body);
|
|
28
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
server.tool(
|
|
33
|
+
"comment_on_post",
|
|
34
|
+
"Comment on a LinkedIn post from your watchlist. Requires the post ID from get_posts results.",
|
|
35
|
+
{
|
|
36
|
+
post_id: z.string().describe("Post ID from get_posts results"),
|
|
37
|
+
user_email: z.string().describe("Email of the LinkedIn account to comment from"),
|
|
38
|
+
comment_text: z.string().describe("The comment text to post"),
|
|
39
|
+
actor_type: z
|
|
40
|
+
.enum(["user", "company"])
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("Comment as user or company page (default: user)"),
|
|
43
|
+
},
|
|
44
|
+
async (params) => {
|
|
45
|
+
const body: Record<string, unknown> = {
|
|
46
|
+
post_id: params.post_id,
|
|
47
|
+
user_email: params.user_email,
|
|
48
|
+
comment_text: params.comment_text,
|
|
49
|
+
};
|
|
50
|
+
if (params.actor_type) body.actor_type = params.actor_type;
|
|
51
|
+
|
|
52
|
+
const result = await client.post("/api-comment", body);
|
|
53
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { OutxClient } from "../client.js";
|
|
4
|
+
|
|
5
|
+
export function registerLinkedInTools(
|
|
6
|
+
server: McpServer,
|
|
7
|
+
client: OutxClient
|
|
8
|
+
): void {
|
|
9
|
+
server.tool(
|
|
10
|
+
"fetch_linkedin_profile",
|
|
11
|
+
"Fetch a LinkedIn profile by slug. Returns full profile data including name, headline, experience, education, and skills. Requires the OutX Chrome extension to be active within the last 48 hours. This tool handles async polling automatically.",
|
|
12
|
+
{
|
|
13
|
+
profile_slug: z
|
|
14
|
+
.string()
|
|
15
|
+
.describe("LinkedIn profile slug (e.g. 'williamhgates' from linkedin.com/in/williamhgates)"),
|
|
16
|
+
},
|
|
17
|
+
async (params) => {
|
|
18
|
+
const response = (await client.post("/linkedin-agent/fetch-profile", {
|
|
19
|
+
profile_slug: params.profile_slug,
|
|
20
|
+
})) as { api_agent_task_id: string };
|
|
21
|
+
|
|
22
|
+
const result = await client.pollTask(response.api_agent_task_id);
|
|
23
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
server.tool(
|
|
28
|
+
"fetch_linkedin_posts",
|
|
29
|
+
"Fetch recent posts from LinkedIn profiles by URN. Use fetch_linkedin_profile first to get the profile URN. Requires the OutX Chrome extension to be active. This tool handles async polling automatically.",
|
|
30
|
+
{
|
|
31
|
+
profile_urns: z
|
|
32
|
+
.array(z.string())
|
|
33
|
+
.min(1)
|
|
34
|
+
.describe("LinkedIn profile URNs (e.g. ['urn:li:fsd_profile:ABC123'])"),
|
|
35
|
+
},
|
|
36
|
+
async (params) => {
|
|
37
|
+
const response = (await client.post(
|
|
38
|
+
"/linkedin-agent/fetch-profiles-posts",
|
|
39
|
+
{ profile_urns: params.profile_urns }
|
|
40
|
+
)) as { api_agent_task_id: string };
|
|
41
|
+
|
|
42
|
+
const result = await client.pollTask(response.api_agent_task_id);
|
|
43
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
server.tool(
|
|
48
|
+
"linkedin_like_post",
|
|
49
|
+
"Like a LinkedIn post by its activity URN. This uses the LinkedIn Data API (direct LinkedIn action). Requires the OutX Chrome extension to be active. This tool handles async polling automatically.",
|
|
50
|
+
{
|
|
51
|
+
social_urn: z
|
|
52
|
+
.string()
|
|
53
|
+
.describe("LinkedIn activity URN of the post to like"),
|
|
54
|
+
},
|
|
55
|
+
async (params) => {
|
|
56
|
+
const response = (await client.post("/linkedin-agent/like-post", {
|
|
57
|
+
social_urn: params.social_urn,
|
|
58
|
+
})) as { api_agent_task_id: string };
|
|
59
|
+
|
|
60
|
+
const result = await client.pollTask(response.api_agent_task_id);
|
|
61
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
server.tool(
|
|
66
|
+
"linkedin_comment_on_post",
|
|
67
|
+
"Comment on a LinkedIn post by its activity URN. This uses the LinkedIn Data API (direct LinkedIn action). Requires the OutX Chrome extension to be active. This tool handles async polling automatically.",
|
|
68
|
+
{
|
|
69
|
+
social_urn: z
|
|
70
|
+
.string()
|
|
71
|
+
.describe("LinkedIn activity URN of the post to comment on"),
|
|
72
|
+
comment_text: z.string().describe("The comment text to post"),
|
|
73
|
+
},
|
|
74
|
+
async (params) => {
|
|
75
|
+
const response = (await client.post("/linkedin-agent/comment-post", {
|
|
76
|
+
social_urn: params.social_urn,
|
|
77
|
+
comment_text: params.comment_text,
|
|
78
|
+
})) as { api_agent_task_id: string };
|
|
79
|
+
|
|
80
|
+
const result = await client.pollTask(response.api_agent_task_id);
|
|
81
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
server.tool(
|
|
86
|
+
"get_task_status",
|
|
87
|
+
"Check the status of an async LinkedIn Data API task. Use this to manually check tasks that timed out. Status values: pending, processing, completed, failed.",
|
|
88
|
+
{
|
|
89
|
+
task_id: z.string().describe("The api_agent_task_id to check"),
|
|
90
|
+
},
|
|
91
|
+
async (params) => {
|
|
92
|
+
const result = await client.get("/linkedin-agent/get-task-status", {
|
|
93
|
+
api_agent_task_id: params.task_id,
|
|
94
|
+
});
|
|
95
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { OutxClient } from "../client.js";
|
|
4
|
+
|
|
5
|
+
export function registerPostTools(
|
|
6
|
+
server: McpServer,
|
|
7
|
+
client: OutxClient
|
|
8
|
+
): void {
|
|
9
|
+
server.tool(
|
|
10
|
+
"get_posts",
|
|
11
|
+
"Search and filter LinkedIn posts from your watchlists. Supports keyword search, date range, seniority, trending, language, and more. Returns 20 posts per page.",
|
|
12
|
+
{
|
|
13
|
+
watchlist_id: z.string().optional().describe("Filter by watchlist ID (omit for all)"),
|
|
14
|
+
search_term: z.string().optional().describe("Search within post content"),
|
|
15
|
+
sort_by: z
|
|
16
|
+
.enum(["recent_first", "oldest_first", "engagement"])
|
|
17
|
+
.optional()
|
|
18
|
+
.describe("Sort order (default: recent_first)"),
|
|
19
|
+
page: z.string().optional().describe("Page number (default: 1)"),
|
|
20
|
+
start_date: z.string().optional().describe("Filter posts after this date (ISO 8601)"),
|
|
21
|
+
end_date: z.string().optional().describe("Filter posts before this date (ISO 8601)"),
|
|
22
|
+
seniority_level: z.string().optional().describe("Filter by author seniority (e.g. 'Director', 'VP')"),
|
|
23
|
+
trending: z.string().optional().describe("Set to 'true' to show only trending posts"),
|
|
24
|
+
lang: z.string().optional().describe("Filter by language code (e.g. 'en', 'es')"),
|
|
25
|
+
post_type: z.string().optional().describe("Filter by post type"),
|
|
26
|
+
labels: z.string().optional().describe("Filter by label"),
|
|
27
|
+
people_slug: z.string().optional().describe("Filter by author's LinkedIn slug"),
|
|
28
|
+
company_slug: z.string().optional().describe("Filter by company's LinkedIn slug"),
|
|
29
|
+
interacted: z.string().optional().describe("Set to 'true' to show only posts you've interacted with"),
|
|
30
|
+
linkedin_post_slug: z.string().optional().describe("Get a specific post by its LinkedIn slug"),
|
|
31
|
+
},
|
|
32
|
+
async (params) => {
|
|
33
|
+
const queryParams: Record<string, string> = {};
|
|
34
|
+
for (const [k, v] of Object.entries(params)) {
|
|
35
|
+
if (v !== undefined) queryParams[k] = v;
|
|
36
|
+
}
|
|
37
|
+
const result = await client.get("/api-posts", queryParams);
|
|
38
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
}
|