@linkedapi/mcp 1.0.0 → 1.0.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.
Files changed (83) hide show
  1. package/README.md +9 -0
  2. package/dist/index.js +42 -9
  3. package/dist/index.js.map +1 -1
  4. package/dist/linked-api-server.d.ts +7 -4
  5. package/dist/linked-api-server.d.ts.map +1 -1
  6. package/dist/linked-api-server.js +46 -14
  7. package/dist/linked-api-server.js.map +1 -1
  8. package/dist/linked-api-tools.d.ts +18 -4
  9. package/dist/linked-api-tools.d.ts.map +1 -1
  10. package/dist/linked-api-tools.js +28 -28
  11. package/dist/linked-api-tools.js.map +1 -1
  12. package/dist/tools/comment-on-post.d.ts.map +1 -1
  13. package/dist/tools/comment-on-post.js +1 -1
  14. package/dist/tools/comment-on-post.js.map +1 -1
  15. package/dist/tools/create-post.js +1 -1
  16. package/dist/tools/create-post.js.map +1 -1
  17. package/dist/tools/execute-custom-workflow.d.ts.map +1 -1
  18. package/dist/tools/execute-custom-workflow.js +1 -1
  19. package/dist/tools/execute-custom-workflow.js.map +1 -1
  20. package/dist/tools/get-conversation.d.ts +3 -1
  21. package/dist/tools/get-conversation.d.ts.map +1 -1
  22. package/dist/tools/get-conversation.js +20 -3
  23. package/dist/tools/get-conversation.js.map +1 -1
  24. package/dist/tools/get-workflow-result.d.ts +3 -1
  25. package/dist/tools/get-workflow-result.d.ts.map +1 -1
  26. package/dist/tools/get-workflow-result.js +7 -4
  27. package/dist/tools/get-workflow-result.js.map +1 -1
  28. package/dist/tools/nv-get-conversation.d.ts +3 -1
  29. package/dist/tools/nv-get-conversation.d.ts.map +1 -1
  30. package/dist/tools/nv-get-conversation.js +20 -3
  31. package/dist/tools/nv-get-conversation.js.map +1 -1
  32. package/dist/tools/nv-send-message.js +1 -1
  33. package/dist/tools/nv-send-message.js.map +1 -1
  34. package/dist/tools/react-to-post.js +1 -1
  35. package/dist/tools/react-to-post.js.map +1 -1
  36. package/dist/tools/remove-connection.js +1 -1
  37. package/dist/tools/remove-connection.js.map +1 -1
  38. package/dist/tools/send-connection-request.js +1 -1
  39. package/dist/tools/send-connection-request.js.map +1 -1
  40. package/dist/tools/send-message.d.ts.map +1 -1
  41. package/dist/tools/send-message.js +1 -1
  42. package/dist/tools/send-message.js.map +1 -1
  43. package/dist/tools/withdraw-connection-request.js +1 -1
  44. package/dist/tools/withdraw-connection-request.js.map +1 -1
  45. package/dist/utils/define-request-timeout.d.ts.map +1 -1
  46. package/dist/utils/define-request-timeout.js +16 -4
  47. package/dist/utils/define-request-timeout.js.map +1 -1
  48. package/dist/utils/execute-with-progress.d.ts +7 -2
  49. package/dist/utils/execute-with-progress.d.ts.map +1 -1
  50. package/dist/utils/execute-with-progress.js +19 -9
  51. package/dist/utils/execute-with-progress.js.map +1 -1
  52. package/dist/utils/handle-linked-api-error.d.ts +1 -1
  53. package/dist/utils/handle-linked-api-error.d.ts.map +1 -1
  54. package/dist/utils/handle-linked-api-error.js +10 -0
  55. package/dist/utils/handle-linked-api-error.js.map +1 -1
  56. package/dist/utils/json-http-transport.d.ts.map +1 -1
  57. package/dist/utils/json-http-transport.js +4 -3
  58. package/dist/utils/json-http-transport.js.map +1 -1
  59. package/dist/utils/linked-api-tool.d.ts +10 -14
  60. package/dist/utils/linked-api-tool.d.ts.map +1 -1
  61. package/dist/utils/linked-api-tool.js +5 -6
  62. package/dist/utils/linked-api-tool.js.map +1 -1
  63. package/package.json +1 -1
  64. package/src/index.ts +46 -9
  65. package/src/linked-api-server.ts +64 -16
  66. package/src/linked-api-tools.ts +52 -31
  67. package/src/tools/comment-on-post.ts +2 -1
  68. package/src/tools/create-post.ts +1 -1
  69. package/src/tools/execute-custom-workflow.ts +2 -1
  70. package/src/tools/get-conversation.ts +27 -3
  71. package/src/tools/get-workflow-result.ts +10 -3
  72. package/src/tools/nv-get-conversation.ts +27 -3
  73. package/src/tools/nv-send-message.ts +1 -1
  74. package/src/tools/react-to-post.ts +1 -1
  75. package/src/tools/remove-connection.ts +1 -1
  76. package/src/tools/send-connection-request.ts +1 -1
  77. package/src/tools/send-message.ts +2 -1
  78. package/src/tools/withdraw-connection-request.ts +1 -1
  79. package/src/utils/define-request-timeout.ts +16 -4
  80. package/src/utils/execute-with-progress.ts +37 -19
  81. package/src/utils/handle-linked-api-error.ts +12 -1
  82. package/src/utils/json-http-transport.ts +4 -3
  83. package/src/utils/linked-api-tool.ts +16 -18
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linkedapi/mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "MCP server that lets AI assistants control LinkedIn accounts and retrieve real-time data.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
package/src/index.ts CHANGED
@@ -17,6 +17,9 @@ import { LinkedApiProgressNotification } from './utils/types';
17
17
 
