@linkedapi/mcp 1.0.1 → 2.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 (62) hide show
  1. package/.github/workflows/merge-main.yaml +13 -13
  2. package/.github/workflows/pr-main.yaml +6 -6
  3. package/README.md +1 -1
  4. package/dist/index.js +0 -23
  5. package/dist/index.js.map +1 -1
  6. package/dist/linked-api-server.d.ts +3 -3
  7. package/dist/linked-api-server.d.ts.map +1 -1
  8. package/dist/linked-api-server.js +37 -55
  9. package/dist/linked-api-server.js.map +1 -1
  10. package/dist/linked-api-tools.d.ts +5 -7
  11. package/dist/linked-api-tools.d.ts.map +1 -1
  12. package/dist/linked-api-tools.js.map +1 -1
  13. package/dist/prompts/index.d.ts +2 -2
  14. package/dist/prompts/index.d.ts.map +1 -1
  15. package/dist/prompts/index.js +3 -1
  16. package/dist/prompts/index.js.map +1 -1
  17. package/dist/tools/get-api-usage-stats.d.ts +0 -2
  18. package/dist/tools/get-api-usage-stats.d.ts.map +1 -1
  19. package/dist/tools/get-api-usage-stats.js.map +1 -1
  20. package/dist/tools/get-conversation.d.ts +4 -7
  21. package/dist/tools/get-conversation.d.ts.map +1 -1
  22. package/dist/tools/get-conversation.js +7 -23
  23. package/dist/tools/get-conversation.js.map +1 -1
  24. package/dist/tools/get-workflow-result.d.ts +6 -6
  25. package/dist/tools/get-workflow-result.d.ts.map +1 -1
  26. package/dist/tools/get-workflow-result.js +37 -13
  27. package/dist/tools/get-workflow-result.js.map +1 -1
  28. package/dist/tools/nv-get-conversation.d.ts +4 -7
  29. package/dist/tools/nv-get-conversation.d.ts.map +1 -1
  30. package/dist/tools/nv-get-conversation.js +7 -23
  31. package/dist/tools/nv-get-conversation.js.map +1 -1
  32. package/dist/types/linked-api-tool-result.type.d.ts +6 -0
  33. package/dist/types/linked-api-tool-result.type.d.ts.map +1 -0
  34. package/dist/types/linked-api-tool-result.type.js +3 -0
  35. package/dist/types/linked-api-tool-result.type.js.map +1 -0
  36. package/dist/utils/handle-linked-api-error.d.ts.map +1 -1
  37. package/dist/utils/handle-linked-api-error.js +0 -19
  38. package/dist/utils/handle-linked-api-error.js.map +1 -1
  39. package/dist/utils/linked-api-tool.d.ts +5 -7
  40. package/dist/utils/linked-api-tool.d.ts.map +1 -1
  41. package/dist/utils/linked-api-tool.js +4 -9
  42. package/dist/utils/linked-api-tool.js.map +1 -1
  43. package/dist/utils/types.d.ts +0 -6
  44. package/dist/utils/types.d.ts.map +1 -1
  45. package/package.json +2 -5
  46. package/src/index.ts +0 -29
  47. package/src/linked-api-server.ts +44 -66
  48. package/src/linked-api-tools.ts +5 -13
  49. package/src/prompts/index.ts +3 -1
  50. package/src/tools/get-api-usage-stats.ts +0 -2
  51. package/src/tools/get-conversation.ts +9 -30
  52. package/src/tools/get-workflow-result.ts +63 -24
  53. package/src/tools/nv-get-conversation.ts +9 -30
  54. package/src/types/linked-api-tool-result.type.ts +12 -0
  55. package/src/utils/handle-linked-api-error.ts +1 -20
  56. package/src/utils/linked-api-tool.ts +11 -25
  57. package/src/utils/types.ts +0 -6
  58. package/dist/utils/execute-with-progress.d.ts +0 -13
  59. package/dist/utils/execute-with-progress.d.ts.map +0 -1
  60. package/dist/utils/execute-with-progress.js +0 -85
  61. package/dist/utils/execute-with-progress.js.map +0 -1
  62. package/src/utils/execute-with-progress.ts +0 -111
