Skip to content

Commit 449deaa

Browse files
committed
build nested input schemas, fix WCC
1 parent 357dcd2 commit 449deaa

File tree

3 files changed

+141
-39
lines changed

3 files changed

+141
-39
lines changed

src/actors.ts

Lines changed: 105 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ApifyClient } from 'apify-client';
33

44
import { ACTOR_ADDITIONAL_INSTRUCTIONS, defaults, MAX_DESCRIPTION_LENGTH, ACTOR_README_MAX_LENGTH } from './const.js';
55
import { log } from './logger.js';
6-
import type { ActorDefinitionPruned, ActorDefinitionWithDesc, SchemaProperties, Tool } from './types.js';
6+
import type { ActorDefinitionPruned, ActorDefinitionWithDesc, IActorInputSchema, ISchemaProperties, Tool } from './types.js';
77

88
export function actorNameToToolName(actorName: string): string {
99
return actorName
@@ -67,7 +67,11 @@ function pruneActorDefinition(response: ActorDefinitionWithDesc): ActorDefinitio
6767
actorFullName: response.actorFullName || '',
6868
buildTag: response?.buildTag || '',
6969
readme: response?.readme || '',
70-
input: response?.input || null,
70+
input: response?.input && 'type' in response.input && 'properties' in response.input
71+
? { ...response.input,
72+
type: response.input.type as string,
73+
properties: response.input.properties as Record<string, ISchemaProperties> }
74+
: undefined,
7175
description: response.description,
7276
defaultRunOptions: response.defaultRunOptions,
7377
};
@@ -77,7 +81,7 @@ function pruneActorDefinition(response: ActorDefinitionWithDesc): ActorDefinitio
7781
* Shortens the description and enum values of schema properties.
7882
* @param properties
7983
*/
80-
export function shortenProperties(properties: { [key: string]: SchemaProperties}): { [key: string]: SchemaProperties } {
84+
export function shortenProperties(properties: { [key: string]: ISchemaProperties}): { [key: string]: ISchemaProperties } {
8185
for (const property of Object.values(properties)) {
8286
if (property.description.length > MAX_DESCRIPTION_LENGTH) {
8387
property.description = `${property.description.slice(0, MAX_DESCRIPTION_LENGTH)}...`;
@@ -106,10 +110,10 @@ export function truncateActorReadme(readme: string, limit = ACTOR_README_MAX_LEN
106110
* Helps determine the type of items in an array schema property.
107111
* Priority order: explicit type in items > prefill type > default value type > editor type.
108112
*/
109-
export function inferArrayItemType(property: SchemaProperties): string | null {
113+
function inferArrayItemType(property: ISchemaProperties): string | null {
110114
return property.items?.type
111-
|| (property.prefill?.length > 0 && typeof property.prefill[0])
112-
|| (property.default?.length > 0 && typeof property.default[0])
115+
|| (Array.isArray(property.prefill) && property.prefill.length > 0 && typeof property.prefill[0])
116+
|| (Array.isArray(property.default) && property.default.length > 0 && typeof property.default[0])
113117
|| (property.editor && getEditorItemType(property.editor))
114118
|| null;
115119

@@ -128,7 +132,7 @@ export function inferArrayItemType(property: SchemaProperties): string | null {
128132
* Add enum values as string to property descriptions.
129133
* @param properties
130134
*/
131-
export function addEnumsToDescriptionsWithExamples(properties: { [key: string]: SchemaProperties }): { [key: string]: SchemaProperties } {
135+
function addEnumsToDescriptionsWithExamples(properties: { [key: string]: ISchemaProperties }): { [key: string]: ISchemaProperties } {
132136
for (const property of Object.values(properties)) {
133137
if (property.enum && property.enum.length > 0) {
134138
property.description = `${property.description}\nPossible values: ${property.enum.join(',')}`;
@@ -146,21 +150,105 @@ export function addEnumsToDescriptionsWithExamples(properties: { [key: string]:
146150
* Filters schema properties to include only the necessary fields.
147151
* @param properties
148152
*/
149-
export function filterSchemaProperties(properties: { [key: string]: SchemaProperties }): { [key: string]: SchemaProperties } {
150-
const filteredProperties: { [key: string]: SchemaProperties } = {};
153+
export function filterSchemaProperties(properties: { [key: string]: ISchemaProperties }): { [key: string]: ISchemaProperties } {
154+
const filteredProperties: { [key: string]: ISchemaProperties } = {};
151155
for (const [key, property] of Object.entries(properties)) {
152-
const { title, description, enum: enumValues, type, default: defaultValue, prefill } = property;
153-
filteredProperties[key] = { title, description, enum: enumValues, type, default: defaultValue, prefill };
154-
if (type === 'array') {
156+
const { title, description, enum: enumValues, type,
157+
default: defaultValue, prefill, properties: subProperties,
158+
items, required } = property;
159+
filteredProperties[key] = { title,
160+
description,
161+
enum: enumValues,
162+
type,
163+
default: defaultValue,
164+
prefill,
165+
properties: subProperties,
166+
items,
167+
required };
168+
if (type === 'array' && !items?.type) {
155169
const itemsType = inferArrayItemType(property);
156170
if (itemsType) {
157-
filteredProperties[key].items = { type: itemsType };
171+
filteredProperties[key].items = {
172+
...filteredProperties[key].items,
173+
title: filteredProperties[key].title ?? 'Item',
174+
description: filteredProperties[key].description ?? 'Item',
175+
type: itemsType,
176+
};
158177
}
159178
}
160179
}
161180
return filteredProperties;
162181
}
163182

183+
/**
184+
* Marks input properties as required by adding a "REQUIRED" prefix to their descriptions.
185+
* Takes an IActorInput object and returns a modified Record of SchemaProperties.
186+
* @param {IActorInputSchema} input - Actor input object containing properties and required fields
187+
* @returns {Record<string, ISchemaProperties>} - Modified properties with required fields marked
188+
*/
189+
function markInputPropertiesAsRequired(input: IActorInputSchema): Record<string, ISchemaProperties> {
190+
const { required = [], properties } = input;
191+
192+
for (const property of Object.keys(properties)) {
193+
if (required.includes(property)) {
194+
properties[property] = {
195+
...properties[property],
196+
description: `**REQUIRED** ${properties[property].description}`,
197+
};
198+
}
199+
}
200+
201+
return properties;
202+
}
203+
204+
/**
205+
* Builds nested properties for object types in the schema.
206+
* @param {Record<string, ISchemaProperties>} properties - The input schema properties
207+
* @returns {Record<string, ISchemaProperties>} Modified properties with nested properties
208+
*/
209+
function buildNestedProperties(properties: Record<string, ISchemaProperties>): Record<string, ISchemaProperties> {
210+
const clonedProperties = { ...properties };
211+
212+
for (const [propertyName, property] of Object.entries(clonedProperties)) {
213+
if (property.type === 'object' && property.editor === 'proxy') {
214+
clonedProperties[propertyName] = {
215+
...property,
216+
properties: {
217+
...property.properties,
218+
useApifyProxy: {
219+
title: 'Use Apify Proxy',
220+
type: 'boolean',
221+
description: 'Whether to use Apify Proxy - ALWAYS SET TO TRUE.',
222+
default: true,
223+
examples: [true],
224+
},
225+
},
226+
required: ['useApifyProxy'],
227+
};
228+
} else if (property.type === 'array' && property.editor === 'requestListSources') {
229+
clonedProperties[propertyName] = {
230+
...property,
231+
items: {
232+
...property.items,
233+
type: 'object',
234+
title: 'Request list source',
235+
description: 'Request list source',
236+
properties: {
237+
url: {
238+
title: 'URL',
239+
type: 'string',
240+
description: 'URL of the request list source',
241+
},
242+
},
243+
},
244+
required: ['useApifyProxy'],
245+
};
246+
}
247+
}
248+
249+
return clonedProperties;
250+
}
251+
164252
/**
165253
* Fetches actor input schemas by Actor IDs or Actor full names and creates MCP tools.
166254
*
@@ -179,8 +267,10 @@ export async function getActorsAsTools(actors: string[]): Promise<Tool[]> {
179267
for (const result of results) {
180268
if (result) {
181269
if (result.input && 'properties' in result.input && result.input) {
182-
const properties = filterSchemaProperties(result.input.properties as { [key: string]: SchemaProperties });
183-
const propertiesShortened = shortenProperties(properties);
270+
const propertiesMarkedAsRequired = markInputPropertiesAsRequired(result.input);
271+
const propertiesObjectsBuilt = buildNestedProperties(propertiesMarkedAsRequired);
272+
const propertiesFiltered = filterSchemaProperties(propertiesObjectsBuilt);
273+
const propertiesShortened = shortenProperties(propertiesFiltered);
184274
result.input.properties = addEnumsToDescriptionsWithExamples(propertiesShortened);
185275
}
186276
try {

src/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import {
3434
searchActorsByKeywords,
3535
GetActorDefinition,
3636
} from './tools.js';
37-
import type { SchemaProperties, Tool } from './types.js';
37+
import type { ISchemaProperties, Tool } from './types.js';
3838

3939
/**
4040
* Create Apify MCP server
@@ -199,7 +199,7 @@ export class ApifyMcpServer {
199199
const parsed = GetActorDefinition.parse(args);
200200
const v = await getActorDefinition(parsed.actorName, parsed.limit);
201201
if (v && v.input && 'properties' in v.input && v.input) {
202-
const properties = filterSchemaProperties(v.input.properties as { [key: string]: SchemaProperties });
202+
const properties = filterSchemaProperties(v.input.properties as { [key: string]: ISchemaProperties });
203203
v.input.properties = shortenProperties(properties);
204204
}
205205
return { content: [{ type: 'text', text: JSON.stringify(v) }] };

src/types.ts

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,48 @@ export type Input = {
99
debugActorInput?: unknown;
1010
};
1111

12-
export interface ActorDefinitionPruned {
13-
id: string;
14-
actorFullName: string;
15-
buildTag?: string;
16-
readme?: string | null;
17-
input?: object | null;
12+
export interface ISchemaProperties {
13+
type: string;
14+
15+
title: string;
1816
description: string;
19-
defaultRunOptions: ActorDefaultRunOptions;
17+
18+
enum?: string[]; // Array of string options for the enum
19+
enumTitles?: string[]; // Array of string titles for the enum
20+
default?: unknown;
21+
prefill?: unknown;
22+
23+
items?: ISchemaProperties;
24+
editor?: string;
25+
examples?: unknown[];
26+
27+
properties?: Record<string, ISchemaProperties>;
28+
required?: string[];
2029
}
2130

22-
export interface ActorDefinitionWithDesc extends ActorDefinition {
31+
export interface IActorInputSchema {
32+
title?: string;
33+
description?: string;
34+
35+
type: string;
36+
37+
properties: Record<string, ISchemaProperties>;
38+
39+
required?: string[];
40+
schemaVersion?: number;
41+
}
42+
43+
export type ActorDefinitionWithDesc = Omit<ActorDefinition, 'input'> & {
2344
id: string;
2445
actorFullName: string;
2546
description: string;
26-
defaultRunOptions: ActorDefaultRunOptions
47+
defaultRunOptions: ActorDefaultRunOptions;
48+
input?: IActorInputSchema;
2749
}
2850

51+
export type ActorDefinitionPruned = Pick<ActorDefinitionWithDesc,
52+
'id' | 'actorFullName' | 'buildTag' | 'readme' | 'input' | 'description' | 'defaultRunOptions'>
53+
2954
export interface Tool {
3055
name: string;
3156
actorFullName: string;
@@ -35,19 +60,6 @@ export interface Tool {
3560
memoryMbytes?: number;
3661
}
3762

38-
export interface SchemaProperties {
39-
title: string;
40-
description: string;
41-
enum: string[]; // Array of string options for the enum
42-
enumTitles?: string[]; // Array of string titles for the enum
43-
type: string; // Data type (e.g., "string")
44-
default: string;
45-
prefill: string;
46-
items?: { type: string; }
47-
editor?: string;
48-
examples?: unknown[];
49-
}
50-
5163
// ActorStoreList for actor-search tool
5264
export interface ActorStats {
5365
totalRuns: number;

0 commit comments

Comments
 (0)