Skip to content
16 changes: 16 additions & 0 deletions src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,19 @@ export const ACTOR_ADDITIONAL_INSTRUCTIONS = `Never call/execute tool/Actor unle

export const TOOL_CACHE_MAX_SIZE = 500;
export const TOOL_CACHE_TTL_SECS = 30 * 60;

export const ACTOR_PRICING_MODEL = {
/** Rental actors */
FLAT_PRICE_PER_MONTH: 'FLAT_PRICE_PER_MONTH',
FREE: 'FREE',
/** Pay per result (PPR) actors */
PRICE_PER_DATASET_ITEM: 'PRICE_PER_DATASET_ITEM',
/** Pay per event (PPE) actors */
PAY_PER_EVENT: 'PAY_PER_EVENT',
} as const;

/**
* Used in search Actors tool to search above the input supplied limit,
* so we can safely filter out rental Actors from the search and ensure we return some results.
*/
export const ACTOR_SEARCH_ABOVE_LIMIT = 50;
28 changes: 23 additions & 5 deletions src/tools/store_collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { z } from 'zod';
import zodToJsonSchema from 'zod-to-json-schema';

import { ApifyClient } from '../apify-client.js';
import { HelperTools } from '../const.js';
import type { ActorStorePruned, HelperTool, PricingInfo, ToolEntry } from '../types.js';
import { ACTOR_SEARCH_ABOVE_LIMIT, HelperTools } from '../const.js';
import type { ActorPricingModel, ActorStorePruned, HelperTool, PricingInfo, ToolEntry } from '../types.js';

function pruneActorStoreInfo(response: ActorStoreList): ActorStorePruned {
const stats = response.stats || {};
Expand Down Expand Up @@ -38,7 +38,7 @@ export async function searchActorsByKeywords(
apifyToken: string,
limit: number | undefined = undefined,
offset: number | undefined = undefined,
): Promise<ActorStorePruned[] | null> {
): Promise<ActorStorePruned[]> {
const client = new ApifyClient({ token: apifyToken });
const results = await client.store().list({ search, limit, offset });
return results.items.map((x) => pruneActorStoreInfo(x));
Expand Down Expand Up @@ -68,6 +68,22 @@ export const searchActorsArgsSchema = z.object({
.describe('Filters the results by the specified category.'),
});

/**
* Filters out actors with the 'FLAT_PRICE_PER_MONTH' pricing model (rental actors)..
*
* Returns new array of Actors excluding those with 'FLAT_PRICE_PER_MONTH' pricing model.
*
* @param actors - Array of ActorStorePruned objects to filter.
* @returns Array of actors excluding those with 'FLAT_PRICE_PER_MONTH' pricing model.
*/
function filterRentalActors(
actors: ActorStorePruned[],
): ActorStorePruned[] {
// Store list API does not support filtering by two pricing models at once,
// so we filter the results manually after fetching them.
return actors.filter((actor) => (actor.currentPricingInfo.pricingModel as ActorPricingModel) !== 'FLAT_PRICE_PER_MONTH');
}

/**
* https://docs.apify.com/api/v2/store-get
*/
Expand All @@ -88,12 +104,14 @@ export const searchActors: ToolEntry = {
call: async (toolArgs) => {
const { args, apifyToken } = toolArgs;
const parsed = searchActorsArgsSchema.parse(args);
const actors = await searchActorsByKeywords(
let actors = await searchActorsByKeywords(
parsed.search,
apifyToken,
parsed.limit,
parsed.limit + ACTOR_SEARCH_ABOVE_LIMIT,
parsed.offset,
);
actors = filterRentalActors(actors || []).slice(0, parsed.limit);

return { content: actors?.map((item) => ({ type: 'text', text: JSON.stringify(item) })) };
},
} as HelperTool,
Expand Down
4 changes: 4 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
import type { ValidateFunction } from 'ajv';
import type { ActorDefaultRunOptions, ActorDefinition } from 'apify-client';

import type { ACTOR_PRICING_MODEL } from './const.js';
import type { ActorsMcpServer } from './mcp/server.js';

export interface ISchemaProperties {
Expand Down Expand Up @@ -190,3 +191,6 @@ export interface ToolCacheEntry {
expiresAt: number;
tool: ToolEntry;
}

// Utility type to get the union of values of an object type
export type ActorPricingModel = (typeof ACTOR_PRICING_MODEL)[keyof typeof ACTOR_PRICING_MODEL];