Skip to main content
Glama

actors-mcp-server

Official
by apify
MIT License
7,198
465
  • Apple
tools.utils.test.ts36.1 kB
import { describe, expect, it } from 'vitest'; import { ACTOR_ENUM_MAX_LENGTH, ACTOR_MAX_DESCRIPTION_LENGTH } from '../../src/const.js'; import { buildApifySpecificProperties, decodeDotPropertyNames, encodeDotPropertyNames, inferArrayItemsTypeIfMissing, inferArrayItemType, markInputPropertiesAsRequired, shortenEnum, shortenProperties, transformActorInputSchemaProperties } from '../../src/tools/utils.js'; import type { IActorInputSchema, ISchemaProperties } from '../../src/types.js'; describe('buildApifySpecificProperties', () => { it('should add resource picker structure to array items with editor resourcePicker', () => { const properties: Record<string, ISchemaProperties> = { resources: { type: 'array', title: 'Resources', description: 'Array of resources', editor: 'resourcePicker', }, otherProp: { type: 'string', title: 'Other property', description: 'Some other property', }, }; const result = buildApifySpecificProperties(properties); // Check that resourcePicker array has proper item structure (string type) expect(result.resources.items).toBeDefined(); expect(result.resources.items?.type).toBe('string'); expect(result.resources.items?.title).toBeDefined(); expect(result.resources.items?.description).toBeDefined(); // Check that other properties remain unchanged expect(result.otherProp).toEqual(properties.otherProp); }); it('should add key and value structure to array items with editor keyValue', () => { const properties: Record<string, ISchemaProperties> = { keyValuePairs: { type: 'array', title: 'Key-Value Pairs', description: 'Array of key-value pairs', editor: 'keyValue', }, otherProp: { type: 'string', title: 'Other property', description: 'Some other property', }, }; const result = buildApifySpecificProperties(properties); // Check that keyValue array has proper item structure expect(result.keyValuePairs.items).toBeDefined(); expect(result.keyValuePairs.items?.type).toBe('object'); expect(result.keyValuePairs.items?.properties?.key).toBeDefined(); expect(result.keyValuePairs.items?.properties?.key.type).toBe('string'); expect(result.keyValuePairs.items?.properties?.value).toBeDefined(); expect(result.keyValuePairs.items?.properties?.value.type).toBe('string'); // Check that other properties remain unchanged expect(result.otherProp).toEqual(properties.otherProp); }); it('should add globs structure to array items with editor globs', () => { const properties: Record<string, ISchemaProperties> = { globs: { type: 'array', title: 'Globs', description: 'Globs array', editor: 'globs', }, otherProp: { type: 'string', title: 'Other property', description: 'Some other property', }, }; const result = buildApifySpecificProperties(properties); // Check that globs array has proper item structure expect(result.globs.items).toBeDefined(); expect(result.globs.items?.type).toBe('object'); expect(result.globs.items?.properties?.glob).toBeDefined(); expect(result.globs.items?.properties?.glob.type).toBe('string'); expect(result.globs.items?.properties?.method).toBeDefined(); expect(result.globs.items?.properties?.method.type).toBe('string'); expect(result.globs.items?.properties?.payload).toBeDefined(); expect(result.globs.items?.properties?.payload.type).toBe('string'); expect(result.globs.items?.properties?.userData).toBeDefined(); expect(result.globs.items?.properties?.userData.type).toBe('object'); expect(result.globs.items?.properties?.headers).toBeDefined(); expect(result.globs.items?.properties?.headers.type).toBe('object'); // Check that other properties remain unchanged expect(result.otherProp).toEqual(properties.otherProp); }); it('should add pseudoUrls structure to array items with items.editor pseudoUrls', () => { const properties: Record<string, ISchemaProperties> = { pseudoUrls: { type: 'array', title: 'PseudoUrls', description: 'PseudoUrls array', editor: 'pseudoUrls', }, otherProp: { type: 'string', title: 'Other property', description: 'Some other property', }, }; const result = buildApifySpecificProperties(properties); // Check that pseudoUrls array has proper item structure expect(result.pseudoUrls.items).toBeDefined(); expect(result.pseudoUrls.items?.type).toBe('object'); expect(result.pseudoUrls.items?.properties?.purl).toBeDefined(); expect(result.pseudoUrls.items?.properties?.purl.type).toBe('string'); expect(result.pseudoUrls.items?.properties?.method).toBeDefined(); expect(result.pseudoUrls.items?.properties?.method.type).toBe('string'); expect(result.pseudoUrls.items?.properties?.payload).toBeDefined(); expect(result.pseudoUrls.items?.properties?.payload.type).toBe('string'); expect(result.pseudoUrls.items?.properties?.userData).toBeDefined(); expect(result.pseudoUrls.items?.properties?.userData.type).toBe('object'); expect(result.pseudoUrls.items?.properties?.headers).toBeDefined(); expect(result.pseudoUrls.items?.properties?.headers.type).toBe('object'); // Check that other properties remain unchanged expect(result.otherProp).toEqual(properties.otherProp); }); it('should add useApifyProxy, apifyProxyGroups, and proxyUrls properties to proxy objects', () => { const properties: Record<string, ISchemaProperties> = { proxy: { type: 'object', editor: 'proxy', title: 'Proxy configuration', description: 'Proxy settings', properties: {}, }, otherProp: { type: 'string', title: 'Other property', description: 'Some other property', }, }; const result = buildApifySpecificProperties(properties); // Check that proxy object has useApifyProxy property expect(result.proxy.properties).toBeDefined(); expect(result.proxy.properties?.useApifyProxy).toBeDefined(); expect(result.proxy.properties?.useApifyProxy.type).toBe('boolean'); expect(result.proxy.properties?.useApifyProxy.default).toBe(true); expect(result.proxy.required).toContain('useApifyProxy'); // Check that proxy object has apifyProxyGroups property expect(result.proxy.properties?.apifyProxyGroups).toBeDefined(); expect(result.proxy.properties?.apifyProxyGroups.type).toBe('array'); expect(result.proxy.properties?.apifyProxyGroups.items).toBeDefined(); expect(result.proxy.properties?.apifyProxyGroups.items?.enum).toEqual([ 'RESIDENTIAL', 'DATACENTER', ]); // Check that proxy object has proxyUrls property expect(result.proxy.properties?.proxyUrls).toBeDefined(); expect(result.proxy.properties?.proxyUrls.type).toBe('array'); expect(result.proxy.properties?.proxyUrls.items).toBeDefined(); expect(result.proxy.properties?.proxyUrls.items?.type).toBe('string'); // Check that other properties remain unchanged expect(result.otherProp).toEqual(properties.otherProp); }); it('should add URL structure to requestListSources array items', () => { const properties: Record<string, ISchemaProperties> = { sources: { type: 'array', editor: 'requestListSources', title: 'Request list sources', description: 'Sources to scrape', }, otherProp: { type: 'string', title: 'Other property', description: 'Some other property', }, }; const result = buildApifySpecificProperties(properties); // Check that requestListSources array has proper item structure expect(result.sources.items).toBeDefined(); expect(result.sources.items?.type).toBe('object'); expect(result.sources.items?.properties?.url).toBeDefined(); expect(result.sources.items?.properties?.url.type).toBe('string'); // Check that other properties remain unchanged expect(result.otherProp).toEqual(properties.otherProp); }); it('should not modify properties that don\'t match special cases', () => { const properties: Record<string, ISchemaProperties> = { regularObject: { type: 'object', title: 'Regular object', description: 'A regular object without special editor', properties: { subProp: { type: 'string', title: 'Sub property', description: 'Sub property description', }, }, }, regularArray: { type: 'array', title: 'Regular array', description: 'A regular array without special editor', items: { type: 'string', title: 'Item', description: 'Item description', }, }, }; const result = buildApifySpecificProperties(properties); // Check that regular properties remain unchanged expect(result).toEqual(properties); }); it('should handle empty properties object', () => { const properties: Record<string, ISchemaProperties> = {}; const result = buildApifySpecificProperties(properties); expect(result).toEqual({}); }); }); describe('markInputPropertiesAsRequired', () => { it('should add REQUIRED prefix to required properties', () => { const input: IActorInputSchema = { title: 'Test Schema', type: 'object', required: ['requiredProp1', 'requiredProp2'], properties: { requiredProp1: { type: 'string', title: 'Required Property 1', description: 'This is required', }, requiredProp2: { type: 'number', title: 'Required Property 2', description: 'This is also required', }, optionalProp: { type: 'boolean', title: 'Optional Property', description: 'This is optional', }, }, }; const result = markInputPropertiesAsRequired(input); // Check that required properties have REQUIRED prefix expect(result.requiredProp1.description).toContain('**REQUIRED**'); expect(result.requiredProp2.description).toContain('**REQUIRED**'); // Check that optional properties remain unchanged expect(result.optionalProp.description).toBe('This is optional'); }); it('should handle input without required fields', () => { const input: IActorInputSchema = { title: 'Test Schema', type: 'object', properties: { prop1: { type: 'string', title: 'Property 1', description: 'Description 1', }, prop2: { type: 'number', title: 'Property 2', description: 'Description 2', }, }, }; const result = markInputPropertiesAsRequired(input); // Check that no properties were modified expect(result).toEqual(input.properties); }); it('should handle empty required array', () => { const input: IActorInputSchema = { title: 'Test Schema', type: 'object', required: [], properties: { prop1: { type: 'string', title: 'Property 1', description: 'Description 1', }, }, }; const result = markInputPropertiesAsRequired(input); // Check that no properties were modified expect(result).toEqual(input.properties); }); }); describe('shortenProperties', () => { it('should truncate long descriptions', () => { const longDescription = 'a'.repeat(ACTOR_MAX_DESCRIPTION_LENGTH + 100); const properties: Record<string, ISchemaProperties> = { prop1: { type: 'string', title: 'Property 1', description: longDescription, }, }; const result = shortenProperties(properties); // Check that description was truncated expect(result.prop1.description.length).toBeLessThanOrEqual(ACTOR_MAX_DESCRIPTION_LENGTH + 3); // +3 for "..." expect(result.prop1.description.endsWith('...')).toBe(true); }); it('should not modify descriptions that are within limits', () => { const description = 'This is a normal description'; const properties: Record<string, ISchemaProperties> = { prop1: { type: 'string', title: 'Property 1', description, }, }; const result = shortenProperties(properties); // Check that description was not modified expect(result.prop1.description).toBe(description); }); it('should shorten enum values if they exceed the limit', () => { // Create an enum with many values to exceed the character limit const value = 'enum-value-'; const enumValues = Array.from({ length: Math.ceil(ACTOR_ENUM_MAX_LENGTH / value.length) + 1 }, (_, i) => `${value}${i}`); const properties: Record<string, ISchemaProperties> = { prop1: { type: 'string', title: 'Property 1', description: 'Property with enum', enum: enumValues, }, }; const result = shortenProperties(properties); // Check that enum was shortened expect(result.prop1.enum).toBeDefined(); if (result.prop1.enum) { expect(result.prop1.enum.length).toBeLessThan(enumValues.length); const totalEnumLen = result.prop1.enum.reduce((sum, v) => sum + v.length, 0); expect(totalEnumLen).toBeLessThanOrEqual(ACTOR_ENUM_MAX_LENGTH); // Calculate total character length of enum values const totalLength = result.prop1.enum.reduce((sum, val) => sum + val.length, 0); expect(totalLength).toBeLessThanOrEqual(ACTOR_ENUM_MAX_LENGTH); } else { expect(result.prop1.enum).toBeUndefined(); } }); it('should shorten items.enum values if they exceed the limit', () => { // Create an enum with many values to exceed the character limit const value = 'enum-value-'; const enumValues = Array.from({ length: Math.ceil(ACTOR_ENUM_MAX_LENGTH / value.length) + 1 }, (_, i) => `${value}${i}`); const properties: Record<string, ISchemaProperties> = { prop1: { type: 'array', title: 'Property 1', description: 'Property with items.enum', items: { type: 'string', title: 'Item', description: 'Item description', enum: enumValues, }, }, }; const result = shortenProperties(properties); // Check that items.enum was shortened expect(result.prop1.items?.enum).toBeDefined(); if (result.prop1.items?.enum) { expect(result.prop1.items.enum.length).toBeLessThan(enumValues.length); const totalLength = result.prop1.items.enum.reduce((sum, val) => sum + val.length, 0); expect(totalLength).toBeLessThanOrEqual(ACTOR_ENUM_MAX_LENGTH); // Calculate total character length of enum values expect(totalLength).toBeLessThanOrEqual(ACTOR_ENUM_MAX_LENGTH); } else { expect(result.prop1.items?.enum).toBeUndefined(); } }); it('should handle properties without enum or items.enum', () => { const properties: Record<string, ISchemaProperties> = { prop1: { type: 'string', title: 'Property 1', description: 'Regular property', }, prop2: { type: 'array', title: 'Property 2', description: 'Array property', items: { type: 'string', title: 'Item', description: 'Item description', }, }, }; const result = shortenProperties(properties); // Check that properties were not modified expect(result).toEqual(properties); }); it('should handle empty enum arrays', () => { const properties: Record<string, ISchemaProperties> = { prop1: { type: 'string', title: 'Property 1', description: 'Property with empty enum', enum: [], }, prop2: { type: 'array', title: 'Property 2', description: 'Array with empty items.enum', items: { type: 'string', title: 'Item', description: 'Item description', enum: [], }, }, }; const result = shortenProperties(properties); // Check that properties were not modified expect(result).toEqual(properties); }); }); describe('encodeDotPropertyNames', () => { it('should replace dots in property names with -dot-', () => { const input = { 'foo.bar': { type: 'string', title: 'Foo Bar', description: 'desc' }, baz: { type: 'number', title: 'Baz', description: 'desc2' }, 'a.b.c': { type: 'boolean', title: 'A B C', description: 'desc3' }, }; const result = encodeDotPropertyNames(input); expect(result['foo-dot-bar']).toBeDefined(); expect(result['a-dot-b-dot-c']).toBeDefined(); expect(result.baz).toBeDefined(); expect(result['foo.bar']).toBeUndefined(); expect(result['a.b.c']).toBeUndefined(); }); it('should not modify property names without dots', () => { const input = { foo: { type: 'string', title: 'Foo', description: 'desc' }, bar: { type: 'number', title: 'Bar', description: 'desc2' }, }; const result = encodeDotPropertyNames(input); expect(result).toEqual(input); }); }); describe('decodeDotPropertyNames', () => { it('should replace -dot- in property names with dots', () => { const input = { 'foo-dot-bar': { type: 'string', title: 'Foo Bar', description: 'desc' }, baz: { type: 'number', title: 'Baz', description: 'desc2' }, 'a-dot-b-dot-c': { type: 'boolean', title: 'A B C', description: 'desc3' }, }; const result = decodeDotPropertyNames(input); expect(result['foo.bar']).toBeDefined(); expect(result['a.b.c']).toBeDefined(); expect(result.baz).toBeDefined(); expect(result['foo-dot-bar']).toBeUndefined(); expect(result['a-dot-b-dot-c']).toBeUndefined(); }); it('should not modify property names without -dot-', () => { const input = { foo: { type: 'string', title: 'Foo', description: 'desc' }, bar: { type: 'number', title: 'Bar', description: 'desc2' }, }; const result = decodeDotPropertyNames(input); expect(result).toEqual(input); }); }); // ---------------------- // Tests for transformActorInputSchemaProperties // ---------------------- describe('transformActorInputSchemaProperties', () => { it('should correctly transform a schema with all Apify-specific types and features', () => { const input: IActorInputSchema = { title: 'Complex Schema', type: 'object', required: [ 'resourcePicker', 'keyValue', 'globs', 'pseudoUrls', 'proxy', 'requestListSources', 'simpleString', 'enumString', 'arrayOfStrings', 'dotted.name', ], properties: { resourcePicker: { type: 'array', title: 'Resource Picker', description: 'Pick a resource', editor: 'resourcePicker', }, keyValue: { type: 'array', title: 'Key Value', description: 'Key value pairs', editor: 'keyValue', }, globs: { type: 'array', title: 'Globs', description: 'Globs array', editor: 'globs', }, pseudoUrls: { type: 'array', title: 'PseudoUrls', description: 'PseudoUrls array', editor: 'pseudoUrls', }, proxy: { type: 'object', title: 'Proxy', description: 'Proxy config', editor: 'proxy', properties: {}, }, requestListSources: { type: 'array', title: 'Request List Sources', description: 'Sources', editor: 'requestListSources', }, simpleString: { type: 'string', title: 'Simple String', description: 'A simple string', }, enumString: { type: 'string', title: 'Enum String', description: 'A string with enum', enum: ['A', 'B', 'C'], default: 'A', }, arrayOfStrings: { type: 'array', title: 'Array of Strings', description: 'An array of strings', prefill: ['foo', 'bar'], }, 'dotted.name': { type: 'number', title: 'Dotted Name', description: 'A property with a dot in its name', }, }, }; const result = transformActorInputSchemaProperties(input); // Resource Picker expect(result.resourcePicker).toBeDefined(); expect(result.resourcePicker.items).toBeDefined(); expect(result.resourcePicker.items?.type).toBe('string'); expect(result.resourcePicker.description).toContain('**REQUIRED**'); // Key Value expect(result.keyValue).toBeDefined(); expect(result.keyValue.items).toBeDefined(); expect(result.keyValue.items?.type).toBe('object'); expect(result.keyValue.items?.properties?.key).toBeDefined(); expect(result.keyValue.items?.properties?.value).toBeDefined(); expect(result.keyValue.description).toContain('**REQUIRED**'); // Globs expect(result.globs).toBeDefined(); expect(result.globs.items).toBeDefined(); expect(result.globs.items?.properties?.glob).toBeDefined(); expect(result.globs.items?.properties?.userData).toBeDefined(); expect(result.globs.description).toContain('**REQUIRED**'); // PseudoUrls expect(result.pseudoUrls).toBeDefined(); expect(result.pseudoUrls.items).toBeDefined(); expect(result.pseudoUrls.items?.properties?.purl).toBeDefined(); expect(result.pseudoUrls.items?.properties?.method).toBeDefined(); expect(result.pseudoUrls.description).toContain('**REQUIRED**'); // Proxy expect(result.proxy).toBeDefined(); expect(result.proxy.properties?.useApifyProxy).toBeDefined(); expect(result.proxy.properties?.apifyProxyGroups).toBeDefined(); expect(result.proxy.properties?.proxyUrls).toBeDefined(); expect(result.proxy.required).toContain('useApifyProxy'); expect(result.proxy.description).toContain('**REQUIRED**'); // Request List Sources expect(result.requestListSources).toBeDefined(); expect(result.requestListSources.items).toBeDefined(); expect(result.requestListSources.items?.properties?.url).toBeDefined(); expect(result.requestListSources.description).toContain('**REQUIRED**'); // Simple String expect(result.simpleString).toBeDefined(); expect(result.simpleString.type).toBe('string'); expect(result.simpleString.description).toContain('**REQUIRED**'); // Enum String expect(result.enumString).toBeDefined(); expect(result.enumString.enum).toBeDefined(); expect(result.enumString.description).toContain('Possible values:'); expect(result.enumString.description).toContain('Example values:'); expect(result.enumString.description).toContain('**REQUIRED**'); // Array of Strings expect(result.arrayOfStrings).toBeDefined(); expect(result.arrayOfStrings.items).toBeDefined(); expect(result.arrayOfStrings.items?.type).toBe('string'); expect(result.arrayOfStrings.description).toContain('**REQUIRED**'); // Dotted property name expect(result['dotted-dot-name']).toBeDefined(); expect(result['dotted-dot-name'].type).toBe('number'); expect(result['dotted-dot-name'].description).toContain('**REQUIRED**'); // Should not have the original dotted name expect(result['dotted.name']).toBeUndefined(); }); it('should apply all transformations in the correct order', () => { const input = { title: 'Test', type: 'object', required: ['foo.bar', 'enumProp'], properties: { 'foo.bar': { type: 'string', title: 'Foo Bar', description: 'desc', }, proxy: { type: 'object', editor: 'proxy', title: 'Proxy', description: 'Proxy desc', properties: {}, }, sources: { type: 'array', editor: 'requestListSources', title: 'Sources', description: 'Sources desc', }, enumProp: { type: 'string', title: 'Enum', description: 'Enum desc', enum: Array.from({ length: 30 }, (_, i) => `val${i}`), }, longDesc: { type: 'string', title: 'Long', description: 'a'.repeat(ACTOR_MAX_DESCRIPTION_LENGTH + 10), }, }, }; const result = transformActorInputSchemaProperties(input); // 1. markInputPropertiesAsRequired: required fields get **REQUIRED** in description expect(result['foo-dot-bar'].description).toContain('**REQUIRED**'); expect(result.enumProp.description).toContain('**REQUIRED**'); // 2. buildNestedProperties: proxy gets useApifyProxy, sources gets url expect(result.proxy.properties).toBeDefined(); expect(result.proxy.properties?.useApifyProxy).toBeDefined(); expect(result.sources.items).toBeDefined(); expect(result.sources.items?.properties?.url).toBeDefined(); // 3. filterSchemaProperties: only allowed fields present expect(Object.keys(result['foo-dot-bar'])).toEqual( expect.arrayContaining(['title', 'description', 'type', 'default', 'prefill', 'properties', 'items', 'required', 'enum']), ); // 4. shortenProperties: longDesc is truncated, enumProp.enum is shortened expect(result.longDesc.description.length).toBeLessThanOrEqual(ACTOR_MAX_DESCRIPTION_LENGTH + 3); if (result.enumProp.enum) { expect(result.enumProp.enum.length).toBeLessThanOrEqual(30); const totalEnumLen = result.enumProp.enum.reduce((sum, v) => sum + v.length, 0); expect(totalEnumLen).toBeLessThanOrEqual(ACTOR_ENUM_MAX_LENGTH); } else { // If enum is too long, it may be set to undefined expect(result.enumProp.enum).toBeUndefined(); } // 5. addEnumsToDescriptionsWithExamples: enum values in description expect(result.enumProp.description).toMatch(/Possible values:/); // 6. encodeDotPropertyNames: foo.bar becomes foo-dot-bar expect(result['foo-dot-bar']).toBeDefined(); expect(result['foo.bar']).toBeUndefined(); }); it('should handle input with no required, no enums, no dots', () => { const input = { title: 'Simple', type: 'object', properties: { simple: { type: 'string', title: 'Simple', description: 'desc', }, }, }; const result = transformActorInputSchemaProperties(input); expect(result.simple.description).toBe('desc'); expect(result.simple.enum).toBeUndefined(); expect(result.simple).toBeDefined(); }); it('should encode all dotted property names', () => { const input = { title: 'Dots', type: 'object', properties: { 'a.b': { type: 'string', title: 'A B', description: 'desc' }, 'c.d.e': { type: 'number', title: 'CDE', description: 'desc2' }, }, }; const result = transformActorInputSchemaProperties(input); expect(result['a-dot-b']).toBeDefined(); expect(result['c-dot-d-dot-e']).toBeDefined(); expect(result['a.b']).toBeUndefined(); expect(result['c.d.e']).toBeUndefined(); }); it('should not mutate the input object', () => { const input = { title: 'Immut', type: 'object', required: ['foo'], properties: { foo: { type: 'string', title: 'Foo', description: 'desc' }, }, }; const inputCopy = JSON.parse(JSON.stringify(input)); transformActorInputSchemaProperties(input); expect(input).toEqual(inputCopy); }); it('should build array items property correctly for stringList editor with place IDs', () => { const input: IActorInputSchema = { type: 'object', schemaVersion: 1, properties: { placeIds: { title: '🗃 Place IDs', type: 'array', description: 'List of place IDs.', editor: 'stringList', }, }, }; const result = transformActorInputSchemaProperties(input); // Verify that array items type was correctly inferred and set expect(result.placeIds.type).toBe('array'); expect(result.placeIds.items).toBeDefined(); expect(result.placeIds.items?.type).toBe('string'); expect(result.placeIds.items?.title).toBe('🗃 Place IDs'); expect(result.placeIds.items?.description).toBe(input.properties.placeIds.description); // Verify that the property name was encoded (dots replaced with -dot-) expect(result.placeIds).toBeDefined(); // Verify that other transformations were applied expect(result.placeIds.title).toBe('🗃 Place IDs'); expect(result.placeIds.description).toBe(input.properties.placeIds.description); }); }); describe('inferArrayItemType', () => { it('infers array item type from editor', () => { const property = { type: 'array', editor: 'stringList', title: '', description: '', enum: [], default: '', prefill: '', }; expect(inferArrayItemType(property)).toBe('string'); }); it('infers string type for stringList editor with place IDs input', () => { const property: ISchemaProperties = { title: 'Place IDs', type: 'array', description: 'List of place IDs.', editor: 'stringList', }; expect(inferArrayItemType(property)).toBe('string'); }); }); describe('inferArrayItemsTypeIfMissing', () => { it('should infer and set items type for array property with stringList editor', () => { const properties: { [key: string]: ISchemaProperties } = { placeIds: { title: '🗃 Place IDs', type: 'array', description: 'List of place IDs.', editor: 'stringList', }, }; const result = inferArrayItemsTypeIfMissing(properties); expect(result.placeIds.items).toBeDefined(); expect(result.placeIds.items?.type).toBe('string'); expect(result.placeIds.items?.title).toBe('🗃 Place IDs'); expect(result.placeIds.items?.description).toBe(properties.placeIds.description); }); it('should not modify array properties that already have items.type defined', () => { const properties: { [key: string]: ISchemaProperties } = { existingArray: { title: 'Existing Array', type: 'array', description: 'Array with existing items type', items: { type: 'number', title: 'Number Item', description: 'A number item', }, }, }; const result = inferArrayItemsTypeIfMissing(properties); expect(result.existingArray.items?.type).toBe('number'); expect(result.existingArray.items?.title).toBe('Number Item'); expect(result.existingArray.items?.description).toBe('A number item'); }); }); describe('shortenEnum', () => { it('shorten enum list', () => { const enumList: string[] = []; const wordLength = 100; const wordCount = ACTOR_ENUM_MAX_LENGTH / wordLength + 1; // exceed the limit for (let i = 0; i < wordCount; i++) { enumList.push('a'.repeat(wordLength)); } const shortenedList = shortenEnum(enumList); expect(shortenedList?.length || 0).toBe(ACTOR_ENUM_MAX_LENGTH / wordLength); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/apify/actors-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server