Skip to content

Commit 65643d8

Browse files
authored
fix: resolve conflict from #580 and wire missing MCP tools (#586) (#587)
* feat(issue-568): Implement groups create, events create, analytics post-metrics, and fix DOM selectors * fix: correct syntax error in e2e helpers for tool names * chore: remove opencode work-log artifact
1 parent d72f296 commit 65643d8

File tree

4 files changed

+149
-2
lines changed

4 files changed

+149
-2
lines changed

packages/core/src/__tests__/e2e/helpers.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,11 @@ import {
6262
LINKEDIN_INBOX_MUTE_THREAD_TOOL,
6363
LINKEDIN_INBOX_SEARCH_RECIPIENTS_TOOL,
6464
LINKEDIN_INBOX_UNARCHIVE_THREAD_TOOL,
65+
LINKEDIN_EVENTS_PREPARE_CREATE_TOOL,
6566
LINKEDIN_EVENTS_PREPARE_RSVP_TOOL,
6667
LINKEDIN_EVENTS_SEARCH_TOOL,
6768
LINKEDIN_EVENTS_VIEW_TOOL,
69+
LINKEDIN_GROUPS_PREPARE_CREATE_TOOL,
6870
LINKEDIN_GROUPS_PREPARE_JOIN_TOOL,
6971
LINKEDIN_GROUPS_PREPARE_LEAVE_TOOL,
7072
LINKEDIN_GROUPS_PREPARE_POST_TOOL,
@@ -1009,9 +1011,11 @@ export const MCP_TOOL_NAMES = {
10091011
activityWebhookPause: LINKEDIN_ACTIVITY_WEBHOOK_PAUSE_TOOL,
10101012
activityWebhookRemove: LINKEDIN_ACTIVITY_WEBHOOK_REMOVE_TOOL,
10111013
activityWebhookResume: LINKEDIN_ACTIVITY_WEBHOOK_RESUME_TOOL,
1014+
eventsPrepareCreate: LINKEDIN_EVENTS_PREPARE_CREATE_TOOL,
10121015
eventsPrepareRsvp: LINKEDIN_EVENTS_PREPARE_RSVP_TOOL,
10131016
eventsSearch: LINKEDIN_EVENTS_SEARCH_TOOL,
10141017
eventsView: LINKEDIN_EVENTS_VIEW_TOOL,
1018+
groupsPrepareCreate: LINKEDIN_GROUPS_PREPARE_CREATE_TOOL,
10151019
groupsPrepareJoin: LINKEDIN_GROUPS_PREPARE_JOIN_TOOL,
10161020
groupsPrepareLeave: LINKEDIN_GROUPS_PREPARE_LEAVE_TOOL,
10171021
groupsPreparePost: LINKEDIN_GROUPS_PREPARE_POST_TOOL,

packages/core/src/linkedinFeed.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -724,13 +724,15 @@ async function extractFeedPosts(
724724
const listItems = Array.from(feedRoot.querySelectorAll("[role='listitem']"));
725725
const postCards = listItems.filter((item) => {
726726
const componentKey = normalize(item.getAttribute("componentkey"));
727-
return /FeedType/i.test(componentKey) || item.hasAttribute("data-urn") || !!item.querySelector("[data-urn]") || !!item.querySelector("a[href*='/feed/update/']") || !!item.querySelector("a[href*='/posts/']");
727+
const dataId = normalize(item.getAttribute("data-id"));
728+
return /FeedType/i.test(componentKey) || item.hasAttribute("data-urn") || !!item.querySelector("[data-urn]") || !!item.querySelector("a[href*='/feed/update/']") || !!item.querySelector("a[href*='/posts/']") || (!!dataId && dataId.includes("urn:li:activity:"));
728729
});
729730

730731
const sduiResults: FeedPostSnapshot[] = [];
731732
for (const card of postCards) {
732733
const componentKey = normalize(card.getAttribute("componentkey") || card.getAttribute("data-urn") || card.querySelector("[data-urn]")?.getAttribute("data-urn") || (card.querySelector("a[href*='/feed/update/']") as HTMLAnchorElement)?.href || (card.querySelector("a[href*='/posts/']") as HTMLAnchorElement)?.href);
733-
const postId = extractSduiPostId(componentKey);
734+
const dataId = normalize(card.getAttribute("data-id"));
735+
const postId = extractSduiPostId(componentKey) || extractSduiPostId(dataId);
734736
if (!postId) {
735737
continue;
736738
}

packages/mcp/src/bin/linkedin-mcp.ts

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,13 @@ import {
118118
LINKEDIN_PROFILE_VIEW_EDITABLE_TOOL,
119119
LINKEDIN_PRIVACY_GET_SETTINGS_TOOL,
120120
LINKEDIN_PRIVACY_PREPARE_UPDATE_SETTING_TOOL,
121+
LINKEDIN_GROUPS_PREPARE_CREATE_TOOL,
121122
LINKEDIN_GROUPS_PREPARE_JOIN_TOOL,
122123
LINKEDIN_GROUPS_PREPARE_LEAVE_TOOL,
123124
LINKEDIN_GROUPS_PREPARE_POST_TOOL,
124125
LINKEDIN_GROUPS_SEARCH_TOOL,
125126
LINKEDIN_GROUPS_VIEW_TOOL,
127+
LINKEDIN_EVENTS_PREPARE_CREATE_TOOL,
126128
LINKEDIN_EVENTS_PREPARE_RSVP_TOOL,
127129
LINKEDIN_EVENTS_SEARCH_TOOL,
128130
LINKEDIN_EVENTS_VIEW_TOOL,
@@ -2315,6 +2317,99 @@ async function handleGroupsView(args: ToolArgs): Promise<ToolResult> {
23152317
}
23162318
}
23172319

2320+
2321+
async function handleGroupsPrepareCreate(args: ToolArgs): Promise<ToolResult> {
2322+
const runtime = createRuntime(args);
2323+
2324+
try {
2325+
const profileName = readString(args, "profileName", "default");
2326+
const name = readRequiredString(args, "name");
2327+
const description = readRequiredString(args, "description");
2328+
const rules = readString(args, "rules", "");
2329+
const industry = readString(args, "industry", "");
2330+
const location = readString(args, "location", "");
2331+
const isUnlisted = readBoolean(args, "isUnlisted", false);
2332+
const operatorNote = readString(args, "operatorNote", "");
2333+
2334+
runtime.logger.log("info", "mcp.groups.prepare_create.start", {
2335+
profileName,
2336+
name,
2337+
});
2338+
2339+
const prepared = runtime.groups.prepareCreateGroup({
2340+
profileName,
2341+
name,
2342+
description,
2343+
...(rules ? { rules } : {}),
2344+
...(industry ? { industry } : {}),
2345+
...(location ? { location } : {}),
2346+
...(args.isUnlisted !== undefined ? { isUnlisted } : {}),
2347+
...(operatorNote ? { operatorNote } : {}),
2348+
});
2349+
2350+
runtime.logger.log("info", "mcp.groups.prepare_create.done", {
2351+
profileName,
2352+
preparedActionId: prepared.preparedActionId,
2353+
});
2354+
2355+
return toToolResult({
2356+
run_id: runtime.runId,
2357+
profile_name: profileName,
2358+
...prepared,
2359+
});
2360+
} finally {
2361+
runtime.close();
2362+
}
2363+
}
2364+
2365+
async function handleEventsPrepareCreate(args: ToolArgs): Promise<ToolResult> {
2366+
const runtime = createRuntime(args);
2367+
2368+
try {
2369+
const profileName = readString(args, "profileName", "default");
2370+
const name = readRequiredString(args, "name");
2371+
const description = readString(args, "description", "");
2372+
const startDate = readString(args, "startDate", "");
2373+
const startTime = readString(args, "startTime", "");
2374+
const endDate = readString(args, "endDate", "");
2375+
const endTime = readString(args, "endTime", "");
2376+
const isOnline = readBoolean(args, "isOnline", false);
2377+
const externalLink = readString(args, "externalLink", "");
2378+
const operatorNote = readString(args, "operatorNote", "");
2379+
2380+
runtime.logger.log("info", "mcp.events.prepare_create.start", {
2381+
profileName,
2382+
name,
2383+
});
2384+
2385+
const prepared = runtime.events.prepareCreateEvent({
2386+
profileName,
2387+
name,
2388+
...(description ? { description } : {}),
2389+
...(startDate ? { startDate } : {}),
2390+
...(startTime ? { startTime } : {}),
2391+
...(endDate ? { endDate } : {}),
2392+
...(endTime ? { endTime } : {}),
2393+
...(args.isOnline !== undefined ? { isOnline } : {}),
2394+
...(externalLink ? { externalLink } : {}),
2395+
...(operatorNote ? { operatorNote } : {}),
2396+
});
2397+
2398+
runtime.logger.log("info", "mcp.events.prepare_create.done", {
2399+
profileName,
2400+
preparedActionId: prepared.preparedActionId,
2401+
});
2402+
2403+
return toToolResult({
2404+
run_id: runtime.runId,
2405+
profile_name: profileName,
2406+
...prepared,
2407+
});
2408+
} finally {
2409+
runtime.close();
2410+
}
2411+
}
2412+
23182413
async function handleGroupsPrepareJoin(args: ToolArgs): Promise<ToolResult> {
23192414
const runtime = createRuntime(args);
23202415

@@ -6829,6 +6924,48 @@ export const LINKEDIN_MCP_TOOL_DEFINITIONS: LinkedInMcpToolDefinition[] = [
68296924
}),
68306925
},
68316926
},
6927+
{
6928+
name: LINKEDIN_GROUPS_PREPARE_CREATE_TOOL,
6929+
description:
6930+
"Prepare to create a LinkedIn group (two-phase: returns confirm token). Use linkedin.actions.confirm to execute.",
6931+
inputSchema: {
6932+
type: "object",
6933+
additionalProperties: false,
6934+
properties: {
6935+
profileName: { type: "string" },
6936+
name: { type: "string", description: "Name of the group" },
6937+
description: { type: "string", description: "Group description" },
6938+
rules: { type: "string", description: "Group rules" },
6939+
industry: { type: "string", description: "Group industry" },
6940+
location: { type: "string", description: "Group location" },
6941+
isUnlisted: { type: "boolean", description: "Make group unlisted" },
6942+
operatorNote: { type: "string" },
6943+
},
6944+
required: ["name", "description"],
6945+
},
6946+
},
6947+
{
6948+
name: LINKEDIN_EVENTS_PREPARE_CREATE_TOOL,
6949+
description:
6950+
"Prepare to create a LinkedIn event (two-phase: returns confirm token). Use linkedin.actions.confirm to execute.",
6951+
inputSchema: {
6952+
type: "object",
6953+
additionalProperties: false,
6954+
properties: {
6955+
profileName: { type: "string" },
6956+
name: { type: "string", description: "Name of the event" },
6957+
description: { type: "string", description: "Event description" },
6958+
startDate: { type: "string", description: "Event start date" },
6959+
startTime: { type: "string", description: "Event start time" },
6960+
endDate: { type: "string", description: "Event end date" },
6961+
endTime: { type: "string", description: "Event end time" },
6962+
isOnline: { type: "boolean", description: "Is this an online event" },
6963+
externalLink: { type: "string", description: "External event link" },
6964+
operatorNote: { type: "string" },
6965+
},
6966+
required: ["name"],
6967+
},
6968+
},
68326969
{
68336970
name: LINKEDIN_GROUPS_PREPARE_JOIN_TOOL,
68346971
description:
@@ -7413,11 +7550,13 @@ const TOOL_HANDLERS: Record<string, ToolHandler> = {
74137550
[LINKEDIN_JOBS_PREPARE_EASY_APPLY_TOOL]: handleJobsPrepareEasyApply,
74147551
[LINKEDIN_GROUPS_SEARCH_TOOL]: handleGroupsSearch,
74157552
[LINKEDIN_GROUPS_VIEW_TOOL]: handleGroupsView,
7553+
[LINKEDIN_GROUPS_PREPARE_CREATE_TOOL]: handleGroupsPrepareCreate,
74167554
[LINKEDIN_GROUPS_PREPARE_JOIN_TOOL]: handleGroupsPrepareJoin,
74177555
[LINKEDIN_GROUPS_PREPARE_LEAVE_TOOL]: handleGroupsPrepareLeave,
74187556
[LINKEDIN_GROUPS_PREPARE_POST_TOOL]: handleGroupsPreparePost,
74197557
[LINKEDIN_EVENTS_SEARCH_TOOL]: handleEventsSearch,
74207558
[LINKEDIN_EVENTS_VIEW_TOOL]: handleEventsView,
7559+
[LINKEDIN_EVENTS_PREPARE_CREATE_TOOL]: handleEventsPrepareCreate,
74217560
[LINKEDIN_EVENTS_PREPARE_RSVP_TOOL]: handleEventsPrepareRsvp,
74227561
[LINKEDIN_ACTIVITY_WATCH_CREATE_TOOL]: handleActivityWatchCreate,
74237562
[LINKEDIN_ACTIVITY_WATCH_LIST_TOOL]: handleActivityWatchList,

packages/mcp/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,14 @@ export const LINKEDIN_JOBS_PREPARE_EASY_APPLY_TOOL =
142142
"linkedin.jobs.prepare_easy_apply";
143143
export const LINKEDIN_GROUPS_SEARCH_TOOL = "linkedin.groups.search";
144144
export const LINKEDIN_GROUPS_VIEW_TOOL = "linkedin.groups.view";
145+
export const LINKEDIN_GROUPS_PREPARE_CREATE_TOOL = "linkedin.groups.prepare_create";
145146
export const LINKEDIN_GROUPS_PREPARE_JOIN_TOOL = "linkedin.groups.prepare_join";
146147
export const LINKEDIN_GROUPS_PREPARE_LEAVE_TOOL =
147148
"linkedin.groups.prepare_leave";
148149
export const LINKEDIN_GROUPS_PREPARE_POST_TOOL = "linkedin.groups.prepare_post";
149150
export const LINKEDIN_EVENTS_SEARCH_TOOL = "linkedin.events.search";
150151
export const LINKEDIN_EVENTS_VIEW_TOOL = "linkedin.events.view";
152+
export const LINKEDIN_EVENTS_PREPARE_CREATE_TOOL = "linkedin.events.prepare_create";
151153
export const LINKEDIN_EVENTS_PREPARE_RSVP_TOOL = "linkedin.events.prepare_rsvp";
152154
export const LINKEDIN_ACTIVITY_WATCH_CREATE_TOOL =
153155
"linkedin.activity_watch.create";

0 commit comments

Comments
 (0)