From abc43ff497cdbe21c90c9e3f2f85ae661b4bcae9 Mon Sep 17 00:00:00 2001 From: MQ Date: Fri, 13 Jun 2025 10:51:10 +0200 Subject: [PATCH 1/2] fix ajv schema compile memory leak, add id to schema --- src/tools/actor.ts | 27 ++++++++++++++++++++++++++- src/tools/utils.ts | 4 ++++ src/types.ts | 1 + 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/tools/actor.ts b/src/tools/actor.ts index 1ba44668..b1ccd4cf 100644 --- a/src/tools/actor.ts +++ b/src/tools/actor.ts @@ -1,4 +1,5 @@ import type { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import type { ValidateFunction } from 'ajv'; import { Ajv } from 'ajv'; import type { ActorCallOptions, ActorRun, Dataset, PaginatedList } from 'apify-client'; import { z } from 'zod'; @@ -26,12 +27,32 @@ import { addEnumsToDescriptionsWithExamples, buildNestedProperties, filterSchemaProperties, + getToolSchemaID, markInputPropertiesAsRequired, shortenProperties, } from './utils.js'; const ajv = new Ajv({ coerceTypes: 'array', strict: false }); +// source https://github.com/ajv-validator/ajv/issues/1413#issuecomment-867064234 +function fixedCompile(schema: object): ValidateFunction { + const validate = ajv.compile(schema); + ajv.removeSchema(schema); + + // Force reset values that aren't reset with removeSchema + /* eslint-disable no-underscore-dangle */ + /* eslint-disable @typescript-eslint/no-explicit-any */ + (ajv.scope as any)._values.schema!.delete(schema); + (ajv.scope as any)._values.validate!.delete(validate); + const schemaIdx = (ajv.scope as any)._scope.schema.indexOf(schema); + const validateIdx = (ajv.scope as any)._scope.validate.indexOf(validate); + if (schemaIdx !== -1) (ajv.scope as any)._scope.schema.splice(schemaIdx, 1); + if (validateIdx !== -1) (ajv.scope as any)._scope.validate.splice(validateIdx, 1); + /* eslint-enable @typescript-eslint/no-explicit-any */ + /* eslint-enable no-underscore-dangle */ + return validate; +} + // Define a named return type for callActorGetDataset export type CallActorGetDatasetResult = { actorRun: ActorRun; @@ -141,12 +162,16 @@ export async function getNormalActorsAsTools( const actorIDOrName = actorsToLoad[i]; if (result) { + const schemaID = getToolSchemaID(result.actorFullName); if (result.input && 'properties' in result.input && result.input) { result.input.properties = markInputPropertiesAsRequired(result.input); result.input.properties = buildNestedProperties(result.input.properties); result.input.properties = filterSchemaProperties(result.input.properties); result.input.properties = shortenProperties(result.input.properties); result.input.properties = addEnumsToDescriptionsWithExamples(result.input.properties); + // Add schema $id, each valid JSON schema should have a unique $id + // see https://json-schema.org/understanding-json-schema/basics#declaring-a-unique-identifier + result.input.$id = schemaID; } try { const memoryMbytes = result.defaultRunOptions?.memoryMbytes || ACTOR_MAX_MEMORY_MBYTES; @@ -157,7 +182,7 @@ export async function getNormalActorsAsTools( actorFullName: result.actorFullName, description: `${result.description} Instructions: ${ACTOR_ADDITIONAL_INSTRUCTIONS}`, inputSchema: result.input || {}, - ajvValidate: ajv.compile(result.input || {}), + ajvValidate: fixedCompile(result.input || {}), memoryMbytes: memoryMbytes > ACTOR_MAX_MEMORY_MBYTES ? ACTOR_MAX_MEMORY_MBYTES : memoryMbytes, }, }; diff --git a/src/tools/utils.ts b/src/tools/utils.ts index a9e02688..ed4f3c5c 100644 --- a/src/tools/utils.ts +++ b/src/tools/utils.ts @@ -8,6 +8,10 @@ export function actorNameToToolName(actorName: string): string { .slice(0, 64); } +export function getToolSchemaID(actorName: string): string { + return `https://apify.com/mcp/${actorNameToToolName(actorName)}/schema.json`; +} + /** * Builds nested properties for object types in the schema. * diff --git a/src/types.ts b/src/types.ts index dc9713c3..572c268d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -24,6 +24,7 @@ export interface ISchemaProperties { } export interface IActorInputSchema { + $id: string; title?: string; description?: string; From 0e34f513a43042d79cad54f739bc6dccd1f2f41a Mon Sep 17 00:00:00 2001 From: MQ Date: Fri, 13 Jun 2025 10:54:17 +0200 Subject: [PATCH 2/2] fix: make $id optional in IActorInputSchema interface --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 572c268d..ad8a4b91 100644 --- a/src/types.ts +++ b/src/types.ts @@ -24,7 +24,7 @@ export interface ISchemaProperties { } export interface IActorInputSchema { - $id: string; + $id?: string; title?: string; description?: string;