@@ -3,23 +3,16 @@ import { buildLinkedApiHttpClient } from '@linkedapi/node/dist/core';
3
3
  import { Tool } from '@modelcontextprotocol/sdk/types.js';
4
4
 
5
5
  import { LinkedApiTools } from './linked-api-tools';
6
- import { defineRequestTimeoutInSeconds } from './utils/define-request-timeout';
7
6
  import { handleLinkedApiError } from './utils/handle-linked-api-error';
8
7
  import { logger } from './utils/logger';
9
- import {
10
- CallToolResult,
11
- ExtendedCallToolRequest,
12
- LinkedApiProgressNotification,
13
- } from './utils/types';
8
+ import { CallToolResult, ExtendedCallToolRequest } from './utils/types';
14
9
 
15
10
  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;
11
+ `Linked API actions are queued into a cloud-browser workflow and may take several minutes. The server returns immediately after starting the workflow with {status: 'pending'|'running', workflowId, operationName, message}. To retrieve the final result, call get_workflow_result with the returned workflowId and operationName it will long-poll until completion or the request budget elapses, then return either the final result or another in-progress snapshot. Do not retry the original tool while a workflow is still running; that creates duplicate queued work.` as const;
17
12
  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
13
 
20
14
  interface TExecuteWithTokensOptions extends TLinkedApiConfig {
21
15
  mcpClient: string;
22
- progressCallback?: (notification: LinkedApiProgressNotification) => void;
23
16
  }
24
17
 
