Skip to content

Commit 85d1011

Browse files
committed
Merge branch 'master' into feat/improve-error-message
2 parents 8f26020 + 6a9e25c commit 85d1011

File tree

15 files changed

+228
-82
lines changed

15 files changed

+228
-82
lines changed

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,25 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [0.5.5](https://github.com/apify/apify-mcp-server/releases/tag/v0.5.5) (2025-11-14)
6+
7+
### 🚀 Features
8+
9+
- Refactor(logging): centralize HTTP error logging ([#336](https://github.com/apify/apify-mcp-server/pull/336)) ([7b0a52d](https://github.com/apify/apify-mcp-server/commit/7b0a52d1e3baeb732747b1cbf58cb70ad1a135a7)) by [@jirispilka](https://github.com/jirispilka)
10+
11+
12+
## [0.5.2](https://github.com/apify/apify-mcp-server/releases/tag/v0.5.2) (2025-11-12)
13+
14+
### 🚀 Features
15+
16+
- Update search-actors tool ([#321](https://github.com/apify/apify-mcp-server/pull/321)) ([602abc5](https://github.com/apify/apify-mcp-server/commit/602abc55bff1f59e937d8e24acc8d7aad64852c2)) by [@jirispilka](https://github.com/jirispilka)
17+
18+
### 🐛 Bug Fixes
19+
20+
- Update readme, add image with clients ([#326](https://github.com/apify/apify-mcp-server/pull/326)) ([da560ab](https://github.com/apify/apify-mcp-server/commit/da560abcbd8b7cb2553032899b8cc963357495e5)) by [@jirispilka](https://github.com/jirispilka)
21+
- Deduplicate error logs, use info for 404/400 errors, fix ajv validate when it contains $ref ([#335](https://github.com/apify/apify-mcp-server/pull/335)) ([0ebbf50](https://github.com/apify/apify-mcp-server/commit/0ebbf5069a38e4168c5b031764136c66db4f2e18)) by [@jirispilka](https://github.com/jirispilka)
22+
23+
524
## [0.5.1](https://github.com/apify/apify-mcp-server/releases/tag/v0.5.1) (2025-10-27)
625

726
### 🚀 Features

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"manifest_version": "0.2",
33
"name": "apify-mcp-server",
44
"display_name": "Apify MCP server",
5-
"version": "0.5.1",
5+
"version": "0.5.5",
66
"description": "Extract data from any website using thousands of tools from the Apify Store.",
77
"long_description": "Apify is the world's largest marketplace of tools for web scraping, data extraction, and web automation. You can extract structured data from social media, e-commerce, search engines, maps, travel sites, or any other website.",
88
"keywords": [

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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@apify/actors-mcp-server",
3-
"version": "0.5.1",
3+
"version": "0.5.5",
44
"type": "module",
55
"description": "Apify MCP Server",
66
"mcpName": "com.apify/apify-mcp-server",

server.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/apify/apify-mcp-server",
88
"source": "github"
99
},
10-
"version": "0.5.1",
10+
"version": "0.5.5",
1111
"remotes": [
1212
{
1313
"type": "streamable-http",

src/actor/server.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,14 @@ export function createExpressApp(
2626
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
2727

2828
function respondWithError(res: Response, error: unknown, logMessage: string, statusCode = 500) {
29-
log.error('Error in request', { logMessage, error });
29+
if (statusCode >= 500) {
30+
// Server errors (>= 500) - log as exception
31+
log.exception(error instanceof Error ? error : new Error(String(error)), 'Error in request', { logMessage, statusCode });
32+
} else {
33+
// Client errors (< 500) - log as softFail without stack trace
34+
const errorMessage = error instanceof Error ? error.message : String(error);
35+
log.softFail('Error in request', { logMessage, error: errorMessage, statusCode });
36+
}
3037
if (!res.headersSent) {
3138
res.status(statusCode).json({
3239
jsonrpc: '2.0',
@@ -105,7 +112,7 @@ export function createExpressApp(
105112
});
106113
const sessionId = new URL(req.url, `http://${req.headers.host}`).searchParams.get('sessionId');
107114
if (!sessionId) {
108-
log.error('No session ID provided in POST request');
115+
log.softFail('No session ID provided in POST request', { statusCode: 400 });
109116
res.status(400).json({
110117
jsonrpc: '2.0',
111118
error: {
@@ -120,7 +127,7 @@ export function createExpressApp(
120127
if (transport) {
121128
await transport.handlePostMessage(req, res);
122129
} else {
123-
log.error('Server is not connected to the client.');
130+
log.softFail('Server is not connected to the client.', { statusCode: 400 });
124131
res.status(400).json({
125132
jsonrpc: '2.0',
126133
error: {
@@ -217,7 +224,7 @@ export function createExpressApp(
217224
return;
218225
}
219226

220-
log.error('Session not found', { sessionId });
227+
log.softFail('Session not found', { sessionId, statusCode: 400 });
221228
res.status(400).send('Bad Request: Session not found').end();
222229
});
223230

src/mcp/client.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/
55
import log from '@apify/log';
66

77
import { TimeoutError } from '../errors.js';
8+
import { logHttpError } from '../utils/logging.js';
89
import { ACTORIZED_MCP_CONNECTION_TIMEOUT_MSEC } from './const.js';
910
import { getMCPServerID } from './utils.js';
1011

@@ -40,8 +41,7 @@ export async function connectMCPClient(
4041
log.warning('Connection to MCP server using SSE transport timed out', { url });
4142
return null;
4243
}
43-
44-
log.error('Failed to connect to MCP server using SSE transport', { cause: error });
44+
logHttpError(error, 'Failed to connect to MCP server using SSE transport', { url, cause: error });
4545
throw error;
4646
}
4747
}

src/mcp/server.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { callActorGetDataset, defaultTools, getActorsAsTools, toolCategories } f
3939
import { decodeDotPropertyNames } from '../tools/utils.js';
4040
import type { ToolEntry } from '../types.js';
4141
import { buildActorResponseContent } from '../utils/actor-response.js';
42+
import { logHttpError } from '../utils/logging.js';
4243
import { buildMCPResponse } from '../utils/mcp.js';
4344
import { createProgressTracker } from '../utils/progress.js';
4445
import { cloneToolEntry, getToolPublicFieldOnly } from '../utils/tools.js';
@@ -485,7 +486,7 @@ export class ActorsMcpServer {
485486
const msg = `APIFY_TOKEN is required but was not provided.
486487
Please set the APIFY_TOKEN environment variable or pass it as a parameter in the request body.
487488
You can obtain your Apify token from https://console.apify.com/account/integrations.`;
488-
log.error(msg);
489+
log.softFail(msg, { statusCode: 400 });
489490
await this.server.sendLoggingMessage({ level: 'error', data: msg });
490491
throw new McpError(
491492
ErrorCode.InvalidParams,
@@ -512,7 +513,7 @@ You can obtain your Apify token from https://console.apify.com/account/integrati
512513
const msg = `Tool "${name}" was not found.
513514
Available tools: ${availableTools.length > 0 ? availableTools.join(', ') : 'none'}.
514515
Please verify the tool name is correct. You can list all available tools using the tools/list request.`;
515-
log.error(msg);
516+
log.softFail(msg, { statusCode: 404 });
516517
await this.server.sendLoggingMessage({ level: 'error', data: msg });
517518
throw new McpError(
518519
ErrorCode.InvalidParams,
@@ -522,7 +523,7 @@ Please verify the tool name is correct. You can list all available tools using t
522523
if (!args) {
523524
const msg = `Missing arguments for tool "${name}".
524525
Please provide the required arguments for this tool. Check the tool's input schema using ${HelperTools.ACTOR_GET_DETAILS} tool to see what parameters are required.`;
525-
log.error(msg);
526+
log.softFail(msg, { statusCode: 400 });
526527
await this.server.sendLoggingMessage({ level: 'error', data: msg });
527528
throw new McpError(
528529
ErrorCode.InvalidParams,
@@ -539,7 +540,7 @@ Please provide the required arguments for this tool. Check the tool's input sche
539540
const msg = `Invalid arguments for tool "${tool.name}".
540541
Validation errors: ${errorMessages}.
541542
Please check the tool's input schema using ${HelperTools.ACTOR_GET_DETAILS} tool and ensure all required parameters are provided with correct types and values.`;
542-
log.error(msg);
543+
log.softFail(msg, { statusCode: 400 });
543544
await this.server.sendLoggingMessage({ level: 'error', data: msg });
544545
throw new McpError(
545546
ErrorCode.InvalidParams,
@@ -580,7 +581,7 @@ Please check the tool's input schema using ${HelperTools.ACTOR_GET_DETAILS} tool
580581
if (!client) {
581582
const msg = `Failed to connect to MCP server at "${tool.serverUrl}".
582583
Please verify the server URL is correct and accessible, and ensure you have a valid Apify token with appropriate permissions.`;
583-
log.error(msg);
584+
log.softFail(msg, { statusCode: 408 }); // 408 Request Timeout
584585
await this.server.sendLoggingMessage({ level: 'error', data: msg });
585586
return buildMCPResponse([msg]);
586587
}
@@ -665,7 +666,7 @@ Please verify the server URL is correct and accessible, and ensure you have a va
665666
}
666667
}
667668
} catch (error) {
668-
log.error('Error occurred while calling tool', { toolName: name, error });
669+
logHttpError(error, 'Error occurred while calling tool', { toolName: name });
669670
const errorMessage = (error instanceof Error) ? error.message : 'Unknown error';
670671
return buildMCPResponse([
671672
`Error calling tool "${name}": ${errorMessage}.
@@ -677,7 +678,7 @@ Please verify the tool name, input parameters, and ensure all required resources
677678
const msg = `Unknown tool type for "${name}".
678679
Available tools: ${availableTools.length > 0 ? availableTools.join(', ') : 'none'}.
679680
Please verify the tool name and ensure the tool is properly registered.`;
680-
log.error(msg);
681+
log.softFail(msg, { statusCode: 404 });
681682
await this.server.sendLoggingMessage({
682683
level: 'error',
683684
data: msg,

src/tools/actor.ts

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { ensureOutputWithinCharLimit, getActorDefinitionStorageFieldNames, getAc
2424
import { fetchActorDetails } from '../utils/actor-details.js';
2525
import { buildActorResponseContent } from '../utils/actor-response.js';
2626
import { ajv } from '../utils/ajv.js';
27+
import { logHttpError } from '../utils/logging.js';
2728
import { buildMCPResponse } from '../utils/mcp.js';
2829
import type { ProgressTracker } from '../utils/progress.js';
2930
import type { JsonSchemaProperty } from '../utils/schema-generation.js';
@@ -84,7 +85,7 @@ export async function callActorGetDataset(
8485
try {
8586
await apifyClient.run(actorRun.id).abort({ gracefully: false });
8687
} catch (e) {
87-
log.error('Error aborting Actor run', { error: e, runId: actorRun.id });
88+
logHttpError(e, 'Error aborting Actor run', { runId: actorRun.id });
8889
}
8990
// Reject to stop waiting
9091
resolve(CLIENT_ABORT);
@@ -244,6 +245,12 @@ async function getMCPServersAsTools(
244245
return [];
245246
}
246247
return await getMCPServerTools(actorId, client, mcpServerUrl);
248+
} catch (error) {
249+
logHttpError(error, 'Failed to connect to MCP server', {
250+
actorFullName: actorInfo.actorDefinitionPruned.actorFullName,
251+
actorId,
252+
});
253+
return [];
247254
} finally {
248255
if (client) await client.close();
249256
}
@@ -273,17 +280,24 @@ export async function getActorsAsTools(
273280
} as ActorInfo;
274281
}
275282

276-
const actorDefinitionPruned = await getActorDefinition(actorIdOrName, apifyClient);
277-
if (!actorDefinitionPruned) {
278-
log.error('Actor not found or definition is not available', { actorName: actorIdOrName });
283+
try {
284+
const actorDefinitionPruned = await getActorDefinition(actorIdOrName, apifyClient);
285+
if (!actorDefinitionPruned) {
286+
log.info('Actor not found or definition is not available', { actorName: actorIdOrName });
287+
return null;
288+
}
289+
// Cache the pruned Actor definition
290+
actorDefinitionPrunedCache.set(actorIdOrName, actorDefinitionPruned);
291+
return {
292+
actorDefinitionPruned,
293+
webServerMcpPath: getActorMCPServerPath(actorDefinitionPruned),
294+
} as ActorInfo;
295+
} catch (error) {
296+
logHttpError(error, 'Failed to fetch Actor definition', {
297+
actorName: actorIdOrName,
298+
});
279299
return null;
280300
}
281-
// Cache the pruned Actor definition
282-
actorDefinitionPrunedCache.set(actorIdOrName, actorDefinitionPruned);
283-
return {
284-
actorDefinitionPruned,
285-
webServerMcpPath: getActorMCPServerPath(actorDefinitionPruned),
286-
} as ActorInfo;
287301
}),
288302
);
289303

@@ -435,12 +449,10 @@ You can search for available Actors using the tool: ${HelperTools.STORE_SEARCH}.
435449
}
436450

437451
/**
438-
* In Skyfire mode, we check for the presence of `skyfire-pay-id`.
439-
* If it is missing, we return instructions to the LLM on how to create it and pass it to the tool.
440-
*/
441-
if (apifyMcpServer.options.skyfireMode
442-
&& args['skyfire-pay-id'] === undefined
443-
) {
452+
* In Skyfire mode, we check for the presence of `skyfire-pay-id`.
453+
* If it is missing, we return instructions to the LLM on how to create it and pass it to the tool.
454+
*/
455+
if (apifyMcpServer.options.skyfireMode && args['skyfire-pay-id'] === undefined) {
444456
return {
445457
content: [{
446458
type: 'text',
@@ -450,8 +462,8 @@ You can search for available Actors using the tool: ${HelperTools.STORE_SEARCH}.
450462
}
451463

452464
/**
453-
* Create Apify token, for Skyfire mode use `skyfire-pay-id` and for normal mode use `apifyToken`.
454-
*/
465+
* Create Apify token, for Skyfire mode use `skyfire-pay-id` and for normal mode use `apifyToken`.
466+
*/
455467
const apifyClient = apifyMcpServer.options.skyfireMode && typeof args['skyfire-pay-id'] === 'string'
456468
? new ApifyClient({ skyfirePayId: args['skyfire-pay-id'] })
457469
: new ApifyClient({ token: apifyToken });
@@ -533,7 +545,7 @@ You can search for available Actors using the tool: ${HelperTools.STORE_SEARCH}.
533545

534546
return { content };
535547
} catch (error) {
536-
log.error('Failed to call Actor', { error, actorName, performStep });
548+
logHttpError(error, 'Failed to call Actor', { actorName, performStep });
537549
return buildMCPResponse([`Failed to call Actor '${actorName}': ${error instanceof Error ? error.message : String(error)}.
538550
Please verify the Actor name, input parameters, and ensure the Actor exists.
539551
You can search for available Actors using the tool: ${HelperTools.STORE_SEARCH}, or get Actor details using: ${HelperTools.ACTOR_GET_DETAILS}.`]);

src/tools/build.ts

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { z } from 'zod';
22
import zodToJsonSchema from 'zod-to-json-schema';
33

4-
import log from '@apify/log';
5-
64
import { ApifyClient } from '../apify-client.js';
75
import { ACTOR_README_MAX_LENGTH, HelperTools } from '../const.js';
86
import type {
@@ -32,7 +30,7 @@ export async function getActorDefinition(
3230
): Promise<ActorDefinitionPruned | null> {
3331
const actorClient = apifyClient.actor(actorIdOrName);
3432
try {
35-
// Fetch actor details
33+
// Fetch Actor details
3634
const actor = await actorClient.get();
3735
if (!actor) {
3836
return null;
@@ -53,9 +51,20 @@ export async function getActorDefinition(
5351
}
5452
return null;
5553
} catch (error) {
56-
const errorMessage = `Failed to fetch input schema for Actor: ${actorIdOrName} with error ${error}.`;
57-
log.error(errorMessage);
58-
throw new Error(errorMessage);
54+
// Check if it's a "not found" error (404 or 400 status codes)
55+
const isNotFound = typeof error === 'object'
56+
&& error !== null
57+
&& 'statusCode' in error
58+
&& (error.statusCode === 404 || error.statusCode === 400);
59+
60+
if (isNotFound) {
61+
// Return null for not found - caller will log appropriately
62+
return null;
63+
}
64+
65+
// For server errors, throw the original error (preserve error type)
66+
// Caller should catch and log
67+
throw error;
5968
}
6069
}
6170
function pruneActorDefinition(response: ActorDefinitionWithDesc): ActorDefinitionPruned {
@@ -121,14 +130,23 @@ export const actorDefinitionTool: ToolEntry = {
121130

122131
const parsed = getActorDefinitionArgsSchema.parse(args);
123132
const apifyClient = new ApifyClient({ token: apifyToken });
124-
const v = await getActorDefinition(parsed.actorName, apifyClient, parsed.limit);
125-
if (!v) {
126-
return { content: [{ type: 'text', text: `Actor '${parsed.actorName}' not found.` }] };
127-
}
128-
if (v && v.input && 'properties' in v.input && v.input) {
129-
const properties = filterSchemaProperties(v.input.properties as { [key: string]: ISchemaProperties });
130-
v.input.properties = shortenProperties(properties);
133+
try {
134+
const v = await getActorDefinition(parsed.actorName, apifyClient, parsed.limit);
135+
if (!v) {
136+
return { content: [{ type: 'text', text: `Actor '${parsed.actorName}' not found.` }] };
137+
}
138+
if (v && v.input && 'properties' in v.input && v.input) {
139+
const properties = filterSchemaProperties(v.input.properties as { [key: string]: ISchemaProperties });
140+
v.input.properties = shortenProperties(properties);
141+
}
142+
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(v)}\n\`\`\`` }] };
143+
} catch (error) {
144+
return {
145+
content: [{
146+
type: 'text',
147+
text: `Failed to fetch Actor definition: ${error instanceof Error ? error.message : String(error)}`,
148+
}],
149+
};
131150
}
132-
return { content: [{ type: 'text', text: `\`\`\`json\n${JSON.stringify(v)}\n\`\`\`` }] };
133151
},
134152
} as const;

0 commit comments

Comments
 (0)