Skip to content

Commit 1fc3109

Browse files
Add "message" property to tools
1 parent c47aae7 commit 1fc3109

16 files changed

Lines changed: 182 additions & 382 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ To start using Linked API MCP, spend 2 minutes reading these essential guides:
1919

2020
Linked API actions run through a cloud browser and are queued like normal automation. Many actions take several minutes, especially searches and profile fetches with optional data.
2121

22-
If a tool returns `workflowId` and `operationName`, the action is still running. Do not retry the original tool because that can queue duplicate work. Call `get_workflow_result` with the exact `workflowId` and `operationName` until the final result is returned.
22+
If a tool returns `status`, `workflowId`, `operationName`, and `message`, the action is still running. Do not retry the original tool because that can queue duplicate work. Call `get_workflow_result` with the exact `workflowId` and `operationName` until the final result is returned. By default `get_workflow_result` long-polls until the workflow completes or the current MCP client's request budget elapses; pass `waitSeconds: 0` for an immediate single-shot snapshot.
2323

2424
## License
2525

package-lock.json

Lines changed: 22 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@linkedapi/mcp",
3-
"version": "1.0.1",
3+
"version": "2.0.0",
44
"description": "MCP server that lets AI assistants control LinkedIn accounts and retrieve real-time data.",
55
"main": "dist/index.js",
66
"bin": {
@@ -30,7 +30,7 @@
3030
"author": "Linked API",
3131
"license": "MIT",
3232
"dependencies": {
33-
"@linkedapi/node": "^1.2.18",
33+
"@linkedapi/node": "^2.0.0",
3434
"@modelcontextprotocol/sdk": "^1.17.4",
3535
"zod": "^4.1.1"
3636
},

src/index.ts

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { LinkedApiMCPServer } from './linked-api-server';
1313
import { availablePrompts, getPromptContent, systemPrompt } from './prompts';
1414
import { JsonHTTPServerTransport } from './utils/json-http-transport';
1515
import { logger } from './utils/logger';
16-
import { LinkedApiProgressNotification } from './utils/types';
1716

1817
function deriveClientFromUserAgent(userAgent: string): string {
1918
const ua = userAgent.toLowerCase();
@@ -139,38 +138,10 @@ async function main() {
139138
mcpClient = deriveClientFromUserAgent(userAgentHeader);
140139
}
141140
}
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-
};
168-
169141
const result = await linkedApiServer.executeWithTokens(request.params, {
170142
linkedApiToken,
171143
identificationToken,
172144
mcpClient,
173-
progressCallback,
174145
});
175146
return result;
176147
} catch (error) {

src/linked-api-server.ts

Lines changed: 44 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,16 @@ import { buildLinkedApiHttpClient } from '@linkedapi/node/dist/core';
33
import { Tool } from '@modelcontextprotocol/sdk/types.js';
44

55
import { LinkedApiTools } from './linked-api-tools';
6-
import { defineRequestTimeoutInSeconds } from './utils/define-request-timeout';
76
import { handleLinkedApiError } from './utils/handle-linked-api-error';
87
import { logger } from './utils/logger';
9-
import {
10-
CallToolResult,
11-
ExtendedCallToolRequest,
12-
LinkedApiProgressNotification,
13-
} from './utils/types';
8+
import { CallToolResult, ExtendedCallToolRequest } from './utils/types';
149

1510
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;
1712
const NON_WORKFLOW_TOOL_NAMES = new Set<string>(['get_workflow_result', 'get_api_usage'] as const);
18-
const NOOP_PROGRESS_CALLBACK = (_notification: LinkedApiProgressNotification): void => {};
1913

2014
interface TExecuteWithTokensOptions extends TLinkedApiConfig {
2115
mcpClient: string;
22-
progressCallback?: (notification: LinkedApiProgressNotification) => void;
2316
}
2417

2518
export class LinkedApiMCPServer {
@@ -47,25 +40,17 @@ export class LinkedApiMCPServer {
4740

4841
public async executeWithTokens(
4942
request: ExtendedCallToolRequest['params'],
50-
{
51-
linkedApiToken,
52-
identificationToken,
53-
mcpClient,
54-
progressCallback = NOOP_PROGRESS_CALLBACK,
55-
}: TExecuteWithTokensOptions,
43+
{ linkedApiToken, identificationToken, mcpClient }: TExecuteWithTokensOptions,
5644
): Promise<CallToolResult> {
57-
const workflowTimeout = defineRequestTimeoutInSeconds(mcpClient) * 1000;
5845
logger.info(
5946
{
6047
toolName: request.name,
6148
arguments: request.arguments,
6249
mcpClient,
63-
workflowTimeout,
6450
},
6551
'Tool execution started',
6652
);
67-
const { name: toolName, arguments: args, _meta } = request;
68-
const progressToken = _meta?.progressToken;
53+
const { name: toolName, arguments: args } = request;
6954

7055
const startTime = Date.now();
7156
try {
@@ -90,23 +75,9 @@ export class LinkedApiMCPServer {
9075
'Tool execution successful',
9176
);
9277
if (result === undefined) {
93-
return {
94-
content: [
95-
{
96-
type: 'text' as const,
97-
text: 'Completed',
98-
},
99-
],
100-
};
78+
return this.text('Completed');
10179
}
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));
11081
}
11182

11283
const linkedapi = new LinkedApi(
@@ -124,15 +95,32 @@ export class LinkedApiMCPServer {
12495
throw new Error(`Unknown tool: ${toolName}`);
12596
}
12697
const params = tool.validate(args);
127-
const { data, errors } = await tool.execute({
98+
const result = await tool.execute({
12899
linkedapi,
129100
args: params as never,
130-
workflowTimeout,
131-
progressToken,
132-
progressCallback,
101+
mcpClient,
133102
});
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;
136124
if (errors.length > 0 && !data) {
137125
logger.error(
138126
{
@@ -161,23 +149,9 @@ export class LinkedApiMCPServer {
161149
'Tool execution successful',
162150
);
163151
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));
172153
}
173-
return {
174-
content: [
175-
{
176-
type: 'text' as const,
177-
text: 'Completed',
178-
},
179-
],
180-
};
154+
return this.text('Completed');
181155
} catch (error) {
182156
const duration = this.calculateDuration(startTime);
183157
if (error instanceof LinkedApiError) {
@@ -198,7 +172,7 @@ export class LinkedApiMCPServer {
198172
},
199173
],
200174
structuredContent: body,
201-
isError: error.type !== 'workflowTimeout',
175+
isError: true,
202176
};
203177
}
204178
const errorMessage = error instanceof Error ? error.message : String(error);
@@ -212,17 +186,21 @@ export class LinkedApiMCPServer {
212186
'Tool execution failed with unknown error',
213187
);
214188

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}`);
223190
}
224191
}
225192

193+
private text(text: string): CallToolResult {
194+
return {
195+
content: [
196+
{
197+
type: 'text' as const,
198+
text,
199+
},
200+
],
201+
};
202+
}
203+
226204
private calculateDuration(startTime: number): string {
227205
const endTime = Date.now();
228206
return `${((endTime - startTime) / 1000).toFixed(2)} seconds`;

src/linked-api-tools.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import LinkedApi, { TMappedResponse } from '@linkedapi/node';
1+
import LinkedApi from '@linkedapi/node';
22
import { Tool } from '@modelcontextprotocol/sdk/types.js';
33

44
import { AdminConnectAccountTool } from './tools/admin-connect-account.js';
@@ -38,26 +38,18 @@ import { SearchPeopleTool } from './tools/search-people.js';
3838
import { SendConnectionRequestTool } from './tools/send-connection-request.js';
3939
import { SendMessageTool } from './tools/send-message.js';
4040
import { WithdrawConnectionRequestTool } from './tools/withdraw-connection-request.js';
41+
import type { TLinkedApiToolResult } from './types/linked-api-tool-result.type.js';
4142
import { AdminTool } from './utils/admin-tool.js';
42-
import { LinkedApiProgressNotification } from './utils/types.js';
4343

4444
interface TRegisteredLinkedApiTool {
4545
readonly name: string;
4646
getTool(): Tool;
4747
validate(args: unknown): unknown;
48-
execute({
49-
linkedapi,
50-
args,
51-
workflowTimeout,
52-
progressToken,
53-
progressCallback,
54-
}: {
48+
execute(options: {
5549
linkedapi: LinkedApi;
5650
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>>;
6153
}
6254

6355
export class LinkedApiTools {

0 commit comments

Comments
 (0)