25
18
  export class LinkedApiMCPServer {
@@ -47,25 +40,17 @@ export class LinkedApiMCPServer {
47
40
 
48
41
  public async executeWithTokens(
49
42
  request: ExtendedCallToolRequest['params'],
50
- {
51
- linkedApiToken,
52
- identificationToken,
53
- mcpClient,
54
- progressCallback = NOOP_PROGRESS_CALLBACK,
55
- }: TExecuteWithTokensOptions,
43
+ { linkedApiToken, identificationToken, mcpClient }: TExecuteWithTokensOptions,
56
44
  ): Promise<CallToolResult> {
57
- const workflowTimeout = defineRequestTimeoutInSeconds(mcpClient) * 1000;
58
45
  logger.info(
59
46
  {
60
47
  toolName: request.name,
61
48
  arguments: request.arguments,
62
49
  mcpClient,
63
- workflowTimeout,
64
50
  },
65
51
  'Tool execution started',
66
52
  );
67
- const { name: toolName, arguments: args, _meta } = request;
68
- const progressToken = _meta?.progressToken;
53
+ const { name: toolName, arguments: args } = request;
69
54
 
70
55
  const startTime = Date.now();
71
56
  try {
@@ -90,23 +75,9 @@ export class LinkedApiMCPServer {
90
75
  'Tool execution successful',
91
76
  );
92
77
  if (result === undefined) {
93
- return {
94
- content: [
95
- {
96
- type: 'text' as const,
97
- text: 'Completed',
98
- },
99
- ],
100
- };
78
+ return this.text('Completed');
101
79
  }
102
- return {
103
- content: [
104
- {
105
- type: 'text' as const,
106
- text: JSON.stringify(result, null, 2),
107
- },
108
- ],
109
- };
80
+ return this.text(JSON.stringify(result, null, 2));
110
81
  }
111
82
 
112
83
  const linkedapi = new LinkedApi(
@@ -124,15 +95,32 @@ export class LinkedApiMCPServer {
124
95
  throw new Error(`Unknown tool: ${toolName}`);
125
96
  }
126
97
  const params = tool.validate(args);
127
- const { data, errors } = await tool.execute({
98
+ const result = await tool.execute({
128
99
  linkedapi,
129
100
  args: params as never,
130
- workflowTimeout,
131
- progressToken,
132
- progressCallback,
101
+ mcpClient,
133
102
  });
134
- const endTime = Date.now();
135
- const duration = `${((endTime - startTime) / 1000).toFixed(2)} seconds`;
103
+ const duration = this.calculateDuration(startTime);
104
+
105
+ if ('workflowStatus' in result) {
106
+ const inProgressBody = {
107
+ status: result.workflowStatus,
108
+ workflowId: result.workflowId,
109
+ operationName: result.operationName,
110
+ message: result.message,
111
+ };
112
+ logger.info(
113
+ {
114
+ toolName,
115
+ duration,
116
+ body: inProgressBody,
117
+ },
118
+ 'Tool execution returned in-progress snapshot',
119
+ );
120
+ return this.text(JSON.stringify(inProgressBody, null, 2));
121
+ }
122
+
123
+ const { data, errors } = result;
136
124
  if (errors.length > 0 && !data) {
137
125
  logger.error(
138
126
  {
@@ -161,23 +149,9 @@ export class LinkedApiMCPServer {
161
149
  'Tool execution successful',
162
150
  );
163
151
  if (data) {
164
- return {
165
- content: [
166
- {
167
- type: 'text' as const,
168
- text: JSON.stringify(data, null, 2),
169
- },
170
- ],
171
- };
152
+ return this.text(JSON.stringify(data, null, 2));
172
153
  }
173
- return {
174
- content: [
175
- {
176
- type: 'text' as const,
177
- text: 'Completed',
178
- },
179
- ],
180
- };
154
+ return this.text('Completed');
181
155
  } catch (error) {
182
156
  const duration = this.calculateDuration(startTime);
183
157
  if (error instanceof LinkedApiError) {
@@ -198,7 +172,7 @@ export class LinkedApiMCPServer {
198
172
  },
199
173
  ],
200
174
  structuredContent: body,
201
- isError: error.type !== 'workflowTimeout',
175
+ isError: true,
202
176
  };
203
177
  }
204
178
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -212,17 +186,21 @@ export class LinkedApiMCPServer {
212
186
  'Tool execution failed with unknown error',
213
187
  );
214
188
 
215
- return {
216
- content: [
217
- {
218
- type: 'text' as const,
219
- text: `Error executing ${toolName}: ${errorMessage}`,
220
- },
221
- ],
222
- };
189
+ return this.text(`Error executing ${toolName}: ${errorMessage}`);
223
190
  }
224
191
  }
225
192
 
193
+ private text(text: string): CallToolResult {
194
+ return {
195
+ content: [
196
+ {
197
+ type: 'text' as const,
198
+ text,
199
+ },
200
+ ],
201
+ };
202
+ }
203
+
226
204
  private calculateDuration(startTime: number): string {
227
205
  const endTime = Date.now();
228
206
  return `${((endTime - startTime) / 1000).toFixed(2)} seconds`;
@@ -1,4 +1,4 @@
1
- import LinkedApi, { TMappedResponse } from '@linkedapi/node';
1
+ import LinkedApi from '@linkedapi/node';
2
2
  import { Tool } from '@modelcontextprotocol/sdk/types.js';
3
3
 
4
4
  import { AdminConnectAccountTool } from './tools/admin-connect-account.js';
@@ -38,26 +38,18 @@ import { SearchPeopleTool } from './tools/search-people.js';
38
38
  import { SendConnectionRequestTool } from './tools/send-connection-request.js';
39
39
  import { SendMessageTool } from './tools/send-message.js';
40
40
  import { WithdrawConnectionRequestTool } from './tools/withdraw-connection-request.js';
41
+ import type { TLinkedApiToolResult } from './types/linked-api-tool-result.type.js';
41
42
  import { AdminTool } from './utils/admin-tool.js';
42
- import { LinkedApiProgressNotification } from './utils/types.js';
43
43
 
44
44
  interface TRegisteredLinkedApiTool {
45
45
  readonly name: string;
46
46
  getTool(): Tool;
47
47
  validate(args: unknown): unknown;
48
- execute({
49
- linkedapi,
50
- args,
51
- workflowTimeout,
52
- progressToken,
53
- progressCallback,
54
- }: {
48
+ execute(options: {
55
49
  linkedapi: LinkedApi;
56
50
  args: never;
57
- workflowTimeout: number;
58
- progressToken?: string | number;
59
- progressCallback: (progress: LinkedApiProgressNotification) => void;
60
- }): Promise<TMappedResponse<unknown>>;
51
+ mcpClient: string;
52
+ }): Promise<TLinkedApiToolResult<unknown>>;
61
53
  }
62
54
 
63
55
  export class LinkedApiTools {
@@ -33,7 +33,7 @@ LINKEDIN WORKFLOWS RUN IN THE BACKGROUND:
33
33
  - Simply continue listening for updates using get_workflow_result
34
34
 
35
35
  WHEN A WORKFLOW IS RUNNING IN BACKGROUND:
36
- 1. You'll receive a status message with: workflowId and operationName
36
+ 1. You'll receive a status message with: workflowId, status, message, and operationName
37
37
  2. Use get_workflow_result(workflowId, operationName) to continue listening for updates
38
38
  3. Keep listening until you get the final results
39
39
  4. This is normal workflow operation - the task is actively running
@@ -41,6 +41,7 @@ WHEN A WORKFLOW IS RUNNING IN BACKGROUND:
41
41
  EXAMPLE BACKGROUND WORKFLOW MESSAGE:
42
42
  {
43
43
  "message": "Workflow is running in the background. Continue listening for updates.",
44
+ "status": "running",
44
45
  "workflowId": "account-12345-abcde",
45
46
  "operationName": "searchCompanies"
46
47
  }
@@ -113,6 +114,7 @@ WHAT TO DO WHEN WORKFLOW RUNS IN BACKGROUND:
113
114
  EXAMPLE BACKGROUND WORKFLOW MESSAGE:
114
115
  {
115
116
  "message": "Workflow is running in the background. Continue listening for updates.",
117
+ "status": "running",
116
118
  "workflowId": "account-12345-abcde",
117
119
  "operationName": "searchCompanies"
118
120
  }
@@ -17,8 +17,6 @@ export class GetApiUsageTool extends LinkedApiTool<TApiUsageParams, TApiUsageAct
17
17
  }: {
18
18
  linkedapi: LinkedApi;
19
19
  args: TApiUsageParams;
20
- workflowTimeout: number;
21
- progressToken?: string | number;
22
20
  }): Promise<TMappedResponse<TApiUsageAction[]>> {
23
21
  return await linkedapi.getApiUsage(args);
24
22
  }
@@ -1,14 +1,13 @@
1
1
  import LinkedApi, {
2
- LinkedApiWorkflowTimeoutError,
2
+ OPERATION_NAME,
3
3
  TConversationPollResult,
4
4
  TMappedResponse,
5
5
  } from '@linkedapi/node';
6
6
  import { Tool } from '@modelcontextprotocol/sdk/types.js';
7
7
  import z from 'zod';
8
8
 
9
- import { executeWithProgress } from '../utils/execute-with-progress.js';
9
+ import type { TWorkflowAck } from '../types/linked-api-tool-result.type.js';
10
10
  import { LinkedApiTool } from '../utils/linked-api-tool.js';
11
- import { LinkedApiProgressNotification } from '../utils/types.js';
12
11
 
13
12
  export class GetConversationTool extends LinkedApiTool<
14
13
  { personUrl: string; since?: string },
@@ -23,40 +22,20 @@ export class GetConversationTool extends LinkedApiTool<
23
22
  public override async execute({
24
23
  linkedapi,
25
24
  args: { personUrl, since },
26
- workflowTimeout,
27
- progressToken,
28
- progressCallback,
29
25
  }: {
30
26
  linkedapi: LinkedApi;
31
27
  args: { personUrl: string; since?: string };
32
- workflowTimeout: number;
33
- progressToken?: string | number;
34
- progressCallback: (progress: LinkedApiProgressNotification) => void;
35
- }): Promise<TMappedResponse<TConversationPollResult>> {
36
- const conversations = await this.getConversation(linkedapi, personUrl, since);
28
+ }): Promise<TMappedResponse<TConversationPollResult> | TWorkflowAck> {
29
+ const conversations = await this.pollConversation(linkedapi, personUrl, since);
37
30
  if (conversations.errors.length === 0) {
38
31
  return conversations;
39
32
  }
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
- }
56
- return await this.getConversation(linkedapi, personUrl, since);
33
+ const ack = await linkedapi.syncConversation.execute({ personUrl });
34
+ return { ...ack,
35
+ operationName: OPERATION_NAME.syncConversation };
57
36
  }
58
37
 
59
- private async getConversation(
38
+ private async pollConversation(
60
39
  linkedapi: LinkedApi,
61
40
  personUrl: string,
62
41
  since?: string,
@@ -78,7 +57,7 @@ After get_workflow_result completes this sync workflow, call get_conversation ag
78
57
  return {
79
58
  name: this.name,
80
59
  description:
81
- 'Allows you to get a conversation with a LinkedIn person using standard LinkedIn messaging.',
60
+ '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.',
82
61
  inputSchema: {
83
62
  type: 'object',
84
63
  properties: {
@@ -1,14 +1,24 @@
1
- import LinkedApi, { OPERATION_NAME, TMappedResponse } from '@linkedapi/node';
1
+ import LinkedApi, {
2
+ Operation,
3
+ OPERATION_NAME,
4
+ TMappedResponse,
5
+ TOperationName,
6
+ TWorkflowInProgressResponse,
7
+ } from '@linkedapi/node';
2
8
  import { Tool } from '@modelcontextprotocol/sdk/types.js';
3
9
  import z from 'zod';
4
10
 
5
- import { executeWithProgress } from '../utils/execute-with-progress.js';
11
+ import type { TWorkflowAck } from '../types/linked-api-tool-result.type.js';
12
+ import { defineRequestTimeoutInSeconds } from '../utils/define-request-timeout.js';
6
13
  import { LinkedApiTool } from '../utils/linked-api-tool.js';
7
- import { LinkedApiProgressNotification } from '../utils/types.js';
14
+
15
+ const POLL_INTERVAL_MS = 5000 as const;
16
+ const REQUEST_BUDGET_BUFFER_SECONDS = 5 as const;
8
17
 
9
18
  interface IGetWorkflowResultParams {
10
19
  workflowId: string;
11
20
  operationName: string;
21
+ waitSeconds?: number;
12
22
  }
13
23
 
14
24
  export class GetWorkflowResultTool extends LinkedApiTool<IGetWorkflowResultParams, unknown> {
@@ -16,50 +26,66 @@ export class GetWorkflowResultTool extends LinkedApiTool<IGetWorkflowResultParam
16
26
  protected readonly schema = z.object({
17
27
  workflowId: z.string(),
18
28
  operationName: z.enum(Object.values(OPERATION_NAME)),
29
+ waitSeconds: z.number().int().min(0).optional(),
19
30
  });
20
31
 
21
32
  public override async execute({
22
33
  linkedapi,
23
- args: { workflowId, operationName },
24
- workflowTimeout,
25
- progressToken,
26
- progressCallback,
34
+ args: { workflowId, operationName, waitSeconds },
35
+ mcpClient,
27
36
  }: {
28
37
  linkedapi: LinkedApi;
29
38
  args: IGetWorkflowResultParams;
30
- workflowTimeout: number;
31
- progressToken?: string | number;
32
- progressCallback: (progress: LinkedApiProgressNotification) => void;
33
- }): Promise<TMappedResponse<unknown>> {
39
+ mcpClient: string;
40
+ }): Promise<TMappedResponse<unknown> | TWorkflowAck> {
41
+ const typedOperationName = operationName as TOperationName;
34
42
  const operation = linkedapi.operations.find(
35
- (operation) => operation.operationName === operationName,
36
- )!;
37
- return await executeWithProgress({
38
- progressCallback,
39
- operation,
40
- workflowTimeout,
41
- workflowId,
42
- progressToken,
43
- });
43
+ (operation) => operation.operationName === typedOperationName,
44
+ )! as Operation<unknown, unknown>;
45
+
46
+ const cap = Math.max(
47
+ defineRequestTimeoutInSeconds(mcpClient) - REQUEST_BUDGET_BUFFER_SECONDS,
48
+ 0,
49
+ );
50
+ const budgetSeconds = Math.min(waitSeconds ?? cap, cap);
51
+ const deadline = Date.now() + budgetSeconds * 1000;
52
+
53
+ while (true) {
54
+ const status = await operation.status(workflowId);
55
+ if (!isWorkflowInProgress(status)) {
56
+ return status;
57
+ }
58
+ const remainingMs = deadline - Date.now();
59
+ if (remainingMs <= 0) {
60
+ return { ...status,
61
+ workflowId,
62
+ operationName: typedOperationName };
63
+ }
64
+ await sleep(Math.min(POLL_INTERVAL_MS, remainingMs));
65
+ }
44
66
  }
45
67
 
46
68
  public override getTool(): Tool {
47
69
  return {
48
70
  name: this.name,
49
71
  description:
50
- 'CONTINUE LISTENING TO BACKGROUND WORKFLOW - THIS IS NORMAL OPERATION! Background workflows are OPTIMAL BEHAVIOR for Linked API operations and keep the MCP client responsive. When a workflow runs in the background, this tool should be used with the provided workflowId and operationName parameters to continue listening for updates. The workflow continues processing in the background while you wait. This is the STANDARD way Linked API works - background processing provides optimal user experience!',
72
+ 'Check the current state of a previously started Linked API workflow. Returns the final result when the workflow has completed, otherwise returns the current in-progress snapshot ({status, workflowId, operationName, message}). The server long-polls up to waitSeconds while the workflow is still running; if the budget elapses without completion, the in-progress snapshot is returned and the client should call this tool again with the same workflowId and operationName.',
51
73
  inputSchema: {
52
74
  type: 'object',
53
75
  properties: {
54
76
  workflowId: {
55
77
  type: 'string',
56
- description:
57
- 'Required. The workflow ID provided in the background workflow status message.',
78
+ description: 'Required. The workflow ID returned by the original Linked API tool call.',
58
79
  },
59
80
  operationName: {
60
81
  type: 'string',
61
82
  description:
62
- 'Required. The operationName provided in the background workflow status message. Use the exact value so the result can be restored correctly.',
83
+ 'Required. The operationName returned by the original Linked API tool call. Used to map the response to the right operation.',
84
+ },
85
+ waitSeconds: {
86
+ type: 'number',
87
+ description:
88
+ 'Optional. Maximum seconds the server should wait for the workflow to complete before returning the current in-progress snapshot. Defaults to the maximum the current MCP client allows; pass 0 to get an immediate one-shot snapshot.',
63
89
  },
64
90
  },
65
91
  required: ['workflowId', 'operationName'],
@@ -67,3 +93,16 @@ export class GetWorkflowResultTool extends LinkedApiTool<IGetWorkflowResultParam
67
93
  };
68
94
  }
69
95
  }
96
+
97
+ function isWorkflowInProgress(
98
+ result: TMappedResponse<unknown> | TWorkflowInProgressResponse,
99
+ ): result is TWorkflowInProgressResponse {
100
+ return (
101
+ 'workflowStatus' in result &&
102
+ (result.workflowStatus === 'running' || result.workflowStatus === 'pending')
103
+ );
104
+ }
105
+
106
+ function sleep(ms: number): Promise<void> {
107
+ return new Promise((resolve) => setTimeout(resolve, ms));
108
+ }
@@ -1,14 +1,13 @@
1
1
  import LinkedApi, {
2
- LinkedApiWorkflowTimeoutError,
2
+ OPERATION_NAME,
3
3
  TConversationPollResult,
4
4
  TMappedResponse,
5
5
  } from '@linkedapi/node';
6
6
  import { Tool } from '@modelcontextprotocol/sdk/types.js';
7
7
  import z from 'zod';
8
8
 
9
- import { executeWithProgress } from '../utils/execute-with-progress.js';
9
+ import type { TWorkflowAck } from '../types/linked-api-tool-result.type.js';
10
10
  import { LinkedApiTool } from '../utils/linked-api-tool.js';
11
- import { LinkedApiProgressNotification } from '../utils/types.js';
12
11
 
13
12
  export class NvGetConversationTool extends LinkedApiTool<
14
13
  { personUrl: string; since?: string },
@@ -23,40 +22,20 @@ export class NvGetConversationTool extends LinkedApiTool<
23
22
  public override async execute({
24
23
  linkedapi,
25
24
  args: { personUrl, since },
26
- workflowTimeout,
27
- progressToken,
28
- progressCallback,
29
25
  }: {
30
26
  linkedapi: LinkedApi;
31
27
  args: { personUrl: string; since?: string };
32
- workflowTimeout: number;
33
- progressToken?: string | number;
34
- progressCallback: (progress: LinkedApiProgressNotification) => void;
35
- }): Promise<TMappedResponse<TConversationPollResult>> {
36
- const conversations = await this.getConversation(linkedapi, personUrl, since);
28
+ }): Promise<TMappedResponse<TConversationPollResult> | TWorkflowAck> {
29
+ const conversations = await this.pollConversation(linkedapi, personUrl, since);
37
30
  if (conversations.errors.length === 0) {
38
31
  return conversations;
39
32
  }
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
- }
56
- return await this.getConversation(linkedapi, personUrl, since);
33
+ const ack = await linkedapi.nvSyncConversation.execute({ personUrl });
34
+ return { ...ack,
35
+ operationName: OPERATION_NAME.nvSyncConversation };
57
36
  }
58
37
 
59
- private async getConversation(
38
+ private async pollConversation(
60
39
  linkedapi: LinkedApi,
61
40
  personUrl: string,
62
41
  since?: string,
@@ -78,7 +57,7 @@ After get_workflow_result completes this sync workflow, call nv_get_conversation
78
57
  return {
79
58
  name: this.name,
80
59
  description:
81
- 'Allows you to get a conversation with a LinkedIn person using Sales Navigator messaging.',
60
+ '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.',
82
61
  inputSchema: {
83
62
  type: 'object',
84
63
  properties: {
@@ -0,0 +1,12 @@
1
+ import type {
2
+ TMappedResponse,
3
+ TOperationName,
4
+ TWorkflowInProgressResponse,
5
+ TWorkflowStartedResponse,
6
+ } from '@linkedapi/node';
7
+
8
+ export type TWorkflowAck = (TWorkflowStartedResponse | TWorkflowInProgressResponse) & {
9
+ operationName: TOperationName;
10
+ };
11
+
12
+ export type TLinkedApiToolResult<TResult> = TWorkflowAck | TMappedResponse<TResult>;
@@ -1,27 +1,8 @@
1
- import { LinkedApiError, LinkedApiWorkflowTimeoutError } from '@linkedapi/node';
1
+ import { LinkedApiError } from '@linkedapi/node';
2
2
 
3
3
  import { authenticationPrompt } from '../prompts';
4
4
 
5
5
  export function handleLinkedApiError(error: LinkedApiError): Record<string, unknown> {
6
- if (error instanceof LinkedApiWorkflowTimeoutError) {
7
- const { message, workflowId, operationName } = error;
8
- return {
9
- status: 'workflow_running',
10
- message,
11
- workflowId,
12
- operationName,
13
- nextAction: {
14
- tool: 'get_workflow_result',
15
- arguments: {
16
- workflowId,
17
- operationName,
18
- },
19
- repeatUntil: 'final data is returned or the tool returns Completed',
20
- },
21
- userMessage:
22
- 'Linked API is still working through the cloud-browser workflow. I will keep checking the existing workflow instead of starting a duplicate request.',
23
- };
24
- }
25
6
  switch (error.type) {
26
7
  case 'identificationTokenRequired':
27
8
  case 'linkedApiTokenRequired':
@@ -1,16 +1,13 @@
1
- import LinkedApi, { Operation, TMappedResponse, TOperationName } from '@linkedapi/node';
1
+ import LinkedApi, { Operation, TOperationName } from '@linkedapi/node';
2
2
  import { Tool } from '@modelcontextprotocol/sdk/types.js';
3
- import { LinkedApiProgressNotification } from 'src/utils/types';
4
3
  import z from 'zod';
5
4
 
6
- import { executeWithProgress } from './execute-with-progress';
5
+ import type { TLinkedApiToolResult, TWorkflowAck } from '../types/linked-api-tool-result.type.js';
7
6
 
8
7
  interface TLinkedApiToolExecuteOptions<TParams> {
9
8
  linkedapi: LinkedApi;
10
9
  args: TParams;
11
- workflowTimeout: number;
12
- progressToken?: string | number;
13
- progressCallback: (progress: LinkedApiProgressNotification) => void;
10
+ mcpClient: string;
14
11
  }
15
12
 
16
13
  export abstract class LinkedApiTool<TParams, TResult> {
@@ -23,34 +20,23 @@ export abstract class LinkedApiTool<TParams, TResult> {
23
20
  return this.schema.parse(args) as TParams;
24
21
  }
25
22
 
26
- public abstract execute({
27
- linkedapi,
28
- args,
29
- workflowTimeout,
30
- progressToken,
31
- progressCallback,
32
- }: TLinkedApiToolExecuteOptions<TParams>): Promise<TMappedResponse<TResult>>;
23
+ public abstract execute(
24
+ options: TLinkedApiToolExecuteOptions<TParams>,
25
+ ): Promise<TLinkedApiToolResult<TResult>>;
33
26
  }
34
27
 
35
28
  export abstract class OperationTool<TParams, TResult> extends LinkedApiTool<TParams, TResult> {
36
29
  public abstract readonly operationName: TOperationName;
37
30
 
38
- public override execute({
31
+ public override async execute({
39
32
  linkedapi,
40
33
  args,
41
- workflowTimeout,
42
- progressToken,
43
- progressCallback,
44
- }: TLinkedApiToolExecuteOptions<TParams>): Promise<TMappedResponse<TResult>> {
34
+ }: TLinkedApiToolExecuteOptions<TParams>): Promise<TWorkflowAck> {
45
35
  const operation = linkedapi.operations.find(
46
36
  (operation) => operation.operationName === this.operationName,
47
37
  )! as Operation<TParams, TResult>;
48
- return executeWithProgress({
49
- progressCallback,
50
- operation,
51
- workflowTimeout,
52
- params: args,
53
- progressToken,
54
- });
38
+ const response = await operation.execute(args);
39
+ return { ...response,
40
+ operationName: this.operationName };
55
41
  }
56
42
  }
@@ -9,9 +9,3 @@ export interface ExtendedCallToolRequest extends CallToolRequest {
9
9
  };
10
10
  };
11
11
  }
12
- export interface LinkedApiProgressNotification {
13
- progressToken?: string | number;
14
- progress: number;
15
- total?: number;
16
- message?: string;
17
- }
@@ -1,13 +0,0 @@
1
- import { Operation, TMappedResponse } from '@linkedapi/node';
2
- import { LinkedApiProgressNotification } from './types';
3
- interface TExecuteWithProgressOptions<TParams, TResult> {
4
- progressCallback: (progress: LinkedApiProgressNotification) => void;
5
- operation: Operation<TParams, TResult>;
6
- workflowTimeout: number;
7
- params?: TParams;
8
- workflowId?: string;
9
- progressToken?: string | number;
10
- }
11
- export declare function executeWithProgress<TParams, TResult>({ progressCallback, operation, workflowTimeout, params, workflowId, progressToken, }: TExecuteWithProgressOptions<TParams, TResult>): Promise<TMappedResponse<TResult>>;
12
- export {};
13
- //# sourceMappingURL=execute-with-progress.d.ts.map