Skip to content

Commit 439f15a

Browse files
chore: better logging, client detection for proper timeout handling (#4)
1 parent 959ba1f commit 439f15a

14 files changed

+288
-88
lines changed

.vscode/launch.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"version": "1.0.0",
3+
"configurations": [
4+
{
5+
"type": "node-terminal",
6+
"name": "Linked API MCP - HTTP",
7+
"request": "launch",
8+
"command": "npm run dev:http",
9+
"envFile": "${workspaceFolder}/.env"
10+
},
11+
{
12+
"type": "node-terminal",
13+
"name": "Linked API MCP - STDIO",
14+
"request": "launch",
15+
"command": "npm run dev:stdio",
16+
"envFile": "${workspaceFolder}/.env"
17+
},
18+
]
19+
}

package-lock.json

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

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "linkedapi-mcp",
3-
"version": "0.3.1",
3+
"version": "0.3.2",
44
"description": "MCP server for Linked API",
55
"main": "dist/index.js",
66
"bin": {
@@ -10,6 +10,8 @@
1010
"scripts": {
1111
"build": "tsc",
1212
"dev": "tsx src/index.ts",
13+
"dev:http": "tsx src/index.ts --http",
14+
"dev:stdio": "tsx src/index.ts",
1315
"start": "node dist/index.js",
1416
"watch": "tsx watch src/index.ts",
1517
"clean": "rm -rf dist",

src/index.ts

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import http from 'node:http';
1111

1212
import { LinkedApiMCPServer } from './linked-api-server';
1313
import { availablePrompts, getPromptContent, systemPrompt } from './prompts';
14-
import { debugLog } from './utils/debug-log';
1514
import { JsonHTTPServerTransport } from './utils/json-http-transport';
15+
import { logger } from './utils/logger';
1616
import { LinkedApiProgressNotification } from './utils/types';
1717

1818
function getArgValue(flag: string): string | undefined {
@@ -83,11 +83,7 @@ async function main() {
8383
});
8484

8585
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
86-
debugLog('Tool request received', {
87-
toolName: request.params.name,
88-
arguments: request.params.arguments,
89-
progressToken: request.params._meta?.progressToken,
90-
});
86+
logger.info('Tool request received');
9187

9288
try {
9389
const localLinkedApiToken = process.env.LINKED_API_TOKEN;
@@ -97,18 +93,30 @@ async function main() {
9793
const identificationToken = (headers['identification-token'] ??
9894
localIdentificationToken ??
9995
'') as string;
96+
const mcpClient = (headers['client'] ?? '') as string;
10097

10198
const result = await linkedApiServer.executeWithTokens(request.params, {
10299
linkedApiToken,
103100
identificationToken,
101+
mcpClient,
104102
});
105103
return result;
106104
} catch (error) {
107-
debugLog('Tool execution failed', {
108-
toolName: request.params.name,
109-
error: error instanceof Error ? error.message : String(error),
110-
});
111-
throw error;
105+
logger.error(
106+
{
107+
toolName: request.params.name,
108+
error: error instanceof Error ? error.message : String(error),
109+
},
110+
'Critical tool execution error',
111+
);
112+
return {
113+
content: [
114+
{
115+
type: 'text',
116+
text: 'Unknown error. Please try again.',
117+
},
118+
],
119+
};
112120
}
113121
});
114122

@@ -130,36 +138,40 @@ async function main() {
130138
// Set query parameters to headers if they are not set
131139
const linkedApiTokenQP = url.searchParams.get('linked-api-token');
132140
const identificationTokenQP = url.searchParams.get('identification-token');
141+
const mcpClient = url.searchParams.get('client');
133142
if (!req.headers['linked-api-token'] && linkedApiTokenQP) {
134143
req.headers['linked-api-token'] = linkedApiTokenQP;
135144
}
136145
if (!req.headers['identification-token'] && identificationTokenQP) {
137146
req.headers['identification-token'] = identificationTokenQP;
138147
}
148+
if (!req.headers['client'] && mcpClient) {
149+
req.headers['client'] = mcpClient;
150+
}
139151
await transport.handleRequest(req, res);
140152
} catch (error) {
141-
debugLog('HTTP request handling failed', {
142-
error: error instanceof Error ? error.message : String(error),
143-
});
153+
logger.error(
154+
{
155+
error: error instanceof Error ? error.message : String(error),
156+
},
157+
'HTTP request handling failed',
158+
);
144159
res.statusCode = 500;
145160
res.end('Internal Server Error');
146161
}
147162
});
148163

