Skip to content

Commit 04f4ac2

Browse files
committed
fix: tool get-actor-detail returns schema with advanced inputs
1 parent 34cac76 commit 04f4ac2

File tree

3 files changed

+114
-36
lines changed

3 files changed

+114
-36
lines changed

src/tools/get-actor-details.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { HelperTools } from '../const.js';
77
import type { IActorInputSchema, InternalTool, ToolEntry } from '../types.js';
88
import { formatActorToActorCard } from '../utils/actor-card.js';
99
import { ajv } from '../utils/ajv.js';
10-
import { filterSchemaProperties, shortenProperties } from './utils.js';
10+
import { transformActorInputForGetDetails } from './utils.js';
1111

1212
const getActorDetailsToolArgsSchema = z.object({
1313
actor: z.string()
@@ -31,7 +31,7 @@ export const getActorDetailsTool: ToolEntry = {
3131
inputSchema: zodToJsonSchema(getActorDetailsToolArgsSchema),
3232
ajvValidate: ajv.compile(zodToJsonSchema(getActorDetailsToolArgsSchema)),
3333
call: async (toolArgs) => {
34-
const { args, apifyToken } = toolArgs;
34+
const { args, apifyToken, apifyMcpServer } = toolArgs;
3535

3636
const parsed = getActorDetailsToolArgsSchema.parse(args);
3737
const client = new ApifyClient({ token: apifyToken });
@@ -47,12 +47,13 @@ export const getActorDetailsTool: ToolEntry = {
4747
};
4848
}
4949

50-
const inputSchema = (buildInfo.actorDefinition.input || {
51-
type: 'object',
52-
properties: {},
53-
}) as IActorInputSchema;
54-
inputSchema.properties = filterSchemaProperties(inputSchema.properties);
55-
inputSchema.properties = shortenProperties(inputSchema.properties);
50+
const inputSchema = transformActorInputForGetDetails(
51+
(buildInfo.actorDefinition.input || {
52+
type: 'object',
53+
properties: {},
54+
}) as IActorInputSchema,
55+
{ separateAdvancedInputs: !apifyMcpServer.options.fullActorSchema },
56+
);
5657

5758
// Use the actor formatter to get the main actor details
5859
const actorCard = formatActorToActorCard(actorInfo);

src/tools/utils.ts

Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -279,17 +279,48 @@ export function decodeDotPropertyNames(properties: Record<string, unknown>): Rec
279279
return decodedProperties;
280280
}
281281

282+
/**
283+
* Separates advanced inputs into a dedicated ADVANCED_INPUT_KEY object based on the first section-captioned property.
284+
* The properties from the first section-captioned property onwards are grouped under the advanced input object.
285+
*/
286+
export function separateAdvancedInputsInSchema(properties: Record<string, ISchemaProperties>): Record<string, ISchemaProperties> {
287+
const propertiesEntries = Object.entries(properties);
288+
const firstSectionCaptionIndex = propertiesEntries.findIndex(([_key, value]) => value.sectionCaption);
289+
if (firstSectionCaptionIndex === -1) {
290+
return properties;
291+
}
292+
293+
const mainInputs = propertiesEntries.slice(0, firstSectionCaptionIndex);
294+
const advancedInputs = propertiesEntries.slice(firstSectionCaptionIndex);
295+
296+
const propObject: Record<string, ISchemaProperties> = Object.fromEntries(mainInputs);
297+
// Use a basic object container here; additional details (like title/description) are not required at this stage
298+
propObject.advancedInput = {
299+
type: 'object',
300+
title: 'Advanced Inputs',
301+
description: 'These inputs are considered advanced and are not required for basic functionality.',
302+
properties: Object.fromEntries(advancedInputs),
303+
} as unknown as ISchemaProperties;
304+
305+
return propObject;
306+
}
307+
282308
export function transformActorInputSchemaProperties(
283309
input: Readonly<IActorInputSchema>,
284310
options?: { separateAdvancedInputs?: boolean },
285311
): ActorInputSchemaProperties {
286312
// Deep clone input to avoid mutating the original object
287313
const inputClone: IActorInputSchema = structuredClone(input);
288314

289-
// Optionally separate advanced inputs section into a single ADVANCED_INPUT_KEY object
290315
if (options?.separateAdvancedInputs) {
291-
const separated = separateAdvancedInputsInSchema(inputClone);
292-
inputClone.properties = separated;
316+
inputClone.properties = separateAdvancedInputsInSchema(inputClone.properties);
317+
if (inputClone.properties.advancedInput) {
318+
// Recursively transform the advanced input properties
319+
inputClone.properties.advancedInput.properties = transformActorInputSchemaProperties(
320+
inputClone.properties.advancedInput as IActorInputSchema,
321+
{ separateAdvancedInputs: false },
322+
);
323+
}
293324
}
294325

295326
let transformedProperties = markInputPropertiesAsRequired(inputClone);
@@ -302,32 +333,25 @@ export function transformActorInputSchemaProperties(
302333
return transformedProperties;
303334
}
304335

305-
/**
306-
* Separates advanced inputs into a dedicated ADVANCED_INPUT_KEY object based on the first section-captioned property.
307-
* The properties from the first section-captioned property onwards are grouped under the advanced input object.
308-
*/
309-
export function separateAdvancedInputsInSchema(input: IActorInputSchema): Record<string, ISchemaProperties> {
310-
if (!input || !input.properties) {
311-
return input.properties ?? {};
312-
}
336+
export function transformActorInputForGetDetails(
337+
input: Readonly<IActorInputSchema>,
338+
options?: { separateAdvancedInputs?: boolean },
339+
) {
340+
// Deep clone input to avoid mutating the original object
341+
const inputClone = structuredClone(input) as IActorInputSchema;
313342

314-
const propertiesEntries = Object.entries(input.properties);
315-
const firstSectionCaptionIndex = propertiesEntries.findIndex(([_key, value]) => value.sectionCaption);
316-
if (firstSectionCaptionIndex === -1) {
317-
return input.properties;
343+
if (options?.separateAdvancedInputs) {
344+
inputClone.properties = separateAdvancedInputsInSchema(inputClone.properties);
345+
if (inputClone.properties.advancedInput) {
346+
// Recursively transform the advanced input properties
347+
inputClone.properties.advancedInput = transformActorInputForGetDetails(
348+
inputClone.properties.advancedInput as IActorInputSchema,
349+
{ separateAdvancedInputs: false },
350+
) as ISchemaProperties;
351+
}
318352
}
319353

320-
const mainInputs = propertiesEntries.slice(0, firstSectionCaptionIndex);
321-
const advancedInputs = propertiesEntries.slice(firstSectionCaptionIndex);
322-
323-
const propObject: Record<string, ISchemaProperties> = Object.fromEntries(mainInputs);
324-
// Use a basic object container here; additional details (like title/description) are not required at this stage
325-
propObject.advancedInput = {
326-
type: 'object',
327-
title: 'Advanced Inputs',
328-
description: 'These inputs are considered advanced and are not required for basic functionality.',
329-
properties: Object.fromEntries(advancedInputs),
330-
} as unknown as ISchemaProperties;
331-
332-
return propObject;
354+
inputClone.properties = filterSchemaProperties(inputClone.properties);
355+
inputClone.properties = shortenProperties(inputClone.properties);
356+
return inputClone;
333357
}

tests/unit/tools.utils.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -786,4 +786,57 @@ describe('transformActorInputSchemaProperties', () => {
786786
transformActorInputSchemaProperties(input);
787787
expect(input).toEqual(inputCopy);
788788
});
789+
790+
it('should separate advanced inputs', () => {
791+
const input = {
792+
title: 'Test',
793+
type: 'object',
794+
required: ['query'],
795+
properties: {
796+
query: {
797+
type: 'string',
798+
title: 'Query',
799+
description: 'Query desc',
800+
},
801+
special: {
802+
type: 'string',
803+
title: 'Special',
804+
description: 'Special desc',
805+
sectionCaption: 'Special',
806+
},
807+
another: {
808+
type: 'string',
809+
title: 'Another',
810+
description: 'Another desc',
811+
},
812+
},
813+
};
814+
const result = transformActorInputSchemaProperties(input, { separateAdvancedInputs: true });
815+
expect(JSON.stringify(result, null, 2)).toMatchInlineSnapshot(`
816+
"{
817+
"query": {
818+
"title": "Query",
819+
"description": "**REQUIRED** Query desc",
820+
"type": "string"
821+
},
822+
"advancedInput": {
823+
"title": "Advanced Inputs",
824+
"description": "These inputs are considered advanced and are not required for basic functionality.",
825+
"type": "object",
826+
"properties": {
827+
"special": {
828+
"title": "Special",
829+
"description": "Special desc",
830+
"type": "string"
831+
},
832+
"another": {
833+
"title": "Another",
834+
"description": "Another desc",
835+
"type": "string"
836+
}
837+
}
838+
}
839+
}"
840+
`);
841+
});
789842
});

0 commit comments

Comments
 (0)