Skip to content

Commit 2251497

Browse files
committed
fix: deduplicate error logs, use info for 404/400 errors, fix ajv validate when it contains $ref
1 parent 80aed10 commit 2251497

File tree

4 files changed

+93
-32
lines changed

4 files changed

+93
-32
lines changed

src/tools/actor.ts

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,14 @@ async function getMCPServersAsTools(
244244
return [];
245245
}
246246
return await getMCPServerTools(actorId, client, mcpServerUrl);
247+
} catch (error) {
248+
// Server error - log and continue processing other actors
249+
log.error('Failed to connect to MCP server', {
250+
actorFullName: actorInfo.actorDefinitionPruned.actorFullName,
251+
actorId,
252+
error,
253+
});
254+
return [];
247255
} finally {
248256
if (client) await client.close();
249257
}
@@ -273,17 +281,26 @@ export async function getActorsAsTools(
273281
} as ActorInfo;
274282
}
275283

276-
const actorDefinitionPruned = await getActorDefinition(actorIdOrName, apifyClient);
277-
if (!actorDefinitionPruned) {
278-
log.error('Actor not found or definition is not available', { actorName: actorIdOrName });
284+
try {
285+
const actorDefinitionPruned = await getActorDefinition(actorIdOrName, apifyClient);
286+
if (!actorDefinitionPruned) {
287+
log.info('Actor not found or definition is not available', { actorName: actorIdOrName });
288+
return null;
289+
}
290+
// Cache the pruned Actor definition
291+
actorDefinitionPrunedCache.set(actorIdOrName, actorDefinitionPruned);
292+
return {
293+
actorDefinitionPruned,
294+
webServerMcpPath: getActorMCPServerPath(actorDefinitionPruned),
295+
} as ActorInfo;
296+
} catch (error) {
297+
// Server error - log and continue processing other actors
298+
log.error('Failed to fetch Actor definition', {
299+
actorName: actorIdOrName,
300+
error,
301+
});
279302
return null;
280303
}
281-
// Cache the pruned Actor definition
282-
actorDefinitionPrunedCache.set(actorIdOrName, actorDefinitionPruned);
283-
return {
284-
actorDefinitionPruned,
285-
webServerMcpPath: getActorMCPServerPath(actorDefinitionPruned),
286-
} as ActorInfo;
287304
}),
288305
);
289306

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;

src/tools/utils.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,16 @@ export function buildActorInputSchema(actorFullName: string, input: IActorInputS
180180
delete working.schemaVersion;
181181
}
182182

183+
// Remove $ref and $schema fields if present
184+
// since AJV cannot resolve external schema references
185+
// $ref and $schema are present in apify/website-content-crawler input schema
186+
if ('$ref' in working) {
187+
delete (working as { $ref?: string }).$ref;
188+
}
189+
if ('$schema' in working) {
190+
delete (working as { $schema?: string }).$schema;
191+
}
192+
183193
let finalSchema = working;
184194
if (isRag) {
185195
finalSchema = pruneSchemaPropertiesByWhitelist(finalSchema, RAG_WEB_BROWSER_WHITELISTED_FIELDS);

src/utils/actor.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,32 @@ export async function getActorMcpUrlCached(
2020
return cached as string | false;
2121
}
2222

23-
const actorDefinitionPruned = await getActorDefinition(actorIdOrName, apifyClient);
24-
const mcpPath = actorDefinitionPruned && getActorMCPServerPath(actorDefinitionPruned);
25-
if (actorDefinitionPruned && mcpPath) {
26-
const url = await getActorMCPServerURL(actorDefinitionPruned.id, mcpPath);
27-
mcpServerCache.set(actorIdOrName, url);
28-
return url;
29-
}
23+
try {
24+
const actorDefinitionPruned = await getActorDefinition(actorIdOrName, apifyClient);
25+
const mcpPath = actorDefinitionPruned && getActorMCPServerPath(actorDefinitionPruned);
26+
if (actorDefinitionPruned && mcpPath) {
27+
const url = await getActorMCPServerURL(actorDefinitionPruned.id, mcpPath);
28+
mcpServerCache.set(actorIdOrName, url);
29+
return url;
30+
}
31+
32+
mcpServerCache.set(actorIdOrName, false);
33+
return false;
34+
} catch (error) {
35+
// Check if it's a "not found" error (404 or 400 status codes)
36+
const isNotFound = typeof error === 'object'
37+
&& error !== null
38+
&& 'statusCode' in error
39+
&& (error.statusCode === 404 || error.statusCode === 400);
3040

31-
mcpServerCache.set(actorIdOrName, false);
32-
return false;
41+
if (isNotFound) {
42+
// Actor doesn't exist - cache false and return false
43+
mcpServerCache.set(actorIdOrName, false);
44+
return false;
45+
}
46+
// Real server error - don't cache, let it propagate
47+
throw error;
48+
}
3349
}
3450

3551
/**

0 commit comments

Comments
 (0)