Skip to content

Commit 924053e

Browse files
authored
fix: ajv compile memory leak (#139)
* fix ajv schema compile memory leak, add id to schema * fix: make $id optional in IActorInputSchema interface
1 parent 014d900 commit 924053e

File tree

3 files changed

+31
-1
lines changed

3 files changed

+31
-1
lines changed

src/tools/actor.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
2+
import type { ValidateFunction } from 'ajv';
23
import { Ajv } from 'ajv';
34
import type { ActorCallOptions, ActorRun, Dataset, PaginatedList } from 'apify-client';
45
import { z } from 'zod';
@@ -26,12 +27,32 @@ import {
2627
addEnumsToDescriptionsWithExamples,
2728
buildNestedProperties,
2829
filterSchemaProperties,
30+
getToolSchemaID,
2931
markInputPropertiesAsRequired,
3032
shortenProperties,
3133
} from './utils.js';
3234

3335
const ajv = new Ajv({ coerceTypes: 'array', strict: false });
3436

37+
// source https://github.com/ajv-validator/ajv/issues/1413#issuecomment-867064234
38+
function fixedCompile(schema: object): ValidateFunction<unknown> {
39+
const validate = ajv.compile(schema);
40+
ajv.removeSchema(schema);
41+
42+
// Force reset values that aren't reset with removeSchema
43+
/* eslint-disable no-underscore-dangle */
44+
/* eslint-disable @typescript-eslint/no-explicit-any */
45+
(ajv.scope as any)._values.schema!.delete(schema);
46+
(ajv.scope as any)._values.validate!.delete(validate);
47+
const schemaIdx = (ajv.scope as any)._scope.schema.indexOf(schema);
48+
const validateIdx = (ajv.scope as any)._scope.validate.indexOf(validate);
49+
if (schemaIdx !== -1) (ajv.scope as any)._scope.schema.splice(schemaIdx, 1);
50+
if (validateIdx !== -1) (ajv.scope as any)._scope.validate.splice(validateIdx, 1);
51+
/* eslint-enable @typescript-eslint/no-explicit-any */
52+
/* eslint-enable no-underscore-dangle */
53+
return validate;
54+
}
55+
3556
// Define a named return type for callActorGetDataset
3657
export type CallActorGetDatasetResult = {
3758
actorRun: ActorRun;
@@ -141,12 +162,16 @@ export async function getNormalActorsAsTools(
141162
const actorIDOrName = actorsToLoad[i];
142163

143164
if (result) {
165+
const schemaID = getToolSchemaID(result.actorFullName);
144166
if (result.input && 'properties' in result.input && result.input) {
145167
result.input.properties = markInputPropertiesAsRequired(result.input);
146168
result.input.properties = buildNestedProperties(result.input.properties);
147169
result.input.properties = filterSchemaProperties(result.input.properties);
148170
result.input.properties = shortenProperties(result.input.properties);
149171
result.input.properties = addEnumsToDescriptionsWithExamples(result.input.properties);
172+
// Add schema $id, each valid JSON schema should have a unique $id
173+
// see https://json-schema.org/understanding-json-schema/basics#declaring-a-unique-identifier
174+
result.input.$id = schemaID;
150175
}
151176
try {
152177
const memoryMbytes = result.defaultRunOptions?.memoryMbytes || ACTOR_MAX_MEMORY_MBYTES;
@@ -157,7 +182,7 @@ export async function getNormalActorsAsTools(
157182
actorFullName: result.actorFullName,
158183
description: `${result.description} Instructions: ${ACTOR_ADDITIONAL_INSTRUCTIONS}`,
159184
inputSchema: result.input || {},
160-
ajvValidate: ajv.compile(result.input || {}),
185+
ajvValidate: fixedCompile(result.input || {}),
161186
memoryMbytes: memoryMbytes > ACTOR_MAX_MEMORY_MBYTES ? ACTOR_MAX_MEMORY_MBYTES : memoryMbytes,
162187
},
163188
};

src/tools/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ export function actorNameToToolName(actorName: string): string {
88
.slice(0, 64);
99
}
1010

11+
export function getToolSchemaID(actorName: string): string {
12+
return `https://apify.com/mcp/${actorNameToToolName(actorName)}/schema.json`;
13+
}
14+
1115
/**
1216
* Builds nested properties for object types in the schema.
1317
*

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface ISchemaProperties {
2424
}
2525

2626
export interface IActorInputSchema {
27+
$id?: string;
2728
title?: string;
2829
description?: string;
2930

0 commit comments

Comments
 (0)