149164
httpServer.listen(port, host, () => {
150-
debugLog('HTTP transport listening', {
151-
host,
152-
port,
153-
});
165+
logger.info({ host }, `HTTP transport listening on port ${port}`);
154166
});
155167
} else {
156168
const transport = new StdioServerTransport();
157169
await server.connect(transport);
158-
debugLog('stdio transport connected');
170+
logger.info('stdio transport connected');
159171
}
160172
}
161173

162174
main().catch((error) => {
163-
debugLog('Fatal error', error);
175+
logger.error(error, 'Fatal error');
164176
process.exit(1);
165177
});

src/linked-api-server.ts

Lines changed: 67 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import { LinkedApi, LinkedApiError, TLinkedApiConfig } from 'linkedapi-node';
33
import { buildLinkedApiHttpClient } from 'linkedapi-node/dist/core';
44

55
import { LinkedApiTools } from './linked-api-tools';
6-
import { debugLog } from './utils/debug-log';
6+
import { defineRequestTimeoutInSeconds } from './utils/define-request-timeout';
77
import { handleLinkedApiError } from './utils/handle-linked-api-error';
8+
import { logger } from './utils/logger';
89
import {
910
CallToolResult,
1011
ExtendedCallToolRequest,
@@ -24,26 +25,52 @@ export class LinkedApiMCPServer {
2425

2526
public async executeWithTokens(
2627
request: ExtendedCallToolRequest['params'],
27-
config: TLinkedApiConfig,
28+
{ linkedApiToken, identificationToken, mcpClient }: TLinkedApiConfig & { mcpClient: string },
2829
): Promise<CallToolResult> {
29-
const linkedApi = new LinkedApi(
30+
const workflowTimeout = defineRequestTimeoutInSeconds(mcpClient) * 1000;
31+
logger.info(
32+
{
33+
toolName: request.name,
34+
arguments: request.arguments,
35+
mcpClient,
36+
workflowTimeout,
37+
},
38+
'Tool execution started',
39+
);
40+
const linkedapi = new LinkedApi(
3041
buildLinkedApiHttpClient(
3142
{
32-
linkedApiToken: config.linkedApiToken!,
33-
identificationToken: config.identificationToken!,
43+
linkedApiToken: linkedApiToken,
44+
identificationToken: identificationToken,
3445
},
3546
'mcp',
3647
),
3748
);
3849

39-
const { name, arguments: args, _meta } = request;
50+
const { name: toolName, arguments: args, _meta } = request;
4051
const progressToken = _meta?.progressToken;
4152

53+
const startTime = Date.now();
4254
try {
43-
const tool = this.tools.toolByName(name)!;
55+
const tool = this.tools.toolByName(toolName)!;
4456
const params = tool.validate(args);
45-
const { data, errors } = await tool.execute(linkedApi, params, progressToken);
57+
const { data, errors } = await tool.execute({
58+
linkedapi,
59+
args: params,
60+
workflowTimeout,
61+
progressToken,
62+
});
63+
const endTime = Date.now();
64+
const duration = `${((endTime - startTime) / 1000).toFixed(2)} seconds`;
4665
if (errors.length > 0 && !data) {
66+
logger.error(
67+
{
68+
toolName,
69+
duration,
70+
errors,
71+
},
72+
'Tool execution failed',
73+
);
4774
return {
4875
content: [
4976
{
@@ -53,6 +80,14 @@ export class LinkedApiMCPServer {
5380
],
5481
};
5582
}
83+
logger.info(
84+
{
85+
toolName,
86+
duration,
87+
data,
88+
},
89+
'Tool execution successful',
90+
);
5691
if (data) {
5792
return {
5893
content: [
@@ -72,8 +107,17 @@ export class LinkedApiMCPServer {
72107
],
73108
};
74109
} catch (error) {
110+
const duration = this.calculateDuration(startTime);
75111
if (error instanceof LinkedApiError) {
76112
const body = handleLinkedApiError(error);
113+
logger.error(
114+
{
115+
toolName,
116+
duration,
117+
body,
118+
},
119+
'Tool execution failed with Linked API error',
120+
);
77121
return {
78122
content: [
79123
{
@@ -84,19 +128,29 @@ export class LinkedApiMCPServer {
84128
};
85129
}
86130
const errorMessage = error instanceof Error ? error.message : String(error);
87-
debugLog(`Tool ${name} execution failed`, {
88-
error: errorMessage,
89-
stack: error instanceof Error ? error.stack : undefined,
90-
});
131+
logger.error(
132+
{
133+
toolName,
134+
duration,
135+
error: errorMessage,
136+
stack: error instanceof Error ? error.stack : undefined,
137+
},
138+
'Tool execution failed with unknown error',
139+
);
91140

92141
return {
93142
content: [
94143
{
95144
type: 'text' as const,
96-
text: `Error executing ${name}: ${errorMessage}`,
145+
text: `Error executing ${toolName}: ${errorMessage}`,
97146
},
98147
],
99148
};
100149
}
101150
}
151+
152+
private calculateDuration(startTime: number): string {
153+
const endTime = Date.now();
154+
return `${((endTime - startTime) / 1000).toFixed(2)} seconds`;
155+
}
102156
}

src/tools/get-api-usage-stats.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,15 @@ export class GetApiUsageTool extends LinkedApiTool<TApiUsageParams, TApiUsageAct
1111
end: z.string(),
1212
});
1313

14-
public override async execute(
15-
linkedapi: LinkedApi,
16-
args: TApiUsageParams,
17-
): Promise<TMappedResponse<TApiUsageAction[]>> {
14+
public override async execute({
15+
linkedapi,
16+
args,
17+
}: {
18+
linkedapi: LinkedApi;
19+
args: TApiUsageParams;
20+
workflowTimeout: number;
21+
progressToken?: string | number;
22+
}): Promise<TMappedResponse<TApiUsageAction[]>> {
1823
return await linkedapi.getApiUsage(args);
1924
}
2025

src/tools/get-conversation.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,15 @@ export class GetConversationTool extends LinkedApiTool<
1414
since: z.string().optional(),
1515
});
1616

17-
public override async execute(
18-
linkedapi: LinkedApi,
19-
{
20-
personUrl,
21-
since,
22-
}: {
23-
personUrl: string;
24-
since?: string;
25-
},
26-
): Promise<TMappedResponse<TConversationPollResult>> {
17+
public override async execute({
18+
linkedapi,
19+
args: { personUrl, since },
20+
}: {
21+
linkedapi: LinkedApi;
22+
args: { personUrl: string; since?: string };
23+
workflowTimeout: number;
24+
progressToken?: string | number;
25+
}): Promise<TMappedResponse<TConversationPollResult>> {
2726
const conversations = await this.getConversation(linkedapi, personUrl, since);
2827
if (conversations.errors.length === 0) {
2928
return conversations;

src/tools/get-workflow-result.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,22 @@ export class GetWorkflowResultTool extends LinkedApiTool<IGetWorkflowResultParam
1717
operationName: z.enum(Object.values(OPERATION_NAME)),
1818
});
1919

20-
public override async execute(
21-
linkedapi: LinkedApi,
22-
args: IGetWorkflowResultParams,
23-
progressToken?: string | number,
24-
): Promise<TMappedResponse<unknown>> {
20+
public override async execute({
21+
linkedapi,
22+
args: { workflowId, operationName },
23+
workflowTimeout,
24+
progressToken,
25+
}: {
26+
linkedapi: LinkedApi;
27+
args: IGetWorkflowResultParams;
28+
workflowTimeout: number;
29+
progressToken?: string | number;
30+
}): Promise<TMappedResponse<unknown>> {
2531
const operation = linkedapi.operations.find(
26-
(operation) => operation.operationName === args.operationName,
32+
(operation) => operation.operationName === operationName,
2733
)!;
28-
return await executeWithProgress(this.progressCallback, operation, {
29-
workflowId: args.workflowId,
34+
return await executeWithProgress(this.progressCallback, operation, workflowTimeout, {
35+
workflowId,
3036
progressToken,
3137
});
3238
}

0 commit comments

Comments
 (0)