Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@linkedapi/mcp",
"version": "2.3.16",
"version": "2.3.17",
"description": "MCP server that lets AI assistants control LinkedIn accounts and retrieve real-time data.",
"main": "dist/index.js",
"bin": {
Expand Down Expand Up @@ -30,7 +30,7 @@
"author": "Linked API",
"license": "MIT",
"dependencies": {
"@linkedapi/node": "^2.3.15",
"@linkedapi/node": "^2.3.17",
"@modelcontextprotocol/sdk": "^1.17.4",
"zod": "^4.1.1"
},
Expand Down
8 changes: 6 additions & 2 deletions src/linked-api-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { NvManageConversationTool } from './tools/nv-manage-conversation.js';
import { NvSearchCompaniesTool } from './tools/nv-search-companies.js';
import { NvSearchPeopleTool } from './tools/nv-search-people.js';
import { NvSendMessageTool } from './tools/nv-send-message.js';
import { NvSyncConversationTool } from './tools/nv-sync-conversation.js';
import { NvSyncInboxTool } from './tools/nv-sync-inbox.js';
import { ReactToCommentTool } from './tools/react-to-comment.js';
import { ReactToPostTool } from './tools/react-to-post.js';
Expand All @@ -62,6 +63,7 @@ import { SearchPostsTool } from './tools/search-posts.js';
import { SendConnectionRequestTool } from './tools/send-connection-request.js';
import { SendFeedbackTool } from './tools/send-feedback.js';
import { SendMessageTool } from './tools/send-message.js';
import { SyncConversationTool } from './tools/sync-conversation.js';
import { SyncInboxTool } from './tools/sync-inbox.js';
import { SyncNetworkTool } from './tools/sync-network.js';
import { WithdrawConnectionRequestTool } from './tools/withdraw-connection-request.js';
Expand Down Expand Up @@ -90,9 +92,10 @@ export class LinkedApiTools {
this.tools = [
// Standard tools
new SendMessageTool(),
new GetConversationTool(),
new SyncInboxTool(),
new GetInboxTool(),
new SyncConversationTool(),
new GetConversationTool(),
new SyncNetworkTool(),
new GetNetworkTool(),
new ManageConversationTool(),
Expand Down Expand Up @@ -125,8 +128,9 @@ export class LinkedApiTools {
new RetrievePerformanceTool(),
// Sales Navigator tools
new NvSendMessageTool(),
new NvGetConversationTool(),
new NvSyncInboxTool(),
new NvSyncConversationTool(),
new NvGetConversationTool(),
new NvManageConversationTool(),
new NvSearchCompaniesTool(),
new NvSearchPeopleTool(),
Expand Down
2 changes: 1 addition & 1 deletion src/tools/get-conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ operationName: OPERATION_NAME.syncConversation };
return {
name: this.name,
description:
'Retrieve a conversation with a LinkedIn person via standard LinkedIn messaging. Returns the conversation immediately if it has already been synced. If the conversation has not been synced yet, this tool starts a syncConversation workflow and returns its ack ({status, workflowId, operationName: "syncConversation", message}); the client should call get_workflow_result with that workflowId until completion, then call get_conversation again to read the synced messages. The result carries syncUntil — the moment syncing of this conversation stops. Once it is in the past the messages are still returned but no longer updated; call sync_conversation again for the same person to resume updates. It also carries personUrn — the permanent LinkedIn member URN (urn:li:member:<id>) of the person, or null when LinkedIn does not expose it.',
'Legacy. Prefer get_inbox with personUrl. Retrieve a conversation with a LinkedIn person via standard LinkedIn messaging. Returns the conversation immediately if it has already been synced. If the conversation has not been synced yet, this tool starts a syncConversation workflow and returns its ack ({status, workflowId, operationName: "syncConversation", message}); the client should call get_workflow_result with that workflowId until completion, then call get_conversation again to read the synced messages. The result carries syncUntil — the moment syncing of this conversation stops. Once it is in the past the messages are still returned but no longer updated; call sync_conversation again for the same person to resume updates. It also carries personUrn — the permanent LinkedIn member URN (urn:li:member:<id>) of the person, or null when LinkedIn does not expose it.',
inputSchema: {
type: 'object',
properties: {
Expand Down
31 changes: 24 additions & 7 deletions src/tools/get-inbox.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,55 @@
import LinkedApi, { TInboxPollRequest, TInboxPollResult, TMappedResponse } from '@linkedapi/node';
import LinkedApi, {
TInboxPersonPollResult,
TInboxPollRequest,
TInboxPollResult,
TMappedResponse,
} from '@linkedapi/node';
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import z from 'zod';

import { LinkedApiTool } from '../utils/linked-api-tool.js';

export class GetInboxTool extends LinkedApiTool<TInboxPollRequest, TInboxPollResult> {
type TGetInboxArgs = TInboxPollRequest & { personUrl?: string };

export class GetInboxTool extends LinkedApiTool<
TGetInboxArgs,
TInboxPollResult | TInboxPersonPollResult
> {
public readonly name = 'get_inbox';
protected readonly schema = z.object({
since: z.string().optional(),
type: z.enum(['st', 'nv']).optional(),
threadId: z.string().optional(),
personUrl: z.string().optional(),
});

public override async execute({
linkedapi,
args: { since, type, threadId },
args: { since, type, threadId, personUrl },
}: {
linkedapi: LinkedApi;
args: TInboxPollRequest;
}): Promise<TMappedResponse<TInboxPollResult>> {
args: TGetInboxArgs;
}): Promise<TMappedResponse<TInboxPollResult | TInboxPersonPollResult>> {
return linkedapi.pollInbox({
since,
type,
threadId,
personUrl,
});
}

public override getTool(): Tool {
return {
name: this.name,
description:
"Get messages from the monitored inbox across all conversations (standard and Sales Navigator), newest first. Requires inbox monitoring to be enabled once with sync_inbox (or nv_sync_inbox for Sales Navigator). Every message carries personUrn — the permanent LinkedIn member URN (urn:li:member:<id>) of the person on the other side of the thread, or null when LinkedIn does not expose it. personHashedUrl is LinkedIn's hashed profile URL and is always present; personPublicUrl is the public vanity URL when that person has been resolved, otherwise null.",
"Default way to read LinkedIn messages (standard and Sales Navigator), newest first. Without personUrl returns new messages across all conversations since inbox monitoring was enabled; requires sync_inbox (or nv_sync_inbox) once per account. With personUrl returns the whole known conversation with that person, including history loaded by sync_conversation, as { messages, threads }: each message also carries inboxMessageId (its id in the result without personUrl, or null) and threadId may be null; each thread carries historyFrom and historyComplete. Check threads[].historyComplete – if false and you need earlier messages, call sync_conversation once, then call get_inbox again. Every message carries personUrn — the permanent LinkedIn member URN (urn:li:member:<id>) of the person on the other side of the thread, or null when LinkedIn does not expose it. personHashedUrl is LinkedIn's hashed profile URL and is always present; personPublicUrl is the public vanity URL when that person has been resolved, otherwise null.",
inputSchema: {
type: 'object',
properties: {
since: {
type: 'string',
description:
"Optional ISO 8601 timestamp to only retrieve messages after this date (e.g., '2024-01-15T10:30:00Z'). If not provided, all captured messages are returned.",
"Optional ISO 8601 timestamp to only retrieve messages at or after this date (e.g., '2024-01-15T10:30:00Z'). If not provided, all captured messages are returned. Message times are whole minutes.",
},
type: {
type: 'string',
Expand All @@ -50,6 +62,11 @@ export class GetInboxTool extends LinkedApiTool<TInboxPollRequest, TInboxPollRes
description:
'Optional conversation thread identifier to restrict the result to a single thread.',
},
personUrl: {
type: 'string',
description:
"Optional LinkedIn URL of one person to get the whole known conversation with them instead of the whole inbox (e.g., 'https://www.linkedin.com/in/john-doe'). A hashed URL (personHashedUrl from a message) always matches; a public URL matches only once Linked API knows that person's public URL, so prefer personHashedUrl when messages from this person show personPublicUrl: null.",
},
},
required: [],
},
Expand Down
2 changes: 1 addition & 1 deletion src/tools/nv-get-conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ operationName: OPERATION_NAME.nvSyncConversation };
return {
name: this.name,
description:
'Retrieve a conversation with a LinkedIn person via Sales Navigator messaging. Returns the conversation immediately if it has already been synced. If the conversation has not been synced yet, this tool starts an nvSyncConversation workflow and returns its ack ({status, workflowId, operationName: "nvSyncConversation", message}); the client should call get_workflow_result with that workflowId until completion, then call nv_get_conversation again to read the synced messages. The result carries personUrn — the permanent LinkedIn member URN (urn:li:member:<id>) of the person, or null when LinkedIn does not expose it.',
'Legacy. Prefer get_inbox with personUrl. Retrieve a conversation with a LinkedIn person via Sales Navigator messaging. Returns the conversation immediately if it has already been synced. If the conversation has not been synced yet, this tool starts an nvSyncConversation workflow and returns its ack ({status, workflowId, operationName: "nvSyncConversation", message}); the client should call get_workflow_result with that workflowId until completion, then call nv_get_conversation again to read the synced messages. The result carries personUrn — the permanent LinkedIn member URN (urn:li:member:<id>) of the person, or null when LinkedIn does not expose it.',
inputSchema: {
type: 'object',
properties: {
Expand Down
6 changes: 3 additions & 3 deletions src/tools/nv-sync-conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@ export class NvSyncConversationTool extends OperationTool<TNvSyncConversationPar
return {
name: this.name,
description:
'Allows you to sync a conversation in Sales Navigator so you can start polling it. (nv.syncConversation action).',
"Loads this person's earlier Sales Navigator message history (nv.syncConversation action). Call once per person. Do not call it to check for new messages – use get_inbox. After it completes, read the history with get_inbox and personUrl.",
inputSchema: {
type: 'object',
properties: {
personUrl: {
type: 'string',
description:
"The LinkedIn URL of the person whose Sales Navigator conversation to sync (e.g., 'https://www.linkedin.com/in/john-doe')",
"The LinkedIn URL of the person whose Sales Navigator message history you want to load (e.g., 'https://www.linkedin.com/in/john-doe')",
},
days: {
type: 'number',
description:
'How many days the conversation stays synchronized, from 1 to 90. Defaults to 30. Counted from the moment the action starts running; a reply does not extend it. Syncing the same person again starts a new period and keeps the history already collected.',
'Syncing period, not the depth of history: how many days new messages keep being added to the loaded history, from 1 to 90. Defaults to 30. The whole conversation is read regardless. Counted from the moment the action starts running; a reply does not extend it. Syncing the same person again keeps the history already collected and never shortens the period.',
},
},
required: ['personUrl'],
Expand Down
2 changes: 1 addition & 1 deletion src/tools/nv-sync-inbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export class NvSyncInboxTool extends OperationTool<TNvSyncInboxParams, unknown>
return {
name: this.name,
description:
'Enable whole-inbox monitoring in Sales Navigator so every incoming conversation can be polled with get_inbox (nv.syncInbox action). Run once per account; only messages that arrive after it is enabled are captured.',
'Recommended first step before reading Sales Navigator messages. Enable whole-inbox monitoring in Sales Navigator so every incoming conversation can be polled with get_inbox (nv.syncInbox action). Run once per account, before sending messages whose replies you want to read; only messages that arrive after it is enabled are captured.',
inputSchema: {
type: 'object',
properties: {},
Expand Down
6 changes: 3 additions & 3 deletions src/tools/sync-conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@ export class SyncConversationTool extends OperationTool<TSyncConversationParams,
return {
name: this.name,
description:
'Allows you to sync a conversation so you can start polling it (st.syncConversation action).',
"Loads this person's earlier message history (st.syncConversation action). Call once per person; repeating it within 24 hours returns quickly. Do not call it to check for new messages – use get_inbox. After it completes, read the history with get_inbox and personUrl.",
inputSchema: {
type: 'object',
properties: {
personUrl: {
type: 'string',
description:
"The LinkedIn URL of the person whose conversation you want to synchronize (e.g., 'https://www.linkedin.com/in/john-doe')",
"The LinkedIn URL of the person whose message history you want to load (e.g., 'https://www.linkedin.com/in/john-doe')",
},
days: {
type: 'number',
description:
'How many days the conversation stays synchronized, from 1 to 90. Defaults to 30. Counted from the moment the action starts running; a reply does not extend it. Syncing the same person again starts a new period and keeps the history already collected.',
'Syncing period, not the depth of history: how many days new messages keep being added to the loaded history, from 1 to 90. Defaults to 30. The whole conversation is read regardless. Counted from the moment the action starts running; a reply does not extend it. Syncing the same person again keeps the history already collected and never shortens the period.',
},
},
required: ['personUrl'],
Expand Down
2 changes: 1 addition & 1 deletion src/tools/sync-inbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export class SyncInboxTool extends OperationTool<TSyncInboxParams, unknown> {
return {
name: this.name,
description:
'Enable whole-inbox monitoring so every incoming conversation can be polled with get_inbox (st.syncInbox action). Run once per account; only messages that arrive after it is enabled are captured.',
'Recommended first step before reading messages. Enable whole-inbox monitoring so every incoming conversation can be polled with get_inbox (st.syncInbox action). Run once per account, before sending messages whose replies you want to read; only messages that arrive after it is enabled are captured.',
inputSchema: {
type: 'object',
properties: {},
Expand Down
Loading