Skip to content

Commit 6d3b6dc

Browse files
Working hours
1 parent 2578bb5 commit 6d3b6dc

7 files changed

Lines changed: 343 additions & 151 deletions

File tree

‎package-lock.json‎

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

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"author": "Linked API",
3131
"license": "MIT",
3232
"dependencies": {
33-
"@linkedapi/node": "^2.3.5",
33+
"@linkedapi/node": "^2.3.6",
3434
"@modelcontextprotocol/sdk": "^1.17.4",
3535
"zod": "^4.1.1"
3636
},

‎src/linked-api-server.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import { logger } from './utils/logger';
88
import { CallToolResult, ExtendedCallToolRequest } from './utils/types';
99

1010
const BACKGROUND_WORKFLOW_DESCRIPTION =
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;
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', pendingReason, 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.
12+
13+
A pending workflow carries pendingReason: 'queued' means it is waiting its turn behind other work on the same account and will start within minutes. 'outsideWorkingHours' means the account has configured working hours and the workflow is parked until they reopen — possibly the next working day. In that case get_workflow_result returns immediately instead of polling, message states when the window opens, and you should report that to the user rather than looping.` as const;
1214
const NON_WORKFLOW_TOOL_NAMES = new Set<string>(['get_workflow_result', 'get_api_usage'] as const);
1315

1416
interface TExecuteWithTokensOptions extends TLinkedApiConfig {
@@ -105,6 +107,9 @@ export class LinkedApiMCPServer {
105107
if ('workflowStatus' in result) {
106108
const inProgressBody = {
107109
status: result.workflowStatus,
110+
// Surfaced so the caller can tell "waiting its turn, minutes" from "parked until the
111+
// account working hours reopen", which can be the next working day.
112+
pendingReason: result.pendingReason ?? null,
108113
workflowId: result.workflowId,
109114
operationName: result.operationName,
110115
message: result.message,

‎src/prompts/index.ts‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,31 @@ LINKEDIN WORKFLOWS RUN IN THE BACKGROUND:
3333
- Simply continue listening for updates using get_workflow_result
3434
3535
WHEN A WORKFLOW IS RUNNING IN BACKGROUND:
36-
1. You'll receive a status message with: workflowId, status, message, and operationName
36+
1. You'll receive a status message with: workflowId, status, pendingReason, message, and operationName
3737
2. Use get_workflow_result(workflowId, operationName) to continue listening for updates
3838
3. Keep listening until you get the final results
3939
4. This is normal workflow operation - the task is actively running
4040
41+
WHEN A WORKFLOW IS PARKED OUTSIDE WORKING HOURS:
42+
- pendingReason is "outsideWorkingHours": the account has configured working hours and this
43+
workflow will not start until they reopen, which may be the next working day
44+
- get_workflow_result returns immediately in this case instead of polling
45+
- Do NOT keep calling get_workflow_result in a loop and do NOT restart the workflow
46+
- Tell the user when the window opens (the message says so) and that the work will start by itself
47+
48+
EXAMPLE PARKED WORKFLOW MESSAGE:
49+
{
50+
"status": "pending",
51+
"pendingReason": "outsideWorkingHours",
52+
"message": "This workflow is scheduled and will start when the account working hours open at Mon, Aug 10, 09:00 (Europe/Berlin).",
53+
"workflowId": "account-12345-abcde",
54+
"operationName": "searchCompanies"
55+
}
56+
57+
RESPONSE: "This account is outside its working hours right now, so the search is queued and will
58+
start automatically on Monday at 09:00 (Europe/Berlin). Nothing else is needed from you."
59+
60+
4161
EXAMPLE BACKGROUND WORKFLOW MESSAGE:
4262
{
4363
"message": "Workflow is running in the background. Continue listening for updates.",

‎src/tools/get-workflow-result.ts‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import LinkedApi, {
2+
LINKED_API_WORKFLOW_PENDING_REASON,
23
Operation,
34
OPERATION_NAME,
45
TMappedResponse,
@@ -55,6 +56,11 @@ export class GetWorkflowResultTool extends LinkedApiTool<IGetWorkflowResultParam
5556
if (!isWorkflowInProgress(status)) {
5657
return status;
5758
}
59+
if (status.pendingReason === LINKED_API_WORKFLOW_PENDING_REASON.outsideWorkingHours) {
60+
return { ...status,
61+
workflowId,
62+
operationName: typedOperationName };
63+
}
5864
const remainingMs = deadline - Date.now();
5965
if (remainingMs <= 0) {
6066
return { ...status,
@@ -69,7 +75,7 @@ operationName: typedOperationName };
6975
return {
7076
name: this.name,
7177
description:
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.',
78+
'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, pendingReason, 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. Exception: when pendingReason is "outsideWorkingHours" the workflow is parked until the account working hours reopen — the server returns immediately without polling, message states when that is, and calling this tool again before then will report the same state.',
7379
inputSchema: {
7480
type: 'object',
7581
properties: {

‎src/utils/handle-linked-api-error.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,18 @@ export function handleLinkedApiError(error: LinkedApiError): Record<string, unkn
1717
type: error.type,
1818
retryable: false,
1919
};
20+
case 'outsideWorkingHours':
21+
return {
22+
message: `${error.message} Retry once the account working hours reopen, or ask the account owner to change the off-hours policy.`,
23+
type: error.type,
24+
retryable: false,
25+
};
26+
case 'workingHoursWaitExpired':
27+
return {
28+
message: `${error.message} Start it again inside the account working hours.`,
29+
type: error.type,
30+
retryable: false,
31+
};
2032
}
2133
return {
2234
message: error.message,

0 commit comments

Comments
 (0)