18
18
  function deriveClientFromUserAgent(userAgent: string): string {
19
19
  const ua = userAgent.toLowerCase();
20
+ if (ua.includes('codex')) return 'codex';
21
+ if (ua.includes('claude-code') || ua.includes('claude code')) return 'claude-code';
22
+ if (ua.includes('claude') || ua.includes('anthropic')) return 'claude';
20
23
  if (ua.includes('cursor')) return 'cursor';
21
24
  if (ua.includes('windsurf')) return 'windsurf';
22
25
  if (ua.includes('vscode') || ua.includes('visual studio code')) return 'vscode';
@@ -33,6 +36,14 @@ function deriveClientFromUserAgent(userAgent: string): string {
33
36
  return userAgent;
34
37
  }
35
38
 
39
+ function normalizeHeaderValue(value: string | Array<string> | undefined): string | undefined {
40
+ if (Array.isArray(value)) {
41
+ return value.find((item) => item.trim().length > 0)?.trim();
42
+ }
43
+ const normalizedValue = value?.trim();
44
+ return normalizedValue && normalizedValue.length > 0 ? normalizedValue : undefined;
45
+ }
46
+
36
47
  function getArgValue(flag: string): string | undefined {
37
48
  const index = process.argv.indexOf(flag);
38
49
  if (index === -1) return undefined;
@@ -61,8 +72,7 @@ async function main() {
61
72
  },
62
73
  );
63
74
 
64
- const progressCallback = (_notification: LinkedApiProgressNotification) => {};
65
- const linkedApiServer = new LinkedApiMCPServer(progressCallback);
75
+ const linkedApiServer = new LinkedApiMCPServer();
66
76
 
67
77
  server.setRequestHandler(ListToolsRequestSchema, async () => {
68
78
  const tools = linkedApiServer.getTools();
@@ -118,22 +128,49 @@ async function main() {
118
128
  const localLinkedApiToken = process.env.LINKED_API_TOKEN;
119
129
  const localIdentificationToken = process.env.IDENTIFICATION_TOKEN;
120
130
  const headers = extra?.requestInfo?.headers ?? {};
121
- const linkedApiToken = (headers['linked-api-token'] ?? localLinkedApiToken ?? '') as string;
122
- const identificationToken = (headers['identification-token'] ??
123
- localIdentificationToken ??
124
- '') as string;
125
- let mcpClient = (headers['client'] ?? '') as string;
131
+ const linkedApiToken =
132
+ normalizeHeaderValue(headers['linked-api-token']) ?? localLinkedApiToken ?? '';
133
+ const identificationToken =
134
+ normalizeHeaderValue(headers['identification-token']) ?? localIdentificationToken ?? '';
135
+ let mcpClient = normalizeHeaderValue(headers['client']) ?? '';
126
136
  if (!mcpClient) {
127
- const userAgentHeader = headers['user-agent'];
128
- if (typeof userAgentHeader === 'string' && userAgentHeader.trim().length > 0) {
137
+ const userAgentHeader = normalizeHeaderValue(headers['user-agent']);
138
+ if (userAgentHeader) {
129
139
  mcpClient = deriveClientFromUserAgent(userAgentHeader);
130
140
  }
131
141
  }
142
+ const progressCallback = (notification: LinkedApiProgressNotification): void => {
143
+ const { progressToken, progress, total, message } = notification;
144
+ if (progressToken === undefined) {
145
+ return;
146
+ }
147
+
148
+ void extra
149
+ .sendNotification({
150
+ method: 'notifications/progress',
151
+ params: {
152
+ progressToken,
153
+ progress,
154
+ total,
155
+ message,
156
+ },
157
+ })
158
+ .catch((error: unknown) => {
159
+ logger.warn(
160
+ {
161
+ toolName: request.params.name,
162
+ error: error instanceof Error ? error.message : String(error),
163
+ },
164
+ 'Failed to send MCP progress notification',
165
+ );
166
+ });
167
+ };
132
168
 
133
169
  const result = await linkedApiServer.executeWithTokens(request.params, {
134
170
  linkedApiToken,
135
171
  identificationToken,
136
172
  mcpClient,
173
+ progressCallback,
137
174
  });
138
175
  return result;
139
176
  } catch (error) {
@@ -12,22 +12,47 @@ import {
12
12
  LinkedApiProgressNotification,
13
13
  } from './utils/types';
14
14
 
15
+ const BACKGROUND_WORKFLOW_DESCRIPTION =
16
+ `Linked API actions are queued into a cloud-browser workflow and may take several minutes. This is normal LinkedIn automation behavior, not a failed request. If the response contains workflowId and operationName, do not retry this tool; call get_workflow_result with those exact values until the final result is returned.` as const;
17
+ const NON_WORKFLOW_TOOL_NAMES = new Set<string>(['get_workflow_result', 'get_api_usage'] as const);
18
+ const NOOP_PROGRESS_CALLBACK = (_notification: LinkedApiProgressNotification): void => {};
19
+
20
+ interface TExecuteWithTokensOptions extends TLinkedApiConfig {
21
+ mcpClient: string;
22
+ progressCallback?: (notification: LinkedApiProgressNotification) => void;
23
+ }
24
+
15
25
  export class LinkedApiMCPServer {
16
26
  private tools: LinkedApiTools;
17
27
 
18
- constructor(progressCallback: (notification: LinkedApiProgressNotification) => void) {
19
- this.tools = new LinkedApiTools(progressCallback);
28
+ constructor() {
29
+ this.tools = new LinkedApiTools();
20
30
  }
21
31
 
22
32
  public getTools(): Tool[] {
23
- const linkedApiTools = this.tools.tools.map((tool) => tool.getTool());
33
+ const linkedApiTools = this.tools.tools.map((tool) => {
34
+ const definition = tool.getTool();
35
+ if (NON_WORKFLOW_TOOL_NAMES.has(definition.name)) {
36
+ return definition;
37
+ }
38
+
39
+ return {
40
+ ...definition,
41
+ description: `${definition.description}\n\n${BACKGROUND_WORKFLOW_DESCRIPTION}`,
42
+ };
43
+ });
24
44
  const adminTools = this.tools.adminTools.map((tool) => tool.getTool());
25
45
  return [...linkedApiTools, ...adminTools];
26
46
  }
27
47
 
28
48
  public async executeWithTokens(
29
49
  request: ExtendedCallToolRequest['params'],
30
- { linkedApiToken, identificationToken, mcpClient }: TLinkedApiConfig & { mcpClient: string },
50
+ {
51
+ linkedApiToken,
52
+ identificationToken,
53
+ mcpClient,
54
+ progressCallback = NOOP_PROGRESS_CALLBACK,
55
+ }: TExecuteWithTokensOptions,
31
56
  ): Promise<CallToolResult> {
32
57
  const workflowTimeout = defineRequestTimeoutInSeconds(mcpClient) * 1000;
33
58
  logger.info(
@@ -46,22 +71,41 @@ export class LinkedApiMCPServer {
46
71
  try {
47
72
  const adminTool = this.tools.adminToolByName(toolName);
48
73
  if (adminTool) {
49
- const admin = new LinkedApiAdmin({ linkedApiToken,
50
- client: 'mcp' });
74
+ const admin = new LinkedApiAdmin({
75
+ linkedApiToken,
76
+ client: 'mcp',
77
+ });
51
78
  const params = adminTool.validate(args);
52
- const result = await adminTool.execute({ admin,
53
- args: params });
79
+ const result = await adminTool.execute({
80
+ admin,
81
+ args: params,
82
+ });
54
83
  const duration = this.calculateDuration(startTime);
55
- logger.info({ toolName,
56
- duration,
57
- data: result }, 'Tool execution successful');
84
+ logger.info(
85
+ {
86
+ toolName,
87
+ duration,
88
+ data: result,
89
+ },
90
+ 'Tool execution successful',
91
+ );
58
92
  if (result === undefined) {
59
- return { content: [{ type: 'text' as const,
60
- text: 'Completed' }] };
93
+ return {
94
+ content: [
95
+ {
96
+ type: 'text' as const,
97
+ text: 'Completed',
98
+ },
99
+ ],
100
+ };
61
101
  }
62
102
  return {
63
- content: [{ type: 'text' as const,
64
- text: JSON.stringify(result, null, 2) }],
103
+ content: [
104
+ {
105
+ type: 'text' as const,
106
+ text: JSON.stringify(result, null, 2),
107
+ },
108
+ ],
65
109
  };
66
110
  }
67
111
 
@@ -82,9 +126,10 @@ text: JSON.stringify(result, null, 2) }],
82
126
  const params = tool.validate(args);
83
127
  const { data, errors } = await tool.execute({
84
128
  linkedapi,
85
- args: params,
129
+ args: params as never,
86
130
  workflowTimeout,
87
131
  progressToken,
132
+ progressCallback,
88
133
  });
89
134
  const endTime = Date.now();
90
135
  const duration = `${((endTime - startTime) / 1000).toFixed(2)} seconds`;
@@ -98,6 +143,7 @@ text: JSON.stringify(result, null, 2) }],
98
143
  'Tool execution failed',
99
144
  );
100
145
  return {
146
+ isError: true,
101
147
  content: [
102
148
  {
103
149
  type: 'text' as const,
@@ -151,6 +197,8 @@ text: JSON.stringify(result, null, 2) }],
151
197
  text: JSON.stringify(body, null, 2),
152
198
  },
153
199
  ],
200
+ structuredContent: body,
201
+ isError: error.type !== 'workflowTimeout',
154
202
  };
155
203
  }
156
204
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -1,3 +1,6 @@
1
+ import LinkedApi, { TMappedResponse } from '@linkedapi/node';
2
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
3
+
1
4
  import { AdminConnectAccountTool } from './tools/admin-connect-account.js';
2
5
  import { AdminDisconnectAccountTool } from './tools/admin-disconnect-account.js';
3
6
  import { AdminGetAccountsTool } from './tools/admin-get-accounts.js';
@@ -36,45 +39,63 @@ import { SendConnectionRequestTool } from './tools/send-connection-request.js';
36
39
  import { SendMessageTool } from './tools/send-message.js';
37
40
  import { WithdrawConnectionRequestTool } from './tools/withdraw-connection-request.js';
38
41
  import { AdminTool } from './utils/admin-tool.js';
39
- import { LinkedApiTool } from './utils/linked-api-tool.js';
40
42
  import { LinkedApiProgressNotification } from './utils/types.js';
41
43
 
44
+ interface TRegisteredLinkedApiTool {
45
+ readonly name: string;
46
+ getTool(): Tool;
47
+ validate(args: unknown): unknown;
48
+ execute({
49
+ linkedapi,
50
+ args,
51
+ workflowTimeout,
52
+ progressToken,
53
+ progressCallback,
54
+ }: {
55
+ linkedapi: LinkedApi;
56
+ args: never;
57
+ workflowTimeout: number;
58
+ progressToken?: string | number;
59
+ progressCallback: (progress: LinkedApiProgressNotification) => void;
60
+ }): Promise<TMappedResponse<unknown>>;
61
+ }
62
+
42
63
  export class LinkedApiTools {
43
- public readonly tools: ReadonlyArray<LinkedApiTool<unknown, unknown>>;
64
+ public readonly tools: ReadonlyArray<TRegisteredLinkedApiTool>;
44
65
  public readonly adminTools: ReadonlyArray<AdminTool<unknown, unknown>>;
45
66
 
46
- constructor(progressCallback: (progress: LinkedApiProgressNotification) => void) {
67
+ constructor() {
47
68
  this.tools = [
48
69
  // Standard tools
49
- new SendMessageTool(progressCallback),
50
- new GetConversationTool(progressCallback),
51
- new CheckConnectionStatusTool(progressCallback),
52
- new RetrieveConnectionsTool(progressCallback),
53
- new SendConnectionRequestTool(progressCallback),
54
- new WithdrawConnectionRequestTool(progressCallback),
55
- new RetrievePendingRequestsTool(progressCallback),
56
- new RemoveConnectionTool(progressCallback),
57
- new SearchCompaniesTool(progressCallback),
58
- new SearchPeopleTool(progressCallback),
59
- new FetchCompanyTool(progressCallback),
60
- new FetchPersonTool(progressCallback),
61
- new FetchPostTool(progressCallback),
62
- new ReactToPostTool(progressCallback),
63
- new CommentOnPostTool(progressCallback),
64
- new CreatePostTool(progressCallback),
65
- new RetrieveSSITool(progressCallback),
66
- new RetrievePerformanceTool(progressCallback),
70
+ new SendMessageTool(),
71
+ new GetConversationTool(),
72
+ new CheckConnectionStatusTool(),
73
+ new RetrieveConnectionsTool(),
74
+ new SendConnectionRequestTool(),
75
+ new WithdrawConnectionRequestTool(),
76
+ new RetrievePendingRequestsTool(),
77
+ new RemoveConnectionTool(),
78
+ new SearchCompaniesTool(),
79
+ new SearchPeopleTool(),
80
+ new FetchCompanyTool(),
81
+ new FetchPersonTool(),
82
+ new FetchPostTool(),
83
+ new ReactToPostTool(),
84
+ new CommentOnPostTool(),
85
+ new CreatePostTool(),
86
+ new RetrieveSSITool(),
87
+ new RetrievePerformanceTool(),
67
88
  // Sales Navigator tools
68
- new NvSendMessageTool(progressCallback),
69
- new NvGetConversationTool(progressCallback),
70
- new NvSearchCompaniesTool(progressCallback),
71
- new NvSearchPeopleTool(progressCallback),
72
- new NvFetchCompanyTool(progressCallback),
73
- new NvFetchPersonTool(progressCallback),
89
+ new NvSendMessageTool(),
90
+ new NvGetConversationTool(),
91
+ new NvSearchCompaniesTool(),
92
+ new NvSearchPeopleTool(),
93
+ new NvFetchCompanyTool(),
94
+ new NvFetchPersonTool(),
74
95
  // Other tools
75
- new ExecuteCustomWorkflowTool(progressCallback),
76
- new GetWorkflowResultTool(progressCallback),
77
- new GetApiUsageTool(progressCallback),
96
+ new ExecuteCustomWorkflowTool(),
97
+ new GetWorkflowResultTool(),
98
+ new GetApiUsageTool(),
78
99
  ];
79
100
 
80
101
  this.adminTools = [
@@ -91,7 +112,7 @@ export class LinkedApiTools {
91
112
  ];
92
113
  }
93
114
 
94
- public toolByName(name: string): LinkedApiTool<unknown, unknown> | undefined {
115
+ public toolByName(name: string): TRegisteredLinkedApiTool | undefined {
95
116
  return this.tools.find((tool) => tool.name === name);
96
117
  }
97
118
 
@@ -16,7 +16,8 @@ export class CommentOnPostTool extends OperationTool<TCommentOnPostParams, unkno
16
16
  public override getTool(): Tool {
17
17
  return {
18
18
  name: this.name,
19
- description: 'Allows you to leave a comment on a post (st.commentOnPost action).',
19
+ description:
20
+ 'Allows you to leave a comment on a post (st.commentOnPost action). If this workflow is still running, do not retry this tool; retrying can post duplicate comments.',
20
21
  inputSchema: {
21
22
  type: 'object',
22
23
  properties: {
@@ -26,7 +26,7 @@ export class CreatePostTool extends OperationTool<TCreatePostParams, unknown> {
26
26
  return {
27
27
  name: this.name,
28
28
  description:
29
- 'Creates a new LinkedIn post with optional media attachments (st.createPost action).',
29
+ 'Creates a new LinkedIn post with optional media attachments (st.createPost action). If this workflow is still running, do not retry this tool; retrying can create duplicate posts.',
30
30
  inputSchema: {
31
31
  type: 'object',
32
32
  properties: {
@@ -17,7 +17,8 @@ export class ExecuteCustomWorkflowTool extends OperationTool<
17
17
  public override getTool(): Tool {
18
18
  return {
19
19
  name: this.name,
20
- description: 'Execute a custom workflow definition',
20
+ description:
21
+ 'Execute a custom workflow definition. If this workflow is still running, do not retry this tool; retrying can duplicate any write actions inside the custom workflow.',
21
22
  inputSchema: {
22
23
  type: 'object',
23
24
  properties: { definition: { type: 'object' } },
@@ -1,8 +1,14 @@
1
- import LinkedApi, { TConversationPollResult, TMappedResponse } from '@linkedapi/node';
1
+ import LinkedApi, {
2
+ LinkedApiWorkflowTimeoutError,
3
+ TConversationPollResult,
4
+ TMappedResponse,
5
+ } from '@linkedapi/node';
2
6
  import { Tool } from '@modelcontextprotocol/sdk/types.js';
3
7
  import z from 'zod';
4
8
 
9
+ import { executeWithProgress } from '../utils/execute-with-progress.js';
5
10
  import { LinkedApiTool } from '../utils/linked-api-tool.js';
11
+ import { LinkedApiProgressNotification } from '../utils/types.js';
6
12
 
7
13
  export class GetConversationTool extends LinkedApiTool<
8
14
  { personUrl: string; since?: string },
@@ -17,18 +23,36 @@ export class GetConversationTool extends LinkedApiTool<
17
23
  public override async execute({
18
24
  linkedapi,
19
25
  args: { personUrl, since },
26
+ workflowTimeout,
27
+ progressToken,
28
+ progressCallback,
20
29
  }: {
21
30
  linkedapi: LinkedApi;
22
31
  args: { personUrl: string; since?: string };
23
32
  workflowTimeout: number;
24
33
  progressToken?: string | number;
34
+ progressCallback: (progress: LinkedApiProgressNotification) => void;
25
35
  }): Promise<TMappedResponse<TConversationPollResult>> {
26
36
  const conversations = await this.getConversation(linkedapi, personUrl, since);
27
37
  if (conversations.errors.length === 0) {
28
38
  return conversations;
29
39
  }
30
- const workflowId = await linkedapi.syncConversation.execute({ personUrl });
31
- await linkedapi.syncConversation.result(workflowId);
40
+ try {
41
+ await executeWithProgress({
42
+ progressCallback,
43
+ operation: linkedapi.syncConversation,
44
+ workflowTimeout,
45
+ params: { personUrl },
46
+ progressToken,
47
+ });
48
+ } catch (error) {
49
+ if (error instanceof LinkedApiWorkflowTimeoutError) {
50
+ error.message = `${error.message}
51
+
52
+ After get_workflow_result completes this sync workflow, call get_conversation again with the same personUrl to read the synchronized messages.`;
53
+ }
54
+ throw error;
55
+ }
32
56
  return await this.getConversation(linkedapi, personUrl, since);
33
57
  }
34
58
 
@@ -4,6 +4,7 @@ import z from 'zod';
4
4
 
5
5
  import { executeWithProgress } from '../utils/execute-with-progress.js';
6
6
  import { LinkedApiTool } from '../utils/linked-api-tool.js';
7
+ import { LinkedApiProgressNotification } from '../utils/types.js';
7
8
 
8
9
  interface IGetWorkflowResultParams {
9
10
  workflowId: string;
@@ -22,16 +23,21 @@ export class GetWorkflowResultTool extends LinkedApiTool<IGetWorkflowResultParam
22
23
  args: { workflowId, operationName },
23
24
  workflowTimeout,
24
25
  progressToken,
26
+ progressCallback,
25
27
  }: {
26
28
  linkedapi: LinkedApi;
27
29
  args: IGetWorkflowResultParams;
28
30
  workflowTimeout: number;
29
31
  progressToken?: string | number;
32
+ progressCallback: (progress: LinkedApiProgressNotification) => void;
30
33
  }): Promise<TMappedResponse<unknown>> {
31
34
  const operation = linkedapi.operations.find(
32
35
  (operation) => operation.operationName === operationName,
33
36
  )!;
34
- return await executeWithProgress(this.progressCallback, operation, workflowTimeout, {
37
+ return await executeWithProgress({
38
+ progressCallback,
39
+ operation,
40
+ workflowTimeout,
35
41
  workflowId,
36
42
  progressToken,
37
43
  });
@@ -47,12 +53,13 @@ export class GetWorkflowResultTool extends LinkedApiTool<IGetWorkflowResultParam
47
53
  properties: {
48
54
  workflowId: {
49
55
  type: 'string',
50
- description: 'The workflow ID provided in the background workflow status message',
56
+ description:
57
+ 'Required. The workflow ID provided in the background workflow status message.',
51
58
  },
52
59
  operationName: {
53
60
  type: 'string',
54
61
  description:
55
- 'Optional function name for proper type restoration (provided in background workflow status if available)',
62
+ 'Required. The operationName provided in the background workflow status message. Use the exact value so the result can be restored correctly.',
56
63
  },
57
64
  },
58
65
  required: ['workflowId', 'operationName'],
@@ -1,8 +1,14 @@
1
- import LinkedApi, { TConversationPollResult, TMappedResponse } from '@linkedapi/node';
1
+ import LinkedApi, {
2
+ LinkedApiWorkflowTimeoutError,
3
+ TConversationPollResult,
4
+ TMappedResponse,
5
+ } from '@linkedapi/node';
2
6
  import { Tool } from '@modelcontextprotocol/sdk/types.js';
3
7
  import z from 'zod';
4
8
 
9
+ import { executeWithProgress } from '../utils/execute-with-progress.js';
5
10
  import { LinkedApiTool } from '../utils/linked-api-tool.js';
11
+ import { LinkedApiProgressNotification } from '../utils/types.js';
6
12
 
7
13
  export class NvGetConversationTool extends LinkedApiTool<
8
14
  { personUrl: string; since?: string },
@@ -17,18 +23,36 @@ export class NvGetConversationTool extends LinkedApiTool<
17
23
  public override async execute({
18
24
  linkedapi,
19
25
  args: { personUrl, since },
26
+ workflowTimeout,
27
+ progressToken,
28
+ progressCallback,
20
29
  }: {
21
30
  linkedapi: LinkedApi;
22
31
  args: { personUrl: string; since?: string };
23
32
  workflowTimeout: number;
24
33
  progressToken?: string | number;
34
+ progressCallback: (progress: LinkedApiProgressNotification) => void;
25
35
  }): Promise<TMappedResponse<TConversationPollResult>> {
26
36
  const conversations = await this.getConversation(linkedapi, personUrl, since);
27
37
  if (conversations.errors.length === 0) {
28
38
  return conversations;
29
39
  }
30
- const workflowId = await linkedapi.nvSyncConversation.execute({ personUrl });
31
- await linkedapi.nvSyncConversation.result(workflowId);
40
+ try {
41
+ await executeWithProgress({
42
+ progressCallback,
43
+ operation: linkedapi.nvSyncConversation,
44
+ workflowTimeout,
45
+ params: { personUrl },
46
+ progressToken,
47
+ });
48
+ } catch (error) {
49
+ if (error instanceof LinkedApiWorkflowTimeoutError) {
50
+ error.message = `${error.message}
51
+
52
+ After get_workflow_result completes this sync workflow, call nv_get_conversation again with the same personUrl to read the synchronized messages.`;
53
+ }
54
+ throw error;
55
+ }
32
56
  return await this.getConversation(linkedapi, personUrl, since);
33
57
  }
34
58
 
@@ -17,7 +17,7 @@ export class NvSendMessageTool extends OperationTool<TNvSendMessageParams, unkno
17
17
  return {
18
18
  name: this.name,
19
19
  description:
20
- 'Allows you to send a message to a person in Sales Navigator (nv.sendMessage action)',
20
+ 'Allows you to send a message to a person in Sales Navigator (nv.sendMessage action). If this workflow is still running, do not retry this tool; retrying can send duplicate Sales Navigator messages to the same person.',
21
21
  inputSchema: {
22
22
  type: 'object',
23
23
  properties: {
@@ -17,7 +17,7 @@ export class ReactToPostTool extends OperationTool<TReactToPostParams, unknown>
17
17
  return {
18
18
  name: this.name,
19
19
  description:
20
- 'Allows you to react to a post using any available reaction type (st.reactToPost action).',
20
+ 'Allows you to react to a post using any available reaction type (st.reactToPost action). If this workflow is still running, do not retry this tool; retrying can queue duplicate reaction attempts.',
21
21
  inputSchema: {
22
22
  type: 'object',
23
23
  properties: {
@@ -15,7 +15,7 @@ export class RemoveConnectionTool extends OperationTool<TRemoveConnectionParams,
15
15
  return {
16
16
  name: this.name,
17
17
  description:
18
- 'Allows you to remove a person from your connections (st.removeConnection action).',
18
+ 'Allows you to remove a person from your connections (st.removeConnection action). If this workflow is still running, do not retry this tool; retrying can queue duplicate removal attempts for the same person.',
19
19
  inputSchema: {
20
20
  type: 'object',
21
21
  properties: {
@@ -20,7 +20,7 @@ export class SendConnectionRequestTool extends OperationTool<
20
20
  return {
21
21
  name: this.name,
22
22
  description:
23
- 'Allows you to send a connection request to a person (st.sendConnectionRequest action).',
23
+ 'Allows you to send a connection request to a person (st.sendConnectionRequest action). If this workflow is still running, do not retry this tool; retrying can queue duplicate connection request attempts for the same person.',
24
24
  inputSchema: {
25
25
  type: 'object',
26
26
  properties: {
@@ -15,7 +15,8 @@ export class SendMessageTool extends OperationTool<TSendMessageParams, unknown>
15
15
  public override getTool(): Tool {
16
16
  return {
17
17
  name: this.name,
18
- description: 'Allows you to send a message to a person (st.sendMessage action).',
18
+ description:
19
+ 'Allows you to send a message to a person (st.sendMessage action). If this workflow is still running, do not retry this tool; retrying can send duplicate messages to the same person.',
19
20
  inputSchema: {
20
21
  type: 'object',
21
22
  properties: {
@@ -19,7 +19,7 @@ export class WithdrawConnectionRequestTool extends OperationTool<
19
19
  return {
20
20
  name: this.name,
21
21
  description:
22
- 'Allows you to withdraw the connection request sent to a person (st.withdrawConnectionRequest action).',
22
+ 'Allows you to withdraw the connection request sent to a person (st.withdrawConnectionRequest action). If this workflow is still running, do not retry this tool; retrying can queue duplicate withdrawal attempts for the same person.',
23
23
  inputSchema: {
24
24
  type: 'object',
25
25
  properties: {
@@ -1,16 +1,28 @@
1
1
  export function defineRequestTimeoutInSeconds(mcpClient: string): number {
2
- const bigTimeout = 600;
2
+ const BIG_TIMEOUT = 600 as const;
3
+ const normalizedMcpClient = mcpClient.trim().toLowerCase();
3
4
 
4
- console.error('mcpClient', mcpClient);
5
- switch (mcpClient) {
5
+ switch (normalizedMcpClient) {
6
+ case 'codex':
7
+ case 'codex-cli':
8
+ case 'codex-desktop':
9
+ case 'openai-codex':
10
+ return BIG_TIMEOUT;
11
+ case 'claude-code':
12
+ case 'claude_code':
13
+ case 'claude code':
14
+ return BIG_TIMEOUT;
6
15
  case 'claude':
16
+ case 'claude-desktop':
17
+ case 'anthropic':
7
18
  return 180;
8
19
  case 'chatgpt':
20
+ case 'openai':
9
21
  return 50;
10
22
  case 'cursor':
11
23
  case 'vscode':
12
24
  case 'windsurf':
13
- return bigTimeout;
25
+ return BIG_TIMEOUT;
14
26
  default:
15
27
  return 60;
16
28
  }