From bb1c10b24b06bcfb28cf175e61cdd1c5b9bc1bed Mon Sep 17 00:00:00 2001 From: MQ Date: Tue, 1 Apr 2025 22:10:35 +0200 Subject: [PATCH 01/42] wip snapshot, decouple logic, split stdio cli, http server and prepare library entrypoint --- package.json | 2 +- src/examples/clientSse.ts | 7 +- src/index.ts | 53 +------- src/main.ts | 104 ++------------- src/mcp-server.ts | 253 +++++++++++++++++++++++++++++++++++ src/server.ts | 270 +++++++++----------------------------- src/stdio.ts | 49 +++++++ src/utils.ts | 30 +++++ tsconfig.json | 3 +- 9 files changed, 419 insertions(+), 352 deletions(-) create mode 100644 src/mcp-server.ts create mode 100644 src/stdio.ts create mode 100644 src/utils.ts diff --git a/package.json b/package.json index 89d4af45..57907acc 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "main": "dist/index.js", "bin": { - "actors-mcp-server": "./dist/index.js" + "actors-mcp-server": "./dist/stdio.js" }, "files": [ "dist", diff --git a/src/examples/clientSse.ts b/src/examples/clientSse.ts index 765e2135..bf9b4e7f 100644 --- a/src/examples/clientSse.ts +++ b/src/examples/clientSse.ts @@ -23,10 +23,11 @@ const dirname = path.dirname(filename); dotenv.config({ path: path.resolve(dirname, '../../.env') }); -const SERVER_URL = 'https://actors-mcp-server.apify.actor/sse'; +const SERVER_URL = process.env.MCP_SERVER_URL_BASE || 'https://actors-mcp-server.apify.actor/sse'; // We need to change forward slash / to underscore -- in the tool name as Anthropic does not allow forward slashes in the tool name const SELECTED_TOOL = actorNameToToolName('apify/rag-web-browser'); -const QUERY = 'web browser for Anthropic'; +//const QUERY = 'web browser for Anthropic'; +const QUERY = 'apify'; if (!process.env.APIFY_TOKEN) { console.error('APIFY_TOKEN is required but not set in the environment variables.'); @@ -98,6 +99,8 @@ async function main(): Promise { } else { console.error('An unknown error occurred:', error); } + } finally { + await client.close(); } } diff --git a/src/index.ts b/src/index.ts index db73e8e7..8c7fcbf9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,49 +1,4 @@ -#!/usr/bin/env node -/** - * This script initializes and starts the Apify MCP server using the Stdio transport. - * - * Usage: - * node --actors= - * - * Command-line arguments: - * --actors - A comma-separated list of actor full names to add to the server. - * - * Example: - * node index.js --actors=apify/google-search-scraper,apify/instagram-scraper - */ - -import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import minimist from 'minimist'; - -import { log } from './logger.js'; -import { ApifyMcpServer } from './server.js'; -import { getActorDiscoveryTools, getActorAutoLoadingTools } from './tools.js'; - -log.setLevel(log.LEVELS.ERROR); - -const argv = minimist(process.argv.slice(2)); -const argActors = argv.actors?.split(',').map((actor: string) => actor.trim()) || []; -const argEnableActorAutoLoading = argv.enableActorAutoLoading || false; - -if (!process.env.APIFY_TOKEN) { - log.error('APIFY_TOKEN is required but not set in the environment variables.'); - process.exit(1); -} - -async function main() { - const server = new ApifyMcpServer(); - await (argActors.length !== 0 - ? server.addToolsFromActors(argActors) - : server.addToolsFromDefaultActors()); - server.updateTools(getActorDiscoveryTools()); - if (argEnableActorAutoLoading) { - server.updateTools(getActorAutoLoadingTools()); - } - const transport = new StdioServerTransport(); - await server.connect(transport); -} - -main().catch((error) => { - console.error('Server error:', error); // eslint-disable-line no-console - process.exit(1); -}); +/* + This file provides essential functions for constructing HTTP and MCP servers, effectively serving as a library. + Acts as a library entrypoint. +*/ diff --git a/src/main.ts b/src/main.ts index 59e02fa8..59c5888a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,9 @@ -import type { ParsedUrlQuery } from 'querystring'; -import { parse } from 'querystring'; +/* +This file serves as an Actor MCP SSE server entry point. +*/ + +import type { ParsedUrlQuery } from 'node:querystring'; +import { parse } from 'node:querystring'; import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import { Actor } from 'apify'; @@ -10,30 +14,25 @@ import express from 'express'; import { HEADER_READINESS_PROBE, Routes } from './const.js'; import { processInput } from './input.js'; import { log } from './logger.js'; -import { ApifyMcpServer } from './server.js'; +import { ApifyMcpServer } from './mcp-server.js'; +import { createServerApp } from './server.js'; import { getActorDiscoveryTools, getActorAutoLoadingTools } from './tools.js'; import type { Input } from './types.js'; +import { isActorStandby } from './utils.js'; await Actor.init(); -const STANDBY_MODE = Actor.getEnv().metaOrigin === 'STANDBY'; -const HOST = Actor.isAtHome() ? process.env.ACTOR_STANDBY_URL : 'http://localhost'; -const PORT = Actor.isAtHome() ? process.env.ACTOR_STANDBY_PORT : 3001; +const HOST = Actor.isAtHome() ? process.env.ACTOR_STANDBY_URL as string : 'http://localhost'; +const PORT = Actor.isAtHome() ? Number(process.env.ACTOR_STANDBY_PORT) : 3001; if (!process.env.APIFY_TOKEN) { log.error('APIFY_TOKEN is required but not set in the environment variables.'); process.exit(1); } -const app = express(); - const mcpServer = new ApifyMcpServer(); -let transport: SSEServerTransport; -const HELP_MESSAGE = `Connect to the server with GET request to ${HOST}/sse?token=YOUR-APIFY-TOKEN` - + ` and then send POST requests to ${HOST}/message?token=YOUR-APIFY-TOKEN`; - -const actorRun = Actor.isAtHome() ? { +const actorRunData = Actor.isAtHome() ? { id: process.env.ACTOR_RUN_ID, actId: process.env.ACTOR_ID, userId: process.env.APIFY_USER_ID, @@ -56,86 +55,11 @@ const actorRun = Actor.isAtHome() ? { standbyUrl: process.env.ACTOR_STANDBY_URL, } : {}; -/** - * Process input parameters and update tools - * If URL contains query parameter actors, add tools from actors, otherwise add tools from default actors - * @param url - */ -async function processParamsAndUpdateTools(url: string) { - const params = parse(url.split('?')[1] || '') as ParsedUrlQuery; - delete params.token; - log.debug(`Received input parameters: ${JSON.stringify(params)}`); - const input = await processInput(params as unknown as Input); - if (input.actors) { - await mcpServer.addToolsFromActors(input.actors as string[]); - } - if (input.enableActorAutoLoading) { - mcpServer.updateTools(getActorAutoLoadingTools()); - } - log.debug(`Server is running in STANDBY mode with the following Actors (tools): ${mcpServer.getToolNames()}. - To use different Actors, provide them in query parameter "actors" or include them in the Actor Task input.`); -} - -app.route(Routes.ROOT) - .get(async (req: Request, res: Response) => { - if (req.headers && req.get(HEADER_READINESS_PROBE) !== undefined) { - log.debug('Received readiness probe'); - res.status(200).json({ message: 'Server is ready' }).end(); - return; - } - try { - log.info(`Received GET message at: ${Routes.ROOT}`); - await processParamsAndUpdateTools(req.url); - res.status(200).json({ message: `Actor is using Model Context Protocol. ${HELP_MESSAGE}`, data: actorRun }).end(); - } catch (error) { - log.error(`Error in GET ${Routes.ROOT} ${error}`); - res.status(500).json({ message: 'Internal Server Error' }).end(); - } - }) - .head((_req: Request, res: Response) => { - res.status(200).end(); - }); - -app.route(Routes.SSE) - .get(async (req: Request, res: Response) => { - try { - log.info(`Received GET message at: ${Routes.SSE}`); - await processParamsAndUpdateTools(req.url); - transport = new SSEServerTransport(Routes.MESSAGE, res); - await mcpServer.connect(transport); - } catch (error) { - log.error(`Error in GET ${Routes.SSE}: ${error}`); - res.status(500).json({ message: 'Internal Server Error' }).end(); - } - }); - -app.route(Routes.MESSAGE) - .post(async (req: Request, res: Response) => { - try { - log.info(`Received POST message at: ${Routes.MESSAGE}`); - if (transport) { - await transport.handlePostMessage(req, res); - } else { - res.status(400).json({ - message: 'Server is not connected to the client. ' - + 'Connect to the server with GET request to /sse endpoint', - }); - } - } catch (error) { - log.error(`Error in POST ${Routes.MESSAGE}: ${error}`); - res.status(500).json({ message: 'Internal Server Error' }).end(); - } - }); - -// Catch-all for undefined routes -app.use((req: Request, res: Response) => { - res.status(404).json({ message: `There is nothing at route ${req.method} ${req.originalUrl}. ${HELP_MESSAGE}` }).end(); -}); - const input = await processInput((await Actor.getInput>()) ?? ({} as Input)); log.info(`Loaded input: ${JSON.stringify(input)} `); -if (STANDBY_MODE) { +if (isActorStandby()) { + const app = createServerApp(HOST, mcpServer, actorRunData); log.info('Actor is running in the STANDBY mode.'); await mcpServer.addToolsFromDefaultActors(); mcpServer.updateTools(getActorDiscoveryTools()); diff --git a/src/mcp-server.ts b/src/mcp-server.ts new file mode 100644 index 00000000..cfa856ed --- /dev/null +++ b/src/mcp-server.ts @@ -0,0 +1,253 @@ +#!/usr/bin/env node +/** + * Model Context Protocol (MCP) server for Apify Actors + */ +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; +import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; +import type { ApifyClientOptions } from 'apify'; +import { Actor } from 'apify'; +import type { ActorCallOptions } from 'apify-client'; +import { ApifyClient } from 'apify-client'; +import type { AxiosRequestConfig } from 'axios'; +import { processInput } from './input.js'; +import type { ParsedUrlQuery } from 'node:querystring'; +import { parse } from 'node:querystring'; +import { getActorAutoLoadingTools } from './tools.js'; +import type { Input } from './types.js'; + +import { + filterSchemaProperties, + getActorDefinition, + getActorsAsTools, + shortenProperties, +} from './actors.js'; +import { + ACTOR_OUTPUT_MAX_CHARS_PER_ITEM, + ACTOR_OUTPUT_TRUNCATED_MESSAGE, + defaults, + InternalTools, + SERVER_NAME, + SERVER_VERSION, + USER_AGENT_ORIGIN, +} from './const.js'; +import { log } from './logger.js'; +import { + RemoveActorToolArgsSchema, + AddActorToToolsArgsSchema, + DiscoverActorsArgsSchema, + searchActorsByKeywords, + GetActorDefinition, +} from './tools.js'; +import type { ISchemaProperties, Tool } from './types.js'; + +/** + * Create Apify MCP server + */ +export class ApifyMcpServer { + private server: Server; + private tools: Map; + + constructor() { + this.server = new Server( + { + name: SERVER_NAME, + version: SERVER_VERSION, + }, + { + capabilities: { + tools: { listChanged: true }, + }, + }, + ); + this.tools = new Map(); + this.setupErrorHandling(); + this.setupToolHandlers(); + } + + /** + * Adds a User-Agent header to the request config. + * @param config + * @private + */ + private addUserAgent(config: AxiosRequestConfig): AxiosRequestConfig { + const updatedConfig = { ...config }; + updatedConfig.headers = updatedConfig.headers ?? {}; + updatedConfig.headers['User-Agent'] = `${updatedConfig.headers['User-Agent'] ?? ''}; ${USER_AGENT_ORIGIN}`; + return updatedConfig; + } + + /** + * Calls an Apify actor and retrieves the dataset items. + * + * It requires the `APIFY_TOKEN` environment variable to be set. + * If the `APIFY_IS_AT_HOME` the dataset items are pushed to the Apify dataset. + * + * @param {string} actorName - The name of the actor to call. + * @param {ActorCallOptions} callOptions - The options to pass to the actor. + * @param {unknown} input - The input to pass to the actor. + * @returns {Promise} - A promise that resolves to an array of dataset items. + * @throws {Error} - Throws an error if the `APIFY_TOKEN` is not set + */ + public async callActorGetDataset( + actorName: string, + input: unknown, + callOptions: ActorCallOptions | undefined = undefined, + ): Promise { + const name = actorName; + try { + log.info(`Calling actor ${name} with input: ${JSON.stringify(input)}`); + + const options: ApifyClientOptions = { requestInterceptors: [this.addUserAgent] }; + const client = new ApifyClient({ ...options, token: process.env.APIFY_TOKEN }); + const actorClient = client.actor(name); + + const results = await actorClient.call(input, callOptions); + const dataset = await client.dataset(results.defaultDatasetId).listItems(); + log.info(`Actor ${name} finished with ${dataset.items.length} items`); + + if (process.env.APIFY_IS_AT_HOME) { + await Actor.pushData(dataset.items); + log.info(`Pushed ${dataset.items.length} items to the dataset`); + } + return dataset.items; + } catch (error) { + log.error(`Error calling actor: ${error}. Actor: ${name}, input: ${JSON.stringify(input)}`); + throw new Error(`Error calling actor: ${error}`); + } + } + + public async addToolsFromActors(actors: string[]) { + const tools = await getActorsAsTools(actors); + this.updateTools(tools); + return tools; + } + + public async addToolsFromDefaultActors() { + await this.addToolsFromActors(defaults.actors); + } + + public updateTools(tools: Tool[]): void { + for (const tool of tools) { + this.tools.set(tool.name, tool); + log.info(`Added/Updated tool: ${tool.actorFullName} (tool: ${tool.name})`); + } + } + + /** + * Returns an array of tool names. + * @returns {string[]} - An array of tool names. + */ + public getToolNames(): string[] { + return Array.from(this.tools.keys()); + } + + private setupErrorHandling(): void { + this.server.onerror = (error) => { + console.error('[MCP Error]', error); // eslint-disable-line no-console + }; + process.on('SIGINT', async () => { + await this.server.close(); + process.exit(0); + }); + } + + private setupToolHandlers(): void { + this.server.setRequestHandler(ListToolsRequestSchema, async () => { + return { tools: Array.from(this.tools.values()) }; + }); + + /** + * Handles the request to call a tool. + * @param {object} request - The request object containing tool name and arguments. + * @throws {Error} - Throws an error if the tool is unknown or arguments are invalid. + */ + this.server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + const tool = Array.from(this.tools.values()).find((t) => t.name === name || t.actorFullName === name); + if (!tool) { + throw new Error(`Unknown tool: ${name}`); + } + if (!args) { + throw new Error(`Missing arguments for tool: ${name}`); + } + log.info(`Validate arguments for tool: ${tool.name} with arguments: ${JSON.stringify(args)}`); + if (!tool.ajvValidate(args)) { + throw new Error(`Invalid arguments for tool ${tool.name}: args: ${JSON.stringify(args)} error: ${JSON.stringify(tool?.ajvValidate.errors)}`); + } + + try { + switch (name) { + case InternalTools.ADD_ACTOR_TO_TOOLS: { + const parsed = AddActorToToolsArgsSchema.parse(args); + const toolsAdded = await this.addToolsFromActors([parsed.actorName]); + await this.server.notification({ method: 'notifications/tools/list_changed' }); + return { content: [{ type: 'text', text: `Actor added: ${toolsAdded.map((t) => `${t.actorFullName} (tool name: ${t.name})`).join(', ')}` }] }; + } + case InternalTools.REMOVE_ACTOR_FROM_TOOLS: { + const parsed = RemoveActorToolArgsSchema.parse(args); + this.tools.delete(parsed.toolName); + await this.server.notification({ method: 'notifications/tools/list_changed' }); + return { content: [{ type: 'text', text: `Tool ${parsed.toolName} was removed` }] }; + } + case InternalTools.DISCOVER_ACTORS: { + const parsed = DiscoverActorsArgsSchema.parse(args); + const actors = await searchActorsByKeywords( + parsed.search, + parsed.limit, + parsed.offset, + ); + return { content: actors?.map((item) => ({ type: 'text', text: JSON.stringify(item) })) }; + } + case InternalTools.GET_ACTOR_DETAILS: { + const parsed = GetActorDefinition.parse(args); + const v = await getActorDefinition(parsed.actorName, parsed.limit); + if (v && v.input && 'properties' in v.input && v.input) { + const properties = filterSchemaProperties(v.input.properties as { [key: string]: ISchemaProperties }); + v.input.properties = shortenProperties(properties); + } + return { content: [{ type: 'text', text: JSON.stringify(v) }] }; + } + default: { + const items = await this.callActorGetDataset(tool.actorFullName, args, { memory: tool.memoryMbytes } as ActorCallOptions); + const content = items.map((item) => { + const text = JSON.stringify(item).slice(0, ACTOR_OUTPUT_MAX_CHARS_PER_ITEM); + return text.length === ACTOR_OUTPUT_MAX_CHARS_PER_ITEM + ? { type: 'text', text: `${text} ... ${ACTOR_OUTPUT_TRUNCATED_MESSAGE}` } + : { type: 'text', text }; + }); + return { content }; + } + } + } catch (error) { + log.error(`Error calling tool: ${error}`); + throw new Error(`Error calling tool: ${error}`); + } + }); + } + + async connect(transport: Transport): Promise { + await this.server.connect(transport); + } +} + +/** + * Process input parameters and update tools + * If URL contains query parameter actors, add tools from actors, otherwise add tools from default actors + * @param url + */ +export async function processParamsAndUpdateTools(url: string, mcpServer: ApifyMcpServer) { + const params = parse(url.split('?')[1] || '') as ParsedUrlQuery; + delete params.token; + log.debug(`Received input parameters: ${JSON.stringify(params)}`); + const input = await processInput(params as unknown as Input); + if (input.actors) { + await mcpServer.addToolsFromActors(input.actors as string[]); + } + if (input.enableActorAutoLoading) { + mcpServer.updateTools(getActorAutoLoadingTools()); + } + log.debug(`Server is running in STANDBY mode with the following Actors (tools): ${mcpServer.getToolNames()}. + To use different Actors, provide them in query parameter "actors" or include them in the Actor Task input.`); +} diff --git a/src/server.ts b/src/server.ts index 46d865d6..600c7490 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,228 +1,80 @@ -#!/usr/bin/env node -/** - * Model Context Protocol (MCP) server for Apify Actors - */ -import { Server } from '@modelcontextprotocol/sdk/server/index.js'; -import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; -import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; -import type { ApifyClientOptions } from 'apify'; -import { Actor } from 'apify'; -import type { ActorCallOptions } from 'apify-client'; -import { ApifyClient } from 'apify-client'; -import type { AxiosRequestConfig } from 'axios'; +import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; +import type { Request, Response } from 'express'; +import express from 'express'; -import { - filterSchemaProperties, - getActorDefinition, - getActorsAsTools, - shortenProperties, -} from './actors.js'; -import { - ACTOR_OUTPUT_MAX_CHARS_PER_ITEM, - ACTOR_OUTPUT_TRUNCATED_MESSAGE, - defaults, - InternalTools, - SERVER_NAME, - SERVER_VERSION, - USER_AGENT_ORIGIN, -} from './const.js'; +import { HEADER_READINESS_PROBE, Routes } from './const.js'; import { log } from './logger.js'; -import { - RemoveActorToolArgsSchema, - AddActorToToolsArgsSchema, - DiscoverActorsArgsSchema, - searchActorsByKeywords, - GetActorDefinition, -} from './tools.js'; -import type { ISchemaProperties, Tool } from './types.js'; +import { type ApifyMcpServer, processParamsAndUpdateTools } from './mcp-server.js'; -/** - * Create Apify MCP server - */ -export class ApifyMcpServer { - private server: Server; - private tools: Map; +export function createServerApp(host: string, + mcpServer: ApifyMcpServer, + additionalData?: object): express.Express { + const HELP_MESSAGE = `Connect to the server with GET request to ${host}/sse?token=YOUR-APIFY-TOKEN` + + ` and then send POST requests to ${host}/message?token=YOUR-APIFY-TOKEN`; - constructor() { - this.server = new Server( - { - name: SERVER_NAME, - version: SERVER_VERSION, - }, - { - capabilities: { - tools: { listChanged: true }, - }, - }, - ); - this.tools = new Map(); - this.setupErrorHandling(); - this.setupToolHandlers(); - } + const app = express(); - /** - * Adds a User-Agent header to the request config. - * @param config - * @private - */ - private addUserAgent(config: AxiosRequestConfig): AxiosRequestConfig { - const updatedConfig = { ...config }; - updatedConfig.headers = updatedConfig.headers ?? {}; - updatedConfig.headers['User-Agent'] = `${updatedConfig.headers['User-Agent'] ?? ''}; ${USER_AGENT_ORIGIN}`; - return updatedConfig; - } - /** - * Calls an Apify actor and retrieves the dataset items. - * - * It requires the `APIFY_TOKEN` environment variable to be set. - * If the `APIFY_IS_AT_HOME` the dataset items are pushed to the Apify dataset. - * - * @param {string} actorName - The name of the actor to call. - * @param {ActorCallOptions} callOptions - The options to pass to the actor. - * @param {unknown} input - The input to pass to the actor. - * @returns {Promise} - A promise that resolves to an array of dataset items. - * @throws {Error} - Throws an error if the `APIFY_TOKEN` is not set - */ - public async callActorGetDataset( - actorName: string, - input: unknown, - callOptions: ActorCallOptions | undefined = undefined, - ): Promise { - const name = actorName; - try { - log.info(`Calling actor ${name} with input: ${JSON.stringify(input)}`); + let transport: SSEServerTransport; - const options: ApifyClientOptions = { requestInterceptors: [this.addUserAgent] }; - const client = new ApifyClient({ ...options, token: process.env.APIFY_TOKEN }); - const actorClient = client.actor(name); - - const results = await actorClient.call(input, callOptions); - const dataset = await client.dataset(results.defaultDatasetId).listItems(); - log.info(`Actor ${name} finished with ${dataset.items.length} items`); - - if (process.env.APIFY_IS_AT_HOME) { - await Actor.pushData(dataset.items); - log.info(`Pushed ${dataset.items.length} items to the dataset`); + app.route(Routes.ROOT) + .get(async (req: Request, res: Response) => { + if (req.headers && req.get(HEADER_READINESS_PROBE) !== undefined) { + log.debug('Received readiness probe'); + res.status(200).json({ message: 'Server is ready' }).end(); + return; } - return dataset.items; - } catch (error) { - log.error(`Error calling actor: ${error}. Actor: ${name}, input: ${JSON.stringify(input)}`); - throw new Error(`Error calling actor: ${error}`); - } - } - - public async addToolsFromActors(actors: string[]) { - const tools = await getActorsAsTools(actors); - this.updateTools(tools); - return tools; - } - - public async addToolsFromDefaultActors() { - await this.addToolsFromActors(defaults.actors); - } - - public updateTools(tools: Tool[]): void { - for (const tool of tools) { - this.tools.set(tool.name, tool); - log.info(`Added/Updated tool: ${tool.actorFullName} (tool: ${tool.name})`); - } - } - - /** - * Returns an array of tool names. - * @returns {string[]} - An array of tool names. - */ - public getToolNames(): string[] { - return Array.from(this.tools.keys()); - } - - private setupErrorHandling(): void { - this.server.onerror = (error) => { - console.error('[MCP Error]', error); // eslint-disable-line no-console - }; - process.on('SIGINT', async () => { - await this.server.close(); - process.exit(0); - }); - } - - private setupToolHandlers(): void { - this.server.setRequestHandler(ListToolsRequestSchema, async () => { - return { tools: Array.from(this.tools.values()) }; + try { + log.info(`Received GET message at: ${Routes.ROOT}`); + await processParamsAndUpdateTools(req.url, mcpServer); + res.setHeader('Content-Type', 'text/event-stream'); + res.setHeader('Cache-Control', 'no-cache'); + res.setHeader('Connection', 'keep-alive'); + res.status(200).json({ message: `Actor is using Model Context Protocol. ${HELP_MESSAGE}`, data: additionalData }).end(); + } catch (error) { + log.error(`Error in GET ${Routes.ROOT} ${error}`); + res.status(500).json({ message: 'Internal Server Error' }).end(); + } + }) + .head((_req: Request, res: Response) => { + res.status(200).end(); }); - /** - * Handles the request to call a tool. - * @param {object} request - The request object containing tool name and arguments. - * @throws {Error} - Throws an error if the tool is unknown or arguments are invalid. - */ - this.server.setRequestHandler(CallToolRequestSchema, async (request) => { - const { name, arguments: args } = request.params; - - const tool = Array.from(this.tools.values()).find((t) => t.name === name || t.actorFullName === name); - if (!tool) { - throw new Error(`Unknown tool: ${name}`); - } - if (!args) { - throw new Error(`Missing arguments for tool: ${name}`); - } - log.info(`Validate arguments for tool: ${tool.name} with arguments: ${JSON.stringify(args)}`); - if (!tool.ajvValidate(args)) { - throw new Error(`Invalid arguments for tool ${tool.name}: args: ${JSON.stringify(args)} error: ${JSON.stringify(tool?.ajvValidate.errors)}`); + app.route(Routes.SSE) + .get(async (req: Request, res: Response) => { + try { + log.info(`Received GET message at: ${Routes.SSE}`); + await processParamsAndUpdateTools(req.url, mcpServer); + transport = new SSEServerTransport(Routes.MESSAGE, res); + await mcpServer.connect(transport); + } catch (error) { + log.error(`Error in GET ${Routes.SSE}: ${error}`); + res.status(500).json({ message: 'Internal Server Error' }).end(); } + }); + app.route(Routes.MESSAGE) + .post(async (req: Request, res: Response) => { try { - switch (name) { - case InternalTools.ADD_ACTOR_TO_TOOLS: { - const parsed = AddActorToToolsArgsSchema.parse(args); - const toolsAdded = await this.addToolsFromActors([parsed.actorName]); - await this.server.notification({ method: 'notifications/tools/list_changed' }); - return { content: [{ type: 'text', text: `Actor added: ${toolsAdded.map((t) => `${t.actorFullName} (tool name: ${t.name})`).join(', ')}` }] }; - } - case InternalTools.REMOVE_ACTOR_FROM_TOOLS: { - const parsed = RemoveActorToolArgsSchema.parse(args); - this.tools.delete(parsed.toolName); - await this.server.notification({ method: 'notifications/tools/list_changed' }); - return { content: [{ type: 'text', text: `Tool ${parsed.toolName} was removed` }] }; - } - case InternalTools.DISCOVER_ACTORS: { - const parsed = DiscoverActorsArgsSchema.parse(args); - const actors = await searchActorsByKeywords( - parsed.search, - parsed.limit, - parsed.offset, - ); - return { content: actors?.map((item) => ({ type: 'text', text: JSON.stringify(item) })) }; - } - case InternalTools.GET_ACTOR_DETAILS: { - const parsed = GetActorDefinition.parse(args); - const v = await getActorDefinition(parsed.actorName, parsed.limit); - if (v && v.input && 'properties' in v.input && v.input) { - const properties = filterSchemaProperties(v.input.properties as { [key: string]: ISchemaProperties }); - v.input.properties = shortenProperties(properties); - } - return { content: [{ type: 'text', text: JSON.stringify(v) }] }; - } - default: { - const items = await this.callActorGetDataset(tool.actorFullName, args, { memory: tool.memoryMbytes } as ActorCallOptions); - const content = items.map((item) => { - const text = JSON.stringify(item).slice(0, ACTOR_OUTPUT_MAX_CHARS_PER_ITEM); - return text.length === ACTOR_OUTPUT_MAX_CHARS_PER_ITEM - ? { type: 'text', text: `${text} ... ${ACTOR_OUTPUT_TRUNCATED_MESSAGE}` } - : { type: 'text', text }; - }); - return { content }; - } + log.info(`Received POST message at: ${Routes.MESSAGE}`); + if (transport) { + await transport.handlePostMessage(req, res); + } else { + res.status(400).json({ + message: 'Server is not connected to the client. ' + + 'Connect to the server with GET request to /sse endpoint', + }); } } catch (error) { - log.error(`Error calling tool: ${error}`); - throw new Error(`Error calling tool: ${error}`); + log.error(`Error in POST ${Routes.MESSAGE}: ${error}`); + res.status(500).json({ message: 'Internal Server Error' }).end(); } }); - } - async connect(transport: Transport): Promise { - await this.server.connect(transport); - } + // Catch-all for undefined routes + app.use((req: Request, res: Response) => { + res.status(404).json({ message: `There is nothing at route ${req.method} ${req.originalUrl}. ${HELP_MESSAGE}` }).end(); + }); + + return app; } diff --git a/src/stdio.ts b/src/stdio.ts new file mode 100644 index 00000000..580ef8b2 --- /dev/null +++ b/src/stdio.ts @@ -0,0 +1,49 @@ +#!/usr/bin/env node +/** + * This script initializes and starts the Apify MCP server using the Stdio transport. + * + * Usage: + * node --actors= + * + * Command-line arguments: + * --actors - A comma-separated list of actor full names to add to the server. + * + * Example: + * node index.js --actors=apify/google-search-scraper,apify/instagram-scraper + */ + +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import minimist from 'minimist'; + +import { log } from './logger.js'; +import { ApifyMcpServer } from './mcp-server.js'; +import { getActorDiscoveryTools, getActorAutoLoadingTools } from './tools.js'; + +log.setLevel(log.LEVELS.ERROR); + +const argv = minimist(process.argv.slice(2)); +const argActors = argv.actors?.split(',').map((actor: string) => actor.trim()) || []; +const argEnableActorAutoLoading = argv.enableActorAutoLoading || false; + +if (!process.env.APIFY_TOKEN) { + log.error('APIFY_TOKEN is required but not set in the environment variables.'); + process.exit(1); +} + +async function main() { + const server = new ApifyMcpServer(); + await (argActors.length !== 0 + ? server.addToolsFromActors(argActors) + : server.addToolsFromDefaultActors()); + server.updateTools(getActorDiscoveryTools()); + if (argEnableActorAutoLoading) { + server.updateTools(getActorAutoLoadingTools()); + } + const transport = new StdioServerTransport(); + await server.connect(transport); +} + +main().catch((error) => { + console.error('Server error:', error); // eslint-disable-line no-console + process.exit(1); +}); diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 00000000..cace0f30 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,30 @@ +import { Actor } from 'apify'; + +export function isActorStandby(): boolean { + return Actor.getEnv().metaOrigin === 'STANDBY'; +} + +export function getActorRunData(): unknown { + return Actor.isAtHome() ? { + id: process.env.ACTOR_RUN_ID, + actId: process.env.ACTOR_ID, + userId: process.env.APIFY_USER_ID, + startedAt: process.env.ACTOR_STARTED_AT, + finishedAt: null, + status: 'RUNNING', + meta: { + origin: process.env.APIFY_META_ORIGIN, + }, + options: { + build: process.env.ACTOR_BUILD_NUMBER, + memoryMbytes: process.env.ACTOR_MEMORY_MBYTES, + }, + buildId: process.env.ACTOR_BUILD_ID, + defaultKeyValueStoreId: process.env.ACTOR_DEFAULT_KEY_VALUE_STORE_ID, + defaultDatasetId: process.env.ACTOR_DEFAULT_DATASET_ID, + defaultRequestQueueId: process.env.ACTOR_DEFAULT_REQUEST_QUEUE_ID, + buildNumber: process.env.ACTOR_BUILD_NUMBER, + containerUrl: process.env.ACTOR_WEB_SERVER_URL, + standbyUrl: process.env.ACTOR_STANDBY_URL, + } : {}; +} diff --git a/tsconfig.json b/tsconfig.json index fb02aa71..149be3a8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,8 @@ "lib": ["ES2022"], "skipLibCheck": true, "typeRoots": ["./types", "./node_modules/@types"], - "strict": true + "strict": true, + "rootDir": "src/" }, "include": ["./src/**/*"], "exclude": ["node_modules"] From 03d772c33df3da82b40bd9d26843d8fc972f5779 Mon Sep 17 00:00:00 2001 From: MQ Date: Wed, 2 Apr 2025 08:52:49 +0200 Subject: [PATCH 02/42] lint --- src/examples/clientSse.ts | 2 +- src/main.ts | 7 ------- src/mcp-server.ts | 17 +++++++---------- src/server.ts | 1 - 4 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/examples/clientSse.ts b/src/examples/clientSse.ts index bf9b4e7f..930266c1 100644 --- a/src/examples/clientSse.ts +++ b/src/examples/clientSse.ts @@ -26,7 +26,7 @@ dotenv.config({ path: path.resolve(dirname, '../../.env') }); const SERVER_URL = process.env.MCP_SERVER_URL_BASE || 'https://actors-mcp-server.apify.actor/sse'; // We need to change forward slash / to underscore -- in the tool name as Anthropic does not allow forward slashes in the tool name const SELECTED_TOOL = actorNameToToolName('apify/rag-web-browser'); -//const QUERY = 'web browser for Anthropic'; +// const QUERY = 'web browser for Anthropic'; const QUERY = 'apify'; if (!process.env.APIFY_TOKEN) { diff --git a/src/main.ts b/src/main.ts index 59c5888a..06b0f8a4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,16 +2,9 @@ This file serves as an Actor MCP SSE server entry point. */ -import type { ParsedUrlQuery } from 'node:querystring'; -import { parse } from 'node:querystring'; - -import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import { Actor } from 'apify'; import type { ActorCallOptions } from 'apify-client'; -import type { Request, Response } from 'express'; -import express from 'express'; -import { HEADER_READINESS_PROBE, Routes } from './const.js'; import { processInput } from './input.js'; import { log } from './logger.js'; import { ApifyMcpServer } from './mcp-server.js'; diff --git a/src/mcp-server.ts b/src/mcp-server.ts index cfa856ed..85070235 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -1,7 +1,9 @@ -#!/usr/bin/env node /** * Model Context Protocol (MCP) server for Apify Actors */ +import type { ParsedUrlQuery } from 'node:querystring'; +import { parse } from 'node:querystring'; + import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; @@ -10,11 +12,6 @@ import { Actor } from 'apify'; import type { ActorCallOptions } from 'apify-client'; import { ApifyClient } from 'apify-client'; import type { AxiosRequestConfig } from 'axios'; -import { processInput } from './input.js'; -import type { ParsedUrlQuery } from 'node:querystring'; -import { parse } from 'node:querystring'; -import { getActorAutoLoadingTools } from './tools.js'; -import type { Input } from './types.js'; import { filterSchemaProperties, @@ -31,15 +28,15 @@ import { SERVER_VERSION, USER_AGENT_ORIGIN, } from './const.js'; +import { processInput } from './input.js'; import { log } from './logger.js'; -import { +import { getActorAutoLoadingTools, RemoveActorToolArgsSchema, AddActorToToolsArgsSchema, DiscoverActorsArgsSchema, searchActorsByKeywords, - GetActorDefinition, -} from './tools.js'; -import type { ISchemaProperties, Tool } from './types.js'; + GetActorDefinition } from './tools.js'; +import type { Input, ISchemaProperties, Tool } from './types.js'; /** * Create Apify MCP server diff --git a/src/server.ts b/src/server.ts index 600c7490..b99f8f80 100644 --- a/src/server.ts +++ b/src/server.ts @@ -14,7 +14,6 @@ export function createServerApp(host: string, const app = express(); - let transport: SSEServerTransport; app.route(Routes.ROOT) From 0ccc99577bf4751a0b641236a31b53285f432847 Mon Sep 17 00:00:00 2001 From: MQ Date: Wed, 2 Apr 2025 09:01:56 +0200 Subject: [PATCH 03/42] improve --- src/index.ts | 3 +++ src/main.ts | 4 ++-- src/mcp-server.ts | 40 ++++++++++++++++++++-------------------- src/server.ts | 8 ++++---- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/index.ts b/src/index.ts index 8c7fcbf9..17c81a02 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,3 +2,6 @@ This file provides essential functions for constructing HTTP and MCP servers, effectively serving as a library. Acts as a library entrypoint. */ + +export { createExpressApp } from './server.js'; +export { ApifyMcpServer } from './mcp-server.js'; diff --git a/src/main.ts b/src/main.ts index 06b0f8a4..b4edd6c9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,7 +8,7 @@ import type { ActorCallOptions } from 'apify-client'; import { processInput } from './input.js'; import { log } from './logger.js'; import { ApifyMcpServer } from './mcp-server.js'; -import { createServerApp } from './server.js'; +import { createExpressApp } from './server.js'; import { getActorDiscoveryTools, getActorAutoLoadingTools } from './tools.js'; import type { Input } from './types.js'; import { isActorStandby } from './utils.js'; @@ -52,7 +52,7 @@ const input = await processInput((await Actor.getInput>()) ?? ({} log.info(`Loaded input: ${JSON.stringify(input)} `); if (isActorStandby()) { - const app = createServerApp(HOST, mcpServer, actorRunData); + const app = createExpressApp(HOST, mcpServer, actorRunData); log.info('Actor is running in the STANDBY mode.'); await mcpServer.addToolsFromDefaultActors(); mcpServer.updateTools(getActorDiscoveryTools()); diff --git a/src/mcp-server.ts b/src/mcp-server.ts index 85070235..cd60c037 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -149,6 +149,26 @@ export class ApifyMcpServer { }); } + /** + * Process input parameters and update tools + * If URL contains query parameter actors, add tools from actors, otherwise add tools from default actors + * @param url + */ + public async processParamsAndUpdateTools(url: string) { + const params = parse(url.split('?')[1] || '') as ParsedUrlQuery; + delete params.token; + log.debug(`Received input parameters: ${JSON.stringify(params)}`); + const input = await processInput(params as unknown as Input); + if (input.actors) { + await this.addToolsFromActors(input.actors as string[]); + } + if (input.enableActorAutoLoading) { + this.updateTools(getActorAutoLoadingTools()); + } + log.debug(`Server is running in STANDBY mode with the following Actors (tools): ${this.getToolNames()}. + To use different Actors, provide them in query parameter "actors" or include them in the Actor Task input.`); + } + private setupToolHandlers(): void { this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: Array.from(this.tools.values()) }; @@ -228,23 +248,3 @@ export class ApifyMcpServer { await this.server.connect(transport); } } - -/** - * Process input parameters and update tools - * If URL contains query parameter actors, add tools from actors, otherwise add tools from default actors - * @param url - */ -export async function processParamsAndUpdateTools(url: string, mcpServer: ApifyMcpServer) { - const params = parse(url.split('?')[1] || '') as ParsedUrlQuery; - delete params.token; - log.debug(`Received input parameters: ${JSON.stringify(params)}`); - const input = await processInput(params as unknown as Input); - if (input.actors) { - await mcpServer.addToolsFromActors(input.actors as string[]); - } - if (input.enableActorAutoLoading) { - mcpServer.updateTools(getActorAutoLoadingTools()); - } - log.debug(`Server is running in STANDBY mode with the following Actors (tools): ${mcpServer.getToolNames()}. - To use different Actors, provide them in query parameter "actors" or include them in the Actor Task input.`); -} diff --git a/src/server.ts b/src/server.ts index b99f8f80..02a46959 100644 --- a/src/server.ts +++ b/src/server.ts @@ -4,9 +4,9 @@ import express from 'express'; import { HEADER_READINESS_PROBE, Routes } from './const.js'; import { log } from './logger.js'; -import { type ApifyMcpServer, processParamsAndUpdateTools } from './mcp-server.js'; +import { type ApifyMcpServer } from './mcp-server.js'; -export function createServerApp(host: string, +export function createExpressApp(host: string, mcpServer: ApifyMcpServer, additionalData?: object): express.Express { const HELP_MESSAGE = `Connect to the server with GET request to ${host}/sse?token=YOUR-APIFY-TOKEN` @@ -25,7 +25,7 @@ export function createServerApp(host: string, } try { log.info(`Received GET message at: ${Routes.ROOT}`); - await processParamsAndUpdateTools(req.url, mcpServer); + await mcpServer.processParamsAndUpdateTools(req.url); res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); @@ -43,7 +43,7 @@ export function createServerApp(host: string, .get(async (req: Request, res: Response) => { try { log.info(`Received GET message at: ${Routes.SSE}`); - await processParamsAndUpdateTools(req.url, mcpServer); + await mcpServer.processParamsAndUpdateTools(req.url); transport = new SSEServerTransport(Routes.MESSAGE, res); await mcpServer.connect(transport); } catch (error) { From 4c1e3df7e5e95939d576f091dcf86d23a761446d Mon Sep 17 00:00:00 2001 From: MQ Date: Wed, 2 Apr 2025 09:23:49 +0200 Subject: [PATCH 04/42] expose lib --- package-lock.json | 2 +- src/index.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index cdf00710..a682849c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "zod-to-json-schema": "^3.24.1" }, "bin": { - "actors-mcp-server": "dist/index.js" + "actors-mcp-server": "dist/stdio.js" }, "devDependencies": { "@anthropic-ai/sdk": "^0.33.1", diff --git a/src/index.ts b/src/index.ts index 17c81a02..7e0a0ab5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,3 +5,4 @@ export { createExpressApp } from './server.js'; export { ApifyMcpServer } from './mcp-server.js'; +export { getActorDiscoveryTools } from './tools.js'; From 255b91d8994dc1c3377d78ab7ea0671508bc3c11 Mon Sep 17 00:00:00 2001 From: MQ Date: Wed, 2 Apr 2025 23:40:07 +0200 Subject: [PATCH 05/42] split tools, add call for internal tools, prepare toolkits --- src/actors.ts | 21 +++-- src/mcp-server.ts | 101 +++++++++------------ src/toolkits/actor-auto-loading-tools.ts | 0 src/toolkits/discovery-tools.ts | 0 src/tools.ts | 106 +++-------------------- src/tools/add-actors-to-tools.ts | 38 ++++++++ src/tools/discover-actors.ts | 58 +++++++++++++ src/tools/get-actors-details.ts | 44 ++++++++++ src/tools/remove-actors-from-tools.ts | 34 ++++++++ src/types.ts | 66 +++++++++++++- 10 files changed, 300 insertions(+), 168 deletions(-) create mode 100644 src/toolkits/actor-auto-loading-tools.ts create mode 100644 src/toolkits/discovery-tools.ts create mode 100644 src/tools/add-actors-to-tools.ts create mode 100644 src/tools/discover-actors.ts create mode 100644 src/tools/get-actors-details.ts create mode 100644 src/tools/remove-actors-from-tools.ts diff --git a/src/actors.ts b/src/actors.ts index c36cf5c8..f75e4ffc 100644 --- a/src/actors.ts +++ b/src/actors.ts @@ -3,7 +3,7 @@ import { ApifyClient } from 'apify-client'; import { ACTOR_ADDITIONAL_INSTRUCTIONS, defaults, MAX_DESCRIPTION_LENGTH, ACTOR_README_MAX_LENGTH, ACTOR_ENUM_MAX_LENGTH } from './const.js'; import { log } from './logger.js'; -import type { ActorDefinitionPruned, ActorDefinitionWithDesc, IActorInputSchema, ISchemaProperties, Tool } from './types.js'; +import type { ActorDefinitionPruned, ActorDefinitionWithDesc, IActorInputSchema, ISchemaProperties, ToolWrap } from './types.js'; export function actorNameToToolName(actorName: string): string { return actorName @@ -316,10 +316,10 @@ function buildNestedProperties(properties: Record): R * @param {string[]} actors - An array of actor IDs or Actor full names. * @returns {Promise} - A promise that resolves to an array of MCP tools. */ -export async function getActorsAsTools(actors: string[]): Promise { +export async function getActorsAsTools(actors: string[]): Promise { const ajv = new Ajv({ coerceTypes: 'array', strict: false }); const results = await Promise.all(actors.map(getActorDefinition)); - const tools = []; + const tools: ToolWrap[] = []; for (const result of results) { if (result) { if (result.input && 'properties' in result.input && result.input) { @@ -332,12 +332,15 @@ export async function getActorsAsTools(actors: string[]): Promise { try { const memoryMbytes = result.defaultRunOptions?.memoryMbytes || defaults.maxMemoryMbytes; tools.push({ - name: actorNameToToolName(result.actorFullName), - actorFullName: result.actorFullName, - description: `${result.description} Instructions: ${ACTOR_ADDITIONAL_INSTRUCTIONS}`, - inputSchema: result.input || {}, - ajvValidate: ajv.compile(result.input || {}), - memoryMbytes: memoryMbytes > defaults.maxMemoryMbytes ? defaults.maxMemoryMbytes : memoryMbytes, + type: 'actor', + tool: { + name: actorNameToToolName(result.actorFullName), + actorFullName: result.actorFullName, + description: `${result.description} Instructions: ${ACTOR_ADDITIONAL_INSTRUCTIONS}`, + inputSchema: result.input || {}, + ajvValidate: ajv.compile(result.input || {}), + memoryMbytes: memoryMbytes > defaults.maxMemoryMbytes ? defaults.maxMemoryMbytes : memoryMbytes, + }, }); } catch (validationError) { log.error(`Failed to compile AJV schema for Actor: ${result.actorFullName}. Error: ${validationError}`); diff --git a/src/mcp-server.ts b/src/mcp-server.ts index cd60c037..84eb9967 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -14,36 +14,27 @@ import { ApifyClient } from 'apify-client'; import type { AxiosRequestConfig } from 'axios'; import { - filterSchemaProperties, - getActorDefinition, getActorsAsTools, - shortenProperties, } from './actors.js'; import { ACTOR_OUTPUT_MAX_CHARS_PER_ITEM, ACTOR_OUTPUT_TRUNCATED_MESSAGE, defaults, - InternalTools, SERVER_NAME, SERVER_VERSION, USER_AGENT_ORIGIN, } from './const.js'; import { processInput } from './input.js'; import { log } from './logger.js'; -import { getActorAutoLoadingTools, - RemoveActorToolArgsSchema, - AddActorToToolsArgsSchema, - DiscoverActorsArgsSchema, - searchActorsByKeywords, - GetActorDefinition } from './tools.js'; -import type { Input, ISchemaProperties, Tool } from './types.js'; +import { getActorAutoLoadingTools } from './tools.js'; +import type { Input, ActorTool, ToolWrap, InternalTool } from './types.js'; /** * Create Apify MCP server */ export class ApifyMcpServer { private server: Server; - private tools: Map; + public tools: Map; constructor() { this.server = new Server( @@ -124,10 +115,10 @@ export class ApifyMcpServer { await this.addToolsFromActors(defaults.actors); } - public updateTools(tools: Tool[]): void { - for (const tool of tools) { - this.tools.set(tool.name, tool); - log.info(`Added/Updated tool: ${tool.actorFullName} (tool: ${tool.name})`); + public updateTools(tools: ToolWrap[]): void { + for (const wrap of tools) { + this.tools.set(wrap.tool.name, wrap); + log.info(`Added/Updated tool: ${wrap.tool.name}`); } } @@ -171,7 +162,8 @@ export class ApifyMcpServer { private setupToolHandlers(): void { this.server.setRequestHandler(ListToolsRequestSchema, async () => { - return { tools: Array.from(this.tools.values()) }; + const tools = Array.from(this.tools.values()).map((tool) => (tool.tool)); + return { tools }; }); /** @@ -182,65 +174,50 @@ export class ApifyMcpServer { this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; - const tool = Array.from(this.tools.values()).find((t) => t.name === name || t.actorFullName === name); + const tool = Array.from(this.tools.values()) + .find((t) => t.tool.name === name || (t.type === 'internal' && (t.tool as ActorTool).actorFullName === name)); if (!tool) { throw new Error(`Unknown tool: ${name}`); } if (!args) { throw new Error(`Missing arguments for tool: ${name}`); } - log.info(`Validate arguments for tool: ${tool.name} with arguments: ${JSON.stringify(args)}`); - if (!tool.ajvValidate(args)) { - throw new Error(`Invalid arguments for tool ${tool.name}: args: ${JSON.stringify(args)} error: ${JSON.stringify(tool?.ajvValidate.errors)}`); + log.info(`Validate arguments for tool: ${tool.tool.name} with arguments: ${JSON.stringify(args)}`); + if (!tool.tool.ajvValidate(args)) { + throw new Error(`Invalid arguments for tool ${tool.tool.name}: args: ${JSON.stringify(args)} error: ${JSON.stringify(tool?.tool.ajvValidate.errors)}`); } try { - switch (name) { - case InternalTools.ADD_ACTOR_TO_TOOLS: { - const parsed = AddActorToToolsArgsSchema.parse(args); - const toolsAdded = await this.addToolsFromActors([parsed.actorName]); - await this.server.notification({ method: 'notifications/tools/list_changed' }); - return { content: [{ type: 'text', text: `Actor added: ${toolsAdded.map((t) => `${t.actorFullName} (tool name: ${t.name})`).join(', ')}` }] }; - } - case InternalTools.REMOVE_ACTOR_FROM_TOOLS: { - const parsed = RemoveActorToolArgsSchema.parse(args); - this.tools.delete(parsed.toolName); - await this.server.notification({ method: 'notifications/tools/list_changed' }); - return { content: [{ type: 'text', text: `Tool ${parsed.toolName} was removed` }] }; - } - case InternalTools.DISCOVER_ACTORS: { - const parsed = DiscoverActorsArgsSchema.parse(args); - const actors = await searchActorsByKeywords( - parsed.search, - parsed.limit, - parsed.offset, - ); - return { content: actors?.map((item) => ({ type: 'text', text: JSON.stringify(item) })) }; - } - case InternalTools.GET_ACTOR_DETAILS: { - const parsed = GetActorDefinition.parse(args); - const v = await getActorDefinition(parsed.actorName, parsed.limit); - if (v && v.input && 'properties' in v.input && v.input) { - const properties = filterSchemaProperties(v.input.properties as { [key: string]: ISchemaProperties }); - v.input.properties = shortenProperties(properties); - } - return { content: [{ type: 'text', text: JSON.stringify(v) }] }; - } - default: { - const items = await this.callActorGetDataset(tool.actorFullName, args, { memory: tool.memoryMbytes } as ActorCallOptions); - const content = items.map((item) => { - const text = JSON.stringify(item).slice(0, ACTOR_OUTPUT_MAX_CHARS_PER_ITEM); - return text.length === ACTOR_OUTPUT_MAX_CHARS_PER_ITEM - ? { type: 'text', text: `${text} ... ${ACTOR_OUTPUT_TRUNCATED_MESSAGE}` } - : { type: 'text', text }; - }); - return { content }; - } + if (tool.type === 'internal') { + const internalTool = tool.tool as InternalTool; + const res = await internalTool.call({ + args, + apifyMcpServer: this, + mcpServer: this.server, + }) as object; + return { + ...res, + }; + } + + if (tool.type === 'actor') { + const actorTool = tool.tool as ActorTool; + + const items = await this.callActorGetDataset(actorTool.actorFullName, args, { memory: actorTool.memoryMbytes } as ActorCallOptions); + const content = items.map((item) => { + const text = JSON.stringify(item).slice(0, ACTOR_OUTPUT_MAX_CHARS_PER_ITEM); + return text.length === ACTOR_OUTPUT_MAX_CHARS_PER_ITEM + ? { type: 'text', text: `${text} ... ${ACTOR_OUTPUT_TRUNCATED_MESSAGE}` } + : { type: 'text', text }; + }); + return { content }; } } catch (error) { log.error(`Error calling tool: ${error}`); throw new Error(`Error calling tool: ${error}`); } + + throw new Error(`Tool ${name} is not implemented`); }); } diff --git a/src/toolkits/actor-auto-loading-tools.ts b/src/toolkits/actor-auto-loading-tools.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/toolkits/discovery-tools.ts b/src/toolkits/discovery-tools.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/tools.ts b/src/tools.ts index 76c581b5..7d69e867 100644 --- a/src/tools.ts +++ b/src/tools.ts @@ -1,107 +1,23 @@ -import { Ajv } from 'ajv'; import type { ActorStoreList } from 'apify-client'; import { ApifyClient } from 'apify-client'; -import { z } from 'zod'; -import { zodToJsonSchema } from 'zod-to-json-schema'; -import { actorNameToToolName } from './actors.js'; -import { ACTOR_README_MAX_LENGTH, InternalTools } from './const.js'; -import type { ActorStorePruned, PricingInfo, Tool } from './types.js'; +import { addActorToTools } from './tools/add-actors-to-tools.js'; +import { discoverActorsTool } from './tools/discover-actors.js'; +import { getActorsDetailsTool } from './tools/get-actors-details.js'; +import { removeActorFromTools } from './tools/remove-actors-from-tools.js'; +import type { ActorStorePruned, PricingInfo, ToolWrap } from './types.js'; -export const DiscoverActorsArgsSchema = z.object({ - limit: z.number() - .int() - .min(1) - .max(100) - .default(10) - .describe('The maximum number of Actors to return. Default value is 10.'), - offset: z.number() - .int() - .min(0) - .default(0) - .describe('The number of elements that should be skipped at the start. Default value is 0.'), - search: z.string() - .default('') - .describe('String of key words to search by. ' - + 'Searches the title, name, description, username, and readme of an Actor.' - + 'Only key word search is supported, no advanced search.' - + 'Always prefer simple keywords over complex queries.'), - category: z.string() - .default('') - .describe('Filters the results by the specified category.'), -}); - -export const RemoveActorToolArgsSchema = z.object({ - toolName: z.string() - .describe('Tool name to remove from available tools.') - .transform((val) => actorNameToToolName(val)), -}); - -export const AddActorToToolsArgsSchema = z.object({ - actorName: z.string() - .describe('Add an Actor to available tools by Actor ID or Actor full name.' - + 'Actor name is always composed from `username/name`'), -}); - -export const GetActorDefinition = z.object({ - actorName: z.string() - .describe('Retrieve input, readme, and other details for Actor ID or Actor full name. ' - + 'Actor name is always composed from `username/name`'), - limit: z.number() - .int() - .default(ACTOR_README_MAX_LENGTH) - .describe(`Truncate the README to this limit. Default value is ${ACTOR_README_MAX_LENGTH}.`), -}); - -export function getActorAutoLoadingTools(): Tool[] { - const ajv = new Ajv({ coerceTypes: 'array', strict: false }); +export function getActorAutoLoadingTools(): ToolWrap[] { return [ - { - name: InternalTools.ADD_ACTOR_TO_TOOLS, - actorFullName: InternalTools.ADD_ACTOR_TO_TOOLS, - description: 'Add an Actor to available tools by Actor ID or Actor name. ' - + 'Do not execute the Actor, only add it and list it in available tools. ' - + 'Never run the tool without user consent! ' - + 'For example, add a tool with username/name when user wants to scrape data from a website.', - inputSchema: zodToJsonSchema(AddActorToToolsArgsSchema), - ajvValidate: ajv.compile(zodToJsonSchema(AddActorToToolsArgsSchema)), - }, - { - name: InternalTools.REMOVE_ACTOR_FROM_TOOLS, - actorFullName: InternalTools.REMOVE_ACTOR_FROM_TOOLS, - description: 'Remove tool by name from available tools. ' - + 'For example, when user says, I do not need a tool username/name anymore', - inputSchema: zodToJsonSchema(RemoveActorToolArgsSchema), - ajvValidate: ajv.compile(zodToJsonSchema(RemoveActorToolArgsSchema)), - }, + addActorToTools, + removeActorFromTools, ]; } -export function getActorDiscoveryTools(): Tool[] { - const ajv = new Ajv({ coerceTypes: 'array', strict: false }); +export function getActorDiscoveryTools(): ToolWrap[] { return [ - { - name: InternalTools.DISCOVER_ACTORS, - actorFullName: InternalTools.DISCOVER_ACTORS, - description: `Discover available Actors using full text search using keywords.` - + `Users try to discover Actors using free form query in this case search query needs to be converted to full text search. ` - + `Prefer Actors from Apify as they are generally more reliable and have better support. ` - + `Returns a list of Actors with name, description, run statistics, pricing, starts, and URL. ` - + `You perhaps need to use this tool several times to find the right Actor. ` - + `Limit number of results returned but ensure that relevant results are returned. `, - inputSchema: zodToJsonSchema(DiscoverActorsArgsSchema), - ajvValidate: ajv.compile(zodToJsonSchema(DiscoverActorsArgsSchema)), - }, - { - name: InternalTools.GET_ACTOR_DETAILS, - actorFullName: InternalTools.GET_ACTOR_DETAILS, - description: 'Get documentation, readme, input schema and other details about an Actor. ' - + 'For example, when user says, I need to know more about web crawler Actor.' - + 'Get details for an Actor with with Actor ID or Actor full name, i.e. username/name.' - + `Limit the length of the README if needed.`, - inputSchema: zodToJsonSchema(GetActorDefinition), - ajvValidate: ajv.compile(zodToJsonSchema(GetActorDefinition)), - }, + discoverActorsTool, + getActorsDetailsTool, ]; } diff --git a/src/tools/add-actors-to-tools.ts b/src/tools/add-actors-to-tools.ts new file mode 100644 index 00000000..9cc48315 --- /dev/null +++ b/src/tools/add-actors-to-tools.ts @@ -0,0 +1,38 @@ +import { Ajv } from 'ajv'; +import { z } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; + +import { InternalTools } from '../const.js'; +import type { ActorTool, InternalTool, ToolWrap } from '../types.js'; + +const ajv = new Ajv({ coerceTypes: 'array', strict: false }); + +export const AddActorToToolsArgsSchema = z.object({ + actorName: z.string() + .describe('Add an Actor to available tools by Actor ID or Actor full name.' + + 'Actor name is always composed from `username/name`'), +}); + +export const addActorToTools: ToolWrap = { + type: 'internal', + tool: { + name: InternalTools.ADD_ACTOR_TO_TOOLS, + description: 'Add an Actor to available tools by Actor ID or Actor name. ' + + 'Do not execute the Actor, only add it and list it in available tools. ' + + 'Never run the tool without user consent! ' + + 'For example, add a tool with username/name when user wants to scrape data from a website.', + inputSchema: zodToJsonSchema(AddActorToToolsArgsSchema), + ajvValidate: ajv.compile(zodToJsonSchema(AddActorToToolsArgsSchema)), + call: async (toolArgs) => { + const { apifyMcpServer, mcpServer, args } = toolArgs; + const parsed = AddActorToToolsArgsSchema.parse(args); + const toolsAdded = await apifyMcpServer.addToolsFromActors([parsed.actorName]); + await mcpServer.notification({ method: 'notifications/tools/list_changed' }); + + return { content: [{ + type: 'text', + text: `Actor added: ${toolsAdded.map((t) => `${(t.tool as ActorTool).actorFullName} (tool name: ${t.tool.name})`).join(', ')}`, + }] }; + }, + } as InternalTool, +}; diff --git a/src/tools/discover-actors.ts b/src/tools/discover-actors.ts new file mode 100644 index 00000000..d1f61f85 --- /dev/null +++ b/src/tools/discover-actors.ts @@ -0,0 +1,58 @@ +import { Ajv } from 'ajv'; +import { z } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; + +import { InternalTools } from '../const.js'; +import { searchActorsByKeywords } from '../tools.js'; +import type { InternalTool, ToolWrap } from '../types.js'; + +const ajv = new Ajv({ coerceTypes: 'array', strict: false }); + +export const DiscoverActorsArgsSchema = z.object({ + limit: z.number() + .int() + .min(1) + .max(100) + .default(10) + .describe('The maximum number of Actors to return. Default value is 10.'), + offset: z.number() + .int() + .min(0) + .default(0) + .describe('The number of elements that should be skipped at the start. Default value is 0.'), + search: z.string() + .default('') + .describe('String of key words to search by. ' + + 'Searches the title, name, description, username, and readme of an Actor.' + + 'Only key word search is supported, no advanced search.' + + 'Always prefer simple keywords over complex queries.'), + category: z.string() + .default('') + .describe('Filters the results by the specified category.'), +}); + +export const discoverActorsTool: ToolWrap = { + type: 'internal', + tool: { + name: InternalTools.DISCOVER_ACTORS, + actorFullName: InternalTools.DISCOVER_ACTORS, + description: `Discover available Actors using full text search using keywords.` + + `Users try to discover Actors using free form query in this case search query needs to be converted to full text search. ` + + `Prefer Actors from Apify as they are generally more reliable and have better support. ` + + `Returns a list of Actors with name, description, run statistics, pricing, starts, and URL. ` + + `You perhaps need to use this tool several times to find the right Actor. ` + + `Limit number of results returned but ensure that relevant results are returned. `, + inputSchema: zodToJsonSchema(DiscoverActorsArgsSchema), + ajvValidate: ajv.compile(zodToJsonSchema(DiscoverActorsArgsSchema)), + call: async (toolArgs) => { + const { args } = toolArgs; + const parsed = DiscoverActorsArgsSchema.parse(args); + const actors = await searchActorsByKeywords( + parsed.search, + parsed.limit, + parsed.offset, + ); + return { content: actors?.map((item) => ({ type: 'text', text: JSON.stringify(item) })) }; + }, + } as InternalTool, +}; diff --git a/src/tools/get-actors-details.ts b/src/tools/get-actors-details.ts new file mode 100644 index 00000000..066b3e5a --- /dev/null +++ b/src/tools/get-actors-details.ts @@ -0,0 +1,44 @@ +import { Ajv } from 'ajv'; +import { z } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; + +import { filterSchemaProperties, getActorDefinition, shortenProperties } from '../actors.js'; +import { ACTOR_README_MAX_LENGTH, InternalTools } from '../const.js'; +import type { InternalTool, ISchemaProperties, ToolWrap } from '../types.js'; + +const ajv = new Ajv({ coerceTypes: 'array', strict: false }); + +export const GetActorDefinition = z.object({ + actorName: z.string() + .describe('Retrieve input, readme, and other details for Actor ID or Actor full name. ' + + 'Actor name is always composed from `username/name`'), + limit: z.number() + .int() + .default(ACTOR_README_MAX_LENGTH) + .describe(`Truncate the README to this limit. Default value is ${ACTOR_README_MAX_LENGTH}.`), +}); + +export const getActorsDetailsTool: ToolWrap = { + type: 'internal', + tool: { + name: InternalTools.GET_ACTOR_DETAILS, + actorFullName: InternalTools.GET_ACTOR_DETAILS, + description: 'Get documentation, readme, input schema and other details about an Actor. ' + + 'For example, when user says, I need to know more about web crawler Actor.' + + 'Get details for an Actor with with Actor ID or Actor full name, i.e. username/name.' + + `Limit the length of the README if needed.`, + inputSchema: zodToJsonSchema(GetActorDefinition), + ajvValidate: ajv.compile(zodToJsonSchema(GetActorDefinition)), + call: async (toolArgs) => { + const { args } = toolArgs; + + const parsed = GetActorDefinition.parse(args); + const v = await getActorDefinition(parsed.actorName, parsed.limit); + if (v && v.input && 'properties' in v.input && v.input) { + const properties = filterSchemaProperties(v.input.properties as { [key: string]: ISchemaProperties }); + v.input.properties = shortenProperties(properties); + } + return { content: [{ type: 'text', text: JSON.stringify(v) }] }; + }, + } as InternalTool, +}; diff --git a/src/tools/remove-actors-from-tools.ts b/src/tools/remove-actors-from-tools.ts new file mode 100644 index 00000000..40b1e022 --- /dev/null +++ b/src/tools/remove-actors-from-tools.ts @@ -0,0 +1,34 @@ +import { Ajv } from 'ajv'; +import { z } from 'zod'; +import { zodToJsonSchema } from 'zod-to-json-schema'; + +import { actorNameToToolName } from '../actors.js'; +import { InternalTools } from '../const.js'; +import type { InternalTool, ToolWrap } from '../types.js'; + +const ajv = new Ajv({ coerceTypes: 'array', strict: false }); + +export const RemoveActorToolArgsSchema = z.object({ + toolName: z.string() + .describe('Tool name to remove from available tools.') + .transform((val) => actorNameToToolName(val)), +}); + +export const removeActorFromTools: ToolWrap = { + type: 'internal', + tool: { + name: InternalTools.REMOVE_ACTOR_FROM_TOOLS, + description: 'Remove tool by name from available tools. ' + + 'For example, when user says, I do not need a tool username/name anymore', + inputSchema: zodToJsonSchema(RemoveActorToolArgsSchema), + ajvValidate: ajv.compile(zodToJsonSchema(RemoveActorToolArgsSchema)), + call: async (toolArgs) => { + const { apifyMcpServer, mcpServer, args } = toolArgs; + + const parsed = RemoveActorToolArgsSchema.parse(args); + apifyMcpServer.tools.delete(parsed.toolName); + await mcpServer.notification({ method: 'notifications/tools/list_changed' }); + return { content: [{ type: 'text', text: `Tool ${parsed.toolName} was removed` }] }; + }, + } as InternalTool, +}; diff --git a/src/types.ts b/src/types.ts index 82f43c5a..877b3456 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,9 @@ +import type { Server } from '@modelcontextprotocol/sdk/server/index.js'; import type { ValidateFunction } from 'ajv'; import type { ActorDefaultRunOptions, ActorDefinition } from 'apify-client'; +import type { ApifyMcpServer } from './mcp-server'; + export type Input = { actors: string[] | string; enableActorAutoLoading?: boolean; @@ -51,15 +54,74 @@ export type ActorDefinitionWithDesc = Omit & { export type ActorDefinitionPruned = Pick -export interface Tool { +/** + * Base interface for all tools in the MCP server. + * Contains common properties shared by all tool types. + */ +export interface ToolBase { + /** Unique name/identifier for the tool */ name: string; - actorFullName: string; + /** Description of what the tool does */ description: string; + /** JSON schema defining the tool's input parameters */ inputSchema: object; + /** AJV validation function for the input schema */ ajvValidate: ValidateFunction; +} + +/** + * Interface for Actor-based tools - tools that wrap Apify Actors. + * Extends ToolBase with Actor-specific properties. + */ +export interface ActorTool extends ToolBase { + /** Full name of the Apify Actor (username/name) */ + actorFullName: string; + /** Optional memory limit in MB for the Actor execution */ memoryMbytes?: number; } +/** + * Arguments passed to internal tool calls. + * Contains both the tool arguments and server references. + */ +export type InternalToolArgs = { + /** Arguments passed to the tool */ + args: Record; + /** Reference to the Apify MCP server instance */ + apifyMcpServer: ApifyMcpServer; + /** Reference to the MCP server instance */ + mcpServer: Server; +} + +/** + * Interface for internal tools - tools implemented directly in the MCP server. + * Extends ToolBase with a call function implementation. + */ +export interface InternalTool extends ToolBase { + /** + * Executes the tool with the given arguments + * @param toolArgs - Arguments and server references + * @returns Promise resolving to the tool's output + */ + call: (toolArgs: InternalToolArgs) => Promise; +} + +/** + * Type discriminator for tools - indicates whether a tool is internal or Actor-based. + */ +export type ToolType = 'internal' | 'actor'; + +/** + * Wrapper interface that combines a tool with its type discriminator. + * Used to store and manage tools of different types uniformly. + */ +export interface ToolWrap { + /** Type of the tool (internal or actor) */ + type: ToolType; + /** The tool instance */ + tool: ActorTool | InternalTool; +} + // ActorStoreList for actor-search tool export interface ActorStats { totalRuns: number; From ffc05ced0aea3111c9d73868ca89d9e476b9e398 Mon Sep 17 00:00:00 2001 From: MQ Date: Thu, 3 Apr 2025 12:35:25 +0200 Subject: [PATCH 06/42] Move tool exports to toolkit modules --- src/index.ts | 2 +- src/main.ts | 3 ++- src/mcp-server.ts | 4 ++-- src/stdio.ts | 3 ++- src/toolkits/actor-auto-loading-tools.ts | 10 ++++++++++ src/toolkits/discovery-tools.ts | 10 ++++++++++ src/tools.ts | 20 +------------------- 7 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/index.ts b/src/index.ts index 7e0a0ab5..0055301f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,4 +5,4 @@ export { createExpressApp } from './server.js'; export { ApifyMcpServer } from './mcp-server.js'; -export { getActorDiscoveryTools } from './tools.js'; +export { getActorDiscoveryTools } from './toolkits/discovery-tools.js'; diff --git a/src/main.ts b/src/main.ts index b4edd6c9..391a22a9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,7 +9,8 @@ import { processInput } from './input.js'; import { log } from './logger.js'; import { ApifyMcpServer } from './mcp-server.js'; import { createExpressApp } from './server.js'; -import { getActorDiscoveryTools, getActorAutoLoadingTools } from './tools.js'; +import { getActorDiscoveryTools } from './toolkits/discovery-tools.js'; +import { getActorAutoLoadingTools } from './toolkits/actor-auto-loading-tools.js'; import type { Input } from './types.js'; import { isActorStandby } from './utils.js'; diff --git a/src/mcp-server.ts b/src/mcp-server.ts index 84eb9967..3c1cac8f 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -26,7 +26,7 @@ import { } from './const.js'; import { processInput } from './input.js'; import { log } from './logger.js'; -import { getActorAutoLoadingTools } from './tools.js'; +import { getActorAutoLoadingTools } from './toolkits/actor-auto-loading-tools.js'; import type { Input, ActorTool, ToolWrap, InternalTool } from './types.js'; /** @@ -175,7 +175,7 @@ export class ApifyMcpServer { const { name, arguments: args } = request.params; const tool = Array.from(this.tools.values()) - .find((t) => t.tool.name === name || (t.type === 'internal' && (t.tool as ActorTool).actorFullName === name)); + .find((t) => t.tool.name === name || (t.type === 'actor' && (t.tool as ActorTool).actorFullName === name)); if (!tool) { throw new Error(`Unknown tool: ${name}`); } diff --git a/src/stdio.ts b/src/stdio.ts index 580ef8b2..1ce4924d 100644 --- a/src/stdio.ts +++ b/src/stdio.ts @@ -17,7 +17,8 @@ import minimist from 'minimist'; import { log } from './logger.js'; import { ApifyMcpServer } from './mcp-server.js'; -import { getActorDiscoveryTools, getActorAutoLoadingTools } from './tools.js'; +import { getActorDiscoveryTools } from './toolkits/discovery-tools.js'; +import { getActorAutoLoadingTools } from './toolkits/actor-auto-loading-tools.js'; log.setLevel(log.LEVELS.ERROR); diff --git a/src/toolkits/actor-auto-loading-tools.ts b/src/toolkits/actor-auto-loading-tools.ts index e69de29b..de5ee091 100644 --- a/src/toolkits/actor-auto-loading-tools.ts +++ b/src/toolkits/actor-auto-loading-tools.ts @@ -0,0 +1,10 @@ +import { addActorToTools } from '../tools/add-actors-to-tools.js'; +import { removeActorFromTools } from '../tools/remove-actors-from-tools.js'; +import type { ToolWrap } from '../types.js'; + +export function getActorAutoLoadingTools(): ToolWrap[] { + return [ + addActorToTools, + removeActorFromTools, + ]; +} diff --git a/src/toolkits/discovery-tools.ts b/src/toolkits/discovery-tools.ts index e69de29b..43e9d293 100644 --- a/src/toolkits/discovery-tools.ts +++ b/src/toolkits/discovery-tools.ts @@ -0,0 +1,10 @@ +import { discoverActorsTool } from '../tools/discover-actors.js'; +import { getActorsDetailsTool } from '../tools/get-actors-details.js'; +import type { ToolWrap } from '../types.js'; + +export function getActorDiscoveryTools(): ToolWrap[] { + return [ + discoverActorsTool, + getActorsDetailsTool, + ]; +} diff --git a/src/tools.ts b/src/tools.ts index 7d69e867..a272a1b4 100644 --- a/src/tools.ts +++ b/src/tools.ts @@ -1,25 +1,7 @@ import type { ActorStoreList } from 'apify-client'; import { ApifyClient } from 'apify-client'; -import { addActorToTools } from './tools/add-actors-to-tools.js'; -import { discoverActorsTool } from './tools/discover-actors.js'; -import { getActorsDetailsTool } from './tools/get-actors-details.js'; -import { removeActorFromTools } from './tools/remove-actors-from-tools.js'; -import type { ActorStorePruned, PricingInfo, ToolWrap } from './types.js'; - -export function getActorAutoLoadingTools(): ToolWrap[] { - return [ - addActorToTools, - removeActorFromTools, - ]; -} - -export function getActorDiscoveryTools(): ToolWrap[] { - return [ - discoverActorsTool, - getActorsDetailsTool, - ]; -} +import type { ActorStorePruned, PricingInfo } from './types.js'; function pruneActorStoreInfo(response: ActorStoreList): ActorStorePruned { const stats = response.stats || {}; From 9241f033474962f5dd84094a548345930ee37701 Mon Sep 17 00:00:00 2001 From: MQ Date: Thu, 3 Apr 2025 12:41:38 +0200 Subject: [PATCH 07/42] Refactor tool exports and update library entry points --- src/index.ts | 6 +++--- src/main.ts | 2 +- src/stdio.ts | 2 +- src/toolkits/index.ts | 2 ++ src/tools/index.ts | 4 ++++ 5 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 src/toolkits/index.ts create mode 100644 src/tools/index.ts diff --git a/src/index.ts b/src/index.ts index 0055301f..b7c13043 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,8 @@ /* - This file provides essential functions for constructing HTTP and MCP servers, effectively serving as a library. - Acts as a library entrypoint. + This file provides essential functions and tools for MCP servers, serving as a library. */ export { createExpressApp } from './server.js'; export { ApifyMcpServer } from './mcp-server.js'; -export { getActorDiscoveryTools } from './toolkits/discovery-tools.js'; +export { getActorAutoLoadingTools, getActorDiscoveryTools } from './toolkits/index.js'; +export { addActorToTools, discoverActorsTool, getActorsDetailsTool, removeActorFromTools } from './tools/index.js'; diff --git a/src/main.ts b/src/main.ts index 391a22a9..b831bfe7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,8 +9,8 @@ import { processInput } from './input.js'; import { log } from './logger.js'; import { ApifyMcpServer } from './mcp-server.js'; import { createExpressApp } from './server.js'; -import { getActorDiscoveryTools } from './toolkits/discovery-tools.js'; import { getActorAutoLoadingTools } from './toolkits/actor-auto-loading-tools.js'; +import { getActorDiscoveryTools } from './toolkits/discovery-tools.js'; import type { Input } from './types.js'; import { isActorStandby } from './utils.js'; diff --git a/src/stdio.ts b/src/stdio.ts index 1ce4924d..525f26b8 100644 --- a/src/stdio.ts +++ b/src/stdio.ts @@ -17,8 +17,8 @@ import minimist from 'minimist'; import { log } from './logger.js'; import { ApifyMcpServer } from './mcp-server.js'; -import { getActorDiscoveryTools } from './toolkits/discovery-tools.js'; import { getActorAutoLoadingTools } from './toolkits/actor-auto-loading-tools.js'; +import { getActorDiscoveryTools } from './toolkits/discovery-tools.js'; log.setLevel(log.LEVELS.ERROR); diff --git a/src/toolkits/index.ts b/src/toolkits/index.ts new file mode 100644 index 00000000..e2a9a2c9 --- /dev/null +++ b/src/toolkits/index.ts @@ -0,0 +1,2 @@ +export { getActorAutoLoadingTools } from './actor-auto-loading-tools.js'; +export { getActorDiscoveryTools } from './discovery-tools.js'; diff --git a/src/tools/index.ts b/src/tools/index.ts new file mode 100644 index 00000000..b47d04fe --- /dev/null +++ b/src/tools/index.ts @@ -0,0 +1,4 @@ +export { discoverActorsTool } from './discover-actors.js'; +export { getActorsDetailsTool } from './get-actors-details.js'; +export { addActorToTools } from './add-actors-to-tools.js'; +export { removeActorFromTools } from './remove-actors-from-tools.js'; From 59a9e0a10c96d81c90bc0ad760cd4ad334b17034 Mon Sep 17 00:00:00 2001 From: MQ Date: Mon, 7 Apr 2025 10:31:40 +0200 Subject: [PATCH 08/42] remove toolkits (move to tools index), pass apify token as arg, clean up old tools.ts, separate Actor readme --- .actor/ACTOR.md | 389 +++++++++++++++++++++++ .actor/actor.json | 1 + src/actors.ts | 39 ++- src/index.ts | 2 +- src/main.ts | 5 +- src/mcp-server.ts | 11 +- src/server.ts | 4 + src/stdio.ts | 3 +- src/toolkits/actor-auto-loading-tools.ts | 10 - src/toolkits/discovery-tools.ts | 10 - src/toolkits/index.ts | 2 - src/tools.ts | 40 --- src/tools/discover-actors.ts | 2 +- src/tools/index.ts | 25 +- 14 files changed, 465 insertions(+), 78 deletions(-) create mode 100644 .actor/ACTOR.md delete mode 100644 src/toolkits/actor-auto-loading-tools.ts delete mode 100644 src/toolkits/discovery-tools.ts delete mode 100644 src/toolkits/index.ts delete mode 100644 src/tools.ts diff --git a/.actor/ACTOR.md b/.actor/ACTOR.md new file mode 100644 index 00000000..4c73585c --- /dev/null +++ b/.actor/ACTOR.md @@ -0,0 +1,389 @@ +# Apify Model Context Protocol (MCP) Server + +[![Actors MCP Server](https://apify.com/actor-badge?actor=apify/actors-mcp-server)](https://apify.com/apify/actors-mcp-server) +[![smithery badge](https://smithery.ai/badge/@apify/actors-mcp-server)](https://smithery.ai/server/@apify/actors-mcp-server) + +Implementation of an MCP server for all [Apify Actors](https://apify.com/store). +This server enables interaction with one or more Apify Actors that can be defined in the MCP Server configuration. + +The server can be used in two ways: +- **🇦 [MCP Server Actor](https://apify.com/apify/actors-mcp-server)** – HTTP server accessible via Server-Sent Events (SSE), see [guide](#-mcp-server-actor) +- **⾕ MCP Server Stdio** – Local server available via standard input/output (stdio), see [guide](#-mcp-server-at-a-local-host) + + +It can also interact with the MCP server using chat-like UI with 💬 [Tester MCP Client](https://apify.com/jiri.spilka/tester-mcp-client) + +# 🎯 What does Apify MCP server do? + +The MCP Server Actor allows an AI assistant to use any [Apify Actor](https://apify.com/store) as a tool to perform a specific task. +For example it can: +- use [Facebook Posts Scraper](https://apify.com/apify/facebook-posts-scraper) to extract data from Facebook posts from multiple pages/profiles +- use [Google Maps Email Extractor](https://apify.com/lukaskrivka/google-maps-with-contact-details) to extract Google Maps contact details +- use [Google Search Results Scraper](https://apify.com/apify/google-search-scraper) to scrape Google Search Engine Results Pages (SERPs) +- use [Instagram Scraper](https://apify.com/apify/instagram-scraper) to scrape Instagram posts, profiles, places, photos, and comments +- use [RAG Web Browser](https://apify.com/apify/web-scraper) to search the web, scrape the top N URLs, and return their content + +# MCP Clients + +To interact with the Apify MCP server, you can use MCP clients such as: +- [Claude Desktop](https://claude.ai/download) (only Stdio support) +- [LibreChat](https://www.librechat.ai/) (stdio and SSE support (yet without Authorization header)) +- [Apify Tester MCP Client](https://apify.com/jiri.spilka/tester-mcp-client) (SSE support with Authorization headers) +- other clients at [https://modelcontextprotocol.io/clients](https://modelcontextprotocol.io/clients) +- more clients at [https://glama.ai/mcp/clients](https://glama.ai/mcp/clients) + +Additionally, you can use simple example clients found in the [examples](https://github.com/apify/actor-mcp-server/tree/main/src/examples) directory. + +When you have Actors integrated with the MCP server, you can ask: +- "Search web and summarize recent trends about AI Agents" +- "Find top 10 best Italian restaurants in San Francisco" +- "Find and analyze Instagram profile of The Rock" +- "Provide a step-by-step guide on using the Model Context Protocol with source URLs." +- "What Apify Actors I can use?" + +The following image shows how the Apify MCP server interacts with the Apify platform and AI clients: + +![Actors-MCP-server](https://raw.githubusercontent.com/apify/actors-mcp-server/refs/heads/master/docs/actors-mcp-server.png) + +With the MCP Tester client you can load Actors dynamically but this is not yet supported by other MCP clients. +We also plan to add more features, see [Roadmap](#-roadmap-march-2025) for more details. + +# 🔄 What is the Model Context Protocol? + +The Model Context Protocol (MCP) allows AI applications (and AI agents), such as Claude Desktop, to connect to external tools and data sources. +MCP is an open protocol that enables secure, controlled interactions between AI applications, AI Agents, and local or remote resources. + +For more information, see the [Model Context Protocol](https://modelcontextprotocol.org/) website or blogpost [What is MCP and why does it matter?](https://blog.apify.com/what-is-model-context-protocol/). + +# 🤖 How is MCP Server related to AI Agents? + +The Apify MCP Server exposes Apify's Actors through the MCP protocol, allowing AI Agents or frameworks that implement the MCP protocol to access all Apify Actors as tools for data extraction, web searching, and other tasks. + +To learn more about AI Agents, explore our blog post: [What are AI Agents?](https://blog.apify.com/what-are-ai-agents/) and browse Apify's curated [AI Agent collection](https://apify.com/store/collections/ai_agents). +Interested in building and monetizing your own AI agent on Apify? Check out our [step-by-step guide](https://blog.apify.com/how-to-build-an-ai-agent/) for creating, publishing, and monetizing AI agents on the Apify platform. + +# 🧱 Components + +## Tools + +### Actors + +Any [Apify Actor](https://apify.com/store) can be used as a tool. +By default, the server is pre-configured with the Actors specified below, but it can be overridden by providing Actor input. + +```text +'apify/instagram-scraper' +'apify/rag-web-browser' +'lukaskrivka/google-maps-with-contact-details' +``` +The MCP server loads the Actor input schema and creates MCP tools corresponding to the Actors. +See this example of input schema for the [RAG Web Browser](https://apify.com/apify/rag-web-browser/input-schema). + +The tool name must always be the full Actor name, such as `apify/rag-web-browser`. +The arguments for an MCP tool represent the input parameters of the Actor. +For example, for the `apify/rag-web-browser` Actor, the arguments are: + +```json +{ + "query": "restaurants in San Francisco", + "maxResults": 3 +} +``` +You don't need to specify the input parameters or which Actor to call, everything is managed by an LLM. +When a tool is called, the arguments are automatically passed to the Actor by the LLM. +You can refer to the specific Actor's documentation for a list of available arguments. + +### Helper tools + +The server provides a set of helper tools to discover available Actors and retrieve their details: +- `get-actor-details`: Retrieves documentation, input schema, and details about a specific Actor. +- `discover-actors`: Searches for relevant Actors using keywords and returns their details. + +There are also tools to manage the available tools list. However, dynamically adding and removing tools requires the MCP client to have the capability to update tools list (handle `ToolListChangedNotificationSchema`), which is typically not supported. + +You can try this functionality using the [Apify Tester MCP Client](https://apify.com/jiri.spilka/tester-mcp-client) Actor. +To enable it, set the `enableActorAutoLoading` parameter. + +- `add-actor-as-tool`: Adds an Actor by name to the available tools list without executing it, requiring user consent to run later. +- `remove-actor-from-tool`: Removes an Actor by name from the available tools list when it's no longer needed. + +## Prompt & Resources + +The server does not provide any resources and prompts. +We plan to add [Apify's dataset](https://docs.apify.com/platform/storage/dataset) and [key-value store](https://docs.apify.com/platform/storage/key-value-store) as resources in the future. + +# ⚙️ Usage + +The Apify MCP Server can be used in two ways: **as an Apify Actor** running at Apify platform +or as a **local server** running on your machine. + +## 🇦 MCP Server Actor + +### Standby web server + +The Actor runs in [**Standby mode**](https://docs.apify.com/platform/actors/running/standby) with an HTTP web server that receives and processes requests. + +Start server with default Actors. To use the Apify MCP Server with set of default Actors, +send an HTTP GET request with your [Apify API token](https://console.apify.com/settings/integrations) to the following URL. +``` +https://actors-mcp-server.apify.actor?token= +``` +It is also possible to start the MCP server with a different set of Actors. +To do this, create a [task](https://docs.apify.com/platform/actors/running/tasks) and specify the list of Actors you want to use. + +Then, run task in Standby mode with the selected Actors. +```shell +https://USERNAME--actors-mcp-server-task.apify.actor?token= +``` + +You can find a list of all available Actors in the [Apify Store](https://apify.com/store). + +#### 💬 Interact with the MCP Server over SSE + +Once the server is running, you can interact with Server-Sent Events (SSE) to send messages to the server and receive responses. +The easiest way is to use [Tester MCP Client](https://apify.com/jiri.spilka/tester-mcp-client) on Apify. + +Most of the MCP clients do not support SSE yet (as of March 2025), but this will likely change. +[Claude Desktop](https://claude.ai/download) does not support SEE yet, but you can use it with Stdio transport, see [MCP Sever at a local host](#-mcp-server-at-a-local-host) for more details. + +In the client settings you need to provide server configuration: +```json +{ + "mcpServers": { + "apify": { + "type": "sse", + "url": "https://actors-mcp-server.apify.actor/sse", + "env": { + "APIFY_TOKEN": "your-apify-token" + } + } + } +} +``` +Alternatively, you can use simple python [client_see.py](https://github.com/apify/actor-mcp-server/tree/main/src/examples/client_sse.py) or test the server using `curl` commands. + +1. Initiate Server-Sent-Events (SSE) by sending a GET request to the following URL: + ``` + curl https://actors-mcp-server.apify.actor/sse?token= + ``` + The server will respond with a `sessionId`, which you can use to send messages to the server: + ```shell + event: endpoint + data: /message?sessionId=a1b + ``` + +2. Send a message to the server by making a POST request with the `sessionId`: + ```shell + curl -X POST "https://actors-mcp-server.apify.actor/message?token=&session_id=a1b" -H "Content-Type: application/json" -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": { + "arguments": { "searchStringsArray": ["restaurants in San Francisco"], "maxCrawledPlacesPerSearch": 3 }, + "name": "lukaskrivka/google-maps-with-contact-details" + } + }' + ``` + The MCP server will start the Actor `lukaskrivka/google-maps-with-contact-details` with the provided arguments as input parameters. + For this POST request, the server will respond with: + + ```text + Accepted + ``` + +3. Receive the response. The server will invoke the specified Actor as a tool using the provided query parameters and stream the response back to the client via SSE. + The response will be returned as JSON text. + + ```text + event: message + data: {"result":{"content":[{"type":"text","text":"{\"searchString\":\"restaurants in San Francisco\",\"rank\":1,\"title\":\"Gary Danko\",\"description\":\"Renowned chef Gary Danko's fixed-price menus of American cuisine ... \",\"price\":\"$100+\"...}}]}} + ``` + +## ⾕ MCP Server at a local host + +You can run the Apify MCP Server on your local machine by configuring it with Claude Desktop or any other [MCP clients](https://modelcontextprotocol.io/clients). +You can also use [Smithery](https://smithery.ai/server/@apify/actors-mcp-server) to install the server automatically. + +### Prerequisites + +- MacOS or Windows +- The latest version of Claude Desktop must be installed (or another MCP client) +- [Node.js](https://nodejs.org/en) (v18 or higher) +- [Apify API Token](https://docs.apify.com/platform/integrations/api#api-token) (`APIFY_TOKEN`) + +#### Claude Desktop + +To configure Claude Desktop to work with the MCP server, follow these steps. For a detailed guide, refer to the [Claude Desktop Users Guide](https://modelcontextprotocol.io/quickstart/user). + +1. Download Claude for desktop + - Available for Windows and macOS. + - For Linux users, you can build a Debian package using this [unofficial build script](https://github.com/aaddrick/claude-desktop-debian). +2. Open the Claude Desktop app and enable **Developer Mode** from the top-left menu bar. +3. Once enabled, open **Settings** (also from the top-left menu bar) and navigate to the **Developer Option**, where you'll find the **Edit Config** button +4. Open configuration file and edit the following file: + + - On macOS: `~/Library/Application\ Support/Claude/claude_desktop_config.json` + - On Windows: `%APPDATA%/Claude/claude_desktop_config.json` + - On Linux: `~/.config/Claude/claude_desktop_config.json` + + ```json + { + "mcpServers": { + "actors-mcp-server": { + "command": "npx", + "args": ["-y", "@apify/actors-mcp-server"], + "env": { + "APIFY_TOKEN": "your-apify-token" + } + } + } + } + ``` + Alternatively, you can use `actors` argument to select one or more Apify Actors: + ```json + { + "mcpServers": { + "actors-mcp-server": { + "command": "npx", + "args": [ + "-y", "@apify/actors-mcp-server", + "--actors", "lukaskrivka/google-maps-with-contact-details,apify/instagram-scraper" + ], + "env": { + "APIFY_TOKEN": "your-apify-token" + } + } + } + } + ``` +5. Restart Claude Desktop + + - Fully quit Claude Desktop (ensure it's not just minimized or closed). + - Restart Claude Desktop. + - Look for the 🔌 icon to confirm that the Exa server is connected. + +6. Open the Claude Desktop chat and ask "What Apify Actors I can use?" + + ![Claude-desktop-with-Actors-MCP-server](https://raw.githubusercontent.com/apify/actors-mcp-server/refs/heads/master/docs/claude-desktop.png) + +7. Examples + + You can ask Claude to perform tasks, such as: + ```text + Find and analyze recent research papers about LLMs. + Find top 10 best Italian restaurants in San Francisco. + Find and analyze instagram profile of the Rock. + ``` + +#### Debugging NPM package @apify/actors-mcp-server with @modelcontextprotocol/inspector + +To debug the server, use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) tool: + +```shell +export APIFY_TOKEN=your-apify-token +npx @modelcontextprotocol/inspector npx -y @apify/actors-mcp-server +``` + +### Installing via Smithery + +To install Apify Actors MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@apify/actors-mcp-server): + +```bash +npx -y @smithery/cli install @apify/actors-mcp-server --client claude +``` + +#### Stdio clients + +Create environment file `.env` with the following content: +```text +APIFY_TOKEN=your-apify-token +# ANTHROPIC_API_KEY is only required when you want to run examples/clientStdioChat.js +ANTHROPIC_API_KEY=your-anthropic-api-token +``` +In the `examples` directory, you can find two clients that interact with the server via +standard input/output (stdio): + +1. [`clientStdio.ts`](https://github.com/apify/actor-mcp-server/tree/main/src/examples/clientStdio.ts) + This client script starts the MCP server with two specified Actors. + It then calls the `apify/rag-web-browser` tool with a query and prints the result. + It demonstrates how to connect to the MCP server, list available tools, and call a specific tool using stdio transport. + ```bash + node dist/examples/clientStdio.js + ``` + +2. [`clientStdioChat.ts`](https://github.com/apify/actor-mcp-server/tree/main/src/examples/clientStdioChat.ts) + This client script also starts the MCP server but provides an interactive command-line chat interface. + It prompts the user to interact with the server, allowing for dynamic tool calls and responses. + This example is useful for testing and debugging interactions with the MCP server in conversational manner. + + ```bash + node dist/examples/clientStdioChat.js + ``` + +# 👷🏼 Development + +## Prerequisites + +- [Node.js](https://nodejs.org/en) (v18 or higher) +- Python 3.9 or higher + +Create environment file `.env` with the following content: +```text +APIFY_TOKEN=your-apify-token +# ANTHROPIC_API_KEY is only required when you want to run examples/clientStdioChat.js +ANTHROPIC_API_KEY=your-anthropic-api-key +``` +## Local client (SSE) + +To test the server with the SSE transport, you can use python script `examples/clientSse.ts`: +Currently, the node.js client does not support to establish a connection to remote server witch custom headers. +You need to change URL to your local server URL in the script. + +```bash +node dist/examples/clientSse.js +``` + +## Debugging + +Since MCP servers operate over standard input/output (stdio), debugging can be challenging. +For the best debugging experience, use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector). + +Build the actor-mcp-server package: + +```bash +npm run build +``` + +You can launch the MCP Inspector via [`npm`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) with this command: + +```bash +export APIFY_TOKEN=your-apify-token +npx @modelcontextprotocol/inspector node ./dist/index.js +``` + +Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging. + +## ⓘ Limitations and feedback + +To limit the context size the properties in the `input schema` are pruned and description is truncated to 500 characters. +Enum fields and titles are truncated to max 50 options. + +Memory for each Actor is limited to 4GB. +Free users have an 8GB limit, 128MB needs to be allocated for running `Actors-MCP-Server`. + +If you need other features or have any feedback, [submit an issue](https://console.apify.com/actors/1lSvMAaRcadrM1Vgv/issues) in Apify Console to let us know. + +# 🚀 Roadmap (March 2025) + +- Add Apify's dataset and key-value store as resources. +- Add tools such as Actor logs and Actor runs for debugging. + +# 📚 Learn more + +- [Model Context Protocol](https://modelcontextprotocol.org/) +- [What are AI Agents?](https://blog.apify.com/what-are-ai-agents/) +- [What is MCP and why does it matter?](https://blog.apify.com/what-is-model-context-protocol/) +- [Tester MCP Client](https://apify.com/jiri.spilka/tester-mcp-client) +- [AI agent workflow: building an agent to query Apify datasets](https://blog.apify.com/ai-agent-workflow/) +- [MCP Client development guide](https://github.com/cyanheads/model-context-protocol-resources/blob/main/guides/mcp-client-development-guide.md) +- [How to build and monetize an AI agent on Apify](https://blog.apify.com/how-to-build-an-ai-agent/) diff --git a/.actor/actor.json b/.actor/actor.json index 789d422b..58d0f033 100644 --- a/.actor/actor.json +++ b/.actor/actor.json @@ -5,5 +5,6 @@ "description": "Implementation of a Model Context Protocol (MCP) Server for Apify Actors that enables AI applications (and AI agents) to interact with Apify Actors", "version": "0.1", "input": "./input_schema.json", + "readme": "./ACTOR.md", "dockerfile": "./Dockerfile" } diff --git a/src/actors.ts b/src/actors.ts index f75e4ffc..b1eab35d 100644 --- a/src/actors.ts +++ b/src/actors.ts @@ -1,9 +1,10 @@ import { Ajv } from 'ajv'; +import type { ActorStoreList } from 'apify-client'; import { ApifyClient } from 'apify-client'; import { ACTOR_ADDITIONAL_INSTRUCTIONS, defaults, MAX_DESCRIPTION_LENGTH, ACTOR_README_MAX_LENGTH, ACTOR_ENUM_MAX_LENGTH } from './const.js'; import { log } from './logger.js'; -import type { ActorDefinitionPruned, ActorDefinitionWithDesc, IActorInputSchema, ISchemaProperties, ToolWrap } from './types.js'; +import type { ActorStorePruned, PricingInfo, ActorDefinitionPruned, ActorDefinitionWithDesc, IActorInputSchema, ISchemaProperties, ToolWrap } from './types.js'; export function actorNameToToolName(actorName: string): string { return actorName @@ -349,3 +350,39 @@ export async function getActorsAsTools(actors: string[]): Promise { } return tools; } + +function pruneActorStoreInfo(response: ActorStoreList): ActorStorePruned { + const stats = response.stats || {}; + const pricingInfo = (response.currentPricingInfo || {}) as PricingInfo; + return { + id: response.id, + name: response.name?.toString() || '', + username: response.username?.toString() || '', + actorFullName: `${response.username}/${response.name}`, + title: response.title?.toString() || '', + description: response.description?.toString() || '', + stats: { + totalRuns: stats.totalRuns, + totalUsers30Days: stats.totalUsers30Days, + publicActorRunStats30Days: 'publicActorRunStats30Days' in stats + ? stats.publicActorRunStats30Days : {}, + }, + currentPricingInfo: { + pricingModel: pricingInfo.pricingModel?.toString() || '', + pricePerUnitUsd: pricingInfo?.pricePerUnitUsd ?? 0, + trialMinutes: pricingInfo?.trialMinutes ?? 0, + }, + url: response.url?.toString() || '', + totalStars: 'totalStars' in response ? (response.totalStars as number) : null, + }; +} + +export async function searchActorsByKeywords( + search: string, + limit: number | undefined = undefined, + offset: number | undefined = undefined, +): Promise { + const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); + const results = await client.store().list({ search, limit, offset }); + return results.items.map((x) => pruneActorStoreInfo(x)); +} diff --git a/src/index.ts b/src/index.ts index b7c13043..2cbb5bbf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,5 +4,5 @@ export { createExpressApp } from './server.js'; export { ApifyMcpServer } from './mcp-server.js'; -export { getActorAutoLoadingTools, getActorDiscoveryTools } from './toolkits/index.js'; +export { getActorAutoLoadingTools, getActorDiscoveryTools } from './tools/index.js'; export { addActorToTools, discoverActorsTool, getActorsDetailsTool, removeActorFromTools } from './tools/index.js'; diff --git a/src/main.ts b/src/main.ts index b831bfe7..08040645 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,8 +9,7 @@ import { processInput } from './input.js'; import { log } from './logger.js'; import { ApifyMcpServer } from './mcp-server.js'; import { createExpressApp } from './server.js'; -import { getActorAutoLoadingTools } from './toolkits/actor-auto-loading-tools.js'; -import { getActorDiscoveryTools } from './toolkits/discovery-tools.js'; +import { getActorAutoLoadingTools, getActorDiscoveryTools } from './tools/index.js'; import type { Input } from './types.js'; import { isActorStandby } from './utils.js'; @@ -70,6 +69,6 @@ if (isActorStandby()) { await Actor.fail('If you need to debug a specific actor, please provide the debugActor and debugActorInput fields in the input'); } const options = { memory: input.maxActorMemoryBytes } as ActorCallOptions; - await mcpServer.callActorGetDataset(input.debugActor!, input.debugActorInput!, options); + await mcpServer.callActorGetDataset(input.debugActor!, input.debugActorInput!, process.env.APIFY_TOKEN, options); await Actor.exit(); } diff --git a/src/mcp-server.ts b/src/mcp-server.ts index 3c1cac8f..4545e97a 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -26,7 +26,7 @@ import { } from './const.js'; import { processInput } from './input.js'; import { log } from './logger.js'; -import { getActorAutoLoadingTools } from './toolkits/actor-auto-loading-tools.js'; +import { getActorAutoLoadingTools } from './tools/index.js'; import type { Input, ActorTool, ToolWrap, InternalTool } from './types.js'; /** @@ -80,6 +80,7 @@ export class ApifyMcpServer { public async callActorGetDataset( actorName: string, input: unknown, + apifyToken: string, callOptions: ActorCallOptions | undefined = undefined, ): Promise { const name = actorName; @@ -87,7 +88,7 @@ export class ApifyMcpServer { log.info(`Calling actor ${name} with input: ${JSON.stringify(input)}`); const options: ApifyClientOptions = { requestInterceptors: [this.addUserAgent] }; - const client = new ApifyClient({ ...options, token: process.env.APIFY_TOKEN }); + const client = new ApifyClient({ ...options, token: apifyToken }); const actorClient = client.actor(name); const results = await actorClient.call(input, callOptions); @@ -172,7 +173,7 @@ export class ApifyMcpServer { * @throws {Error} - Throws an error if the tool is unknown or arguments are invalid. */ this.server.setRequestHandler(CallToolRequestSchema, async (request) => { - const { name, arguments: args } = request.params; + const { name, arguments: args, apifyToken } = request.params; const tool = Array.from(this.tools.values()) .find((t) => t.tool.name === name || (t.type === 'actor' && (t.tool as ActorTool).actorFullName === name)); @@ -203,7 +204,9 @@ export class ApifyMcpServer { if (tool.type === 'actor') { const actorTool = tool.tool as ActorTool; - const items = await this.callActorGetDataset(actorTool.actorFullName, args, { memory: actorTool.memoryMbytes } as ActorCallOptions); + const items = await this.callActorGetDataset(actorTool.actorFullName, args, apifyToken as string, { + memory: actorTool.memoryMbytes, + } as ActorCallOptions); const content = items.map((item) => { const text = JSON.stringify(item).slice(0, ACTOR_OUTPUT_MAX_CHARS_PER_ITEM); return text.length === ACTOR_OUTPUT_MAX_CHARS_PER_ITEM diff --git a/src/server.ts b/src/server.ts index 02a46959..4b9b4329 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,3 +1,7 @@ +/* + * This file contains the express server implementation used for standby Actor mode. + */ + import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import type { Request, Response } from 'express'; import express from 'express'; diff --git a/src/stdio.ts b/src/stdio.ts index 525f26b8..30caadca 100644 --- a/src/stdio.ts +++ b/src/stdio.ts @@ -17,8 +17,7 @@ import minimist from 'minimist'; import { log } from './logger.js'; import { ApifyMcpServer } from './mcp-server.js'; -import { getActorAutoLoadingTools } from './toolkits/actor-auto-loading-tools.js'; -import { getActorDiscoveryTools } from './toolkits/discovery-tools.js'; +import { getActorAutoLoadingTools, getActorDiscoveryTools } from './tools/index.js'; log.setLevel(log.LEVELS.ERROR); diff --git a/src/toolkits/actor-auto-loading-tools.ts b/src/toolkits/actor-auto-loading-tools.ts deleted file mode 100644 index de5ee091..00000000 --- a/src/toolkits/actor-auto-loading-tools.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { addActorToTools } from '../tools/add-actors-to-tools.js'; -import { removeActorFromTools } from '../tools/remove-actors-from-tools.js'; -import type { ToolWrap } from '../types.js'; - -export function getActorAutoLoadingTools(): ToolWrap[] { - return [ - addActorToTools, - removeActorFromTools, - ]; -} diff --git a/src/toolkits/discovery-tools.ts b/src/toolkits/discovery-tools.ts deleted file mode 100644 index 43e9d293..00000000 --- a/src/toolkits/discovery-tools.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { discoverActorsTool } from '../tools/discover-actors.js'; -import { getActorsDetailsTool } from '../tools/get-actors-details.js'; -import type { ToolWrap } from '../types.js'; - -export function getActorDiscoveryTools(): ToolWrap[] { - return [ - discoverActorsTool, - getActorsDetailsTool, - ]; -} diff --git a/src/toolkits/index.ts b/src/toolkits/index.ts deleted file mode 100644 index e2a9a2c9..00000000 --- a/src/toolkits/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { getActorAutoLoadingTools } from './actor-auto-loading-tools.js'; -export { getActorDiscoveryTools } from './discovery-tools.js'; diff --git a/src/tools.ts b/src/tools.ts deleted file mode 100644 index a272a1b4..00000000 --- a/src/tools.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { ActorStoreList } from 'apify-client'; -import { ApifyClient } from 'apify-client'; - -import type { ActorStorePruned, PricingInfo } from './types.js'; - -function pruneActorStoreInfo(response: ActorStoreList): ActorStorePruned { - const stats = response.stats || {}; - const pricingInfo = (response.currentPricingInfo || {}) as PricingInfo; - return { - id: response.id, - name: response.name?.toString() || '', - username: response.username?.toString() || '', - actorFullName: `${response.username}/${response.name}`, - title: response.title?.toString() || '', - description: response.description?.toString() || '', - stats: { - totalRuns: stats.totalRuns, - totalUsers30Days: stats.totalUsers30Days, - publicActorRunStats30Days: 'publicActorRunStats30Days' in stats - ? stats.publicActorRunStats30Days : {}, - }, - currentPricingInfo: { - pricingModel: pricingInfo.pricingModel?.toString() || '', - pricePerUnitUsd: pricingInfo?.pricePerUnitUsd ?? 0, - trialMinutes: pricingInfo?.trialMinutes ?? 0, - }, - url: response.url?.toString() || '', - totalStars: 'totalStars' in response ? (response.totalStars as number) : null, - }; -} - -export async function searchActorsByKeywords( - search: string, - limit: number | undefined = undefined, - offset: number | undefined = undefined, -): Promise { - const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); - const results = await client.store().list({ search, limit, offset }); - return results.items.map((x) => pruneActorStoreInfo(x)); -} diff --git a/src/tools/discover-actors.ts b/src/tools/discover-actors.ts index d1f61f85..6142b059 100644 --- a/src/tools/discover-actors.ts +++ b/src/tools/discover-actors.ts @@ -2,8 +2,8 @@ import { Ajv } from 'ajv'; import { z } from 'zod'; import zodToJsonSchema from 'zod-to-json-schema'; +import { searchActorsByKeywords } from '../actors.js'; import { InternalTools } from '../const.js'; -import { searchActorsByKeywords } from '../tools.js'; import type { InternalTool, ToolWrap } from '../types.js'; const ajv = new Ajv({ coerceTypes: 'array', strict: false }); diff --git a/src/tools/index.ts b/src/tools/index.ts index b47d04fe..480b344a 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -1,4 +1,21 @@ -export { discoverActorsTool } from './discover-actors.js'; -export { getActorsDetailsTool } from './get-actors-details.js'; -export { addActorToTools } from './add-actors-to-tools.js'; -export { removeActorFromTools } from './remove-actors-from-tools.js'; +import type { ToolWrap } from '../types.js'; +import { addActorToTools } from './add-actors-to-tools.js'; +import { discoverActorsTool } from './discover-actors.js'; +import { getActorsDetailsTool } from './get-actors-details.js'; +import { removeActorFromTools } from './remove-actors-from-tools.js'; + +export { addActorToTools, removeActorFromTools, discoverActorsTool, getActorsDetailsTool }; + +export function getActorAutoLoadingTools(): ToolWrap[] { + return [ + addActorToTools, + removeActorFromTools, + ]; +} + +export function getActorDiscoveryTools(): ToolWrap[] { + return [ + discoverActorsTool, + getActorsDetailsTool, + ]; +} From 99bee44c5ff6b2f4fe14cfe8f886e6594a792ab6 Mon Sep 17 00:00:00 2001 From: MQ Date: Mon, 7 Apr 2025 11:15:20 +0200 Subject: [PATCH 09/42] fix stdio.js path, get Apify token from env var for stdio and standby Actor use case --- .actor/ACTOR.md | 2 +- README.md | 2 +- package.json | 2 +- src/examples/clientStdio.ts | 2 +- src/examples/clientStdioChat.ts | 2 +- src/mcp-server.ts | 6 +++++- src/stdio.ts | 2 +- 7 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.actor/ACTOR.md b/.actor/ACTOR.md index 4c73585c..99f0c1da 100644 --- a/.actor/ACTOR.md +++ b/.actor/ACTOR.md @@ -358,7 +358,7 @@ You can launch the MCP Inspector via [`npm`](https://docs.npmjs.com/downloading- ```bash export APIFY_TOKEN=your-apify-token -npx @modelcontextprotocol/inspector node ./dist/index.js +npx @modelcontextprotocol/inspector node ./dist/stdio.js ``` Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging. diff --git a/README.md b/README.md index 4c73585c..99f0c1da 100644 --- a/README.md +++ b/README.md @@ -358,7 +358,7 @@ You can launch the MCP Inspector via [`npm`](https://docs.npmjs.com/downloading- ```bash export APIFY_TOKEN=your-apify-token -npx @modelcontextprotocol/inspector node ./dist/index.js +npx @modelcontextprotocol/inspector node ./dist/stdio.js ``` Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging. diff --git a/package.json b/package.json index 57907acc..3c176d01 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "lint:fix": "eslint . --fix", "build": "tsc", "watch": "tsc --watch", - "inspector": "npx @modelcontextprotocol/inspector dist/index.js", + "inspector": "npx @modelcontextprotocol/inspector dist/stdio.js", "test": "vitest run" }, "author": "Apify", diff --git a/src/examples/clientStdio.ts b/src/examples/clientStdio.ts index 468f007f..7df29efd 100644 --- a/src/examples/clientStdio.ts +++ b/src/examples/clientStdio.ts @@ -22,7 +22,7 @@ const filename = fileURLToPath(import.meta.url); const dirname = path.dirname(filename); dotenv.config({ path: path.resolve(dirname, '../../.env') }); -const SERVER_PATH = path.resolve(dirname, '../../dist/index.js'); +const SERVER_PATH = path.resolve(dirname, '../../dist/stdio.js'); const NODE_PATH = execSync(process.platform === 'win32' ? 'where node' : 'which node').toString().trim(); const TOOLS = 'apify/rag-web-browser,lukaskrivka/google-maps-with-contact-details'; diff --git a/src/examples/clientStdioChat.ts b/src/examples/clientStdioChat.ts index 1ebdf607..d7c122e2 100644 --- a/src/examples/clientStdioChat.ts +++ b/src/examples/clientStdioChat.ts @@ -41,7 +41,7 @@ const MAX_TOKENS = 2048; // Maximum tokens for Claude response // const CLAUDE_MODEL = 'claude-3-5-haiku-20241022'; // a fastest model const CLAUDE_MODEL = 'claude-3-haiku-20240307'; // a fastest and most compact model for near-instant responsiveness const DEBUG = true; -const DEBUG_SERVER_PATH = path.resolve(dirname, '../../dist/index.js'); +const DEBUG_SERVER_PATH = path.resolve(dirname, '../../dist/stdio.js'); const NODE_PATH = execSync('which node').toString().trim(); diff --git a/src/mcp-server.ts b/src/mcp-server.ts index 4545e97a..62b575e2 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -173,7 +173,11 @@ export class ApifyMcpServer { * @throws {Error} - Throws an error if the tool is unknown or arguments are invalid. */ this.server.setRequestHandler(CallToolRequestSchema, async (request) => { - const { name, arguments: args, apifyToken } = request.params; + const { name, arguments: args } = request.params; + const apifyToken = request.params.apifyToken || process.env.APIFY_TOKEN; + if (!apifyToken) { + throw new Error('APIFY_TOKEN is required but not set in the environment variables or passed as a parameter.'); + } const tool = Array.from(this.tools.values()) .find((t) => t.tool.name === name || (t.type === 'actor' && (t.tool as ActorTool).actorFullName === name)); diff --git a/src/stdio.ts b/src/stdio.ts index 30caadca..2b4b1cc5 100644 --- a/src/stdio.ts +++ b/src/stdio.ts @@ -9,7 +9,7 @@ * --actors - A comma-separated list of actor full names to add to the server. * * Example: - * node index.js --actors=apify/google-search-scraper,apify/instagram-scraper + * node stdio.js --actors=apify/google-search-scraper,apify/instagram-scraper */ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; From e0af0b19bd0fa8b9cd03a26aae78893b22286db7 Mon Sep 17 00:00:00 2001 From: MQ Date: Mon, 7 Apr 2025 11:48:01 +0200 Subject: [PATCH 10/42] export helper functions, fix typos and minor issues --- src/actors.ts | 4 ++-- src/index.ts | 1 + src/main.ts | 8 ++++---- src/mcp-server.ts | 7 +++---- src/stdio.ts | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/actors.ts b/src/actors.ts index b1eab35d..ebeb552a 100644 --- a/src/actors.ts +++ b/src/actors.ts @@ -14,8 +14,8 @@ export function actorNameToToolName(actorName: string): string { } /** - * Get actor input schema by actor name. - * First, fetch the actor details to get the default build tag and buildId. + * Get Actor input schema by Actor name. + * First, fetch the Actor details to get the default build tag and buildId. * Then, fetch the build details and return actorName, description, and input schema. * @param {string} actorIdOrName - Actor ID or Actor full name. * @param {number} limit - Truncate the README to this limit. diff --git a/src/index.ts b/src/index.ts index 2cbb5bbf..afd1e16c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,3 +6,4 @@ export { createExpressApp } from './server.js'; export { ApifyMcpServer } from './mcp-server.js'; export { getActorAutoLoadingTools, getActorDiscoveryTools } from './tools/index.js'; export { addActorToTools, discoverActorsTool, getActorsDetailsTool, removeActorFromTools } from './tools/index.js'; +export { getActorsAsTools, searchActorsByKeywords } from './actors.js'; diff --git a/src/main.ts b/src/main.ts index 08040645..f18a2e0e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,6 @@ -/* -This file serves as an Actor MCP SSE server entry point. -*/ +/** + * This file serves as an Actor MCP SSE server entry point. + */ import { Actor } from 'apify'; import type { ActorCallOptions } from 'apify-client'; @@ -66,7 +66,7 @@ if (isActorStandby()) { log.info('Actor is not designed to run in the NORMAL model (use this mode only for debugging purposes)'); if (input && !input.debugActor && !input.debugActorInput) { - await Actor.fail('If you need to debug a specific actor, please provide the debugActor and debugActorInput fields in the input'); + await Actor.fail('If you need to debug a specific Actor, please provide the debugActor and debugActorInput fields in the input'); } const options = { memory: input.maxActorMemoryBytes } as ActorCallOptions; await mcpServer.callActorGetDataset(input.debugActor!, input.debugActorInput!, process.env.APIFY_TOKEN, options); diff --git a/src/mcp-server.ts b/src/mcp-server.ts index 62b575e2..50e7cff8 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -7,8 +7,7 @@ import { parse } from 'node:querystring'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; -import type { ApifyClientOptions } from 'apify'; -import { Actor } from 'apify'; +import { Actor, type ApifyClientOptions } from 'apify'; import type { ActorCallOptions } from 'apify-client'; import { ApifyClient } from 'apify-client'; import type { AxiosRequestConfig } from 'axios'; @@ -85,7 +84,7 @@ export class ApifyMcpServer { ): Promise { const name = actorName; try { - log.info(`Calling actor ${name} with input: ${JSON.stringify(input)}`); + log.info(`Calling Actor ${name} with input: ${JSON.stringify(input)}`); const options: ApifyClientOptions = { requestInterceptors: [this.addUserAgent] }; const client = new ApifyClient({ ...options, token: apifyToken }); @@ -102,7 +101,7 @@ export class ApifyMcpServer { return dataset.items; } catch (error) { log.error(`Error calling actor: ${error}. Actor: ${name}, input: ${JSON.stringify(input)}`); - throw new Error(`Error calling actor: ${error}`); + throw new Error(`Error calling Actor: ${error}`); } } diff --git a/src/stdio.ts b/src/stdio.ts index 2b4b1cc5..facdf12b 100644 --- a/src/stdio.ts +++ b/src/stdio.ts @@ -6,7 +6,7 @@ * node --actors= * * Command-line arguments: - * --actors - A comma-separated list of actor full names to add to the server. + * --actors - A comma-separated list of Actor full names to add to the server. * * Example: * node stdio.js --actors=apify/google-search-scraper,apify/instagram-scraper From 0697fc27030da8e8146774ceef2d6d5a8c211deb Mon Sep 17 00:00:00 2001 From: MQ Date: Mon, 7 Apr 2025 11:54:26 +0200 Subject: [PATCH 11/42] tsconfig remove root dir --- tsconfig.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 149be3a8..fb02aa71 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,8 +9,7 @@ "lib": ["ES2022"], "skipLibCheck": true, "typeRoots": ["./types", "./node_modules/@types"], - "strict": true, - "rootDir": "src/" + "strict": true }, "include": ["./src/**/*"], "exclude": ["node_modules"] From e982411ba81d82727d0a873f636bcaf7e769209b Mon Sep 17 00:00:00 2001 From: MQ Date: Mon, 7 Apr 2025 15:06:13 +0200 Subject: [PATCH 12/42] Refactor actor run data handling by moving logic to getActorRunData function and updating types. --- .gitignore | 1 + src/main.ts | 27 ++------------------------- src/types.ts | 23 +++++++++++++++++++++++ src/utils.ts | 6 ++++-- 4 files changed, 30 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index 1e23c8f4..75950be2 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ storage/key_value_stores/default/* # Added by Apify CLI .venv .env +.aider* diff --git a/src/main.ts b/src/main.ts index f18a2e0e..afa7de9a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -11,7 +11,7 @@ import { ApifyMcpServer } from './mcp-server.js'; import { createExpressApp } from './server.js'; import { getActorAutoLoadingTools, getActorDiscoveryTools } from './tools/index.js'; import type { Input } from './types.js'; -import { isActorStandby } from './utils.js'; +import { getActorRunData, isActorStandby } from './utils.js'; await Actor.init(); @@ -25,34 +25,11 @@ if (!process.env.APIFY_TOKEN) { const mcpServer = new ApifyMcpServer(); -const actorRunData = Actor.isAtHome() ? { - id: process.env.ACTOR_RUN_ID, - actId: process.env.ACTOR_ID, - userId: process.env.APIFY_USER_ID, - startedAt: process.env.ACTOR_STARTED_AT, - finishedAt: null, - status: 'RUNNING', - meta: { - origin: process.env.APIFY_META_ORIGIN, - }, - options: { - build: process.env.ACTOR_BUILD_NUMBER, - memoryMbytes: process.env.ACTOR_MEMORY_MBYTES, - }, - buildId: process.env.ACTOR_BUILD_ID, - defaultKeyValueStoreId: process.env.ACTOR_DEFAULT_KEY_VALUE_STORE_ID, - defaultDatasetId: process.env.ACTOR_DEFAULT_DATASET_ID, - defaultRequestQueueId: process.env.ACTOR_DEFAULT_REQUEST_QUEUE_ID, - buildNumber: process.env.ACTOR_BUILD_NUMBER, - containerUrl: process.env.ACTOR_WEB_SERVER_URL, - standbyUrl: process.env.ACTOR_STANDBY_URL, -} : {}; - const input = await processInput((await Actor.getInput>()) ?? ({} as Input)); log.info(`Loaded input: ${JSON.stringify(input)} `); if (isActorStandby()) { - const app = createExpressApp(HOST, mcpServer, actorRunData); + const app = createExpressApp(HOST, mcpServer, getActorRunData() || {}); log.info('Actor is running in the STANDBY mode.'); await mcpServer.addToolsFromDefaultActors(); mcpServer.updateTools(getActorDiscoveryTools()); diff --git a/src/types.ts b/src/types.ts index 877b3456..5688c2d9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -147,3 +147,26 @@ export interface ActorStorePruned { url: string; totalStars?: number | null; } + +export interface ActorRunData { + id?: string; + actId?: string; + userId?: string; + startedAt?: string; + finishedAt: null; + status: 'RUNNING'; + meta: { + origin?: string; + }; + options: { + build?: string; + memoryMbytes?: string; + }; + buildId?: string; + defaultKeyValueStoreId?: string; + defaultDatasetId?: string; + defaultRequestQueueId?: string; + buildNumber?: string; + containerUrl?: string; + standbyUrl?: string; +} diff --git a/src/utils.ts b/src/utils.ts index cace0f30..27843242 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,10 +1,12 @@ import { Actor } from 'apify'; +import type { ActorRunData } from './types'; + export function isActorStandby(): boolean { return Actor.getEnv().metaOrigin === 'STANDBY'; } -export function getActorRunData(): unknown { +export function getActorRunData(): ActorRunData | null { return Actor.isAtHome() ? { id: process.env.ACTOR_RUN_ID, actId: process.env.ACTOR_ID, @@ -26,5 +28,5 @@ export function getActorRunData(): unknown { buildNumber: process.env.ACTOR_BUILD_NUMBER, containerUrl: process.env.ACTOR_WEB_SERVER_URL, standbyUrl: process.env.ACTOR_STANDBY_URL, - } : {}; + } : null; } From ca13d547a247b67531803df4d35adf3909803085 Mon Sep 17 00:00:00 2001 From: MQ Date: Mon, 7 Apr 2025 15:40:10 +0200 Subject: [PATCH 13/42] split actors.ts --- src/actors.ts | 388 -------------------------- src/actors/details.ts | 87 ++++++ src/actors/schema.ts | 207 ++++++++++++++ src/actors/search.ts | 40 +++ src/actors/tools.ts | 66 +++++ src/actors/utils.ts | 6 + src/examples/clientSse.ts | 2 +- src/examples/clientStdio.ts | 2 +- src/index.ts | 3 +- src/mcp-server.ts | 2 +- src/tools/discover-actors.ts | 2 +- src/tools/get-actors-details.ts | 3 +- src/tools/remove-actors-from-tools.ts | 2 +- 13 files changed, 415 insertions(+), 395 deletions(-) delete mode 100644 src/actors.ts create mode 100644 src/actors/details.ts create mode 100644 src/actors/schema.ts create mode 100644 src/actors/search.ts create mode 100644 src/actors/tools.ts create mode 100644 src/actors/utils.ts diff --git a/src/actors.ts b/src/actors.ts deleted file mode 100644 index ebeb552a..00000000 --- a/src/actors.ts +++ /dev/null @@ -1,388 +0,0 @@ -import { Ajv } from 'ajv'; -import type { ActorStoreList } from 'apify-client'; -import { ApifyClient } from 'apify-client'; - -import { ACTOR_ADDITIONAL_INSTRUCTIONS, defaults, MAX_DESCRIPTION_LENGTH, ACTOR_README_MAX_LENGTH, ACTOR_ENUM_MAX_LENGTH } from './const.js'; -import { log } from './logger.js'; -import type { ActorStorePruned, PricingInfo, ActorDefinitionPruned, ActorDefinitionWithDesc, IActorInputSchema, ISchemaProperties, ToolWrap } from './types.js'; - -export function actorNameToToolName(actorName: string): string { - return actorName - .replace(/\//g, '-slash-') - .replace(/\./g, '-dot-') - .slice(0, 64); -} - -/** - * Get Actor input schema by Actor name. - * First, fetch the Actor details to get the default build tag and buildId. - * Then, fetch the build details and return actorName, description, and input schema. - * @param {string} actorIdOrName - Actor ID or Actor full name. - * @param {number} limit - Truncate the README to this limit. - * @returns {Promise} - The actor definition with description or null if not found. - */ -export async function getActorDefinition(actorIdOrName: string, limit: number = ACTOR_README_MAX_LENGTH): Promise { - const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); - const actorClient = client.actor(actorIdOrName); - - try { - // Fetch actor details - const actor = await actorClient.get(); - if (!actor) { - log.error(`Failed to fetch input schema for Actor: ${actorIdOrName}. Actor not found.`); - return null; - } - - // fnesveda: The default build is not necessarily tagged, you can specify any build number as default build. - // There will be a new API endpoint to fetch a default build. - // For now, we'll use the tagged build, it will work for 90% of Actors. Later, we can update this. - const tag = actor.defaultRunOptions?.build || ''; - const buildId = actor.taggedBuilds?.[tag]?.buildId || ''; - - if (!buildId) { - log.error(`Failed to fetch input schema for Actor: ${actorIdOrName}. Build ID not found.`); - return null; - } - // Fetch build details and return the input schema - const buildDetails = await client.build(buildId).get(); - if (buildDetails?.actorDefinition) { - const actorDefinitions = buildDetails?.actorDefinition as ActorDefinitionWithDesc; - actorDefinitions.id = actor.id; - actorDefinitions.readme = truncateActorReadme(actorDefinitions.readme || '', limit); - actorDefinitions.description = actor.description || ''; - actorDefinitions.actorFullName = `${actor.username}/${actor.name}`; - actorDefinitions.defaultRunOptions = actor.defaultRunOptions; - return pruneActorDefinition(actorDefinitions); - } - return null; - } catch (error) { - const errorMessage = `Failed to fetch input schema for Actor: ${actorIdOrName} with error ${error}.`; - log.error(errorMessage); - throw new Error(errorMessage); - } -} - -function pruneActorDefinition(response: ActorDefinitionWithDesc): ActorDefinitionPruned { - return { - id: response.id, - actorFullName: response.actorFullName || '', - buildTag: response?.buildTag || '', - readme: response?.readme || '', - input: response?.input && 'type' in response.input && 'properties' in response.input - ? { ...response.input, - type: response.input.type as string, - properties: response.input.properties as Record } - : undefined, - description: response.description, - defaultRunOptions: response.defaultRunOptions, - }; -} - -/** - * Helper function to shorten the enum list if it is too long. - * - * @param {string[]} enumList - The list of enum values to be shortened. - * @returns {string[] | undefined} - The shortened enum list or undefined if the list is too long. - */ -export function shortenEnum(enumList: string[]): string[] | undefined { - let charCount = 0; - const resultEnumList = enumList.filter((enumValue) => { - charCount += enumValue.length; - return charCount <= ACTOR_ENUM_MAX_LENGTH; - }); - - return resultEnumList.length > 0 ? resultEnumList : undefined; -} - -/** - * Shortens the description, enum, and items.enum properties of the schema properties. - * @param properties - */ -export function shortenProperties(properties: { [key: string]: ISchemaProperties}): { [key: string]: ISchemaProperties } { - for (const property of Object.values(properties)) { - if (property.description.length > MAX_DESCRIPTION_LENGTH) { - property.description = `${property.description.slice(0, MAX_DESCRIPTION_LENGTH)}...`; - } - - if (property.enum && property.enum?.length > 0) { - property.enum = shortenEnum(property.enum); - } - - if (property.items?.enum && property.items.enum.length > 0) { - property.items.enum = shortenEnum(property.items.enum); - } - } - - return properties; -} - -/** Prune Actor README if it is too long - * If the README is too long - * - We keep the README as it is up to the limit. - * - After the limit, we keep heading only - * - We add a note that the README was truncated because it was too long. - */ -export function truncateActorReadme(readme: string, limit = ACTOR_README_MAX_LENGTH): string { - if (readme.length <= limit) { - return readme; - } - const readmeFirst = readme.slice(0, limit); - const readmeRest = readme.slice(limit); - const lines = readmeRest.split('\n'); - const prunedReadme = lines.filter((line) => line.startsWith('#')); - return `${readmeFirst}\n\nREADME was truncated because it was too long. Remaining headers:\n${prunedReadme.join(', ')}`; -} -/** - * Helps determine the type of items in an array schema property. - * Priority order: explicit type in items > prefill type > default value type > editor type. - * - * Based on JSON schema, the array needs a type, and most of the time Actor input schema does not have this, so we need to infer that. - * - */ -export function inferArrayItemType(property: ISchemaProperties): string | null { - return property.items?.type - || (Array.isArray(property.prefill) && property.prefill.length > 0 && typeof property.prefill[0]) - || (Array.isArray(property.default) && property.default.length > 0 && typeof property.default[0]) - || (property.editor && getEditorItemType(property.editor)) - || null; - - function getEditorItemType(editor: string): string | null { - const editorTypeMap: Record = { - requestListSources: 'object', - stringList: 'string', - json: 'object', - globs: 'object', - }; - return editorTypeMap[editor] || null; - } -} - -/** - * Add enum values as string to property descriptions. - * - * This is done as a preventive measure to prevent cases where library or agent framework - * does not handle enums or examples based on JSON schema definition. - * - * https://json-schema.org/understanding-json-schema/reference/enum - * https://json-schema.org/understanding-json-schema/reference/annotations - * - * @param properties - */ -function addEnumsToDescriptionsWithExamples(properties: Record): Record { - for (const property of Object.values(properties)) { - if (property.enum && property.enum.length > 0) { - property.description = `${property.description}\nPossible values: ${property.enum.slice(0, 20).join(',')}`; - } - const value = property.prefill ?? property.default; - if (value && !(Array.isArray(value) && value.length === 0)) { - property.examples = Array.isArray(value) ? value : [value]; - property.description = `${property.description}\nExample values: ${JSON.stringify(value)}`; - } - } - return properties; -} - -/** - * Filters schema properties to include only the necessary fields. - * - * This is done to reduce the size of the input schema and to make it more readable. - * - * @param properties - */ -export function filterSchemaProperties(properties: { [key: string]: ISchemaProperties }): { [key: string]: ISchemaProperties } { - const filteredProperties: { [key: string]: ISchemaProperties } = {}; - for (const [key, property] of Object.entries(properties)) { - filteredProperties[key] = { - title: property.title, - description: property.description, - enum: property.enum, - type: property.type, - default: property.default, - prefill: property.prefill, - properties: property.properties, - items: property.items, - required: property.required, - }; - if (property.type === 'array' && !property.items?.type) { - const itemsType = inferArrayItemType(property); - if (itemsType) { - filteredProperties[key].items = { - ...filteredProperties[key].items, - title: filteredProperties[key].title ?? 'Item', - description: filteredProperties[key].description ?? 'Item', - type: itemsType, - }; - } - } - } - return filteredProperties; -} - -/** - * Marks input properties as required by adding a "REQUIRED" prefix to their descriptions. - * Takes an IActorInput object and returns a modified Record of SchemaProperties. - * - * This is done for maximum compatibility in case where library or agent framework does not consider - * required fields and does not handle the JSON schema properly: we are prepending this to the description - * as a preventive measure. - * @param {IActorInputSchema} input - Actor input object containing properties and required fields - * @returns {Record} - Modified properties with required fields marked - */ -function markInputPropertiesAsRequired(input: IActorInputSchema): Record { - const { required = [], properties } = input; - - for (const property of Object.keys(properties)) { - if (required.includes(property)) { - properties[property] = { - ...properties[property], - description: `**REQUIRED** ${properties[property].description}`, - }; - } - } - - return properties; -} - -/** - * Builds nested properties for object types in the schema. - * - * Specifically handles special cases like proxy configuration and request list sources - * by adding predefined nested properties to these object types. - * This is necessary for the agent to correctly infer how to structure object inputs - * when passing arguments to the Actor. - * - * For proxy objects (type='object', editor='proxy'), adds 'useApifyProxy' property. - * For request list sources (type='array', editor='requestListSources'), adds URL structure to items. - * - * @param {Record} properties - The input schema properties - * @returns {Record} Modified properties with nested properties - */ -function buildNestedProperties(properties: Record): Record { - const clonedProperties = { ...properties }; - - for (const [propertyName, property] of Object.entries(clonedProperties)) { - if (property.type === 'object' && property.editor === 'proxy') { - clonedProperties[propertyName] = { - ...property, - properties: { - ...property.properties, - useApifyProxy: { - title: 'Use Apify Proxy', - type: 'boolean', - description: 'Whether to use Apify Proxy - ALWAYS SET TO TRUE.', - default: true, - examples: [true], - }, - }, - required: ['useApifyProxy'], - }; - } else if (property.type === 'array' && property.editor === 'requestListSources') { - clonedProperties[propertyName] = { - ...property, - items: { - ...property.items, - type: 'object', - title: 'Request list source', - description: 'Request list source', - properties: { - url: { - title: 'URL', - type: 'string', - description: 'URL of the request list source', - }, - }, - }, - }; - } - } - - return clonedProperties; -} - -/** - * Fetches actor input schemas by Actor IDs or Actor full names and creates MCP tools. - * - * This function retrieves the input schemas for the specified actors and compiles them into MCP tools. - * It uses the AJV library to validate the input schemas. - * - * Tool name can't contain /, so it is replaced with _ - * - * The input schema processing workflow: - * 1. Properties are marked as required using markInputPropertiesAsRequired() to add "REQUIRED" prefix to descriptions - * 2. Nested properties are built by analyzing editor type (proxy, requestListSources) using buildNestedProperties() - * 3. Properties are filtered using filterSchemaProperties() - * 4. Properties are shortened using shortenProperties() - * 5. Enums are added to descriptions with examples using addEnumsToDescriptionsWithExamples() - * - * @param {string[]} actors - An array of actor IDs or Actor full names. - * @returns {Promise} - A promise that resolves to an array of MCP tools. - */ -export async function getActorsAsTools(actors: string[]): Promise { - const ajv = new Ajv({ coerceTypes: 'array', strict: false }); - const results = await Promise.all(actors.map(getActorDefinition)); - const tools: ToolWrap[] = []; - for (const result of results) { - if (result) { - if (result.input && 'properties' in result.input && result.input) { - result.input.properties = markInputPropertiesAsRequired(result.input); - result.input.properties = buildNestedProperties(result.input.properties); - result.input.properties = filterSchemaProperties(result.input.properties); - result.input.properties = shortenProperties(result.input.properties); - result.input.properties = addEnumsToDescriptionsWithExamples(result.input.properties); - } - try { - const memoryMbytes = result.defaultRunOptions?.memoryMbytes || defaults.maxMemoryMbytes; - tools.push({ - type: 'actor', - tool: { - name: actorNameToToolName(result.actorFullName), - actorFullName: result.actorFullName, - description: `${result.description} Instructions: ${ACTOR_ADDITIONAL_INSTRUCTIONS}`, - inputSchema: result.input || {}, - ajvValidate: ajv.compile(result.input || {}), - memoryMbytes: memoryMbytes > defaults.maxMemoryMbytes ? defaults.maxMemoryMbytes : memoryMbytes, - }, - }); - } catch (validationError) { - log.error(`Failed to compile AJV schema for Actor: ${result.actorFullName}. Error: ${validationError}`); - } - } - } - return tools; -} - -function pruneActorStoreInfo(response: ActorStoreList): ActorStorePruned { - const stats = response.stats || {}; - const pricingInfo = (response.currentPricingInfo || {}) as PricingInfo; - return { - id: response.id, - name: response.name?.toString() || '', - username: response.username?.toString() || '', - actorFullName: `${response.username}/${response.name}`, - title: response.title?.toString() || '', - description: response.description?.toString() || '', - stats: { - totalRuns: stats.totalRuns, - totalUsers30Days: stats.totalUsers30Days, - publicActorRunStats30Days: 'publicActorRunStats30Days' in stats - ? stats.publicActorRunStats30Days : {}, - }, - currentPricingInfo: { - pricingModel: pricingInfo.pricingModel?.toString() || '', - pricePerUnitUsd: pricingInfo?.pricePerUnitUsd ?? 0, - trialMinutes: pricingInfo?.trialMinutes ?? 0, - }, - url: response.url?.toString() || '', - totalStars: 'totalStars' in response ? (response.totalStars as number) : null, - }; -} - -export async function searchActorsByKeywords( - search: string, - limit: number | undefined = undefined, - offset: number | undefined = undefined, -): Promise { - const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); - const results = await client.store().list({ search, limit, offset }); - return results.items.map((x) => pruneActorStoreInfo(x)); -} diff --git a/src/actors/details.ts b/src/actors/details.ts new file mode 100644 index 00000000..bf3cb819 --- /dev/null +++ b/src/actors/details.ts @@ -0,0 +1,87 @@ +import { ApifyClient } from 'apify-client'; + +import { ACTOR_README_MAX_LENGTH } from '../const.js'; +import { log } from '../logger.js'; +import type { ActorDefinitionPruned, ActorDefinitionWithDesc, ISchemaProperties } from '../types.js'; + +/** + * Get Actor input schema by Actor name. + * First, fetch the Actor details to get the default build tag and buildId. + * Then, fetch the build details and return actorName, description, and input schema. + * @param {string} actorIdOrName - Actor ID or Actor full name. + * @param {number} limit - Truncate the README to this limit. + * @returns {Promise} - The actor definition with description or null if not found. + */ +export async function getActorDefinition(actorIdOrName: string, limit: number = ACTOR_README_MAX_LENGTH): Promise { + const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); + const actorClient = client.actor(actorIdOrName); + + try { + // Fetch actor details + const actor = await actorClient.get(); + if (!actor) { + log.error(`Failed to fetch input schema for Actor: ${actorIdOrName}. Actor not found.`); + return null; + } + + // fnesveda: The default build is not necessarily tagged, you can specify any build number as default build. + // There will be a new API endpoint to fetch a default build. + // For now, we'll use the tagged build, it will work for 90% of Actors. Later, we can update this. + const tag = actor.defaultRunOptions?.build || ''; + const buildId = actor.taggedBuilds?.[tag]?.buildId || ''; + + if (!buildId) { + log.error(`Failed to fetch input schema for Actor: ${actorIdOrName}. Build ID not found.`); + return null; + } + // Fetch build details and return the input schema + const buildDetails = await client.build(buildId).get(); + if (buildDetails?.actorDefinition) { + const actorDefinitions = buildDetails?.actorDefinition as ActorDefinitionWithDesc; + actorDefinitions.id = actor.id; + actorDefinitions.readme = truncateActorReadme(actorDefinitions.readme || '', limit); + actorDefinitions.description = actor.description || ''; + actorDefinitions.actorFullName = `${actor.username}/${actor.name}`; + actorDefinitions.defaultRunOptions = actor.defaultRunOptions; + return pruneActorDefinition(actorDefinitions); + } + return null; + } catch (error) { + const errorMessage = `Failed to fetch input schema for Actor: ${actorIdOrName} with error ${error}.`; + log.error(errorMessage); + throw new Error(errorMessage); + } +} + +function pruneActorDefinition(response: ActorDefinitionWithDesc): ActorDefinitionPruned { + return { + id: response.id, + actorFullName: response.actorFullName || '', + buildTag: response?.buildTag || '', + readme: response?.readme || '', + input: response?.input && 'type' in response.input && 'properties' in response.input + ? { ...response.input, + type: response.input.type as string, + properties: response.input.properties as Record } + : undefined, + description: response.description, + defaultRunOptions: response.defaultRunOptions, + }; +} + +/** Prune Actor README if it is too long + * If the README is too long + * - We keep the README as it is up to the limit. + * - After the limit, we keep heading only + * - We add a note that the README was truncated because it was too long. + */ +export function truncateActorReadme(readme: string, limit = ACTOR_README_MAX_LENGTH): string { + if (readme.length <= limit) { + return readme; + } + const readmeFirst = readme.slice(0, limit); + const readmeRest = readme.slice(limit); + const lines = readmeRest.split('\n'); + const prunedReadme = lines.filter((line) => line.startsWith('#')); + return `${readmeFirst}\n\nREADME was truncated because it was too long. Remaining headers:\n${prunedReadme.join(', ')}`; +} diff --git a/src/actors/schema.ts b/src/actors/schema.ts new file mode 100644 index 00000000..cd8b2728 --- /dev/null +++ b/src/actors/schema.ts @@ -0,0 +1,207 @@ +import { MAX_DESCRIPTION_LENGTH, ACTOR_ENUM_MAX_LENGTH } from '../const.js'; +import type { IActorInputSchema, ISchemaProperties } from '../types.js'; + +/** + * Builds nested properties for object types in the schema. + * + * Specifically handles special cases like proxy configuration and request list sources + * by adding predefined nested properties to these object types. + * This is necessary for the agent to correctly infer how to structure object inputs + * when passing arguments to the Actor. + * + * For proxy objects (type='object', editor='proxy'), adds 'useApifyProxy' property. + * For request list sources (type='array', editor='requestListSources'), adds URL structure to items. + * + * @param {Record} properties - The input schema properties + * @returns {Record} Modified properties with nested properties + */ +export function buildNestedProperties(properties: Record): Record { + const clonedProperties = { ...properties }; + + for (const [propertyName, property] of Object.entries(clonedProperties)) { + if (property.type === 'object' && property.editor === 'proxy') { + clonedProperties[propertyName] = { + ...property, + properties: { + ...property.properties, + useApifyProxy: { + title: 'Use Apify Proxy', + type: 'boolean', + description: 'Whether to use Apify Proxy - ALWAYS SET TO TRUE.', + default: true, + examples: [true], + }, + }, + required: ['useApifyProxy'], + }; + } else if (property.type === 'array' && property.editor === 'requestListSources') { + clonedProperties[propertyName] = { + ...property, + items: { + ...property.items, + type: 'object', + title: 'Request list source', + description: 'Request list source', + properties: { + url: { + title: 'URL', + type: 'string', + description: 'URL of the request list source', + }, + }, + }, + }; + } + } + + return clonedProperties; +} + +/** + * Filters schema properties to include only the necessary fields. + * + * This is done to reduce the size of the input schema and to make it more readable. + * + * @param properties + */ +export function filterSchemaProperties(properties: { [key: string]: ISchemaProperties }): { [key: string]: ISchemaProperties } { + const filteredProperties: { [key: string]: ISchemaProperties } = {}; + for (const [key, property] of Object.entries(properties)) { + filteredProperties[key] = { + title: property.title, + description: property.description, + enum: property.enum, + type: property.type, + default: property.default, + prefill: property.prefill, + properties: property.properties, + items: property.items, + required: property.required, + }; + if (property.type === 'array' && !property.items?.type) { + const itemsType = inferArrayItemType(property); + if (itemsType) { + filteredProperties[key].items = { + ...filteredProperties[key].items, + title: filteredProperties[key].title ?? 'Item', + description: filteredProperties[key].description ?? 'Item', + type: itemsType, + }; + } + } + } + return filteredProperties; +} + +/** + * Marks input properties as required by adding a "REQUIRED" prefix to their descriptions. + * Takes an IActorInput object and returns a modified Record of SchemaProperties. + * + * This is done for maximum compatibility in case where library or agent framework does not consider + * required fields and does not handle the JSON schema properly: we are prepending this to the description + * as a preventive measure. + * @param {IActorInputSchema} input - Actor input object containing properties and required fields + * @returns {Record} - Modified properties with required fields marked + */ +export function markInputPropertiesAsRequired(input: IActorInputSchema): Record { + const { required = [], properties } = input; + + for (const property of Object.keys(properties)) { + if (required.includes(property)) { + properties[property] = { + ...properties[property], + description: `**REQUIRED** ${properties[property].description}`, + }; + } + } + + return properties; +} + +/** + * Helps determine the type of items in an array schema property. + * Priority order: explicit type in items > prefill type > default value type > editor type. + * + * Based on JSON schema, the array needs a type, and most of the time Actor input schema does not have this, so we need to infer that. + * + */ +export function inferArrayItemType(property: ISchemaProperties): string | null { + return property.items?.type + || (Array.isArray(property.prefill) && property.prefill.length > 0 && typeof property.prefill[0]) + || (Array.isArray(property.default) && property.default.length > 0 && typeof property.default[0]) + || (property.editor && getEditorItemType(property.editor)) + || null; + + function getEditorItemType(editor: string): string | null { + const editorTypeMap: Record = { + requestListSources: 'object', + stringList: 'string', + json: 'object', + globs: 'object', + }; + return editorTypeMap[editor] || null; + } +} + +/** + * Add enum values as string to property descriptions. + * + * This is done as a preventive measure to prevent cases where library or agent framework + * does not handle enums or examples based on JSON schema definition. + * + * https://json-schema.org/understanding-json-schema/reference/enum + * https://json-schema.org/understanding-json-schema/reference/annotations + * + * @param properties + */ +export function addEnumsToDescriptionsWithExamples(properties: Record): Record { + for (const property of Object.values(properties)) { + if (property.enum && property.enum.length > 0) { + property.description = `${property.description}\nPossible values: ${property.enum.slice(0, 20).join(',')}`; + } + const value = property.prefill ?? property.default; + if (value && !(Array.isArray(value) && value.length === 0)) { + property.examples = Array.isArray(value) ? value : [value]; + property.description = `${property.description}\nExample values: ${JSON.stringify(value)}`; + } + } + return properties; +} + +/** + * Helper function to shorten the enum list if it is too long. + * + * @param {string[]} enumList - The list of enum values to be shortened. + * @returns {string[] | undefined} - The shortened enum list or undefined if the list is too long. + */ +export function shortenEnum(enumList: string[]): string[] | undefined { + let charCount = 0; + const resultEnumList = enumList.filter((enumValue) => { + charCount += enumValue.length; + return charCount <= ACTOR_ENUM_MAX_LENGTH; + }); + + return resultEnumList.length > 0 ? resultEnumList : undefined; +} + +/** + * Shortens the description, enum, and items.enum properties of the schema properties. + * @param properties + */ +export function shortenProperties(properties: { [key: string]: ISchemaProperties}): { [key: string]: ISchemaProperties } { + for (const property of Object.values(properties)) { + if (property.description.length > MAX_DESCRIPTION_LENGTH) { + property.description = `${property.description.slice(0, MAX_DESCRIPTION_LENGTH)}...`; + } + + if (property.enum && property.enum?.length > 0) { + property.enum = shortenEnum(property.enum); + } + + if (property.items?.enum && property.items.enum.length > 0) { + property.items.enum = shortenEnum(property.items.enum); + } + } + + return properties; +} diff --git a/src/actors/search.ts b/src/actors/search.ts new file mode 100644 index 00000000..1bd0cb90 --- /dev/null +++ b/src/actors/search.ts @@ -0,0 +1,40 @@ +import type { ActorStoreList } from 'apify-client'; +import { ApifyClient } from 'apify-client'; + +import type { ActorStorePruned, PricingInfo } from '../types.js'; + +function pruneActorStoreInfo(response: ActorStoreList): ActorStorePruned { + const stats = response.stats || {}; + const pricingInfo = (response.currentPricingInfo || {}) as PricingInfo; + return { + id: response.id, + name: response.name?.toString() || '', + username: response.username?.toString() || '', + actorFullName: `${response.username}/${response.name}`, + title: response.title?.toString() || '', + description: response.description?.toString() || '', + stats: { + totalRuns: stats.totalRuns, + totalUsers30Days: stats.totalUsers30Days, + publicActorRunStats30Days: 'publicActorRunStats30Days' in stats + ? stats.publicActorRunStats30Days : {}, + }, + currentPricingInfo: { + pricingModel: pricingInfo.pricingModel?.toString() || '', + pricePerUnitUsd: pricingInfo?.pricePerUnitUsd ?? 0, + trialMinutes: pricingInfo?.trialMinutes ?? 0, + }, + url: response.url?.toString() || '', + totalStars: 'totalStars' in response ? (response.totalStars as number) : null, + }; +} + +export async function searchActorsByKeywords( + search: string, + limit: number | undefined = undefined, + offset: number | undefined = undefined, +): Promise { + const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); + const results = await client.store().list({ search, limit, offset }); + return results.items.map((x) => pruneActorStoreInfo(x)); +} diff --git a/src/actors/tools.ts b/src/actors/tools.ts new file mode 100644 index 00000000..8db1335f --- /dev/null +++ b/src/actors/tools.ts @@ -0,0 +1,66 @@ +import { Ajv } from 'ajv'; + +import { ACTOR_ADDITIONAL_INSTRUCTIONS, defaults } from '../const.js'; +import { log } from '../logger.js'; +import type { ToolWrap } from '../types.js'; +import { getActorDefinition } from './details.js'; +import { + addEnumsToDescriptionsWithExamples, + buildNestedProperties, + filterSchemaProperties, + markInputPropertiesAsRequired, + shortenProperties, +} from './schema.js'; +import { actorNameToToolName } from './utils.js'; + +/** + * Fetches actor input schemas by Actor IDs or Actor full names and creates MCP tools. + * + * This function retrieves the input schemas for the specified actors and compiles them into MCP tools. + * It uses the AJV library to validate the input schemas. + * + * Tool name can't contain /, so it is replaced with _ + * + * The input schema processing workflow: + * 1. Properties are marked as required using markInputPropertiesAsRequired() to add "REQUIRED" prefix to descriptions + * 2. Nested properties are built by analyzing editor type (proxy, requestListSources) using buildNestedProperties() + * 3. Properties are filtered using filterSchemaProperties() + * 4. Properties are shortened using shortenProperties() + * 5. Enums are added to descriptions with examples using addEnumsToDescriptionsWithExamples() + * + * @param {string[]} actors - An array of actor IDs or Actor full names. + * @returns {Promise} - A promise that resolves to an array of MCP tools. + */ +export async function getActorsAsTools(actors: string[]): Promise { + const ajv = new Ajv({ coerceTypes: 'array', strict: false }); + const results = await Promise.all(actors.map(getActorDefinition)); + const tools: ToolWrap[] = []; + for (const result of results) { + if (result) { + if (result.input && 'properties' in result.input && result.input) { + result.input.properties = markInputPropertiesAsRequired(result.input); + result.input.properties = buildNestedProperties(result.input.properties); + result.input.properties = filterSchemaProperties(result.input.properties); + result.input.properties = shortenProperties(result.input.properties); + result.input.properties = addEnumsToDescriptionsWithExamples(result.input.properties); + } + try { + const memoryMbytes = result.defaultRunOptions?.memoryMbytes || defaults.maxMemoryMbytes; + tools.push({ + type: 'actor', + tool: { + name: actorNameToToolName(result.actorFullName), + actorFullName: result.actorFullName, + description: `${result.description} Instructions: ${ACTOR_ADDITIONAL_INSTRUCTIONS}`, + inputSchema: result.input || {}, + ajvValidate: ajv.compile(result.input || {}), + memoryMbytes: memoryMbytes > defaults.maxMemoryMbytes ? defaults.maxMemoryMbytes : memoryMbytes, + }, + }); + } catch (validationError) { + log.error(`Failed to compile AJV schema for Actor: ${result.actorFullName}. Error: ${validationError}`); + } + } + } + return tools; +} diff --git a/src/actors/utils.ts b/src/actors/utils.ts new file mode 100644 index 00000000..5531c45b --- /dev/null +++ b/src/actors/utils.ts @@ -0,0 +1,6 @@ +export function actorNameToToolName(actorName: string): string { + return actorName + .replace(/\//g, '-slash-') + .replace(/\./g, '-dot-') + .slice(0, 64); +} diff --git a/src/examples/clientSse.ts b/src/examples/clientSse.ts index 930266c1..d3cb01c4 100644 --- a/src/examples/clientSse.ts +++ b/src/examples/clientSse.ts @@ -15,7 +15,7 @@ import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js'; import dotenv from 'dotenv'; import { EventSource } from 'eventsource'; -import { actorNameToToolName } from '../actors.js'; +import { actorNameToToolName } from '../actors/utils.js'; const REQUEST_TIMEOUT = 120_000; // 2 minutes const filename = fileURLToPath(import.meta.url); diff --git a/src/examples/clientStdio.ts b/src/examples/clientStdio.ts index 7df29efd..cb7d6259 100644 --- a/src/examples/clientStdio.ts +++ b/src/examples/clientStdio.ts @@ -15,7 +15,7 @@ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js' import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js'; import dotenv from 'dotenv'; -import { actorNameToToolName } from '../actors.js'; +import { actorNameToToolName } from '../actors/utils.js'; // Resolve dirname equivalent in ES module const filename = fileURLToPath(import.meta.url); diff --git a/src/index.ts b/src/index.ts index afd1e16c..343bb8cf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,4 +6,5 @@ export { createExpressApp } from './server.js'; export { ApifyMcpServer } from './mcp-server.js'; export { getActorAutoLoadingTools, getActorDiscoveryTools } from './tools/index.js'; export { addActorToTools, discoverActorsTool, getActorsDetailsTool, removeActorFromTools } from './tools/index.js'; -export { getActorsAsTools, searchActorsByKeywords } from './actors.js'; +export { getActorsAsTools } from './actors/tools.js'; +export { searchActorsByKeywords } from './actors/search.js'; diff --git a/src/mcp-server.ts b/src/mcp-server.ts index 50e7cff8..1d6d13e5 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -14,7 +14,7 @@ import type { AxiosRequestConfig } from 'axios'; import { getActorsAsTools, -} from './actors.js'; +} from './actors/tools.js'; import { ACTOR_OUTPUT_MAX_CHARS_PER_ITEM, ACTOR_OUTPUT_TRUNCATED_MESSAGE, diff --git a/src/tools/discover-actors.ts b/src/tools/discover-actors.ts index 6142b059..e837d735 100644 --- a/src/tools/discover-actors.ts +++ b/src/tools/discover-actors.ts @@ -2,7 +2,7 @@ import { Ajv } from 'ajv'; import { z } from 'zod'; import zodToJsonSchema from 'zod-to-json-schema'; -import { searchActorsByKeywords } from '../actors.js'; +import { searchActorsByKeywords } from '../actors/search.js'; import { InternalTools } from '../const.js'; import type { InternalTool, ToolWrap } from '../types.js'; diff --git a/src/tools/get-actors-details.ts b/src/tools/get-actors-details.ts index 066b3e5a..ac30f2d3 100644 --- a/src/tools/get-actors-details.ts +++ b/src/tools/get-actors-details.ts @@ -2,7 +2,8 @@ import { Ajv } from 'ajv'; import { z } from 'zod'; import zodToJsonSchema from 'zod-to-json-schema'; -import { filterSchemaProperties, getActorDefinition, shortenProperties } from '../actors.js'; +import { getActorDefinition } from '../actors/details.js'; +import { filterSchemaProperties, shortenProperties } from '../actors/schema.js'; import { ACTOR_README_MAX_LENGTH, InternalTools } from '../const.js'; import type { InternalTool, ISchemaProperties, ToolWrap } from '../types.js'; diff --git a/src/tools/remove-actors-from-tools.ts b/src/tools/remove-actors-from-tools.ts index 40b1e022..6a47df05 100644 --- a/src/tools/remove-actors-from-tools.ts +++ b/src/tools/remove-actors-from-tools.ts @@ -2,7 +2,7 @@ import { Ajv } from 'ajv'; import { z } from 'zod'; import { zodToJsonSchema } from 'zod-to-json-schema'; -import { actorNameToToolName } from '../actors.js'; +import { actorNameToToolName } from '../actors/utils.js'; import { InternalTools } from '../const.js'; import type { InternalTool, ToolWrap } from '../types.js'; From 2c7edaf7788c4d1409a43fee25ff41de465eba9c Mon Sep 17 00:00:00 2001 From: MQ Date: Mon, 7 Apr 2025 15:46:20 +0200 Subject: [PATCH 14/42] fix test imports --- tests/actors-test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/actors-test.ts b/tests/actors-test.ts index fafefb97..3db052e1 100644 --- a/tests/actors-test.ts +++ b/tests/actors-test.ts @@ -1,6 +1,7 @@ import { describe, it, expect } from 'vitest'; -import { actorNameToToolName, inferArrayItemType, shortenEnum } from '../src/actors.js'; +import { shortenEnum, inferArrayItemType } from '../src/actors/schema.js'; +import { actorNameToToolName } from '../src/actors/utils.js'; import { ACTOR_ENUM_MAX_LENGTH } from '../src/const.js'; describe('actors', () => { From f8ad397f8b3f8ca3c243b73727f21cc9956c70e5 Mon Sep 17 00:00:00 2001 From: MQ Date: Mon, 7 Apr 2025 15:56:56 +0200 Subject: [PATCH 15/42] simplify mcp server class, move to actors/call.ts --- src/actors/call.ts | 60 ++++++++++++++++++++++++++++++++++++++++++++++ src/main.ts | 3 ++- src/mcp-server.ts | 60 ++-------------------------------------------- 3 files changed, 64 insertions(+), 59 deletions(-) create mode 100644 src/actors/call.ts diff --git a/src/actors/call.ts b/src/actors/call.ts new file mode 100644 index 00000000..b25fc145 --- /dev/null +++ b/src/actors/call.ts @@ -0,0 +1,60 @@ +import { Actor, type ApifyClientOptions } from 'apify'; +import type { ActorCallOptions } from 'apify-client'; +import { ApifyClient } from 'apify-client'; +import type { AxiosRequestConfig } from 'axios'; + +import { USER_AGENT_ORIGIN } from '../const.js'; +import { log } from '../logger.js'; + +/** +* Calls an Apify actor and retrieves the dataset items. +* +* It requires the `APIFY_TOKEN` environment variable to be set. +* If the `APIFY_IS_AT_HOME` the dataset items are pushed to the Apify dataset. +* +* @param {string} actorName - The name of the actor to call. +* @param {ActorCallOptions} callOptions - The options to pass to the actor. +* @param {unknown} input - The input to pass to the actor. +* @returns {Promise} - A promise that resolves to an array of dataset items. +* @throws {Error} - Throws an error if the `APIFY_TOKEN` is not set +*/ +export async function callActorGetDataset( + actorName: string, + input: unknown, + apifyToken: string, + callOptions: ActorCallOptions | undefined = undefined, +): Promise { + const name = actorName; + try { + log.info(`Calling Actor ${name} with input: ${JSON.stringify(input)}`); + + const options: ApifyClientOptions = { requestInterceptors: [addUserAgent] }; + const client = new ApifyClient({ ...options, token: apifyToken }); + const actorClient = client.actor(name); + + const results = await actorClient.call(input, callOptions); + const dataset = await client.dataset(results.defaultDatasetId).listItems(); + log.info(`Actor ${name} finished with ${dataset.items.length} items`); + + if (process.env.APIFY_IS_AT_HOME) { + await Actor.pushData(dataset.items); + log.info(`Pushed ${dataset.items.length} items to the dataset`); + } + return dataset.items; + } catch (error) { + log.error(`Error calling actor: ${error}. Actor: ${name}, input: ${JSON.stringify(input)}`); + throw new Error(`Error calling Actor: ${error}`); + } +} + +/** + * Adds a User-Agent header to the request config. + * @param config + * @private + */ +function addUserAgent(config: AxiosRequestConfig): AxiosRequestConfig { + const updatedConfig = { ...config }; + updatedConfig.headers = updatedConfig.headers ?? {}; + updatedConfig.headers['User-Agent'] = `${updatedConfig.headers['User-Agent'] ?? ''}; ${USER_AGENT_ORIGIN}`; + return updatedConfig; +} diff --git a/src/main.ts b/src/main.ts index afa7de9a..03679626 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,6 +5,7 @@ import { Actor } from 'apify'; import type { ActorCallOptions } from 'apify-client'; +import { callActorGetDataset } from './actors/call.js'; import { processInput } from './input.js'; import { log } from './logger.js'; import { ApifyMcpServer } from './mcp-server.js'; @@ -46,6 +47,6 @@ if (isActorStandby()) { await Actor.fail('If you need to debug a specific Actor, please provide the debugActor and debugActorInput fields in the input'); } const options = { memory: input.maxActorMemoryBytes } as ActorCallOptions; - await mcpServer.callActorGetDataset(input.debugActor!, input.debugActorInput!, process.env.APIFY_TOKEN, options); + await callActorGetDataset(input.debugActor!, input.debugActorInput!, process.env.APIFY_TOKEN, options); await Actor.exit(); } diff --git a/src/mcp-server.ts b/src/mcp-server.ts index 1d6d13e5..dbbe1017 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -7,11 +7,9 @@ import { parse } from 'node:querystring'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; -import { Actor, type ApifyClientOptions } from 'apify'; import type { ActorCallOptions } from 'apify-client'; -import { ApifyClient } from 'apify-client'; -import type { AxiosRequestConfig } from 'axios'; +import { callActorGetDataset } from './actors/call.js'; import { getActorsAsTools, } from './actors/tools.js'; @@ -21,7 +19,6 @@ import { defaults, SERVER_NAME, SERVER_VERSION, - USER_AGENT_ORIGIN, } from './const.js'; import { processInput } from './input.js'; import { log } from './logger.js'; @@ -52,59 +49,6 @@ export class ApifyMcpServer { this.setupToolHandlers(); } - /** - * Adds a User-Agent header to the request config. - * @param config - * @private - */ - private addUserAgent(config: AxiosRequestConfig): AxiosRequestConfig { - const updatedConfig = { ...config }; - updatedConfig.headers = updatedConfig.headers ?? {}; - updatedConfig.headers['User-Agent'] = `${updatedConfig.headers['User-Agent'] ?? ''}; ${USER_AGENT_ORIGIN}`; - return updatedConfig; - } - - /** - * Calls an Apify actor and retrieves the dataset items. - * - * It requires the `APIFY_TOKEN` environment variable to be set. - * If the `APIFY_IS_AT_HOME` the dataset items are pushed to the Apify dataset. - * - * @param {string} actorName - The name of the actor to call. - * @param {ActorCallOptions} callOptions - The options to pass to the actor. - * @param {unknown} input - The input to pass to the actor. - * @returns {Promise} - A promise that resolves to an array of dataset items. - * @throws {Error} - Throws an error if the `APIFY_TOKEN` is not set - */ - public async callActorGetDataset( - actorName: string, - input: unknown, - apifyToken: string, - callOptions: ActorCallOptions | undefined = undefined, - ): Promise { - const name = actorName; - try { - log.info(`Calling Actor ${name} with input: ${JSON.stringify(input)}`); - - const options: ApifyClientOptions = { requestInterceptors: [this.addUserAgent] }; - const client = new ApifyClient({ ...options, token: apifyToken }); - const actorClient = client.actor(name); - - const results = await actorClient.call(input, callOptions); - const dataset = await client.dataset(results.defaultDatasetId).listItems(); - log.info(`Actor ${name} finished with ${dataset.items.length} items`); - - if (process.env.APIFY_IS_AT_HOME) { - await Actor.pushData(dataset.items); - log.info(`Pushed ${dataset.items.length} items to the dataset`); - } - return dataset.items; - } catch (error) { - log.error(`Error calling actor: ${error}. Actor: ${name}, input: ${JSON.stringify(input)}`); - throw new Error(`Error calling Actor: ${error}`); - } - } - public async addToolsFromActors(actors: string[]) { const tools = await getActorsAsTools(actors); this.updateTools(tools); @@ -207,7 +151,7 @@ export class ApifyMcpServer { if (tool.type === 'actor') { const actorTool = tool.tool as ActorTool; - const items = await this.callActorGetDataset(actorTool.actorFullName, args, apifyToken as string, { + const items = await callActorGetDataset(actorTool.actorFullName, args, apifyToken as string, { memory: actorTool.memoryMbytes, } as ActorCallOptions); const content = items.map((item) => { From 204b358e2c1bfd4f5301b1e5b30a7cb8f48f0f85 Mon Sep 17 00:00:00 2001 From: MQ Date: Mon, 7 Apr 2025 16:22:32 +0200 Subject: [PATCH 16/42] decouple processParamsAndUpdateTools --- src/input.ts | 6 +++++- src/main.ts | 2 +- src/mcp-server.ts | 23 ++++++++++++----------- src/utils.ts | 11 ++++++++++- tests/utils-test.ts | 35 +++++++++++++++++++++++++++++++++++ 5 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 tests/utils-test.ts diff --git a/src/input.ts b/src/input.ts index a59bc1a2..bfce84f0 100644 --- a/src/input.ts +++ b/src/input.ts @@ -5,13 +5,17 @@ import type { Input } from './types.js'; * @param originalInput * @returns input */ -export async function processInput(originalInput: Partial): Promise { +export function processInput(originalInput: Partial): Input { const input = originalInput as Input; // actors can be a string or an array of strings if (input.actors && typeof input.actors === 'string') { input.actors = input.actors.split(',').map((format: string) => format.trim()) as string[]; } + // Convert string boolean to actual boolean + if (input.enableActorAutoLoading && typeof input.enableActorAutoLoading === 'string') { + input.enableActorAutoLoading = input.enableActorAutoLoading === 'true'; + } if (!input.enableActorAutoLoading) { input.enableActorAutoLoading = false; } diff --git a/src/main.ts b/src/main.ts index 03679626..b8d39462 100644 --- a/src/main.ts +++ b/src/main.ts @@ -26,7 +26,7 @@ if (!process.env.APIFY_TOKEN) { const mcpServer = new ApifyMcpServer(); -const input = await processInput((await Actor.getInput>()) ?? ({} as Input)); +const input = processInput((await Actor.getInput>()) ?? ({} as Input)); log.info(`Loaded input: ${JSON.stringify(input)} `); if (isActorStandby()) { diff --git a/src/mcp-server.ts b/src/mcp-server.ts index dbbe1017..94c52cdc 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -1,8 +1,6 @@ /** * Model Context Protocol (MCP) server for Apify Actors */ -import type { ParsedUrlQuery } from 'node:querystring'; -import { parse } from 'node:querystring'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; @@ -20,10 +18,10 @@ import { SERVER_NAME, SERVER_VERSION, } from './const.js'; -import { processInput } from './input.js'; import { log } from './logger.js'; import { getActorAutoLoadingTools } from './tools/index.js'; -import type { Input, ActorTool, ToolWrap, InternalTool } from './types.js'; +import type { ActorTool, ToolWrap, InternalTool } from './types.js'; +import { parseInputParamsFromUrl } from './utils.js'; /** * Create Apify MCP server @@ -84,24 +82,27 @@ export class ApifyMcpServer { }); } + public enableActorAutoLoading() { + this.updateTools(getActorAutoLoadingTools()); + log.debug('Enabled Actor auto-loading tools'); + } + /** * Process input parameters and update tools * If URL contains query parameter actors, add tools from actors, otherwise add tools from default actors * @param url */ public async processParamsAndUpdateTools(url: string) { - const params = parse(url.split('?')[1] || '') as ParsedUrlQuery; - delete params.token; - log.debug(`Received input parameters: ${JSON.stringify(params)}`); - const input = await processInput(params as unknown as Input); + const input = parseInputParamsFromUrl(url); if (input.actors) { await this.addToolsFromActors(input.actors as string[]); } if (input.enableActorAutoLoading) { - this.updateTools(getActorAutoLoadingTools()); + this.enableActorAutoLoading(); } - log.debug(`Server is running in STANDBY mode with the following Actors (tools): ${this.getToolNames()}. - To use different Actors, provide them in query parameter "actors" or include them in the Actor Task input.`); + + log.debug(`Server is running in STANDBY mode with Actors: ${this.getToolNames()}. ` + + 'To use different Actors, provide them in "actors" query param or Actor Task input.'); } private setupToolHandlers(): void { diff --git a/src/utils.ts b/src/utils.ts index 27843242..0f6a19df 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,15 @@ +import { parse } from 'node:querystring'; + import { Actor } from 'apify'; -import type { ActorRunData } from './types'; +import { processInput } from './input.js'; +import type { ActorRunData, Input } from './types'; + +export function parseInputParamsFromUrl(url: string): Input { + const query = url.split('?')[1] || ''; + const params = parse(query) as unknown as Input; + return processInput(params); +} export function isActorStandby(): boolean { return Actor.getEnv().metaOrigin === 'STANDBY'; diff --git a/tests/utils-test.ts b/tests/utils-test.ts new file mode 100644 index 00000000..da540d66 --- /dev/null +++ b/tests/utils-test.ts @@ -0,0 +1,35 @@ +import { describe, it, expect } from 'vitest'; + +import { parseInputParamsFromUrl } from '../src/utils.js'; + +describe('parseInputParamsFromUrl', () => { + it('should parse actors from URL query params', () => { + const url = 'https://actors-mcp-server.apify.actor?token=123&actors=apify/web-scraper'; + const result = parseInputParamsFromUrl(url); + expect(result.actors).toEqual(['apify/web-scraper']); + }); + + it('should parse multiple actors from URL', () => { + const url = 'https://actors-mcp-server.apify.actor?actors=apify/instagram-scraper,lukaskrivka/google-maps'; + const result = parseInputParamsFromUrl(url); + expect(result.actors).toEqual(['apify/instagram-scraper', 'lukaskrivka/google-maps']); + }); + + it('should handle URL without query params', () => { + const url = 'https://actors-mcp-server.apify.actor'; + const result = parseInputParamsFromUrl(url); + expect(result.actors).toBeUndefined(); + }); + + it('should parse enableActorAutoLoading flag', () => { + const url = 'https://actors-mcp-server.apify.actor?enableActorAutoLoading=true'; + const result = parseInputParamsFromUrl(url); + expect(result.enableActorAutoLoading).toBe(true); + }); + + it('should handle actors as string parameter', () => { + const url = 'https://actors-mcp-server.apify.actor?actors=apify/rag-web-browser'; + const result = parseInputParamsFromUrl(url); + expect(result.actors).toEqual(['apify/rag-web-browser']); + }); +}); From 8ed82abcc8858211e261c1a2266c6040284b770b Mon Sep 17 00:00:00 2001 From: MQ Date: Mon, 7 Apr 2025 16:24:21 +0200 Subject: [PATCH 17/42] dataset push only in normal mode for debugging --- src/actors/call.ts | 4 ---- src/main.ts | 5 ++++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/actors/call.ts b/src/actors/call.ts index b25fc145..786a52f8 100644 --- a/src/actors/call.ts +++ b/src/actors/call.ts @@ -36,10 +36,6 @@ export async function callActorGetDataset( const dataset = await client.dataset(results.defaultDatasetId).listItems(); log.info(`Actor ${name} finished with ${dataset.items.length} items`); - if (process.env.APIFY_IS_AT_HOME) { - await Actor.pushData(dataset.items); - log.info(`Pushed ${dataset.items.length} items to the dataset`); - } return dataset.items; } catch (error) { log.error(`Error calling actor: ${error}. Actor: ${name}, input: ${JSON.stringify(input)}`); diff --git a/src/main.ts b/src/main.ts index b8d39462..fd966476 100644 --- a/src/main.ts +++ b/src/main.ts @@ -47,6 +47,9 @@ if (isActorStandby()) { await Actor.fail('If you need to debug a specific Actor, please provide the debugActor and debugActorInput fields in the input'); } const options = { memory: input.maxActorMemoryBytes } as ActorCallOptions; - await callActorGetDataset(input.debugActor!, input.debugActorInput!, process.env.APIFY_TOKEN, options); + const items = await callActorGetDataset(input.debugActor!, input.debugActorInput!, process.env.APIFY_TOKEN, options); + + await Actor.pushData(items); + log.info(`Pushed ${items.length} items to the dataset`); await Actor.exit(); } From 915faf8a9d38cb4cd66f1ccf8b8474b294525c82 Mon Sep 17 00:00:00 2001 From: MQ Date: Mon, 7 Apr 2025 16:30:44 +0200 Subject: [PATCH 18/42] add server tests --- src/actors/call.ts | 2 +- tests/server-test.ts | 64 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 tests/server-test.ts diff --git a/src/actors/call.ts b/src/actors/call.ts index 786a52f8..eab1a258 100644 --- a/src/actors/call.ts +++ b/src/actors/call.ts @@ -1,4 +1,4 @@ -import { Actor, type ApifyClientOptions } from 'apify'; +import { type ApifyClientOptions } from 'apify'; import type { ActorCallOptions } from 'apify-client'; import { ApifyClient } from 'apify-client'; import type { AxiosRequestConfig } from 'axios'; diff --git a/tests/server-test.ts b/tests/server-test.ts new file mode 100644 index 00000000..6bd4091c --- /dev/null +++ b/tests/server-test.ts @@ -0,0 +1,64 @@ +import type { Server } from '@modelcontextprotocol/sdk/server.js'; +import express from 'express'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; + +import { log } from '../src/logger.js'; +import { ApifyMcpServer } from '../src/mcp-server.js'; + +describe('ApifyMcpServer initialization', () => { + let app: express.Express; + let server: ApifyMcpServer; + let mcpServer: Server; + const testPort = 7357; + + beforeEach(async () => { + app = express(); + server = new ApifyMcpServer(); + log.setLevel(log.LEVELS.OFF); + + // Setup basic express route to trigger server initialization + app.get('/', async (req, res) => { + await server.processParamsAndUpdateTools(req.url); + res.sendStatus(200); + }); + + // Start test server + await new Promise((resolve) => { + mcpServer = app.listen(testPort, () => resolve()); + }); + }); + + afterEach(async () => { + await new Promise((resolve) => { + mcpServer.close(() => resolve()); + }); + }); + + it('should load actors from query parameters', async () => { + // Test with multiple actors including different username cases + const testActors = ['apify/rag-web-browser', 'apify/instagram-scraper']; + + // Make request to trigger server initialization + const response = await fetch(`http://localhost:${testPort}/?actors=${testActors.join(',')}`); + expect(response.status).toBe(200); + + // Verify loaded tools + const toolNames = server.getToolNames(); + expect(toolNames).toEqual(expect.arrayContaining([ + 'apify-slash-rag-web-browser', + 'apify-slash-instagram-scraper', + ])); + expect(toolNames.length).toBe(testActors.length); + }); + + it('should enable auto-loading tools when flag is set', async () => { + const response = await fetch(`http://localhost:${testPort}/?enableActorAutoLoading=true`); + expect(response.status).toBe(200); + + const toolNames = server.getToolNames(); + expect(toolNames).toEqual([ + 'add-actor-to-tools', + 'remove-actor-from-tools', + ]); + }); +}); From f1d630d488fee83c8d156af80f6cfdc68218b1cb Mon Sep 17 00:00:00 2001 From: MQ Date: Mon, 7 Apr 2025 16:37:02 +0200 Subject: [PATCH 19/42] update processParamsAndUpdateTools docs --- src/mcp-server.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mcp-server.ts b/src/mcp-server.ts index 94c52cdc..1050d434 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -89,7 +89,8 @@ export class ApifyMcpServer { /** * Process input parameters and update tools - * If URL contains query parameter actors, add tools from actors, otherwise add tools from default actors + * If URL contains query parameter `actors`, add tools from Actors. + * If URL contains query parameter `enableActorAutoLoading`, enable auto-loading of Actors. * @param url */ public async processParamsAndUpdateTools(url: string) { From ce711e9096ce2f51f6c3f78c0966a27ef73ad945 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Wed, 9 Apr 2025 21:50:42 +0200 Subject: [PATCH 20/42] fix: clearly separate Actor related code and rest of the codebase. Only export ApifyMcpServer from the package --- package-lock.json | 709 +++++++++++++----- package.json | 11 +- src/actor/README.md | 6 + src/actor/const.ts | 20 + src/{ => actor}/input.ts | 3 + src/{ => actor}/server.ts | 25 +- src/actor/types.ts | 30 + src/{ => actor}/utils.ts | 21 +- src/actors/call.ts | 56 -- src/actors/search.ts | 40 - src/actors/utils.ts | 6 - src/const.ts | 46 +- src/examples/clientSse.ts | 2 +- src/examples/clientStdio.ts | 2 +- src/index.ts | 6 +- src/logger.ts | 5 - src/main.ts | 29 +- src/mcp-server.ts | 67 +- src/stdio.ts | 33 +- src/{actors/tools.ts => tools/actor.ts} | 70 +- src/tools/add-actors-to-tools.ts | 38 - src/{actors/details.ts => tools/build.ts} | 59 +- src/tools/discover-actors.ts | 58 -- src/tools/get-actors-details.ts | 45 -- src/tools/helpers.ts | 64 ++ src/tools/index.ts | 27 +- src/tools/remove-actors-from-tools.ts | 34 - src/tools/store_collection.ts | 93 +++ src/{actors/schema.ts => tools/utils.ts} | 21 +- src/types.ts | 48 +- .../{server-test.ts => actor-server-test.ts} | 40 +- tests/{actors-test.ts => actor-test.ts} | 3 +- tests/{utils-test.ts => actor-utils-test.ts} | 2 +- 33 files changed, 1047 insertions(+), 672 deletions(-) create mode 100644 src/actor/README.md create mode 100644 src/actor/const.ts rename src/{ => actor}/input.ts (96%) rename src/{ => actor}/server.ts (81%) create mode 100644 src/actor/types.ts rename src/{ => actor}/utils.ts (65%) delete mode 100644 src/actors/call.ts delete mode 100644 src/actors/search.ts delete mode 100644 src/actors/utils.ts delete mode 100644 src/logger.ts rename src/{actors/tools.ts => tools/actor.ts} (52%) delete mode 100644 src/tools/add-actors-to-tools.ts rename src/{actors/details.ts => tools/build.ts} (61%) delete mode 100644 src/tools/discover-actors.ts delete mode 100644 src/tools/get-actors-details.ts create mode 100644 src/tools/helpers.ts delete mode 100644 src/tools/remove-actors-from-tools.ts create mode 100644 src/tools/store_collection.ts rename src/{actors/schema.ts => tools/utils.ts} (94%) rename tests/{server-test.ts => actor-server-test.ts} (57%) rename tests/{actors-test.ts => actor-test.ts} (93%) rename tests/{utils-test.ts => actor-utils-test.ts} (95%) diff --git a/package-lock.json b/package-lock.json index a682849c..ec6a1d02 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,11 @@ "version": "0.1.21", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.3.1", + "@apify/log": "^2.5.16", + "@modelcontextprotocol/sdk": "^1.9.0", "ajv": "^8.17.1", - "apify": "^3.2.6", - "apify-client": "^2.11.2", + "apify": "^3.4.0", + "apify-client": "^2.12.1", "express": "^4.21.2", "minimist": "^1.2.8", "zod": "^3.24.1", @@ -109,15 +110,15 @@ "license": "MIT" }, "node_modules/@apify/consts": { - "version": "2.35.0", - "resolved": "https://registry.npmjs.org/@apify/consts/-/consts-2.35.0.tgz", - "integrity": "sha512-ICUoIzyxSWuQZXtMwMxrnfogPTagQS2fDY1jgZZCikv4WF0c4a3/HKpIprHZ3lI90+jjF0r8TtEVEcWIaRgP4Q==", + "version": "2.39.0", + "resolved": "https://registry.npmjs.org/@apify/consts/-/consts-2.39.0.tgz", + "integrity": "sha512-rO9+uzv7kP5XNiz8K+cYcgDmLPK9WZQ9Xlg2VEa2YB5MXDVcBnStdZm4loX/vPPpAGK5ztQnpBnYlA8PURBcXQ==", "license": "Apache-2.0" }, "node_modules/@apify/datastructures": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@apify/datastructures/-/datastructures-2.0.2.tgz", - "integrity": "sha512-IN9A0s2SCHoZZE1tf4xKgk4fxHM5/0I/UrXhWbn/rSv7E5sA2o0NyHdwcMY2Go9f5qd+E7VAbX6WnESTE6GLeA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@apify/datastructures/-/datastructures-2.0.3.tgz", + "integrity": "sha512-E6yQyc/XZDqJopbaGmhzZXMJqwGf96ELtDANZa0t68jcOAJZS+pF7YUfQOLszXq6JQAdnRvTH2caotL6urX7HA==", "license": "Apache-2.0" }, "node_modules/@apify/eslint-config": { @@ -154,12 +155,12 @@ } }, "node_modules/@apify/log": { - "version": "2.5.11", - "resolved": "https://registry.npmjs.org/@apify/log/-/log-2.5.11.tgz", - "integrity": "sha512-QDAFqOsZkpeV3UR9eYCGskI8rHSDnhF+hnbUea3MvfIhH5PfvbWIpdUrb7s04XnfnXbWpRSN+7TYLWBbHf+X5Q==", + "version": "2.5.16", + "resolved": "https://registry.npmjs.org/@apify/log/-/log-2.5.16.tgz", + "integrity": "sha512-/kZJN/vs80yRpihcLxG2kLj45f6stky4RUIC1ea5Vmkb0i6Zv1l/sUqDLBimZMaQkSDCxM1j+Yhc5QaPlgJZrg==", "license": "Apache-2.0", "dependencies": { - "@apify/consts": "^2.35.0", + "@apify/consts": "^2.39.0", "ansi-colors": "^4.1.1" } }, @@ -179,18 +180,18 @@ } }, "node_modules/@apify/pseudo_url": { - "version": "2.0.52", - "resolved": "https://registry.npmjs.org/@apify/pseudo_url/-/pseudo_url-2.0.52.tgz", - "integrity": "sha512-7YhvBhcKLBlM9Pz4HsvpBhO7dyFmWf1gY4xldgEgPyIwlkycP6rsqg73U2fHxb5oUaxFPk99jBIaEwmIhonSGw==", + "version": "2.0.57", + "resolved": "https://registry.npmjs.org/@apify/pseudo_url/-/pseudo_url-2.0.57.tgz", + "integrity": "sha512-5LJBocbpBYE9vfb6UDqcqAq3azC0b2PHxRXRz8JFGU4qt7o3Gzi4p16lImZ2qy9/x3plWYWxbYsewX5aH/aamA==", "license": "Apache-2.0", "dependencies": { - "@apify/log": "^2.5.11" + "@apify/log": "^2.5.16" } }, "node_modules/@apify/timeout": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@apify/timeout/-/timeout-0.3.1.tgz", - "integrity": "sha512-sLIuOqfySki/7AXiQ1yZoCI07vX6aYFLgP6YaJ8e8YLn8CFsRERma/Crxcz0zyCaxhc7C7EPgcs1O+p/djZchw==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@apify/timeout/-/timeout-0.3.2.tgz", + "integrity": "sha512-JnOLIOpqfm366q7opKrA6HrL0iYRpYYDn8Mi77sMR2GZ1fPbwMWCVzN23LJWfJV7izetZbCMrqRUXsR1etZ7dA==", "license": "Apache-2.0" }, "node_modules/@apify/tsconfig": { @@ -201,19 +202,19 @@ "license": "Apache-2.0" }, "node_modules/@apify/utilities": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@apify/utilities/-/utilities-2.11.1.tgz", - "integrity": "sha512-g0oll9k7CG+ivwQ9mZiMuEZQHUXbrO+zdPtIY5xliUrp4UmJ0mmnbf6JOk3GXOeAcZaM4Acfm7g9H18Bxih1mA==", + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/@apify/utilities/-/utilities-2.15.3.tgz", + "integrity": "sha512-jCyZ3ZhosjHSnaPND6l9/gPp0y+bH3G+Gz+/eIFRMXE418T9me0X828Ua2sCROcj/TrNg+1DZbdE3GWqOMdyuw==", "license": "Apache-2.0", "dependencies": { - "@apify/consts": "^2.35.0", - "@apify/log": "^2.5.11" + "@apify/consts": "^2.39.0", + "@apify/log": "^2.5.16" } }, "node_modules/@crawlee/core": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/@crawlee/core/-/core-3.12.1.tgz", - "integrity": "sha512-hom1ALM1Gn+ZpdFxIcQwmNjHV7mZhcOsPGIqhZuEWSETPV3lcMX67ZPs9UU3nIjsPgKuDC3FjIv6TXNu8Ga55A==", + "version": "3.13.2", + "resolved": "https://registry.npmjs.org/@crawlee/core/-/core-3.13.2.tgz", + "integrity": "sha512-k37uWReG4zztMoloFpmLlNP7Sc+LfpCr26xGIimu5pHTqjtegpN6VFI1k8N3pPSdqYWXMFCXnzsQWdTxZ/7Iyw==", "license": "Apache-2.0", "dependencies": { "@apify/consts": "^2.20.0", @@ -222,9 +223,9 @@ "@apify/pseudo_url": "^2.0.30", "@apify/timeout": "^0.3.0", "@apify/utilities": "^2.7.10", - "@crawlee/memory-storage": "3.12.1", - "@crawlee/types": "3.12.1", - "@crawlee/utils": "3.12.1", + "@crawlee/memory-storage": "3.13.2", + "@crawlee/types": "3.13.2", + "@crawlee/utils": "3.13.2", "@sapphire/async-queue": "^1.5.1", "@vladfrangu/async_event_emitter": "^2.2.2", "csv-stringify": "^6.2.0", @@ -244,13 +245,13 @@ } }, "node_modules/@crawlee/memory-storage": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/@crawlee/memory-storage/-/memory-storage-3.12.1.tgz", - "integrity": "sha512-N3WqfNIgo8m5ycJvUM5BvAZoqgglwEq11HsIlAUX4AGRKfYiFLziGpkBdhm2JAaD3/VWyZeEKyWgpkRiKZXo2w==", + "version": "3.13.2", + "resolved": "https://registry.npmjs.org/@crawlee/memory-storage/-/memory-storage-3.13.2.tgz", + "integrity": "sha512-vJDffE9pUOQ56VCcCdaCG9viDkxbwiuog1j/2lwi0uIGLeoE5SR0OrGJF9Gr2UfD2t3jY+m+T0ZY2p+NaxwuhQ==", "license": "Apache-2.0", "dependencies": { "@apify/log": "^2.4.0", - "@crawlee/types": "3.12.1", + "@crawlee/types": "3.13.2", "@sapphire/async-queue": "^1.5.0", "@sapphire/shapeshift": "^3.0.0", "content-type": "^1.0.4", @@ -265,9 +266,9 @@ } }, "node_modules/@crawlee/types": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/@crawlee/types/-/types-3.12.1.tgz", - "integrity": "sha512-KiYqRxYTB89Osy7sBbKhOSUyUusO85aGHvLLzxWAjQPrIhrY09t2kuVZhmTnjXTbgucQdMeWFrQjQuNeq8yPtA==", + "version": "3.13.2", + "resolved": "https://registry.npmjs.org/@crawlee/types/-/types-3.13.2.tgz", + "integrity": "sha512-7LASqr6Uwj049vMy5XCb0j9BBIKP3RdDKV2B74x5JheSss/fsj8+2pa+RJQL4L7tvIzKK36ndmYzQ0v83H0vbQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.4.0" @@ -277,17 +278,17 @@ } }, "node_modules/@crawlee/utils": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/@crawlee/utils/-/utils-3.12.1.tgz", - "integrity": "sha512-GknM4VD77coaAXkb2MPRkqYGVWHGMPUDuyBFmMZLGPnenHX7VoywZK+4Xw5o6FMLk0krPF/oAPAyD52fHv2B2Q==", + "version": "3.13.2", + "resolved": "https://registry.npmjs.org/@crawlee/utils/-/utils-3.13.2.tgz", + "integrity": "sha512-Nn/lXdaNoD47+CyYx8tv86KuFj6gyrEzZT9bvZoIs1sKIx4ZZNEFa/A4yQqQkaJX+UfFWBp2NK48LDSr+Og69w==", "license": "Apache-2.0", "dependencies": { "@apify/log": "^2.4.0", "@apify/ps-tree": "^1.2.0", - "@crawlee/types": "3.12.1", + "@crawlee/types": "3.13.2", "@types/sax": "^1.2.7", "cheerio": "1.0.0-rc.12", - "file-type": "^19.0.0", + "file-type": "^20.0.0", "got-scraping": "^4.0.3", "ow": "^0.28.1", "robots-parser": "^3.0.1", @@ -299,46 +300,6 @@ "node": ">=16.0.0" } }, - "node_modules/@crawlee/utils/node_modules/cheerio": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", - "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", - "license": "MIT", - "dependencies": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "htmlparser2": "^8.0.1", - "parse5": "^7.0.0", - "parse5-htmlparser2-tree-adapter": "^7.0.0" - }, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "node_modules/@crawlee/utils/node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", @@ -1066,12 +1027,18 @@ "dev": true }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.3.1.tgz", - "integrity": "sha512-Fu3HstNO03/S5nvwh3KjRfP5JOSMl6IbOBxRl6JBDXMFRHPSJ4kiiV7n5yJjY51GtrFw3Y+V/vdsweN5bxULWQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.9.0.tgz", + "integrity": "sha512-Jq2EUCQpe0iyO5FGpzVYDNFR6oR53AIrwph9yWl7uSc7IWUMsrmpmSaTGra5hQNunXpM+9oit85p924jWuHzUA==", "license": "MIT", "dependencies": { "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.3", + "eventsource": "^3.0.2", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" @@ -1080,6 +1047,257 @@ "node": ">=18" } }, + "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1425,6 +1643,24 @@ "node": ">=14.16" } }, + "node_modules/@tokenizer/inflate": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", + "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "fflate": "^0.8.2", + "token-types": "^6.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/@tokenizer/token": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", @@ -2000,20 +2236,20 @@ } }, "node_modules/apify": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/apify/-/apify-3.2.6.tgz", - "integrity": "sha512-Uh8vWFb+hv6R2aPSol6QTFbUAdhRtXsVut7Cc0c/C8BbcNOqJlU2RY4xsw8clmc0fW/1UCIxMJxNRfbYnaelXQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/apify/-/apify-3.4.0.tgz", + "integrity": "sha512-jVxyujEpZq9XnKJQnqH9PHIC4MlQqXbXlhdgvM/LOcnvtVdVJCbfXgyYZFOARgeX+Asc47uv9s66v23fDoc0AA==", "license": "Apache-2.0", "dependencies": { "@apify/consts": "^2.23.0", "@apify/input_secrets": "^1.1.40", "@apify/log": "^2.4.3", "@apify/timeout": "^0.3.0", - "@apify/utilities": "^2.9.3", - "@crawlee/core": "^3.9.0", - "@crawlee/types": "^3.9.0", - "@crawlee/utils": "^3.9.0", - "apify-client": "^2.9.0", + "@apify/utilities": "^2.13.0", + "@crawlee/core": "^3.13.0", + "@crawlee/types": "^3.13.0", + "@crawlee/utils": "^3.13.0", + "apify-client": "^2.12.1", "fs-extra": "^11.2.0", "ow": "^0.28.2", "semver": "^7.5.4", @@ -2025,9 +2261,9 @@ } }, "node_modules/apify-client": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/apify-client/-/apify-client-2.12.0.tgz", - "integrity": "sha512-h04rPVft8tNjnwZswqF2k46bdHZWsDsfOE8PkmklZ9+/s/mb/Q/dMOXCx0u2+RTc8QoAkYS9LYs97wZyUWpoag==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/apify-client/-/apify-client-2.12.1.tgz", + "integrity": "sha512-5K9VfSwjb2veD0ts1mScI5mVVuw/eTqF3tjyQj/jGyi7o3WIP5NI+3cXWz/aZGIQtPMLSjJ1VAVGu/of+wcmpw==", "license": "Apache-2.0", "dependencies": { "@apify/consts": "^2.25.0", @@ -2215,9 +2451,9 @@ } }, "node_modules/axios": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", - "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -2314,9 +2550,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", - "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", "funding": [ { "type": "opencollective", @@ -2448,9 +2684,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001690", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", - "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==", + "version": "1.0.30001712", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001712.tgz", + "integrity": "sha512-MBqPpGYYdQ7/hfKiet9SCI+nmN5/hp4ZzveOJubl5DTAMa5oggjAuoi0Z4onBpKPFI2ePGnQuQIzF3VxDjDJig==", "funding": [ { "type": "opencollective", @@ -2509,6 +2745,27 @@ "node": ">= 16" } }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, "node_modules/cheerio-select": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", @@ -2608,11 +2865,23 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -2715,7 +2984,6 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2900,9 +3168,9 @@ } }, "node_modules/domutils": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.1.tgz", - "integrity": "sha512-xWXmuRnN9OMP6ptPd2+H0cCbcYBULa5YDTbMm/2lvkWvNA3O4wcW+GvzooqBuNM8yy6pl3VIAeJTUUWUbfI5Fw==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", @@ -2968,9 +3236,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.76", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.76.tgz", - "integrity": "sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ==", + "version": "1.5.134", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.134.tgz", + "integrity": "sha512-zSwzrLg3jNP3bwsLqWHmS5z2nIOQ5ngMnfMZOWWtXnqqQkPVyOipxK98w+1beLw1TB+EImPNcG8wVP/cLVs2Og==", "license": "ISC" }, "node_modules/encodeurl": { @@ -3612,7 +3880,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.2.tgz", "integrity": "sha512-YolzkJNxsTL3tCJMWFxpxtG2sCjbZ4LQUBUrkdaJK0ub0p6lmJt+2+1SwhKjLc652lpH9L/79Ptez972H9tphw==", - "dev": true, "license": "MIT", "dependencies": { "eventsource-parser": "^3.0.0" @@ -3625,7 +3892,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.0.tgz", "integrity": "sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==", - "dev": true, "license": "MIT", "engines": { "node": ">=18.0.0" @@ -3686,6 +3952,21 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-rate-limit": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", + "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "^4.11 || 5 || ^5.0.0-beta.1" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -3767,6 +4048,12 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -3781,15 +4068,15 @@ } }, "node_modules/file-type": { - "version": "19.6.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-19.6.0.tgz", - "integrity": "sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==", + "version": "20.4.1", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.4.1.tgz", + "integrity": "sha512-hw9gNZXUfZ02Jo0uafWLaFVPter5/k2rfcrjFJJHX/77xtSDOfJuEFb6oKlFV86FLP1SuyHMW1PSk0U9M5tKkQ==", "license": "MIT", "dependencies": { - "get-stream": "^9.0.1", - "strtok3": "^9.0.1", + "@tokenizer/inflate": "^0.2.6", + "strtok3": "^10.2.0", "token-types": "^6.0.0", - "uint8array-extras": "^1.3.0" + "uint8array-extras": "^1.4.0" }, "engines": { "node": ">=18" @@ -3974,9 +4261,9 @@ "license": "MIT" }, "node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -4043,9 +4330,9 @@ } }, "node_modules/generative-bayesian-network": { - "version": "2.1.61", - "resolved": "https://registry.npmjs.org/generative-bayesian-network/-/generative-bayesian-network-2.1.61.tgz", - "integrity": "sha512-eIcVAU5I3SHKDwSqQxan6sRGAC0PRgj6rVNGUoKcErtAlbMsO8fr4Wr0hcBzLAc+6YKeNRpUKXWkEo/q1RLvTg==", + "version": "2.1.63", + "resolved": "https://registry.npmjs.org/generative-bayesian-network/-/generative-bayesian-network-2.1.63.tgz", + "integrity": "sha512-nH1t4R9nlWSmvFoI4DEcpXd0+yoGZcySVuUBkXhR09/Mf7O9AWFmR8lWqVGIoxpBS0WRNpV/qj4swKGGQAxAPQ==", "license": "Apache-2.0", "dependencies": { "adm-zip": "^0.5.9", @@ -4179,9 +4466,9 @@ } }, "node_modules/got": { - "version": "14.4.5", - "resolved": "https://registry.npmjs.org/got/-/got-14.4.5.tgz", - "integrity": "sha512-sq+uET8TnNKRNnjEOPJzMcxeI0irT8BBNmf+GtZcJpmhYsQM1DSKmCROUjPWKsXZ5HzwD5Cf5/RV+QD9BSTxJg==", + "version": "14.4.7", + "resolved": "https://registry.npmjs.org/got/-/got-14.4.7.tgz", + "integrity": "sha512-DI8zV1231tqiGzOiOzQWDhsBmncFW7oQDH6Zgy6pDPrqJuVZMtoSgPLLsBZQj8Jg4JFfwoOsDA8NGtLQLnIx2g==", "license": "MIT", "dependencies": { "@sindresorhus/is": "^7.0.1", @@ -4204,9 +4491,9 @@ } }, "node_modules/got-scraping": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/got-scraping/-/got-scraping-4.0.8.tgz", - "integrity": "sha512-QCptrUWsxgtP8LAnGZqjuJMwbLELlst1DF/Ba30OUOk7wi/LJtNwuYPUxoielRxTxd9QQ38FL/CWyRVc7m7ZkQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/got-scraping/-/got-scraping-4.1.1.tgz", + "integrity": "sha512-MbT+NMMU4VgvOg2tFIPOSIrMfH986fm0LJ17RxBLKlyTs3gh3xIMETpe+zdPaXY7tH1j6YYeqtfG0TnVMx6V2g==", "license": "Apache-2.0", "dependencies": { "got": "^14.2.1", @@ -4397,13 +4684,13 @@ } }, "node_modules/header-generator": { - "version": "2.1.61", - "resolved": "https://registry.npmjs.org/header-generator/-/header-generator-2.1.61.tgz", - "integrity": "sha512-jwTzKvIeTcm4ghHSeQeHwdUGLuWqLg3zrOkt+Dsrr+NeuHtLlDdtisgNkpKn2yjJ2OAakwxmKWC41SNkEH0/5w==", + "version": "2.1.63", + "resolved": "https://registry.npmjs.org/header-generator/-/header-generator-2.1.63.tgz", + "integrity": "sha512-dicAWZb/zXmI3fPuCw1Ra1sTTC9x9cc5EOhwugmI/keXnRc3BzxDQTGHge+MeAdqhpo2ZOFTEV6u08lXJUA2uQ==", "license": "Apache-2.0", "dependencies": { "browserslist": "^4.21.1", - "generative-bayesian-network": "^2.1.61", + "generative-bayesian-network": "^2.1.63", "ow": "^0.28.1", "tslib": "^2.4.0" }, @@ -4411,6 +4698,25 @@ "node": ">=16.0.0" } }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", @@ -4801,6 +5107,12 @@ "node": ">=8" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -4969,7 +5281,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/js-yaml": { @@ -5358,6 +5669,15 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", @@ -5481,6 +5801,15 @@ "node": ">= 0.8" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5650,7 +5979,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5697,12 +6025,12 @@ } }, "node_modules/peek-readable": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.3.1.tgz", - "integrity": "sha512-GVlENSDW6KHaXcd9zkZltB7tCLosKB/4Hg0fqBJkAoBgYG2Tn1xtMgXtSUuMU9AK/gCm/tTdT8mgAeF4YNeeqw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-7.0.0.tgz", + "integrity": "sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ==", "license": "MIT", "engines": { - "node": ">=14.16" + "node": ">=18" }, "funding": { "type": "github", @@ -5728,6 +6056,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -5862,9 +6199,9 @@ "license": "MIT" }, "node_modules/quick-lru": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-7.0.0.tgz", - "integrity": "sha512-MX8gB7cVYTrYcFfAnfLlhRd0+Toyl8yX8uBx1MrX7K0jegiz9TumwOK27ldXrgDlHRdVi+MqU9Ssw6dr4BNreg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-7.0.1.tgz", + "integrity": "sha512-kLjThirJMkWKutUKbZ8ViqFc09tDQhlbQo2MNuVeLWbRauqYP96Sm6nzlQ24F0HFjUNZ4i9+AgldJ9H6DZXi7g==", "license": "MIT", "engines": { "node": ">=18" @@ -6089,6 +6426,31 @@ "fsevents": "~2.3.2" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -6319,7 +6681,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -6332,7 +6693,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6571,16 +6931,16 @@ } }, "node_modules/strtok3": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-9.1.1.tgz", - "integrity": "sha512-FhwotcEqjr241ZbjFzjlIYg6c5/L/s4yBGWSMvJ9UoExiSqL+FnFA/CaeZx17WGaZMS/4SOZp8wH18jSS4R4lw==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.2.2.tgz", + "integrity": "sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg==", "license": "MIT", "dependencies": { "@tokenizer/token": "^0.3.0", - "peek-readable": "^5.3.1" + "peek-readable": "^7.0.0" }, "engines": { - "node": ">=16" + "node": ">=18" }, "funding": { "type": "github", @@ -6666,21 +7026,21 @@ } }, "node_modules/tldts": { - "version": "6.1.70", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.70.tgz", - "integrity": "sha512-/W1YVgYVJd9ZDjey5NXadNh0mJXkiUMUue9Zebd0vpdo1sU+H4zFFTaJ1RKD4N6KFoHfcXy6l+Vu7bh+bdWCzA==", + "version": "6.1.85", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.85.tgz", + "integrity": "sha512-gBdZ1RjCSevRPFix/hpaUWeak2/RNUZB4/8frF1r5uYMHjFptkiT0JXIebWvgI/0ZHXvxaUDDJshiA0j6GdL3w==", "license": "MIT", "dependencies": { - "tldts-core": "^6.1.70" + "tldts-core": "^6.1.85" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "6.1.70", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.70.tgz", - "integrity": "sha512-RNnIXDB1FD4T9cpQRErEqw6ZpjLlGdMOitdV+0xtbsnwr4YFka1zpc7D4KD+aAn8oSG5JyFrdasZTE04qDE9Yg==", + "version": "6.1.85", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.85.tgz", + "integrity": "sha512-DTjUVvxckL1fIoPSb3KE7ISNtkWSawZdpfxGxwiIrZoO6EbHVDXXUIlIuWympPaeS+BLGyggozX/HTMsRAdsoA==", "license": "MIT" }, "node_modules/to-regex-range": { @@ -6723,9 +7083,9 @@ } }, "node_modules/tough-cookie": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", - "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", "license": "BSD-3-Clause", "dependencies": { "tldts": "^6.1.32" @@ -6820,9 +7180,9 @@ } }, "node_modules/type-fest": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.31.0.tgz", - "integrity": "sha512-yCxltHW07Nkhv/1F6wWBr8kz+5BGMfP+RbRSYFnegVb0qV/UMT0G0ElBloPVerqn4M2ZV80Ir1FtCcYv1cT6vQ==", + "version": "4.39.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.39.1.tgz", + "integrity": "sha512-uW9qzd66uyHYxwyVBYiwS4Oi0qZyUqwjU+Oevr6ZogYiXt99EOYtwvzMSLw1c3lYo2HzJsep/NB23iEVEgjG/w==", "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=16" @@ -7015,9 +7375,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "funding": [ { "type": "opencollective", @@ -7035,7 +7395,7 @@ "license": "MIT", "dependencies": { "escalade": "^3.2.0", - "picocolors": "^1.1.0" + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -7708,7 +8068,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -7834,6 +8193,12 @@ "node": ">=0.10.0" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, "node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", diff --git a/package.json b/package.json index 3c176d01..13541705 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,11 @@ "name": "@apify/actors-mcp-server", "version": "0.1.21", "type": "module", - "description": "Model Context Protocol Server for Apify Actors", + "description": "Model Context Protocol Server for Apify", "engines": { "node": ">=18.0.0" }, - "main": "dist/index.js", + "main": "dist/stdio.js", "bin": { "actors-mcp-server": "./dist/stdio.js" }, @@ -30,10 +30,11 @@ "model context protocol" ], "dependencies": { - "@modelcontextprotocol/sdk": "^1.3.1", + "@apify/log": "^2.5.16", + "@modelcontextprotocol/sdk": "^1.9.0", "ajv": "^8.17.1", - "apify": "^3.2.6", - "apify-client": "^2.11.2", + "apify": "^3.4.0", + "apify-client": "^2.12.1", "express": "^4.21.2", "minimist": "^1.2.8", "zod": "^3.24.1", diff --git a/src/actor/README.md b/src/actor/README.md new file mode 100644 index 00000000..6c32ec58 --- /dev/null +++ b/src/actor/README.md @@ -0,0 +1,6 @@ +# Actor + +Code related to Apify Actor called Actors-MCP-Server. +This Actor will be deprecated in favor of Apify MCP Server, therefore we are keeping it separate from the main codebase. + +The only exception is the `src/main.ts` file that also belongs to the Actor. diff --git a/src/actor/const.ts b/src/actor/const.ts new file mode 100644 index 00000000..b88eb38c --- /dev/null +++ b/src/actor/const.ts @@ -0,0 +1,20 @@ +/** + * Constants for the Actor. + */ +export const HEADER_READINESS_PROBE = 'x-apify-container-server-readiness-probe'; + +export const defaults = { + actors: [ + 'apify/instagram-scraper', + 'apify/rag-web-browser', + 'lukaskrivka/google-maps-with-contact-details', + ], + enableActorAutoLoading: false, + maxMemoryMbytes: 4096, +}; + +export enum Routes { + ROOT = '/', + SSE = '/sse', + MESSAGE = '/message', +} diff --git a/src/input.ts b/src/actor/input.ts similarity index 96% rename from src/input.ts rename to src/actor/input.ts index bfce84f0..c7812ff6 100644 --- a/src/input.ts +++ b/src/actor/input.ts @@ -1,3 +1,6 @@ +/* + * Actor input processing. + */ import type { Input } from './types.js'; /** diff --git a/src/server.ts b/src/actor/server.ts similarity index 81% rename from src/server.ts rename to src/actor/server.ts index 4b9b4329..179fb30c 100644 --- a/src/server.ts +++ b/src/actor/server.ts @@ -1,18 +1,21 @@ /* - * This file contains the express server implementation used for standby Actor mode. + * Express server implementation used for standby Actor mode. */ import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; import type { Request, Response } from 'express'; import express from 'express'; +import log from '@apify/log'; + import { HEADER_READINESS_PROBE, Routes } from './const.js'; -import { log } from './logger.js'; -import { type ApifyMcpServer } from './mcp-server.js'; +import { type ApifyMcpServer } from '../mcp-server.js'; +import { getActorRunData, processParamsGetTools } from './utils.js'; -export function createExpressApp(host: string, +export function createExpressApp( + host: string, mcpServer: ApifyMcpServer, - additionalData?: object): express.Express { +): express.Express { const HELP_MESSAGE = `Connect to the server with GET request to ${host}/sse?token=YOUR-APIFY-TOKEN` + ` and then send POST requests to ${host}/message?token=YOUR-APIFY-TOKEN`; @@ -29,11 +32,14 @@ export function createExpressApp(host: string, } try { log.info(`Received GET message at: ${Routes.ROOT}`); - await mcpServer.processParamsAndUpdateTools(req.url); + const tools = await processParamsGetTools(req.url); + if (tools) { + mcpServer.updateTools(tools); + } res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); - res.status(200).json({ message: `Actor is using Model Context Protocol. ${HELP_MESSAGE}`, data: additionalData }).end(); + res.status(200).json({ message: `Actor is using Model Context Protocol. ${HELP_MESSAGE}`, data: getActorRunData() }).end(); } catch (error) { log.error(`Error in GET ${Routes.ROOT} ${error}`); res.status(500).json({ message: 'Internal Server Error' }).end(); @@ -47,7 +53,10 @@ export function createExpressApp(host: string, .get(async (req: Request, res: Response) => { try { log.info(`Received GET message at: ${Routes.SSE}`); - await mcpServer.processParamsAndUpdateTools(req.url); + const tools = await processParamsGetTools(req.url); + if (tools) { + mcpServer.updateTools(tools); + } transport = new SSEServerTransport(Routes.MESSAGE, res); await mcpServer.connect(transport); } catch (error) { diff --git a/src/actor/types.ts b/src/actor/types.ts new file mode 100644 index 00000000..5e9d3fc8 --- /dev/null +++ b/src/actor/types.ts @@ -0,0 +1,30 @@ +export type Input = { + actors: string[] | string; + enableActorAutoLoading?: boolean; + maxActorMemoryBytes?: number; + debugActor?: string; + debugActorInput?: unknown; +}; + +export interface ActorRunData { + id?: string; + actId?: string; + userId?: string; + startedAt?: string; + finishedAt: null; + status: 'RUNNING'; + meta: { + origin?: string; + }; + options: { + build?: string; + memoryMbytes?: string; + }; + buildId?: string; + defaultKeyValueStoreId?: string; + defaultDatasetId?: string; + defaultRequestQueueId?: string; + buildNumber?: string; + containerUrl?: string; + standbyUrl?: string; +} diff --git a/src/utils.ts b/src/actor/utils.ts similarity index 65% rename from src/utils.ts rename to src/actor/utils.ts index 0f6a19df..2a826e9e 100644 --- a/src/utils.ts +++ b/src/actor/utils.ts @@ -3,7 +3,9 @@ import { parse } from 'node:querystring'; import { Actor } from 'apify'; import { processInput } from './input.js'; -import type { ActorRunData, Input } from './types'; +import type { ActorRunData, Input } from './types.js'; +import { addTool, getActorsAsTools, removeTool } from '../tools/index.js'; +import type { ToolWrap } from '../types.js'; export function parseInputParamsFromUrl(url: string): Input { const query = url.split('?')[1] || ''; @@ -11,8 +13,21 @@ export function parseInputParamsFromUrl(url: string): Input { return processInput(params); } -export function isActorStandby(): boolean { - return Actor.getEnv().metaOrigin === 'STANDBY'; +/** + * Process input parameters and get tools + * If URL contains query parameter `actors`, return tools from Actors otherwise return null. + * @param url + */ +export async function processParamsGetTools(url: string) { + const input = parseInputParamsFromUrl(url); + let tools: ToolWrap[] = []; + if (input.actors) { + tools = await getActorsAsTools(input.actors as string[]); + } + if (input.enableActorAutoLoading) { + tools.push(addTool, removeTool); + } + return tools; } export function getActorRunData(): ActorRunData | null { diff --git a/src/actors/call.ts b/src/actors/call.ts deleted file mode 100644 index eab1a258..00000000 --- a/src/actors/call.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { type ApifyClientOptions } from 'apify'; -import type { ActorCallOptions } from 'apify-client'; -import { ApifyClient } from 'apify-client'; -import type { AxiosRequestConfig } from 'axios'; - -import { USER_AGENT_ORIGIN } from '../const.js'; -import { log } from '../logger.js'; - -/** -* Calls an Apify actor and retrieves the dataset items. -* -* It requires the `APIFY_TOKEN` environment variable to be set. -* If the `APIFY_IS_AT_HOME` the dataset items are pushed to the Apify dataset. -* -* @param {string} actorName - The name of the actor to call. -* @param {ActorCallOptions} callOptions - The options to pass to the actor. -* @param {unknown} input - The input to pass to the actor. -* @returns {Promise} - A promise that resolves to an array of dataset items. -* @throws {Error} - Throws an error if the `APIFY_TOKEN` is not set -*/ -export async function callActorGetDataset( - actorName: string, - input: unknown, - apifyToken: string, - callOptions: ActorCallOptions | undefined = undefined, -): Promise { - const name = actorName; - try { - log.info(`Calling Actor ${name} with input: ${JSON.stringify(input)}`); - - const options: ApifyClientOptions = { requestInterceptors: [addUserAgent] }; - const client = new ApifyClient({ ...options, token: apifyToken }); - const actorClient = client.actor(name); - - const results = await actorClient.call(input, callOptions); - const dataset = await client.dataset(results.defaultDatasetId).listItems(); - log.info(`Actor ${name} finished with ${dataset.items.length} items`); - - return dataset.items; - } catch (error) { - log.error(`Error calling actor: ${error}. Actor: ${name}, input: ${JSON.stringify(input)}`); - throw new Error(`Error calling Actor: ${error}`); - } -} - -/** - * Adds a User-Agent header to the request config. - * @param config - * @private - */ -function addUserAgent(config: AxiosRequestConfig): AxiosRequestConfig { - const updatedConfig = { ...config }; - updatedConfig.headers = updatedConfig.headers ?? {}; - updatedConfig.headers['User-Agent'] = `${updatedConfig.headers['User-Agent'] ?? ''}; ${USER_AGENT_ORIGIN}`; - return updatedConfig; -} diff --git a/src/actors/search.ts b/src/actors/search.ts deleted file mode 100644 index 1bd0cb90..00000000 --- a/src/actors/search.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { ActorStoreList } from 'apify-client'; -import { ApifyClient } from 'apify-client'; - -import type { ActorStorePruned, PricingInfo } from '../types.js'; - -function pruneActorStoreInfo(response: ActorStoreList): ActorStorePruned { - const stats = response.stats || {}; - const pricingInfo = (response.currentPricingInfo || {}) as PricingInfo; - return { - id: response.id, - name: response.name?.toString() || '', - username: response.username?.toString() || '', - actorFullName: `${response.username}/${response.name}`, - title: response.title?.toString() || '', - description: response.description?.toString() || '', - stats: { - totalRuns: stats.totalRuns, - totalUsers30Days: stats.totalUsers30Days, - publicActorRunStats30Days: 'publicActorRunStats30Days' in stats - ? stats.publicActorRunStats30Days : {}, - }, - currentPricingInfo: { - pricingModel: pricingInfo.pricingModel?.toString() || '', - pricePerUnitUsd: pricingInfo?.pricePerUnitUsd ?? 0, - trialMinutes: pricingInfo?.trialMinutes ?? 0, - }, - url: response.url?.toString() || '', - totalStars: 'totalStars' in response ? (response.totalStars as number) : null, - }; -} - -export async function searchActorsByKeywords( - search: string, - limit: number | undefined = undefined, - offset: number | undefined = undefined, -): Promise { - const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); - const results = await client.store().list({ search, limit, offset }); - return results.items.map((x) => pruneActorStoreInfo(x)); -} diff --git a/src/actors/utils.ts b/src/actors/utils.ts deleted file mode 100644 index 5531c45b..00000000 --- a/src/actors/utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -export function actorNameToToolName(actorName: string): string { - return actorName - .replace(/\//g, '-slash-') - .replace(/\./g, '-dot-') - .slice(0, 64); -} diff --git a/src/const.ts b/src/const.ts index cb8f06f4..491884ca 100644 --- a/src/const.ts +++ b/src/const.ts @@ -1,37 +1,29 @@ -export const SERVER_NAME = 'apify-mcp-server'; -export const SERVER_VERSION = '0.1.0'; - -export const HEADER_READINESS_PROBE = 'x-apify-container-server-readiness-probe'; -export const MAX_DESCRIPTION_LENGTH = 500; -export const USER_AGENT_ORIGIN = 'Origin/mcp-server'; - -export const defaults = { - actors: [ - 'apify/instagram-scraper', - 'apify/rag-web-browser', - 'lukaskrivka/google-maps-with-contact-details', - ], - enableActorAutoLoading: false, - maxMemoryMbytes: 4096, -}; - +// Actor input const export const ACTOR_README_MAX_LENGTH = 5_000; export const ACTOR_ENUM_MAX_LENGTH = 200; +export const ACTOR_MAX_DESCRIPTION_LENGTH = 500; + +// Actor output const export const ACTOR_OUTPUT_MAX_CHARS_PER_ITEM = 5_000; export const ACTOR_OUTPUT_TRUNCATED_MESSAGE = `Output was truncated because it will not fit into context.` + `There is no reason to call this tool again!`; + export const ACTOR_ADDITIONAL_INSTRUCTIONS = 'Never call/execute tool/Actor unless confirmed by the user. ' + 'Always limit the number of results in the call arguments.'; -export enum InternalTools { - DISCOVER_ACTORS = 'discover-actors', - ADD_ACTOR_TO_TOOLS = 'add-actor-to-tools', - REMOVE_ACTOR_FROM_TOOLS = 'remove-actor-from-tools', - GET_ACTOR_DETAILS = 'get-actor-details', -} +// Actor run const +export const ACTOR_MAX_MEMORY_MBYTES = 4_096; // If the Actor requires 8GB of memory, free users can't run actors-mcp-server and requested Actor + +// MCP Server +export const SERVER_NAME = 'apify-mcp-server'; +export const SERVER_VERSION = '0.1.0'; + +// User agent headers +export const USER_AGENT_ORIGIN = 'Origin/mcp-server'; -export enum Routes { - ROOT = '/', - SSE = '/sse', - MESSAGE = '/message', +export enum HelperTools { + SEARCH = 'search', + ADD_TOOL = 'add-tool', + REMOVE_TOOL = 'remove-tool', + GET_TOOL_DETAILS = 'get-tool-details', } diff --git a/src/examples/clientSse.ts b/src/examples/clientSse.ts index d3cb01c4..5e2c2d56 100644 --- a/src/examples/clientSse.ts +++ b/src/examples/clientSse.ts @@ -15,7 +15,7 @@ import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js'; import dotenv from 'dotenv'; import { EventSource } from 'eventsource'; -import { actorNameToToolName } from '../actors/utils.js'; +import { actorNameToToolName } from '../tools/utils.js'; const REQUEST_TIMEOUT = 120_000; // 2 minutes const filename = fileURLToPath(import.meta.url); diff --git a/src/examples/clientStdio.ts b/src/examples/clientStdio.ts index cb7d6259..8e5627d4 100644 --- a/src/examples/clientStdio.ts +++ b/src/examples/clientStdio.ts @@ -15,7 +15,7 @@ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js' import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js'; import dotenv from 'dotenv'; -import { actorNameToToolName } from '../actors/utils.js'; +import { actorNameToToolName } from '../tools/utils.js'; // Resolve dirname equivalent in ES module const filename = fileURLToPath(import.meta.url); diff --git a/src/index.ts b/src/index.ts index 343bb8cf..23d35e74 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,6 @@ /* This file provides essential functions and tools for MCP servers, serving as a library. + It should be the only file that is exported from the package. */ -export { createExpressApp } from './server.js'; export { ApifyMcpServer } from './mcp-server.js'; -export { getActorAutoLoadingTools, getActorDiscoveryTools } from './tools/index.js'; -export { addActorToTools, discoverActorsTool, getActorsDetailsTool, removeActorFromTools } from './tools/index.js'; -export { getActorsAsTools } from './actors/tools.js'; -export { searchActorsByKeywords } from './actors/search.js'; diff --git a/src/logger.ts b/src/logger.ts deleted file mode 100644 index 39ab7c96..00000000 --- a/src/logger.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { log } from 'apify'; - -log.setLevel(log.LEVELS.DEBUG); - -export { log }; diff --git a/src/main.ts b/src/main.ts index fd966476..dad56187 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,18 +1,20 @@ /** - * This file serves as an Actor MCP SSE server entry point. + * Serves as an Actor MCP SSE server entry point. + * This file needs to be named `main.ts` to be recognized by the Apify platform. */ import { Actor } from 'apify'; import type { ActorCallOptions } from 'apify-client'; -import { callActorGetDataset } from './actors/call.js'; -import { processInput } from './input.js'; -import { log } from './logger.js'; +import log from '@apify/log'; + +import { processInput } from './actor/input.js'; +import { createExpressApp } from './actor/server.js'; +import type { Input } from './actor/types'; import { ApifyMcpServer } from './mcp-server.js'; -import { createExpressApp } from './server.js'; -import { getActorAutoLoadingTools, getActorDiscoveryTools } from './tools/index.js'; -import type { Input } from './types.js'; -import { getActorRunData, isActorStandby } from './utils.js'; +import { actorDefinitionTool, addTool, removeTool, searchTool, callActorGetDataset } from './tools/index.js'; + +const STANDBY_MODE = Actor.getEnv().metaOrigin === 'STANDBY'; await Actor.init(); @@ -29,14 +31,15 @@ const mcpServer = new ApifyMcpServer(); const input = processInput((await Actor.getInput>()) ?? ({} as Input)); log.info(`Loaded input: ${JSON.stringify(input)} `); -if (isActorStandby()) { - const app = createExpressApp(HOST, mcpServer, getActorRunData() || {}); +if (STANDBY_MODE) { + const app = createExpressApp(HOST, mcpServer); log.info('Actor is running in the STANDBY mode.'); - await mcpServer.addToolsFromDefaultActors(); - mcpServer.updateTools(getActorDiscoveryTools()); + const tools = [searchTool, actorDefinitionTool]; + mcpServer.updateTools([searchTool, actorDefinitionTool]); if (input.enableActorAutoLoading) { - mcpServer.updateTools(getActorAutoLoadingTools()); + tools.push(addTool, removeTool); } + mcpServer.updateTools(tools); app.listen(PORT, () => { log.info(`The Actor web server is listening for user requests at ${HOST}`); }); diff --git a/src/mcp-server.ts b/src/mcp-server.ts index 1050d434..f9d9eb6f 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -7,27 +7,22 @@ import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import type { ActorCallOptions } from 'apify-client'; -import { callActorGetDataset } from './actors/call.js'; -import { - getActorsAsTools, -} from './actors/tools.js'; +import log from '@apify/log'; + import { ACTOR_OUTPUT_MAX_CHARS_PER_ITEM, ACTOR_OUTPUT_TRUNCATED_MESSAGE, - defaults, SERVER_NAME, SERVER_VERSION, } from './const.js'; -import { log } from './logger.js'; -import { getActorAutoLoadingTools } from './tools/index.js'; -import type { ActorTool, ToolWrap, InternalTool } from './types.js'; -import { parseInputParamsFromUrl } from './utils.js'; +import { actorDefinitionTool, callActorGetDataset, searchTool } from './tools/index.js'; +import type { ActorTool, HelperTool, ToolWrap } from './types.js'; /** * Create Apify MCP server */ export class ApifyMcpServer { - private server: Server; + public server: Server; public tools: Map; constructor() { @@ -45,23 +40,22 @@ export class ApifyMcpServer { this.tools = new Map(); this.setupErrorHandling(); this.setupToolHandlers(); - } - - public async addToolsFromActors(actors: string[]) { - const tools = await getActorsAsTools(actors); - this.updateTools(tools); - return tools; - } - public async addToolsFromDefaultActors() { - await this.addToolsFromActors(defaults.actors); + // Add default tools + this.updateTools([searchTool, actorDefinitionTool]); } - public updateTools(tools: ToolWrap[]): void { + /** + * Upsert new tools. + * @param tools - Array of tool wrappers. + * @returns Array of tool wrappers. + */ + public updateTools(tools: ToolWrap[]) { for (const wrap of tools) { this.tools.set(wrap.tool.name, wrap); - log.info(`Added/Updated tool: ${wrap.tool.name}`); + log.info(`Added/updated tool: ${wrap.tool.name}`); } + return tools; } /** @@ -82,31 +76,12 @@ export class ApifyMcpServer { }); } - public enableActorAutoLoading() { - this.updateTools(getActorAutoLoadingTools()); - log.debug('Enabled Actor auto-loading tools'); - } - - /** - * Process input parameters and update tools - * If URL contains query parameter `actors`, add tools from Actors. - * If URL contains query parameter `enableActorAutoLoading`, enable auto-loading of Actors. - * @param url - */ - public async processParamsAndUpdateTools(url: string) { - const input = parseInputParamsFromUrl(url); - if (input.actors) { - await this.addToolsFromActors(input.actors as string[]); - } - if (input.enableActorAutoLoading) { - this.enableActorAutoLoading(); - } - - log.debug(`Server is running in STANDBY mode with Actors: ${this.getToolNames()}. ` - + 'To use different Actors, provide them in "actors" query param or Actor Task input.'); - } - private setupToolHandlers(): void { + /** + * Handles the request to list tools. + * @param {object} request - The request object. + * @returns {object} - The response object containing the tools. + */ this.server.setRequestHandler(ListToolsRequestSchema, async () => { const tools = Array.from(this.tools.values()).map((tool) => (tool.tool)); return { tools }; @@ -139,7 +114,7 @@ export class ApifyMcpServer { try { if (tool.type === 'internal') { - const internalTool = tool.tool as InternalTool; + const internalTool = tool.tool as HelperTool; const res = await internalTool.call({ args, apifyMcpServer: this, diff --git a/src/stdio.ts b/src/stdio.ts index facdf12b..f130e21b 100644 --- a/src/stdio.ts +++ b/src/stdio.ts @@ -15,35 +15,40 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import minimist from 'minimist'; -import { log } from './logger.js'; +import log from '@apify/log'; + +import { defaults } from './actor/const.js'; import { ApifyMcpServer } from './mcp-server.js'; -import { getActorAutoLoadingTools, getActorDiscoveryTools } from './tools/index.js'; +import { addTool, removeTool, getActorsAsTools } from './tools/index.js'; +// Configure logging, set to ERROR log.setLevel(log.LEVELS.ERROR); -const argv = minimist(process.argv.slice(2)); -const argActors = argv.actors?.split(',').map((actor: string) => actor.trim()) || []; -const argEnableActorAutoLoading = argv.enableActorAutoLoading || false; +// Parse command line arguments +const { actors = '', enableActorAutoLoading = false } = minimist(process.argv.slice(2)); +const actorList = actors ? actors.split(',').map((a: string) => a.trim()) : []; +// Validate environment if (!process.env.APIFY_TOKEN) { log.error('APIFY_TOKEN is required but not set in the environment variables.'); process.exit(1); } async function main() { - const server = new ApifyMcpServer(); - await (argActors.length !== 0 - ? server.addToolsFromActors(argActors) - : server.addToolsFromDefaultActors()); - server.updateTools(getActorDiscoveryTools()); - if (argEnableActorAutoLoading) { - server.updateTools(getActorAutoLoadingTools()); + const mcpServer = new ApifyMcpServer(); + // Initialize tools + const tools = await getActorsAsTools(actorList.length ? actorList : defaults.actors); + if (enableActorAutoLoading) { + tools.push(addTool, removeTool); } + mcpServer.updateTools(tools); + + // Start server const transport = new StdioServerTransport(); - await server.connect(transport); + await mcpServer.connect(transport); } main().catch((error) => { - console.error('Server error:', error); // eslint-disable-line no-console + log.error('Server error:', error); process.exit(1); }); diff --git a/src/actors/tools.ts b/src/tools/actor.ts similarity index 52% rename from src/actors/tools.ts rename to src/tools/actor.ts index 8db1335f..b6223dd5 100644 --- a/src/actors/tools.ts +++ b/src/tools/actor.ts @@ -1,17 +1,73 @@ import { Ajv } from 'ajv'; +import type { ApifyClientOptions } from 'apify'; +import type { ActorCallOptions } from 'apify-client'; +import { ApifyClient } from 'apify-client'; +import type { AxiosRequestConfig } from 'axios'; + +import log from '@apify/log'; -import { ACTOR_ADDITIONAL_INSTRUCTIONS, defaults } from '../const.js'; -import { log } from '../logger.js'; import type { ToolWrap } from '../types.js'; -import { getActorDefinition } from './details.js'; +import { getActorDefinition } from './build.js'; +import { ACTOR_ADDITIONAL_INSTRUCTIONS, ACTOR_MAX_MEMORY_MBYTES, USER_AGENT_ORIGIN } from '../const.js'; import { + actorNameToToolName, addEnumsToDescriptionsWithExamples, buildNestedProperties, filterSchemaProperties, markInputPropertiesAsRequired, shortenProperties, -} from './schema.js'; -import { actorNameToToolName } from './utils.js'; +} from './utils.js'; + +/** + * Adds a User-Agent header to the request config. + * @param config + * @private + */ +export function addUserAgent(config: AxiosRequestConfig): AxiosRequestConfig { + const updatedConfig = { ...config }; + updatedConfig.headers = updatedConfig.headers ?? {}; + updatedConfig.headers['User-Agent'] = `${updatedConfig.headers['User-Agent'] ?? ''}; ${USER_AGENT_ORIGIN}`; + return updatedConfig; +} + +/** + * Calls an Apify actor and retrieves the dataset items. + * + * + * It requires the `APIFY_TOKEN` environment variable to be set. + * If the `APIFY_IS_AT_HOME` the dataset items are pushed to the Apify dataset. + * + * @param {string} actorName - The name of the actor to call. + * @param {ActorCallOptions} callOptions - The options to pass to the actor. + * @param {unknown} input - The input to pass to the actor. + * @param {string} apifyToken - The Apify token to use for authentication. + * @returns {Promise} - A promise that resolves to an array of dataset items. + * @throws {Error} - Throws an error if the `APIFY_TOKEN` is not set + */ +export async function callActorGetDataset( + actorName: string, + input: unknown, + apifyToken: string, + callOptions: ActorCallOptions | undefined = undefined, +): Promise { + const name = actorName; + try { + log.info(`Calling Actor ${name} with input: ${JSON.stringify(input)}`); + + const options: ApifyClientOptions = { requestInterceptors: [addUserAgent] }; + const client = new ApifyClient({ ...options, token: apifyToken }); + const actorClient = client.actor(name); + + const results = await actorClient.call(input, callOptions); + const dataset = await client.dataset(results.defaultDatasetId).listItems(); + log.info(`Actor ${name} finished with ${dataset.items.length} items`); + + return dataset.items; + } catch (error) { + log.error(`Error calling actor: ${error}. Actor: ${name}, input: ${JSON.stringify(input)}`); + throw new Error(`Error calling Actor: ${error}`); + } +} /** * Fetches actor input schemas by Actor IDs or Actor full names and creates MCP tools. @@ -45,7 +101,7 @@ export async function getActorsAsTools(actors: string[]): Promise { result.input.properties = addEnumsToDescriptionsWithExamples(result.input.properties); } try { - const memoryMbytes = result.defaultRunOptions?.memoryMbytes || defaults.maxMemoryMbytes; + const memoryMbytes = result.defaultRunOptions?.memoryMbytes || ACTOR_MAX_MEMORY_MBYTES; tools.push({ type: 'actor', tool: { @@ -54,7 +110,7 @@ export async function getActorsAsTools(actors: string[]): Promise { description: `${result.description} Instructions: ${ACTOR_ADDITIONAL_INSTRUCTIONS}`, inputSchema: result.input || {}, ajvValidate: ajv.compile(result.input || {}), - memoryMbytes: memoryMbytes > defaults.maxMemoryMbytes ? defaults.maxMemoryMbytes : memoryMbytes, + memoryMbytes: memoryMbytes > ACTOR_MAX_MEMORY_MBYTES ? ACTOR_MAX_MEMORY_MBYTES : memoryMbytes, }, }); } catch (validationError) { diff --git a/src/tools/add-actors-to-tools.ts b/src/tools/add-actors-to-tools.ts deleted file mode 100644 index 9cc48315..00000000 --- a/src/tools/add-actors-to-tools.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Ajv } from 'ajv'; -import { z } from 'zod'; -import zodToJsonSchema from 'zod-to-json-schema'; - -import { InternalTools } from '../const.js'; -import type { ActorTool, InternalTool, ToolWrap } from '../types.js'; - -const ajv = new Ajv({ coerceTypes: 'array', strict: false }); - -export const AddActorToToolsArgsSchema = z.object({ - actorName: z.string() - .describe('Add an Actor to available tools by Actor ID or Actor full name.' - + 'Actor name is always composed from `username/name`'), -}); - -export const addActorToTools: ToolWrap = { - type: 'internal', - tool: { - name: InternalTools.ADD_ACTOR_TO_TOOLS, - description: 'Add an Actor to available tools by Actor ID or Actor name. ' - + 'Do not execute the Actor, only add it and list it in available tools. ' - + 'Never run the tool without user consent! ' - + 'For example, add a tool with username/name when user wants to scrape data from a website.', - inputSchema: zodToJsonSchema(AddActorToToolsArgsSchema), - ajvValidate: ajv.compile(zodToJsonSchema(AddActorToToolsArgsSchema)), - call: async (toolArgs) => { - const { apifyMcpServer, mcpServer, args } = toolArgs; - const parsed = AddActorToToolsArgsSchema.parse(args); - const toolsAdded = await apifyMcpServer.addToolsFromActors([parsed.actorName]); - await mcpServer.notification({ method: 'notifications/tools/list_changed' }); - - return { content: [{ - type: 'text', - text: `Actor added: ${toolsAdded.map((t) => `${(t.tool as ActorTool).actorFullName} (tool name: ${t.tool.name})`).join(', ')}`, - }] }; - }, - } as InternalTool, -}; diff --git a/src/actors/details.ts b/src/tools/build.ts similarity index 61% rename from src/actors/details.ts rename to src/tools/build.ts index bf3cb819..10c5dfb6 100644 --- a/src/actors/details.ts +++ b/src/tools/build.ts @@ -1,8 +1,15 @@ +import { Ajv } from 'ajv'; import { ApifyClient } from 'apify-client'; +import { z } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; -import { ACTOR_README_MAX_LENGTH } from '../const.js'; -import { log } from '../logger.js'; -import type { ActorDefinitionPruned, ActorDefinitionWithDesc, ISchemaProperties } from '../types.js'; +import log from '@apify/log'; + +import { ACTOR_README_MAX_LENGTH, HelperTools } from '../const.js'; +import type { ActorDefinitionPruned, ActorDefinitionWithDesc, InternalTool, ISchemaProperties, ToolWrap } from '../types.js'; +import { filterSchemaProperties, shortenProperties } from './utils.js'; + +const ajv = new Ajv({ coerceTypes: 'array', strict: false }); /** * Get Actor input schema by Actor name. @@ -15,7 +22,6 @@ import type { ActorDefinitionPruned, ActorDefinitionWithDesc, ISchemaProperties export async function getActorDefinition(actorIdOrName: string, limit: number = ACTOR_README_MAX_LENGTH): Promise { const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); const actorClient = client.actor(actorIdOrName); - try { // Fetch actor details const actor = await actorClient.get(); @@ -52,7 +58,6 @@ export async function getActorDefinition(actorIdOrName: string, limit: number = throw new Error(errorMessage); } } - function pruneActorDefinition(response: ActorDefinitionWithDesc): ActorDefinitionPruned { return { id: response.id, @@ -60,22 +65,23 @@ function pruneActorDefinition(response: ActorDefinitionWithDesc): ActorDefinitio buildTag: response?.buildTag || '', readme: response?.readme || '', input: response?.input && 'type' in response.input && 'properties' in response.input - ? { ...response.input, + ? { + ...response.input, type: response.input.type as string, - properties: response.input.properties as Record } + properties: response.input.properties as Record, + } : undefined, description: response.description, defaultRunOptions: response.defaultRunOptions, }; } - /** Prune Actor README if it is too long * If the README is too long * - We keep the README as it is up to the limit. * - After the limit, we keep heading only * - We add a note that the README was truncated because it was too long. */ -export function truncateActorReadme(readme: string, limit = ACTOR_README_MAX_LENGTH): string { +function truncateActorReadme(readme: string, limit = ACTOR_README_MAX_LENGTH): string { if (readme.length <= limit) { return readme; } @@ -85,3 +91,38 @@ export function truncateActorReadme(readme: string, limit = ACTOR_README_MAX_LEN const prunedReadme = lines.filter((line) => line.startsWith('#')); return `${readmeFirst}\n\nREADME was truncated because it was too long. Remaining headers:\n${prunedReadme.join(', ')}`; } + +const GetActorDefinitionArgsSchema = z.object({ + actorName: z.string() + .describe('Retrieve input, readme, and other details for Actor ID or Actor full name. ' + + 'Actor name is always composed from `username/name`'), + limit: z.number() + .int() + .default(ACTOR_README_MAX_LENGTH) + .describe(`Truncate the README to this limit. Default value is ${ACTOR_README_MAX_LENGTH}.`), +}); + +export const actorDefinitionTool: ToolWrap = { + type: 'internal', + tool: { + name: HelperTools.GET_TOOL_DETAILS, + actorFullName: HelperTools.GET_TOOL_DETAILS, + description: 'Get documentation, readme, input schema and other details about an Actor. ' + + 'For example, when user says, I need to know more about web crawler Actor.' + + 'Get details for an Actor with with Actor ID or Actor full name, i.e. username/name.' + + `Limit the length of the README if needed.`, + inputSchema: zodToJsonSchema(GetActorDefinitionArgsSchema), + ajvValidate: ajv.compile(zodToJsonSchema(GetActorDefinitionArgsSchema)), + call: async (toolArgs) => { + const { args } = toolArgs; + + const parsed = GetActorDefinitionArgsSchema.parse(args); + const v = await getActorDefinition(parsed.actorName, parsed.limit); + if (v && v.input && 'properties' in v.input && v.input) { + const properties = filterSchemaProperties(v.input.properties as { [key: string]: ISchemaProperties }); + v.input.properties = shortenProperties(properties); + } + return { content: [{ type: 'text', text: JSON.stringify(v) }] }; + }, + } as InternalTool, +}; diff --git a/src/tools/discover-actors.ts b/src/tools/discover-actors.ts deleted file mode 100644 index e837d735..00000000 --- a/src/tools/discover-actors.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Ajv } from 'ajv'; -import { z } from 'zod'; -import zodToJsonSchema from 'zod-to-json-schema'; - -import { searchActorsByKeywords } from '../actors/search.js'; -import { InternalTools } from '../const.js'; -import type { InternalTool, ToolWrap } from '../types.js'; - -const ajv = new Ajv({ coerceTypes: 'array', strict: false }); - -export const DiscoverActorsArgsSchema = z.object({ - limit: z.number() - .int() - .min(1) - .max(100) - .default(10) - .describe('The maximum number of Actors to return. Default value is 10.'), - offset: z.number() - .int() - .min(0) - .default(0) - .describe('The number of elements that should be skipped at the start. Default value is 0.'), - search: z.string() - .default('') - .describe('String of key words to search by. ' - + 'Searches the title, name, description, username, and readme of an Actor.' - + 'Only key word search is supported, no advanced search.' - + 'Always prefer simple keywords over complex queries.'), - category: z.string() - .default('') - .describe('Filters the results by the specified category.'), -}); - -export const discoverActorsTool: ToolWrap = { - type: 'internal', - tool: { - name: InternalTools.DISCOVER_ACTORS, - actorFullName: InternalTools.DISCOVER_ACTORS, - description: `Discover available Actors using full text search using keywords.` - + `Users try to discover Actors using free form query in this case search query needs to be converted to full text search. ` - + `Prefer Actors from Apify as they are generally more reliable and have better support. ` - + `Returns a list of Actors with name, description, run statistics, pricing, starts, and URL. ` - + `You perhaps need to use this tool several times to find the right Actor. ` - + `Limit number of results returned but ensure that relevant results are returned. `, - inputSchema: zodToJsonSchema(DiscoverActorsArgsSchema), - ajvValidate: ajv.compile(zodToJsonSchema(DiscoverActorsArgsSchema)), - call: async (toolArgs) => { - const { args } = toolArgs; - const parsed = DiscoverActorsArgsSchema.parse(args); - const actors = await searchActorsByKeywords( - parsed.search, - parsed.limit, - parsed.offset, - ); - return { content: actors?.map((item) => ({ type: 'text', text: JSON.stringify(item) })) }; - }, - } as InternalTool, -}; diff --git a/src/tools/get-actors-details.ts b/src/tools/get-actors-details.ts deleted file mode 100644 index ac30f2d3..00000000 --- a/src/tools/get-actors-details.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Ajv } from 'ajv'; -import { z } from 'zod'; -import zodToJsonSchema from 'zod-to-json-schema'; - -import { getActorDefinition } from '../actors/details.js'; -import { filterSchemaProperties, shortenProperties } from '../actors/schema.js'; -import { ACTOR_README_MAX_LENGTH, InternalTools } from '../const.js'; -import type { InternalTool, ISchemaProperties, ToolWrap } from '../types.js'; - -const ajv = new Ajv({ coerceTypes: 'array', strict: false }); - -export const GetActorDefinition = z.object({ - actorName: z.string() - .describe('Retrieve input, readme, and other details for Actor ID or Actor full name. ' - + 'Actor name is always composed from `username/name`'), - limit: z.number() - .int() - .default(ACTOR_README_MAX_LENGTH) - .describe(`Truncate the README to this limit. Default value is ${ACTOR_README_MAX_LENGTH}.`), -}); - -export const getActorsDetailsTool: ToolWrap = { - type: 'internal', - tool: { - name: InternalTools.GET_ACTOR_DETAILS, - actorFullName: InternalTools.GET_ACTOR_DETAILS, - description: 'Get documentation, readme, input schema and other details about an Actor. ' - + 'For example, when user says, I need to know more about web crawler Actor.' - + 'Get details for an Actor with with Actor ID or Actor full name, i.e. username/name.' - + `Limit the length of the README if needed.`, - inputSchema: zodToJsonSchema(GetActorDefinition), - ajvValidate: ajv.compile(zodToJsonSchema(GetActorDefinition)), - call: async (toolArgs) => { - const { args } = toolArgs; - - const parsed = GetActorDefinition.parse(args); - const v = await getActorDefinition(parsed.actorName, parsed.limit); - if (v && v.input && 'properties' in v.input && v.input) { - const properties = filterSchemaProperties(v.input.properties as { [key: string]: ISchemaProperties }); - v.input.properties = shortenProperties(properties); - } - return { content: [{ type: 'text', text: JSON.stringify(v) }] }; - }, - } as InternalTool, -}; diff --git a/src/tools/helpers.ts b/src/tools/helpers.ts new file mode 100644 index 00000000..3c720a4b --- /dev/null +++ b/src/tools/helpers.ts @@ -0,0 +1,64 @@ +import { Ajv } from 'ajv'; +import { z } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; + +import { HelperTools } from '../const.js'; +import { actorNameToToolName } from './utils.js'; +import type { ActorTool, InternalTool, ToolWrap } from '../types'; +import { getActorsAsTools } from './actor.js'; + +const ajv = new Ajv({ coerceTypes: 'array', strict: false }); +export const AddToolArgsSchema = z.object({ + actorName: z.string() + .describe('Add a tool to available tools by Tool ID or tool full name.' + + 'Tool name is always composed from `username/name`'), +}); +export const addTool: ToolWrap = { + type: 'internal', + tool: { + name: HelperTools.ADD_TOOL, + description: 'Add an Actor to available tools by Actor ID or Actor name. ' + + 'Do not execute the Actor, only add it and list it in available tools. ' + + 'Never run the tool without user consent! ' + + 'For example, add a tool with username/name when user wants to scrape data from a website.', + inputSchema: zodToJsonSchema(AddToolArgsSchema), + ajvValidate: ajv.compile(zodToJsonSchema(AddToolArgsSchema)), + call: async (toolArgs) => { + const { apifyMcpServer, mcpServer, args } = toolArgs; + const parsed = AddToolArgsSchema.parse(args); + const tools = await getActorsAsTools([parsed.actorName]); + const toolsAdded = apifyMcpServer.updateTools(tools); + await mcpServer.notification({ method: 'notifications/tools/list_changed' }); + + return { + content: [{ + type: 'text', + text: `Actor added: ${toolsAdded.map((t) => `${(t.tool as ActorTool).actorFullName} (tool name: ${t.tool.name})`).join(', ')}`, + }], + }; + }, + } as InternalTool, +}; +export const RemoveToolArgsSchema = z.object({ + toolName: z.string() + .describe('Tool name to remove from available tools.') + .transform((val) => actorNameToToolName(val)), +}); +export const removeTool: ToolWrap = { + type: 'internal', + tool: { + name: HelperTools.REMOVE_TOOL, + description: 'Remove tool by name from available tools. ' + + 'For example, when user says, I do not need a tool username/name anymore', + inputSchema: zodToJsonSchema(RemoveToolArgsSchema), + ajvValidate: ajv.compile(zodToJsonSchema(RemoveToolArgsSchema)), + call: async (toolArgs) => { + const { apifyMcpServer, mcpServer, args } = toolArgs; + + const parsed = RemoveToolArgsSchema.parse(args); + apifyMcpServer.tools.delete(parsed.toolName); + await mcpServer.notification({ method: 'notifications/tools/list_changed' }); + return { content: [{ type: 'text', text: `Tool ${parsed.toolName} was removed` }] }; + }, + } as InternalTool, +}; diff --git a/src/tools/index.ts b/src/tools/index.ts index 480b344a..b6f141e0 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -1,21 +1,8 @@ -import type { ToolWrap } from '../types.js'; -import { addActorToTools } from './add-actors-to-tools.js'; -import { discoverActorsTool } from './discover-actors.js'; -import { getActorsDetailsTool } from './get-actors-details.js'; -import { removeActorFromTools } from './remove-actors-from-tools.js'; +// Import specific tools that are being used +import { callActorGetDataset, getActorsAsTools } from './actor.js'; +import { actorDefinitionTool } from './build.js'; +import { addTool, removeTool } from './helpers.js'; +import { searchTool } from './store_collection.js'; -export { addActorToTools, removeActorFromTools, discoverActorsTool, getActorsDetailsTool }; - -export function getActorAutoLoadingTools(): ToolWrap[] { - return [ - addActorToTools, - removeActorFromTools, - ]; -} - -export function getActorDiscoveryTools(): ToolWrap[] { - return [ - discoverActorsTool, - getActorsDetailsTool, - ]; -} +// Export only the tools that are being used +export { addTool, removeTool, actorDefinitionTool, searchTool, getActorsAsTools, callActorGetDataset }; diff --git a/src/tools/remove-actors-from-tools.ts b/src/tools/remove-actors-from-tools.ts deleted file mode 100644 index 6a47df05..00000000 --- a/src/tools/remove-actors-from-tools.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Ajv } from 'ajv'; -import { z } from 'zod'; -import { zodToJsonSchema } from 'zod-to-json-schema'; - -import { actorNameToToolName } from '../actors/utils.js'; -import { InternalTools } from '../const.js'; -import type { InternalTool, ToolWrap } from '../types.js'; - -const ajv = new Ajv({ coerceTypes: 'array', strict: false }); - -export const RemoveActorToolArgsSchema = z.object({ - toolName: z.string() - .describe('Tool name to remove from available tools.') - .transform((val) => actorNameToToolName(val)), -}); - -export const removeActorFromTools: ToolWrap = { - type: 'internal', - tool: { - name: InternalTools.REMOVE_ACTOR_FROM_TOOLS, - description: 'Remove tool by name from available tools. ' - + 'For example, when user says, I do not need a tool username/name anymore', - inputSchema: zodToJsonSchema(RemoveActorToolArgsSchema), - ajvValidate: ajv.compile(zodToJsonSchema(RemoveActorToolArgsSchema)), - call: async (toolArgs) => { - const { apifyMcpServer, mcpServer, args } = toolArgs; - - const parsed = RemoveActorToolArgsSchema.parse(args); - apifyMcpServer.tools.delete(parsed.toolName); - await mcpServer.notification({ method: 'notifications/tools/list_changed' }); - return { content: [{ type: 'text', text: `Tool ${parsed.toolName} was removed` }] }; - }, - } as InternalTool, -}; diff --git a/src/tools/store_collection.ts b/src/tools/store_collection.ts new file mode 100644 index 00000000..5130624d --- /dev/null +++ b/src/tools/store_collection.ts @@ -0,0 +1,93 @@ +import { Ajv } from 'ajv'; +import type { ActorStoreList } from 'apify-client'; +import { ApifyClient } from 'apify-client'; +import { z } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; + +import { HelperTools } from '../const.js'; +import type { ActorStorePruned, HelperTool, PricingInfo, ToolWrap } from '../types.js'; + +function pruneActorStoreInfo(response: ActorStoreList): ActorStorePruned { + const stats = response.stats || {}; + const pricingInfo = (response.currentPricingInfo || {}) as PricingInfo; + return { + id: response.id, + name: response.name?.toString() || '', + username: response.username?.toString() || '', + actorFullName: `${response.username}/${response.name}`, + title: response.title?.toString() || '', + description: response.description?.toString() || '', + stats: { + totalRuns: stats.totalRuns, + totalUsers30Days: stats.totalUsers30Days, + publicActorRunStats30Days: 'publicActorRunStats30Days' in stats + ? stats.publicActorRunStats30Days : {}, + }, + currentPricingInfo: { + pricingModel: pricingInfo.pricingModel?.toString() || '', + pricePerUnitUsd: pricingInfo?.pricePerUnitUsd ?? 0, + trialMinutes: pricingInfo?.trialMinutes ?? 0, + }, + url: response.url?.toString() || '', + totalStars: 'totalStars' in response ? (response.totalStars as number) : null, + }; +} + +export async function searchActorsByKeywords( + search: string, + limit: number | undefined = undefined, + offset: number | undefined = undefined, +): Promise { + const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); + const results = await client.store().list({ search, limit, offset }); + return results.items.map((x) => pruneActorStoreInfo(x)); +} + +const ajv = new Ajv({ coerceTypes: 'array', strict: false }); +export const SearchToolArgsSchema = z.object({ + limit: z.number() + .int() + .min(1) + .max(100) + .default(10) + .describe('The maximum number of Actors to return. Default value is 10.'), + offset: z.number() + .int() + .min(0) + .default(0) + .describe('The number of elements that should be skipped at the start. Default value is 0.'), + search: z.string() + .default('') + .describe('String of key words to search by. ' + + 'Searches the title, name, description, username, and readme of an Actor.' + + 'Only key word search is supported, no advanced search.' + + 'Always prefer simple keywords over complex queries.'), + category: z.string() + .default('') + .describe('Filters the results by the specified category.'), +}); +export const searchTool: ToolWrap = { + type: 'internal', + tool: { + name: HelperTools.SEARCH, + actorFullName: HelperTools.SEARCH, + description: `Discover available Actors using full text search using keywords.` + + `Users try to discover Actors using free form query in this case search query needs to be converted to full text search. ` + + `Prefer Actors from Apify as they are generally more reliable and have better support. ` + + `Returns a list of Actors with name, description, run statistics, pricing, starts, and URL. ` + + `You perhaps need to use this tool several times to find the right Actor. ` + + `Limit number of results returned but ensure that relevant results are returned. `, + inputSchema: zodToJsonSchema(SearchToolArgsSchema), + ajvValidate: ajv.compile(zodToJsonSchema(SearchToolArgsSchema)), + call: async (toolArgs) => { + const { args } = toolArgs; + const parsed = SearchToolArgsSchema.parse(args); + const actors = await searchActorsByKeywords( + parsed.search, + parsed.limit, + parsed.offset, + ); + return { content: actors?.map((item) => ({ type: 'text', text: JSON.stringify(item) })) }; + }, + } as HelperTool, +}; diff --git a/src/actors/schema.ts b/src/tools/utils.ts similarity index 94% rename from src/actors/schema.ts rename to src/tools/utils.ts index cd8b2728..a9e02688 100644 --- a/src/actors/schema.ts +++ b/src/tools/utils.ts @@ -1,6 +1,13 @@ -import { MAX_DESCRIPTION_LENGTH, ACTOR_ENUM_MAX_LENGTH } from '../const.js'; +import { ACTOR_ENUM_MAX_LENGTH, ACTOR_MAX_DESCRIPTION_LENGTH } from '../const.js'; import type { IActorInputSchema, ISchemaProperties } from '../types.js'; +export function actorNameToToolName(actorName: string): string { + return actorName + .replace(/\//g, '-slash-') + .replace(/\./g, '-dot-') + .slice(0, 64); +} + /** * Builds nested properties for object types in the schema. * @@ -64,7 +71,9 @@ export function buildNestedProperties(properties: Record MAX_DESCRIPTION_LENGTH) { - property.description = `${property.description.slice(0, MAX_DESCRIPTION_LENGTH)}...`; + if (property.description.length > ACTOR_MAX_DESCRIPTION_LENGTH) { + property.description = `${property.description.slice(0, ACTOR_MAX_DESCRIPTION_LENGTH)}...`; } if (property.enum && property.enum?.length > 0) { diff --git a/src/types.ts b/src/types.ts index 5688c2d9..c6dfa98d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,15 +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 { ApifyMcpServer } from './mcp-server'; - -export type Input = { - actors: string[] | string; - enableActorAutoLoading?: boolean; - maxActorMemoryBytes?: number; - debugActor?: string; - debugActorInput?: unknown; -}; +import type { ApifyMcpServer } from './mcp-server.js'; export interface ISchemaProperties { type: string; @@ -97,7 +89,7 @@ export type InternalToolArgs = { * Interface for internal tools - tools implemented directly in the MCP server. * Extends ToolBase with a call function implementation. */ -export interface InternalTool extends ToolBase { +export interface HelperTool extends ToolBase { /** * Executes the tool with the given arguments * @param toolArgs - Arguments and server references @@ -109,7 +101,7 @@ export interface InternalTool extends ToolBase { /** * Type discriminator for tools - indicates whether a tool is internal or Actor-based. */ -export type ToolType = 'internal' | 'actor'; +export type ToolType = 'internal' | 'actor' | 'actor-mcp'; /** * Wrapper interface that combines a tool with its type discriminator. @@ -119,7 +111,7 @@ export interface ToolWrap { /** Type of the tool (internal or actor) */ type: ToolType; /** The tool instance */ - tool: ActorTool | InternalTool; + tool: ActorTool | HelperTool; } // ActorStoreList for actor-search tool @@ -148,25 +140,15 @@ export interface ActorStorePruned { totalStars?: number | null; } -export interface ActorRunData { - id?: string; - actId?: string; - userId?: string; - startedAt?: string; - finishedAt: null; - status: 'RUNNING'; - meta: { - origin?: string; - }; - options: { - build?: string; - memoryMbytes?: string; - }; - buildId?: string; - defaultKeyValueStoreId?: string; - defaultDatasetId?: string; - defaultRequestQueueId?: string; - buildNumber?: string; - containerUrl?: string; - standbyUrl?: string; +/** + * Interface for internal tools - tools implemented directly in the MCP server. + * Extends ToolBase with a call function implementation. + */ +export interface InternalTool extends ToolBase { + /** + * Executes the tool with the given arguments + * @param toolArgs - Arguments and server references + * @returns Promise resolving to the tool's output + */ + call: (toolArgs: InternalToolArgs) => Promise; } diff --git a/tests/server-test.ts b/tests/actor-server-test.ts similarity index 57% rename from tests/server-test.ts rename to tests/actor-server-test.ts index 6bd4091c..4dd7ec85 100644 --- a/tests/server-test.ts +++ b/tests/actor-server-test.ts @@ -1,45 +1,47 @@ -import type { Server } from '@modelcontextprotocol/sdk/server.js'; -import express from 'express'; +import type { Server as HttpServer } from 'http'; + +import type { Express } from 'express'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { log } from '../src/logger.js'; +import log from '@apify/log'; + +import { createExpressApp } from '../src/actor/server.js'; +import { HelperTools } from '../src/const.js'; import { ApifyMcpServer } from '../src/mcp-server.js'; describe('ApifyMcpServer initialization', () => { - let app: express.Express; + let app: Express; let server: ApifyMcpServer; - let mcpServer: Server; + let httpServer: HttpServer; const testPort = 7357; + const testHost = `http://localhost:${testPort}`; beforeEach(async () => { - app = express(); server = new ApifyMcpServer(); log.setLevel(log.LEVELS.OFF); - // Setup basic express route to trigger server initialization - app.get('/', async (req, res) => { - await server.processParamsAndUpdateTools(req.url); - res.sendStatus(200); - }); + // Create express app using the proper server setup + app = createExpressApp(testHost, server); // Start test server await new Promise((resolve) => { - mcpServer = app.listen(testPort, () => resolve()); + httpServer = app.listen(testPort, () => resolve()); }); }); afterEach(async () => { await new Promise((resolve) => { - mcpServer.close(() => resolve()); + httpServer.close(() => resolve()); }); }); it('should load actors from query parameters', async () => { // Test with multiple actors including different username cases const testActors = ['apify/rag-web-browser', 'apify/instagram-scraper']; + const numberOfHelperTools = 2; // Make request to trigger server initialization - const response = await fetch(`http://localhost:${testPort}/?actors=${testActors.join(',')}`); + const response = await fetch(`${testHost}/?actors=${testActors.join(',')}`); expect(response.status).toBe(200); // Verify loaded tools @@ -48,17 +50,19 @@ describe('ApifyMcpServer initialization', () => { 'apify-slash-rag-web-browser', 'apify-slash-instagram-scraper', ])); - expect(toolNames.length).toBe(testActors.length); + expect(toolNames.length).toBe(testActors.length + numberOfHelperTools); }); it('should enable auto-loading tools when flag is set', async () => { - const response = await fetch(`http://localhost:${testPort}/?enableActorAutoLoading=true`); + const response = await fetch(`${testHost}/?enableActorAutoLoading=true`); expect(response.status).toBe(200); const toolNames = server.getToolNames(); expect(toolNames).toEqual([ - 'add-actor-to-tools', - 'remove-actor-from-tools', + HelperTools.SEARCH, + HelperTools.GET_TOOL_DETAILS, + HelperTools.ADD_TOOL, + HelperTools.REMOVE_TOOL, ]); }); }); diff --git a/tests/actors-test.ts b/tests/actor-test.ts similarity index 93% rename from tests/actors-test.ts rename to tests/actor-test.ts index 3db052e1..ba7137ba 100644 --- a/tests/actors-test.ts +++ b/tests/actor-test.ts @@ -1,8 +1,7 @@ import { describe, it, expect } from 'vitest'; -import { shortenEnum, inferArrayItemType } from '../src/actors/schema.js'; -import { actorNameToToolName } from '../src/actors/utils.js'; import { ACTOR_ENUM_MAX_LENGTH } from '../src/const.js'; +import { actorNameToToolName, inferArrayItemType, shortenEnum } from '../src/tools/utils.js'; describe('actors', () => { describe('actorNameToToolName', () => { diff --git a/tests/utils-test.ts b/tests/actor-utils-test.ts similarity index 95% rename from tests/utils-test.ts rename to tests/actor-utils-test.ts index da540d66..fa77b62f 100644 --- a/tests/utils-test.ts +++ b/tests/actor-utils-test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; -import { parseInputParamsFromUrl } from '../src/utils.js'; +import { parseInputParamsFromUrl } from '../src/actor/utils.js'; describe('parseInputParamsFromUrl', () => { it('should parse actors from URL query params', () => { From b78fb6b1caadb2ec886eb0b11702bda10b151fdb Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Wed, 9 Apr 2025 22:20:04 +0200 Subject: [PATCH 21/42] fix: Update packages, add TODOs --- .github/scripts/before-beta-release.cjs | 6 +- package-lock.json | 2178 +++++++++++------------ package.json | 18 +- src/mcp-server.ts | 18 +- src/tools/helpers.ts | 6 +- 5 files changed, 1058 insertions(+), 1168 deletions(-) diff --git a/.github/scripts/before-beta-release.cjs b/.github/scripts/before-beta-release.cjs index 26ae4e1f..5d38045f 100644 --- a/.github/scripts/before-beta-release.cjs +++ b/.github/scripts/before-beta-release.cjs @@ -1,6 +1,6 @@ -const { execSync } = require('child_process'); -const fs = require('fs'); -const path = require('path'); +const { execSync } = require('node:child_process'); +const fs = require('node:fs'); +const path = require('node:path'); const PKG_JSON_PATH = path.join(__dirname, '..', '..', 'package.json'); diff --git a/package-lock.json b/package-lock.json index ec6a1d02..34159360 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,16 +25,16 @@ "devDependencies": { "@anthropic-ai/sdk": "^0.33.1", "@anthropic-ai/tokenizer": "^0.0.4", - "@apify/eslint-config": "^0.5.0-beta.2", + "@apify/eslint-config": "^1.0.0", "@apify/tsconfig": "^0.1.0", "@types/express": "^4.0.0", "@types/minimist": "^1.2.5", "dotenv": "^16.4.7", - "eslint": "^9.17.0", + "eslint": "^9.19.0", "eventsource": "^3.0.2", "tsx": "^4.6.2", "typescript": "^5.3.3", - "typescript-eslint": "^8.18.2", + "typescript-eslint": "^8.23.0", "vitest": "^3.0.8" }, "engines": { @@ -57,30 +57,6 @@ "node-fetch": "^2.6.7" } }, - "node_modules/@anthropic-ai/sdk/node_modules/@types/node": { - "version": "18.19.70", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.70.tgz", - "integrity": "sha512-RE+K0+KZoEpDUbGGctnGdkrLFwi1eYKTlIHNl2Um98mUkGsm1u2Ff6Ltd0e8DktTtC98uy7rSj+hO8t/QuLoVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@anthropic-ai/sdk/node_modules/form-data-encoder": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", - "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@anthropic-ai/sdk/node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true, - "license": "MIT" - }, "node_modules/@anthropic-ai/tokenizer": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/@anthropic-ai/tokenizer/-/tokenizer-0.0.4.tgz", @@ -92,23 +68,6 @@ "tiktoken": "^1.0.10" } }, - "node_modules/@anthropic-ai/tokenizer/node_modules/@types/node": { - "version": "18.19.70", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.70.tgz", - "integrity": "sha512-RE+K0+KZoEpDUbGGctnGdkrLFwi1eYKTlIHNl2Um98mUkGsm1u2Ff6Ltd0e8DktTtC98uy7rSj+hO8t/QuLoVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@anthropic-ai/tokenizer/node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true, - "license": "MIT" - }, "node_modules/@apify/consts": { "version": "2.39.0", "resolved": "https://registry.npmjs.org/@apify/consts/-/consts-2.39.0.tgz", @@ -122,35 +81,41 @@ "license": "Apache-2.0" }, "node_modules/@apify/eslint-config": { - "version": "0.5.0-beta.2", - "resolved": "https://registry.npmjs.org/@apify/eslint-config/-/eslint-config-0.5.0-beta.2.tgz", - "integrity": "sha512-I6Eb8w62HTMRVVjp86NtV1nQiU24qycqOrEA0NkYuAjcdZiNTmxVBoz1hIyQcerp9kdQ9r3c3p9I6lb+uoRuyQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@apify/eslint-config/-/eslint-config-1.0.0.tgz", + "integrity": "sha512-8dAMgX5qf7B5W5NhzymtweD1Llh7f4gytgg3kYtyA1bVBdh3BT3cTUhEUDeeZSyeyQL2ZkSMPrfLP/3sd5dcUA==", "dev": true, "license": "ISC", "dependencies": { - "@eslint/compat": "^1.2.2", + "@eslint/compat": "^1.2.6", + "@jirimoravcik/eslint-plugin-import": "2.32.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-plugin-import": "^2.31.0", - "globals": "^15.11.0" + "eslint-plugin-simple-import-sort": "^12.1.1", + "globals": "^15.14.0" }, "peerDependencies": { - "eslint": "^9.0.0", - "typescript-eslint": "^8.0.0" + "eslint": "^9.19.0", + "eslint-plugin-jest": "^28.11.0", + "typescript-eslint": "^8.23.0" }, "peerDependenciesMeta": { + "eslint-plugin-jest": { + "optional": true + }, "typescript-eslint": { "optional": true } } }, "node_modules/@apify/input_secrets": { - "version": "1.1.60", - "resolved": "https://registry.npmjs.org/@apify/input_secrets/-/input_secrets-1.1.60.tgz", - "integrity": "sha512-ZSL6aCjCs/zjq5Y6187G6FlstGKmUD3vmx4Ew+C20lnxRyAFUd3wuk+lrDvfuRl9KB8GHvXod+UIGna6+/UMZw==", + "version": "1.1.70", + "resolved": "https://registry.npmjs.org/@apify/input_secrets/-/input_secrets-1.1.70.tgz", + "integrity": "sha512-fKuNdeYCMIwz5GxBydI+uu/cOnSye1l39SJcZ5FA38knpQHURZxSpYbtLcb98/MOUuzKOSCQvIbPyx1OwO1Pxg==", "license": "Apache-2.0", "dependencies": { - "@apify/log": "^2.5.11", - "@apify/utilities": "^2.11.1", + "@apify/log": "^2.5.16", + "@apify/utilities": "^2.15.3", "ow": "^0.28.2" } }, @@ -244,6 +209,30 @@ "node": ">=16.0.0" } }, + "node_modules/@crawlee/core/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@crawlee/core/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@crawlee/memory-storage": { "version": "3.13.2", "resolved": "https://registry.npmjs.org/@crawlee/memory-storage/-/memory-storage-3.13.2.tgz", @@ -301,9 +290,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", - "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", + "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", "cpu": [ "ppc64" ], @@ -318,9 +307,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", - "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", + "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", "cpu": [ "arm" ], @@ -335,9 +324,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", - "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", + "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", "cpu": [ "arm64" ], @@ -352,9 +341,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", - "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", + "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", "cpu": [ "x64" ], @@ -369,9 +358,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", - "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", + "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", "cpu": [ "arm64" ], @@ -386,9 +375,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", - "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", + "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", "cpu": [ "x64" ], @@ -403,9 +392,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", - "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", + "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", "cpu": [ "arm64" ], @@ -420,9 +409,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", - "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", + "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", "cpu": [ "x64" ], @@ -437,9 +426,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", - "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", + "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", "cpu": [ "arm" ], @@ -454,9 +443,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", - "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", + "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", "cpu": [ "arm64" ], @@ -471,9 +460,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", - "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", + "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", "cpu": [ "ia32" ], @@ -488,9 +477,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", - "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", + "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", "cpu": [ "loong64" ], @@ -505,9 +494,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", - "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", + "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", "cpu": [ "mips64el" ], @@ -522,9 +511,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", - "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", + "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", "cpu": [ "ppc64" ], @@ -539,9 +528,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", - "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", + "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", "cpu": [ "riscv64" ], @@ -556,9 +545,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", - "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", + "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", "cpu": [ "s390x" ], @@ -573,9 +562,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", - "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", + "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", "cpu": [ "x64" ], @@ -590,13 +579,14 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", - "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", + "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -606,9 +596,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", - "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", + "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", "cpu": [ "x64" ], @@ -623,9 +613,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", - "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", + "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", "cpu": [ "arm64" ], @@ -640,9 +630,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", - "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", + "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", "cpu": [ "x64" ], @@ -657,9 +647,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", - "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", + "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", "cpu": [ "x64" ], @@ -674,9 +664,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", - "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", + "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", "cpu": [ "arm64" ], @@ -691,9 +681,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", - "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", + "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", "cpu": [ "ia32" ], @@ -708,9 +698,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", - "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", + "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", "cpu": [ "x64" ], @@ -725,9 +715,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", + "integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==", "dev": true, "license": "MIT", "dependencies": { @@ -767,9 +757,9 @@ } }, "node_modules/@eslint/compat": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.4.tgz", - "integrity": "sha512-S8ZdQj/N69YAtuqFt7653jwcvuUj131+6qGLUyDqfDg1OIoBQ66OCuXC473YQfO2AaxITTutiRQiDwoo7ZLYyg==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.8.tgz", + "integrity": "sha512-LqCYHdWL/QqKIJuZ/ucMAv8d4luKGs4oCPgpt8mWztQAtPrHfXKQ/XAUc8ljCHAfJCn6SvkpTcGt5Tsh8saowA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -785,13 +775,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", - "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.5", + "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -799,34 +789,38 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/@eslint/config-array/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/@eslint/config-helpers": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", + "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "license": "Apache-2.0", "engines": { - "node": "*" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz", - "integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", + "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -837,9 +831,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", - "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, "license": "MIT", "dependencies": { @@ -877,15 +871,22 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/@eslint/eslintrc/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/@eslint/eslintrc/node_modules/globals": { @@ -908,23 +909,10 @@ "dev": true, "license": "MIT" }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/@eslint/js": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", - "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.24.0.tgz", + "integrity": "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==", "dev": true, "license": "MIT", "engines": { @@ -932,9 +920,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", - "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -942,18 +930,32 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz", - "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==", + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", + "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", "dev": true, "license": "Apache-2.0", "dependencies": { + "@eslint/core": "^0.13.0", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", + "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1007,9 +1009,9 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", - "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1020,11 +1022,46 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@jirimoravcik/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/@jirimoravcik/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-WkGO098hw9/rFizGLQHTDWruVGetXLOXs6pENWfWkIKXHk4qh5vfBuXvEhyO9Y9+ESfHFIJKAp1heo1B/Ozg8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.9.0", @@ -1101,6 +1138,23 @@ "node": ">=6.6.0" } }, + "node_modules/@modelcontextprotocol/sdk/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/@modelcontextprotocol/sdk/node_modules/express": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", @@ -1337,247 +1391,280 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.35.0.tgz", - "integrity": "sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.39.0.tgz", + "integrity": "sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.35.0.tgz", - "integrity": "sha512-FtKddj9XZudurLhdJnBl9fl6BwCJ3ky8riCXjEw3/UIbjmIY58ppWwPEvU3fNu+W7FUsAsB1CdH+7EQE6CXAPA==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.39.0.tgz", + "integrity": "sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.35.0.tgz", - "integrity": "sha512-Uk+GjOJR6CY844/q6r5DR/6lkPFOw0hjfOIzVx22THJXMxktXG6CbejseJFznU8vHcEBLpiXKY3/6xc+cBm65Q==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.39.0.tgz", + "integrity": "sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.35.0.tgz", - "integrity": "sha512-3IrHjfAS6Vkp+5bISNQnPogRAW5GAV1n+bNCrDwXmfMHbPl5EhTmWtfmwlJxFRUCBZ+tZ/OxDyU08aF6NI/N5Q==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.39.0.tgz", + "integrity": "sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.35.0.tgz", - "integrity": "sha512-sxjoD/6F9cDLSELuLNnY0fOrM9WA0KrM0vWm57XhrIMf5FGiN8D0l7fn+bpUeBSU7dCgPV2oX4zHAsAXyHFGcQ==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.39.0.tgz", + "integrity": "sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.35.0.tgz", - "integrity": "sha512-2mpHCeRuD1u/2kruUiHSsnjWtHjqVbzhBkNVQ1aVD63CcexKVcQGwJ2g5VphOd84GvxfSvnnlEyBtQCE5hxVVw==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.39.0.tgz", + "integrity": "sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.35.0.tgz", - "integrity": "sha512-mrA0v3QMy6ZSvEuLs0dMxcO2LnaCONs1Z73GUDBHWbY8tFFocM6yl7YyMu7rz4zS81NDSqhrUuolyZXGi8TEqg==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.39.0.tgz", + "integrity": "sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.35.0.tgz", - "integrity": "sha512-DnYhhzcvTAKNexIql8pFajr0PiDGrIsBYPRvCKlA5ixSS3uwo/CWNZxB09jhIapEIg945KOzcYEAGGSmTSpk7A==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.39.0.tgz", + "integrity": "sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.35.0.tgz", - "integrity": "sha512-uagpnH2M2g2b5iLsCTZ35CL1FgyuzzJQ8L9VtlJ+FckBXroTwNOaD0z0/UF+k5K3aNQjbm8LIVpxykUOQt1m/A==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.39.0.tgz", + "integrity": "sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.35.0.tgz", - "integrity": "sha512-XQxVOCd6VJeHQA/7YcqyV0/88N6ysSVzRjJ9I9UA/xXpEsjvAgDTgH3wQYz5bmr7SPtVK2TsP2fQ2N9L4ukoUg==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.39.0.tgz", + "integrity": "sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.35.0.tgz", - "integrity": "sha512-5pMT5PzfgwcXEwOaSrqVsz/LvjDZt+vQ8RT/70yhPU06PTuq8WaHhfT1LW+cdD7mW6i/J5/XIkX/1tCAkh1W6g==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.39.0.tgz", + "integrity": "sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.35.0.tgz", - "integrity": "sha512-c+zkcvbhbXF98f4CtEIP1EBA/lCic5xB0lToneZYvMeKu5Kamq3O8gqrxiYYLzlZH6E3Aq+TSW86E4ay8iD8EA==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.39.0.tgz", + "integrity": "sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.35.0.tgz", - "integrity": "sha512-s91fuAHdOwH/Tad2tzTtPX7UZyytHIRR6V4+2IGlV0Cej5rkG0R61SX4l4y9sh0JBibMiploZx3oHKPnQBKe4g==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.39.0.tgz", + "integrity": "sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.39.0.tgz", + "integrity": "sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.35.0.tgz", - "integrity": "sha512-hQRkPQPLYJZYGP+Hj4fR9dDBMIM7zrzJDWFEMPdTnTy95Ljnv0/4w/ixFw3pTBMEuuEuoqtBINYND4M7ujcuQw==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.39.0.tgz", + "integrity": "sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.35.0.tgz", - "integrity": "sha512-Pim1T8rXOri+0HmV4CdKSGrqcBWX0d1HoPnQ0uw0bdp1aP5SdQVNBy8LjYncvnLgu3fnnCt17xjWGd4cqh8/hA==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.39.0.tgz", + "integrity": "sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.35.0.tgz", - "integrity": "sha512-QysqXzYiDvQWfUiTm8XmJNO2zm9yC9P/2Gkrwg2dH9cxotQzunBHYr6jk4SujCTqnfGxduOmQcI7c2ryuW8XVg==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.39.0.tgz", + "integrity": "sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.35.0.tgz", - "integrity": "sha512-OUOlGqPkVJCdJETKOCEf1mw848ZyJ5w50/rZ/3IBQVdLfR5jk/6Sr5m3iO2tdPgwo0x7VcncYuOvMhBWZq8ayg==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.39.0.tgz", + "integrity": "sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.35.0.tgz", - "integrity": "sha512-2/lsgejMrtwQe44glq7AFFHLfJBPafpsTa6JvP2NGef/ifOa4KBoglVf7AKN7EV9o32evBPRqfg96fEHzWo5kw==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.39.0.tgz", + "integrity": "sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.35.0.tgz", - "integrity": "sha512-PIQeY5XDkrOysbQblSW7v3l1MDZzkTEzAfTPkj5VAu3FW8fS4ynyLg2sINp0fp3SjZ8xkRYpLqoKcYqAkhU1dw==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.39.0.tgz", + "integrity": "sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -1661,6 +1748,23 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/@tokenizer/inflate/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/@tokenizer/token": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", @@ -1689,9 +1793,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "dev": true, "license": "MIT" }, @@ -1763,12 +1867,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", - "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "version": "18.19.86", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.86.tgz", + "integrity": "sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ==", "license": "MIT", "dependencies": { - "undici-types": "~6.20.0" + "undici-types": "~5.26.4" } }, "node_modules/@types/node-fetch": { @@ -1783,9 +1887,9 @@ } }, "node_modules/@types/qs": { - "version": "6.9.17", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", - "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "version": "6.9.18", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", + "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", "dev": true, "license": "MIT" }, @@ -1829,21 +1933,21 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.2.tgz", - "integrity": "sha512-adig4SzPLjeQ0Tm+jvsozSGiCliI2ajeURDGHjZ2llnA+A67HihCQ+a3amtPhUakd1GlwHxSRvzOZktbEvhPPg==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.1.tgz", + "integrity": "sha512-ba0rr4Wfvg23vERs3eB+P3lfj2E+2g3lhWcCVukUuhtcdUx5lSIFZlGFEBHKr+3zizDa/TvZTptdNHVZWAkSBg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.18.2", - "@typescript-eslint/type-utils": "8.18.2", - "@typescript-eslint/utils": "8.18.2", - "@typescript-eslint/visitor-keys": "8.18.2", + "@typescript-eslint/scope-manager": "8.29.1", + "@typescript-eslint/type-utils": "8.29.1", + "@typescript-eslint/utils": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1855,20 +1959,20 @@ "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.2.tgz", - "integrity": "sha512-y7tcq4StgxQD4mDr9+Jb26dZ+HTZ/SkfqpXSiqeUXZHxOUyjWDKsmwKhJ0/tApR08DgOhrFAoAhyB80/p3ViuA==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.1.tgz", + "integrity": "sha512-zczrHVEqEaTwh12gWBIJWj8nx+ayDcCJs06yoNMY0kwjMWDM6+kppljY+BxWI06d2Ja+h4+WdufDcwMnnMEWmg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.18.2", - "@typescript-eslint/types": "8.18.2", - "@typescript-eslint/typescript-estree": "8.18.2", - "@typescript-eslint/visitor-keys": "8.18.2", + "@typescript-eslint/scope-manager": "8.29.1", + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/typescript-estree": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1", "debug": "^4.3.4" }, "engines": { @@ -1880,18 +1984,36 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.2.tgz", - "integrity": "sha512-YJFSfbd0CJjy14r/EvWapYgV4R5CHzptssoag2M7y3Ra7XNta6GPAJPPP5KGB9j14viYXyrzRO5GkX7CRfo8/g==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.1.tgz", + "integrity": "sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.18.2", - "@typescript-eslint/visitor-keys": "8.18.2" + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1902,16 +2024,16 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.2.tgz", - "integrity": "sha512-AB/Wr1Lz31bzHfGm/jgbFR0VB0SML/hd2P1yxzKDM48YmP7vbyJNHRExUE/wZsQj2wUCvbWH8poNHFuxLqCTnA==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.1.tgz", + "integrity": "sha512-DkDUSDwZVCYN71xA4wzySqqcZsHKic53A4BLqmrWFFpOpNSoxX233lwGu/2135ymTCR04PoKiEEEvN1gFYg4Tw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.18.2", - "@typescript-eslint/utils": "8.18.2", + "@typescript-eslint/typescript-estree": "8.29.1", + "@typescript-eslint/utils": "8.29.1", "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1922,13 +2044,31 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/@typescript-eslint/types": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.2.tgz", - "integrity": "sha512-Z/zblEPp8cIvmEn6+tPDIHUbRu/0z5lqZ+NvolL5SvXWT5rQy7+Nch83M0++XzO0XrWRFWECgOAyE8bsJTl1GQ==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.1.tgz", + "integrity": "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ==", "dev": true, "license": "MIT", "engines": { @@ -1940,20 +2080,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.2.tgz", - "integrity": "sha512-WXAVt595HjpmlfH4crSdM/1bcsqh+1weFRWIa9XMTx/XHZ9TCKMcr725tLYqWOgzKdeDrqVHxFotrvWcEsk2Tg==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.1.tgz", + "integrity": "sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.18.2", - "@typescript-eslint/visitor-keys": "8.18.2", + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1963,20 +2103,77 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, - "node_modules/@typescript-eslint/utils": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.2.tgz", - "integrity": "sha512-Cr4A0H7DtVIPkauj4sTSXVl+VBWewE9/o40KcF3TV9aqDEOWoXF3/+oRXNby3DYzZeCATvbdksYsGZzplwnK/Q==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.1.tgz", + "integrity": "sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.18.2", - "@typescript-eslint/types": "8.18.2", - "@typescript-eslint/typescript-estree": "8.18.2" + "@typescript-eslint/scope-manager": "8.29.1", + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/typescript-estree": "8.29.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1987,17 +2184,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.2.tgz", - "integrity": "sha512-zORcwn4C3trOWiCqFQP1x6G3xTRyZ1LYydnj51cRnJ6hxBlr/cKPckk+PKPUw/fXmvfKTcw7bwY3w9izgx5jZw==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.1.tgz", + "integrity": "sha512-RGLh5CRaUEf02viP5c1Vh1cMGffQscyHe7HPAzGpfmfflFg1wUz2rYxd+OZqwpeypYvZ8UxSxuIpF++fmOzEcg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.18.2", + "@typescript-eslint/types": "8.29.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -2009,13 +2206,14 @@ } }, "node_modules/@vitest/expect": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.8.tgz", - "integrity": "sha512-Xu6TTIavTvSSS6LZaA3EebWFr6tsoXPetOWNMOlc7LO88QVVBwq2oQWBoDiLCN6YTvNYsGSjqOO8CAdjom5DCQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.1.tgz", + "integrity": "sha512-q/zjrW9lgynctNbwvFtQkGK9+vvHA5UzVi2V8APrp1C6fG6/MuYYkmlx4FubuqLycCeSdHD5aadWfua/Vr0EUA==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.8", - "@vitest/utils": "3.0.8", + "@vitest/spy": "3.1.1", + "@vitest/utils": "3.1.1", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" }, @@ -2024,12 +2222,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.8.tgz", - "integrity": "sha512-n3LjS7fcW1BCoF+zWZxG7/5XvuYH+lsFg+BDwwAz0arIwHQJFUEsKBQ0BLU49fCxuM/2HSeBPHQD8WjgrxMfow==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.1.tgz", + "integrity": "sha512-bmpJJm7Y7i9BBELlLuuM1J1Q6EQ6K5Ye4wcyOpOMXMcePYKSIYlpcrCm4l/O6ja4VJA5G2aMJiuZkZdnxlC3SA==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.8", + "@vitest/spy": "3.1.1", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -2050,10 +2249,11 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.8.tgz", - "integrity": "sha512-BNqwbEyitFhzYMYHUVbIvepOyeQOSFA/NeJMIP9enMntkkxLgOcgABH6fjyXG85ipTgvero6noreavGIqfJcIg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.1.tgz", + "integrity": "sha512-dg0CIzNx+hMMYfNmSqJlLSXEmnNhMswcn3sXO7Tpldr0LiGmg3eXdLLhwkv2ZqgHb/d5xg5F7ezNFRA1fA13yA==", "dev": true, + "license": "MIT", "dependencies": { "tinyrainbow": "^2.0.0" }, @@ -2062,12 +2262,13 @@ } }, "node_modules/@vitest/runner": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.8.tgz", - "integrity": "sha512-c7UUw6gEcOzI8fih+uaAXS5DwjlBaCJUo7KJ4VvJcjL95+DSR1kova2hFuRt3w41KZEFcOEiq098KkyrjXeM5w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.1.tgz", + "integrity": "sha512-X/d46qzJuEDO8ueyjtKfxffiXraPRfmYasoC4i5+mlLEJ10UvPb0XH5M9C3gWuxd7BAQhpK42cJgJtq53YnWVA==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/utils": "3.0.8", + "@vitest/utils": "3.1.1", "pathe": "^2.0.3" }, "funding": { @@ -2075,12 +2276,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.8.tgz", - "integrity": "sha512-x8IlMGSEMugakInj44nUrLSILh/zy1f2/BgH0UeHpNyOocG18M9CWVIFBaXPt8TrqVZWmcPjwfG/ht5tnpba8A==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.1.tgz", + "integrity": "sha512-bByMwaVWe/+1WDf9exFxWWgAixelSdiwo2p33tpqIlM14vW7PRV5ppayVXtfycqze4Qhtwag5sVhX400MLBOOw==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.8", + "@vitest/pretty-format": "3.1.1", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -2089,10 +2291,11 @@ } }, "node_modules/@vitest/spy": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.8.tgz", - "integrity": "sha512-MR+PzJa+22vFKYb934CejhR4BeRpMSoxkvNoDit68GQxRLSf11aT6CTj3XaqUU9rxgWJFnqicN/wxw6yBRkI1Q==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.1.tgz", + "integrity": "sha512-+EmrUOOXbKzLkTDwlsc/xrwOlPDXyVk3Z6P6K4oiCndxz7YLpp/0R0UsWVOKT0IXWjjBJuSMk6D27qipaupcvQ==", "dev": true, + "license": "MIT", "dependencies": { "tinyspy": "^3.0.2" }, @@ -2101,12 +2304,13 @@ } }, "node_modules/@vitest/utils": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.8.tgz", - "integrity": "sha512-nkBC3aEhfX2PdtQI/QwAWp8qZWwzASsU4Npbcd5RdMPBSSLCpkZp52P3xku3s3uA0HIEhGvEcF8rNkBsz9dQ4Q==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.1.tgz", + "integrity": "sha512-1XIjflyaU2k3HMArJ50bwSh3wKWPD6Q47wz/NUSmRV0zNywPc4w79ARjg/i/aNINHwA+mIALhUVqD9/aUvZNgg==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.8", + "@vitest/pretty-format": "3.1.1", "loupe": "^3.1.3", "tinyrainbow": "^2.0.0" }, @@ -2151,9 +2355,9 @@ } }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, "license": "MIT", "bin": { @@ -2183,9 +2387,9 @@ } }, "node_modules/agentkeepalive": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", - "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", "license": "MIT", "dependencies": { "humanize-ms": "^1.2.1" @@ -2278,6 +2482,18 @@ "type-fest": "^4.0.0" } }, + "node_modules/apify/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2330,18 +2546,19 @@ } }, "node_modules/array.prototype.findlastindex": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", - "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", + "es-abstract": "^1.23.9", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -2415,10 +2632,21 @@ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" } }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/async-retry": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", @@ -2528,12 +2756,14 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, "node_modules/braces": { @@ -2595,6 +2825,7 @@ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -2646,9 +2877,9 @@ } }, "node_modules/call-bind-apply-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", - "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2659,13 +2890,13 @@ } }, "node_modules/call-bound": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", - "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "get-intrinsic": "^1.2.6" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -2708,6 +2939,7 @@ "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", "dev": true, + "license": "MIT", "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", @@ -2741,6 +2973,7 @@ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 16" } @@ -2981,20 +3214,13 @@ } }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "ms": "^2.1.1" } }, "node_modules/decompress-response": { @@ -3029,6 +3255,7 @@ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -3263,9 +3490,9 @@ } }, "node_modules/es-abstract": { - "version": "1.23.8", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.8.tgz", - "integrity": "sha512-lfab8IzDn6EpI1ibZakcgS6WsfEBiB+43cuJo+wgylx1xKXf+Sp+YR3vFuQwC/u3sxYwV8Cxe3B0DpVUu/WiJQ==", + "version": "1.23.9", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", + "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", "dev": true, "license": "MIT", "dependencies": { @@ -3280,10 +3507,11 @@ "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", + "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.2.6", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", @@ -3304,11 +3532,12 @@ "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", - "own-keys": "^1.0.0", + "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", @@ -3348,12 +3577,13 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -3363,28 +3593,31 @@ } }, "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", - "dev": true, + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.4", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" } }, "node_modules/es-shim-unscopables": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", - "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", "dev": true, "license": "MIT", "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/es-to-primitive": { @@ -3406,9 +3639,9 @@ } }, "node_modules/esbuild": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", - "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", + "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3419,30 +3652,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.23.1", - "@esbuild/android-arm": "0.23.1", - "@esbuild/android-arm64": "0.23.1", - "@esbuild/android-x64": "0.23.1", - "@esbuild/darwin-arm64": "0.23.1", - "@esbuild/darwin-x64": "0.23.1", - "@esbuild/freebsd-arm64": "0.23.1", - "@esbuild/freebsd-x64": "0.23.1", - "@esbuild/linux-arm": "0.23.1", - "@esbuild/linux-arm64": "0.23.1", - "@esbuild/linux-ia32": "0.23.1", - "@esbuild/linux-loong64": "0.23.1", - "@esbuild/linux-mips64el": "0.23.1", - "@esbuild/linux-ppc64": "0.23.1", - "@esbuild/linux-riscv64": "0.23.1", - "@esbuild/linux-s390x": "0.23.1", - "@esbuild/linux-x64": "0.23.1", - "@esbuild/netbsd-x64": "0.23.1", - "@esbuild/openbsd-arm64": "0.23.1", - "@esbuild/openbsd-x64": "0.23.1", - "@esbuild/sunos-x64": "0.23.1", - "@esbuild/win32-arm64": "0.23.1", - "@esbuild/win32-ia32": "0.23.1", - "@esbuild/win32-x64": "0.23.1" + "@esbuild/aix-ppc64": "0.25.2", + "@esbuild/android-arm": "0.25.2", + "@esbuild/android-arm64": "0.25.2", + "@esbuild/android-x64": "0.25.2", + "@esbuild/darwin-arm64": "0.25.2", + "@esbuild/darwin-x64": "0.25.2", + "@esbuild/freebsd-arm64": "0.25.2", + "@esbuild/freebsd-x64": "0.25.2", + "@esbuild/linux-arm": "0.25.2", + "@esbuild/linux-arm64": "0.25.2", + "@esbuild/linux-ia32": "0.25.2", + "@esbuild/linux-loong64": "0.25.2", + "@esbuild/linux-mips64el": "0.25.2", + "@esbuild/linux-ppc64": "0.25.2", + "@esbuild/linux-riscv64": "0.25.2", + "@esbuild/linux-s390x": "0.25.2", + "@esbuild/linux-x64": "0.25.2", + "@esbuild/netbsd-arm64": "0.25.2", + "@esbuild/netbsd-x64": "0.25.2", + "@esbuild/openbsd-arm64": "0.25.2", + "@esbuild/openbsd-x64": "0.25.2", + "@esbuild/sunos-x64": "0.25.2", + "@esbuild/win32-arm64": "0.25.2", + "@esbuild/win32-ia32": "0.25.2", + "@esbuild/win32-x64": "0.25.2" } }, "node_modules/escalade": { @@ -3474,22 +3708,23 @@ } }, "node_modules/eslint": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", - "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.24.0.tgz", + "integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.9.0", - "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.17.0", - "@eslint/plugin-kit": "^0.2.3", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.0", + "@eslint/core": "^0.12.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.24.0", + "@eslint/plugin-kit": "^0.2.7", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.1", + "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", @@ -3497,7 +3732,7 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.2.0", + "eslint-scope": "^8.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", @@ -3553,16 +3788,6 @@ "eslint-plugin-import": "^2.25.2" } }, - "node_modules/eslint-config-airbnb-base/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/eslint-import-resolver-node": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", @@ -3575,16 +3800,6 @@ "resolve": "^1.22.4" } }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, "node_modules/eslint-module-utils": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", @@ -3603,16 +3818,6 @@ } } }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, "node_modules/eslint-plugin-import": { "version": "2.31.0", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", @@ -3647,54 +3852,20 @@ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, - "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/eslint-plugin-simple-import-sort": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-12.1.1.tgz", + "integrity": "sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==", "dev": true, "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "peerDependencies": { + "eslint": ">=5.0.0" } }, "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -3738,15 +3909,22 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/eslint/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/eslint/node_modules/json-schema-traverse": { @@ -3756,19 +3934,6 @@ "dev": true, "license": "MIT" }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/espree": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", @@ -3828,6 +3993,7 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" } @@ -3877,31 +4043,32 @@ } }, "node_modules/eventsource": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.2.tgz", - "integrity": "sha512-YolzkJNxsTL3tCJMWFxpxtG2sCjbZ4LQUBUrkdaJK0ub0p6lmJt+2+1SwhKjLc652lpH9L/79Ptez972H9tphw==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.6.tgz", + "integrity": "sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==", "license": "MIT", "dependencies": { - "eventsource-parser": "^3.0.0" + "eventsource-parser": "^3.0.1" }, "engines": { "node": ">=18.0.0" } }, "node_modules/eventsource-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.0.tgz", - "integrity": "sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz", + "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==", "license": "MIT", "engines": { "node": ">=18.0.0" } }, "node_modules/expect-type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.0.tgz", - "integrity": "sha512-80F22aiJ3GLyVnS/B3HzgR6RelZVumzj9jkL0Rhz4h0xYbNW9PjlQz5h3J/SShErbXBc295vseR4/MIbVmUbeA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", + "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=12.0.0" } @@ -3989,9 +4156,9 @@ "license": "MIT" }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "license": "MIT", "dependencies": { @@ -3999,7 +4166,7 @@ "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -4033,15 +4200,25 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", - "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], "license": "BSD-3-Clause" }, "node_modules/fastq": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", - "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "dev": true, "license": "ISC", "dependencies": { @@ -4163,9 +4340,9 @@ } }, "node_modules/flatted": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", - "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, "license": "ISC" }, @@ -4190,23 +4367,30 @@ } }, "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, "license": "MIT", "dependencies": { - "is-callable": "^1.1.3" + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" }, "engines": { @@ -4214,13 +4398,11 @@ } }, "node_modules/form-data-encoder": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.0.2.tgz", - "integrity": "sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==", - "license": "MIT", - "engines": { - "node": ">= 18" - } + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "dev": true, + "license": "MIT" }, "node_modules/formdata-node": { "version": "4.4.1", @@ -4340,21 +4522,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", - "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "dunder-proto": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", + "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "math-intrinsics": "^1.0.0" + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -4363,6 +4545,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", @@ -4398,9 +4593,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", - "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", + "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", "dev": true, "license": "MIT", "dependencies": { @@ -4424,9 +4619,9 @@ } }, "node_modules/globals": { - "version": "15.14.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", - "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", "dev": true, "license": "MIT", "engines": { @@ -4578,6 +4773,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/got/node_modules/form-data-encoder": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.0.2.tgz", + "integrity": "sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -4659,7 +4863,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -4816,9 +5019,9 @@ } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4891,13 +5094,17 @@ } }, "node_modules/is-async-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", - "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", "dev": true, "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -4923,13 +5130,13 @@ } }, "node_modules/is-boolean-object": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.1.tgz", - "integrity": "sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", + "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" }, "engines": { @@ -5030,13 +5237,16 @@ } }, "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", "dev": true, "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -5238,13 +5448,13 @@ } }, "node_modules/is-weakref": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.0.tgz", - "integrity": "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.2" + "call-bound": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -5388,6 +5598,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", "license": "MIT" }, "node_modules/lodash.merge": { @@ -5401,7 +5612,8 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lowercase-keys": { "version": "3.0.0", @@ -5420,6 +5632,7 @@ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } @@ -5535,18 +5748,16 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "*" } }, "node_modules/minimist": { @@ -5565,9 +5776,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.9.tgz", - "integrity": "sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -5575,6 +5786,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -5679,9 +5891,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -5722,15 +5934,16 @@ } }, "node_modules/object.entries": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", - "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "es-object-atoms": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -6001,13 +6214,15 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/pathval": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 14.16" } @@ -6066,9 +6281,9 @@ } }, "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", "dev": true, "license": "MIT", "engines": { @@ -6094,6 +6309,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -6247,19 +6463,19 @@ } }, "node_modules/reflect.getprototypeof": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.9.tgz", - "integrity": "sha512-r0Ay04Snci87djAsI4U+WNRcSw5S4pOH7qFjd/veA5gC7TbqESR3tcj28ia95L/fYUDw11JKP7uqUKUAfVvV5Q==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", - "dunder-proto": "^1.0.1", - "es-abstract": "^1.23.6", + "es-abstract": "^1.23.9", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "gopd": "^1.2.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" }, "engines": { @@ -6270,15 +6486,17 @@ } }, "node_modules/regexp.prototype.flags": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", - "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", "set-function-name": "^2.0.2" }, "engines": { @@ -6369,9 +6587,9 @@ } }, "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, "license": "MIT", "engines": { @@ -6389,12 +6607,13 @@ } }, "node_modules/rollup": { - "version": "4.35.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.35.0.tgz", - "integrity": "sha512-kg6oI4g+vc41vePJyO6dHt/yl0Rz3Thv0kJeVQ3D1kS3E5XSuKbPc29G4IpT/Kv1KQwgHVcN+HtyS+HYLNSvQg==", + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.39.0.tgz", + "integrity": "sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==", "dev": true, + "license": "MIT", "dependencies": { - "@types/estree": "1.0.6" + "@types/estree": "1.0.7" }, "bin": { "rollup": "dist/bin/rollup" @@ -6404,25 +6623,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.35.0", - "@rollup/rollup-android-arm64": "4.35.0", - "@rollup/rollup-darwin-arm64": "4.35.0", - "@rollup/rollup-darwin-x64": "4.35.0", - "@rollup/rollup-freebsd-arm64": "4.35.0", - "@rollup/rollup-freebsd-x64": "4.35.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.35.0", - "@rollup/rollup-linux-arm-musleabihf": "4.35.0", - "@rollup/rollup-linux-arm64-gnu": "4.35.0", - "@rollup/rollup-linux-arm64-musl": "4.35.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.35.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.35.0", - "@rollup/rollup-linux-riscv64-gnu": "4.35.0", - "@rollup/rollup-linux-s390x-gnu": "4.35.0", - "@rollup/rollup-linux-x64-gnu": "4.35.0", - "@rollup/rollup-linux-x64-musl": "4.35.0", - "@rollup/rollup-win32-arm64-msvc": "4.35.0", - "@rollup/rollup-win32-ia32-msvc": "4.35.0", - "@rollup/rollup-win32-x64-msvc": "4.35.0", + "@rollup/rollup-android-arm-eabi": "4.39.0", + "@rollup/rollup-android-arm64": "4.39.0", + "@rollup/rollup-darwin-arm64": "4.39.0", + "@rollup/rollup-darwin-x64": "4.39.0", + "@rollup/rollup-freebsd-arm64": "4.39.0", + "@rollup/rollup-freebsd-x64": "4.39.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.39.0", + "@rollup/rollup-linux-arm-musleabihf": "4.39.0", + "@rollup/rollup-linux-arm64-gnu": "4.39.0", + "@rollup/rollup-linux-arm64-musl": "4.39.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.39.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.39.0", + "@rollup/rollup-linux-riscv64-gnu": "4.39.0", + "@rollup/rollup-linux-riscv64-musl": "4.39.0", + "@rollup/rollup-linux-s390x-gnu": "4.39.0", + "@rollup/rollup-linux-x64-gnu": "4.39.0", + "@rollup/rollup-linux-x64-musl": "4.39.0", + "@rollup/rollup-win32-arm64-msvc": "4.39.0", + "@rollup/rollup-win32-ia32-msvc": "4.39.0", + "@rollup/rollup-win32-x64-msvc": "4.39.0", "fsevents": "~2.3.2" } }, @@ -6442,6 +6662,23 @@ "node": ">= 18" } }, + "node_modules/router/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/router/node_modules/path-to-regexp": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", @@ -6563,15 +6800,13 @@ "license": "ISC" }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" } }, "node_modules/send": { @@ -6671,6 +6906,21 @@ "node": ">= 0.4" } }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -6774,7 +7024,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/signal-exit": { "version": "3.0.7", @@ -6787,6 +7038,7 @@ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -6807,7 +7059,8 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/statuses": { "version": "2.0.1", @@ -6819,10 +7072,11 @@ } }, "node_modules/std-env": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.1.tgz", - "integrity": "sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==", - "dev": true + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" }, "node_modules/stream-chain": { "version": "2.2.5", @@ -6980,9 +7234,9 @@ "license": "MIT" }, "node_modules/tiktoken": { - "version": "1.0.18", - "resolved": "https://registry.npmjs.org/tiktoken/-/tiktoken-1.0.18.tgz", - "integrity": "sha512-DXJesdYwmBHtkmz1sji+UMZ4AOEE8F7Uw/PS/uy0XfkKOzZC4vXkYXHMYyDT+grdflvF4bggtPt9cYaqOMslBw==", + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/tiktoken/-/tiktoken-1.0.20.tgz", + "integrity": "sha512-zVIpXp84kth/Ni2me1uYlJgl2RZ2EjxwDaWLeDY/s6fZiyO9n1QoTOM5P7ZSYfToPvAvwYNMbg5LETVYVKyzfQ==", "dev": true, "license": "MIT" }, @@ -6990,19 +7244,22 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/tinyexec": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/tinypool": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", "dev": true, + "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" } @@ -7012,6 +7269,7 @@ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -7021,6 +7279,7 @@ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -7102,16 +7361,16 @@ "license": "MIT" }, "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/tsconfig-paths": { @@ -7147,13 +7406,13 @@ "license": "0BSD" }, "node_modules/tsx": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz", - "integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==", + "version": "4.19.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.3.tgz", + "integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "~0.23.0", + "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "bin": { @@ -7283,9 +7542,9 @@ } }, "node_modules/typescript": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", - "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -7297,15 +7556,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.18.2.tgz", - "integrity": "sha512-KuXezG6jHkvC3MvizeXgupZzaG5wjhU3yE8E7e6viOvAvD9xAWYp8/vy0WULTGe9DYDWcQu7aW03YIV3mSitrQ==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.29.1.tgz", + "integrity": "sha512-f8cDkvndhbQMPcysk6CUSGBWV+g1utqdn71P5YKwMumVMOG/5k7cHq0KyG4O52nB0oKS4aN2Tp5+wB4APJGC+w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.18.2", - "@typescript-eslint/parser": "8.18.2", - "@typescript-eslint/utils": "8.18.2" + "@typescript-eslint/eslint-plugin": "8.29.1", + "@typescript-eslint/parser": "8.29.1", + "@typescript-eslint/utils": "8.29.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7316,7 +7575,7 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/uint8array-extras": { @@ -7351,9 +7610,9 @@ } }, "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "license": "MIT" }, "node_modules/universalify": { @@ -7442,10 +7701,11 @@ } }, "node_modules/vite": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.1.tgz", - "integrity": "sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q==", + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.5.tgz", + "integrity": "sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.25.0", "postcss": "^8.5.3", @@ -7513,10 +7773,11 @@ } }, "node_modules/vite-node": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.8.tgz", - "integrity": "sha512-6PhR4H9VGlcwXZ+KWCdMqbtG649xCPZqfI9j2PsK1FcXgEzro5bGHcVKFCTqPLaNKZES8Evqv4LwvZARsq5qlg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.1.tgz", + "integrity": "sha512-V+IxPAE2FvXpTCHXyNem0M+gWm6J7eRyWPR6vYoG/Gl+IscNOjXzztUhimQgTxaAoUoj40Qqimaa0NLIOOAH4w==", "dev": true, + "license": "MIT", "dependencies": { "cac": "^6.7.14", "debug": "^4.4.0", @@ -7534,455 +7795,50 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", - "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", - "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", - "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", - "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", - "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", - "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", - "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", - "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", - "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", - "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", - "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", - "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", - "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", - "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", - "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", - "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", - "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", - "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", - "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", - "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", - "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", - "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", - "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", - "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/esbuild": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", - "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", + "node_modules/vite-node/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" }, "engines": { - "node": ">=18" + "node": ">=6.0" }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.1", - "@esbuild/android-arm": "0.25.1", - "@esbuild/android-arm64": "0.25.1", - "@esbuild/android-x64": "0.25.1", - "@esbuild/darwin-arm64": "0.25.1", - "@esbuild/darwin-x64": "0.25.1", - "@esbuild/freebsd-arm64": "0.25.1", - "@esbuild/freebsd-x64": "0.25.1", - "@esbuild/linux-arm": "0.25.1", - "@esbuild/linux-arm64": "0.25.1", - "@esbuild/linux-ia32": "0.25.1", - "@esbuild/linux-loong64": "0.25.1", - "@esbuild/linux-mips64el": "0.25.1", - "@esbuild/linux-ppc64": "0.25.1", - "@esbuild/linux-riscv64": "0.25.1", - "@esbuild/linux-s390x": "0.25.1", - "@esbuild/linux-x64": "0.25.1", - "@esbuild/netbsd-arm64": "0.25.1", - "@esbuild/netbsd-x64": "0.25.1", - "@esbuild/openbsd-arm64": "0.25.1", - "@esbuild/openbsd-x64": "0.25.1", - "@esbuild/sunos-x64": "0.25.1", - "@esbuild/win32-arm64": "0.25.1", - "@esbuild/win32-ia32": "0.25.1", - "@esbuild/win32-x64": "0.25.1" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/vitest": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.8.tgz", - "integrity": "sha512-dfqAsNqRGUc8hB9OVR2P0w8PZPEckti2+5rdZip0WIz9WW0MnImJ8XiR61QhqLa92EQzKP2uPkzenKOAHyEIbA==", - "dev": true, - "dependencies": { - "@vitest/expect": "3.0.8", - "@vitest/mocker": "3.0.8", - "@vitest/pretty-format": "^3.0.8", - "@vitest/runner": "3.0.8", - "@vitest/snapshot": "3.0.8", - "@vitest/spy": "3.0.8", - "@vitest/utils": "3.0.8", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.1.tgz", + "integrity": "sha512-kiZc/IYmKICeBAZr9DQ5rT7/6bD9G7uqQEki4fxazi1jdVl2mWGzedtBs5s6llz59yQhVb7FFY2MbHzHCnT79Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "3.1.1", + "@vitest/mocker": "3.1.1", + "@vitest/pretty-format": "^3.1.1", + "@vitest/runner": "3.1.1", + "@vitest/snapshot": "3.1.1", + "@vitest/spy": "3.1.1", + "@vitest/utils": "3.1.1", "chai": "^5.2.0", "debug": "^4.4.0", - "expect-type": "^1.1.0", + "expect-type": "^1.2.0", "magic-string": "^0.30.17", "pathe": "^2.0.3", - "std-env": "^3.8.0", + "std-env": "^3.8.1", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", - "vite-node": "3.0.8", + "vite-node": "3.1.1", "why-is-node-running": "^2.3.0" }, "bin": { @@ -7998,8 +7854,8 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.0.8", - "@vitest/ui": "3.0.8", + "@vitest/browser": "3.1.1", + "@vitest/ui": "3.1.1", "happy-dom": "*", "jsdom": "*" }, @@ -8027,6 +7883,24 @@ } } }, + "node_modules/vitest/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/web-streams-polyfill": { "version": "4.0.0-beta.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", @@ -8147,16 +8021,17 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", - "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "for-each": "^0.3.3", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" }, @@ -8172,6 +8047,7 @@ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, + "license": "MIT", "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" @@ -8200,9 +8076,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -8234,17 +8110,19 @@ } }, "node_modules/zod": { - "version": "3.24.1", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", - "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "version": "3.24.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", + "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/zod-to-json-schema": { - "version": "3.24.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.1.tgz", - "integrity": "sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==", + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "license": "ISC", "peerDependencies": { "zod": "^3.24.1" } diff --git a/package.json b/package.json index 13541705..0073acba 100644 --- a/package.json +++ b/package.json @@ -43,28 +43,30 @@ "devDependencies": { "@anthropic-ai/sdk": "^0.33.1", "@anthropic-ai/tokenizer": "^0.0.4", - "@apify/eslint-config": "^0.5.0-beta.2", + "@apify/eslint-config": "^1.0.0", "@apify/tsconfig": "^0.1.0", "@types/express": "^4.0.0", "@types/minimist": "^1.2.5", "dotenv": "^16.4.7", - "eslint": "^9.17.0", + "eslint": "^9.19.0", "eventsource": "^3.0.2", "tsx": "^4.6.2", "typescript": "^5.3.3", - "typescript-eslint": "^8.18.2", + "typescript-eslint": "^8.23.0", "vitest": "^3.0.8" }, "scripts": { "start": "npm run start:dev", "start:prod": "node dist/main.js", "start:dev": "tsx src/main.ts", - "lint": "eslint .", - "lint:fix": "eslint . --fix", - "build": "tsc", - "watch": "tsc --watch", + "lint": "./node_modules/.bin/eslint .", + "lint:fix": "./node_modules/.bin/eslint . --fix", + "build": "tsc -b src", + "build:watch": "tsc -b src -w", "inspector": "npx @modelcontextprotocol/inspector dist/stdio.js", - "test": "vitest run" + "test": "vitest run", + "type-check": "tsc --noEmit", + "clean": "tsc -b src --clean" }, "author": "Apify", "license": "MIT" diff --git a/src/mcp-server.ts b/src/mcp-server.ts index f9d9eb6f..2ccaa4b4 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -95,13 +95,17 @@ export class ApifyMcpServer { this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; const apifyToken = request.params.apifyToken || process.env.APIFY_TOKEN; + + // Validate token if (!apifyToken) { throw new Error('APIFY_TOKEN is required but not set in the environment variables or passed as a parameter.'); } + // Find tool by name or actor full name const tool = Array.from(this.tools.values()) .find((t) => t.tool.name === name || (t.type === 'actor' && (t.tool as ActorTool).actorFullName === name)); if (!tool) { + // TODO: handle errors better, server.sendLoggingMessage ( ) throw new Error(`Unknown tool: ${name}`); } if (!args) { @@ -113,6 +117,7 @@ export class ApifyMcpServer { } try { + // Handle internal tool if (tool.type === 'internal') { const internalTool = tool.tool as HelperTool; const res = await internalTool.call({ @@ -120,17 +125,20 @@ export class ApifyMcpServer { apifyMcpServer: this, mcpServer: this.server, }) as object; - return { - ...res, - }; + + return { ...res }; } + // Handle actor tool if (tool.type === 'actor') { const actorTool = tool.tool as ActorTool; - const items = await callActorGetDataset(actorTool.actorFullName, args, apifyToken as string, { + const callOptions: ActorCallOptions = { memory: actorTool.memoryMbytes, - } as ActorCallOptions); + }; + + const items = await callActorGetDataset(actorTool.actorFullName, args, apifyToken as string, callOptions); + const content = items.map((item) => { const text = JSON.stringify(item).slice(0, ACTOR_OUTPUT_MAX_CHARS_PER_ITEM); return text.length === ACTOR_OUTPUT_MAX_CHARS_PER_ITEM diff --git a/src/tools/helpers.ts b/src/tools/helpers.ts index 3c720a4b..fbe18af4 100644 --- a/src/tools/helpers.ts +++ b/src/tools/helpers.ts @@ -17,12 +17,13 @@ export const addTool: ToolWrap = { type: 'internal', tool: { name: HelperTools.ADD_TOOL, - description: 'Add an Actor to available tools by Actor ID or Actor name. ' - + 'Do not execute the Actor, only add it and list it in available tools. ' + description: 'Add a tool to available tools by Actor ID or Actor name. ' + + 'Do not execute the tool, only add it and list it in available tools. ' + 'Never run the tool without user consent! ' + 'For example, add a tool with username/name when user wants to scrape data from a website.', inputSchema: zodToJsonSchema(AddToolArgsSchema), ajvValidate: ajv.compile(zodToJsonSchema(AddToolArgsSchema)), + // TODO: I don't like that we are passing apifyMcpServer and mcpServer to the tool call: async (toolArgs) => { const { apifyMcpServer, mcpServer, args } = toolArgs; const parsed = AddToolArgsSchema.parse(args); @@ -52,6 +53,7 @@ export const removeTool: ToolWrap = { + 'For example, when user says, I do not need a tool username/name anymore', inputSchema: zodToJsonSchema(RemoveToolArgsSchema), ajvValidate: ajv.compile(zodToJsonSchema(RemoveToolArgsSchema)), + // TODO: I don't like that we are passing apifyMcpServer and mcpServer to the tool call: async (toolArgs) => { const { apifyMcpServer, mcpServer, args } = toolArgs; From 8735e8f99ba2d71db732978a1a5ffaae5dc3f898 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Wed, 9 Apr 2025 22:38:28 +0200 Subject: [PATCH 22/42] fix: lint issue --- package.json | 4 ++-- src/examples/clientSse.ts | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 0073acba..63595afa 100644 --- a/package.json +++ b/package.json @@ -61,8 +61,8 @@ "start:dev": "tsx src/main.ts", "lint": "./node_modules/.bin/eslint .", "lint:fix": "./node_modules/.bin/eslint . --fix", - "build": "tsc -b src", - "build:watch": "tsc -b src -w", + "build": "tsc", + "build:watch": "tsc -w", "inspector": "npx @modelcontextprotocol/inspector dist/stdio.js", "test": "vitest run", "type-check": "tsc --noEmit", diff --git a/src/examples/clientSse.ts b/src/examples/clientSse.ts index 5e2c2d56..4116e529 100644 --- a/src/examples/clientSse.ts +++ b/src/examples/clientSse.ts @@ -13,7 +13,7 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'; import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js'; import dotenv from 'dotenv'; -import { EventSource } from 'eventsource'; +import { EventSource, EventSourceInit } from 'eventsource'; import { actorNameToToolName } from '../tools/utils.js'; @@ -34,8 +34,19 @@ if (!process.env.APIFY_TOKEN) { process.exit(1); } +// Declare EventSource on globalThis if not available (needed for Node.js environment) +declare global { + var EventSource: { + new(url: string, eventSourceInitDict?: EventSourceInit): EventSource; + prototype: EventSource; + CONNECTING: 0; + OPEN: 1; + CLOSED: 2; + }; // eslint-disable-line no-var +} + if (typeof globalThis.EventSource === 'undefined') { - globalThis.EventSource = EventSource as unknown as typeof globalThis.EventSource; + globalThis.EventSource = EventSource; } async function main(): Promise { From 6879923a946536b8ea422e2bed943f1ed021a458 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Fri, 11 Apr 2025 09:13:51 +0200 Subject: [PATCH 23/42] fix: rename server to ActorsMcpServer --- package.json | 2 +- src/actor/server.ts | 4 ++-- src/examples/clientSse.ts | 1 - src/index.ts | 5 +++-- src/main.ts | 4 ++-- src/mcp-server.ts | 2 +- src/stdio.ts | 4 ++-- src/types.ts | 4 ++-- tests/actor-server-test.ts | 6 +++--- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 63595afa..87acf5ef 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "engines": { "node": ">=18.0.0" }, - "main": "dist/stdio.js", + "main": "dist/index.js", "bin": { "actors-mcp-server": "./dist/stdio.js" }, diff --git a/src/actor/server.ts b/src/actor/server.ts index 179fb30c..85563ff5 100644 --- a/src/actor/server.ts +++ b/src/actor/server.ts @@ -9,12 +9,12 @@ import express from 'express'; import log from '@apify/log'; import { HEADER_READINESS_PROBE, Routes } from './const.js'; -import { type ApifyMcpServer } from '../mcp-server.js'; +import { type ActorsMcpServer } from '../mcp-server.js'; import { getActorRunData, processParamsGetTools } from './utils.js'; export function createExpressApp( host: string, - mcpServer: ApifyMcpServer, + mcpServer: ActorsMcpServer, ): express.Express { const HELP_MESSAGE = `Connect to the server with GET request to ${host}/sse?token=YOUR-APIFY-TOKEN` + ` and then send POST requests to ${host}/message?token=YOUR-APIFY-TOKEN`; diff --git a/src/examples/clientSse.ts b/src/examples/clientSse.ts index 4116e529..c77d1fa6 100644 --- a/src/examples/clientSse.ts +++ b/src/examples/clientSse.ts @@ -106,7 +106,6 @@ async function main(): Promise { } catch (error: unknown) { if (error instanceof Error) { console.error('Error:', error.message); - console.error(error.stack); } else { console.error('An unknown error occurred:', error); } diff --git a/src/index.ts b/src/index.ts index 23d35e74..e20c084d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ /* This file provides essential functions and tools for MCP servers, serving as a library. - It should be the only file that is exported from the package. + The ActorsMcpServer should be the only class exported from the package */ -export { ApifyMcpServer } from './mcp-server.js'; +import { ActorsMcpServer } from './mcp-server.js'; +export default ActorsMcpServer; diff --git a/src/main.ts b/src/main.ts index dad56187..ba4d9b4b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -11,7 +11,7 @@ import log from '@apify/log'; import { processInput } from './actor/input.js'; import { createExpressApp } from './actor/server.js'; import type { Input } from './actor/types'; -import { ApifyMcpServer } from './mcp-server.js'; +import { ActorsMcpServer } from './mcp-server.js'; import { actorDefinitionTool, addTool, removeTool, searchTool, callActorGetDataset } from './tools/index.js'; const STANDBY_MODE = Actor.getEnv().metaOrigin === 'STANDBY'; @@ -26,7 +26,7 @@ if (!process.env.APIFY_TOKEN) { process.exit(1); } -const mcpServer = new ApifyMcpServer(); +const mcpServer = new ActorsMcpServer(); const input = processInput((await Actor.getInput>()) ?? ({} as Input)); log.info(`Loaded input: ${JSON.stringify(input)} `); diff --git a/src/mcp-server.ts b/src/mcp-server.ts index 2ccaa4b4..b183d946 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -21,7 +21,7 @@ import type { ActorTool, HelperTool, ToolWrap } from './types.js'; /** * Create Apify MCP server */ -export class ApifyMcpServer { +export class ActorsMcpServer { public server: Server; public tools: Map; diff --git a/src/stdio.ts b/src/stdio.ts index f130e21b..2d53a373 100644 --- a/src/stdio.ts +++ b/src/stdio.ts @@ -18,7 +18,7 @@ import minimist from 'minimist'; import log from '@apify/log'; import { defaults } from './actor/const.js'; -import { ApifyMcpServer } from './mcp-server.js'; +import { ActorsMcpServer } from './mcp-server.js'; import { addTool, removeTool, getActorsAsTools } from './tools/index.js'; // Configure logging, set to ERROR @@ -35,7 +35,7 @@ if (!process.env.APIFY_TOKEN) { } async function main() { - const mcpServer = new ApifyMcpServer(); + const mcpServer = new ActorsMcpServer(); // Initialize tools const tools = await getActorsAsTools(actorList.length ? actorList : defaults.actors); if (enableActorAutoLoading) { diff --git a/src/types.ts b/src/types.ts index c6dfa98d..dc8da045 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,7 +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 { ApifyMcpServer } from './mcp-server.js'; +import type { ActorsMcpServer } from './mcp-server.js'; export interface ISchemaProperties { type: string; @@ -80,7 +80,7 @@ export type InternalToolArgs = { /** Arguments passed to the tool */ args: Record; /** Reference to the Apify MCP server instance */ - apifyMcpServer: ApifyMcpServer; + apifyMcpServer: ActorsMcpServer; /** Reference to the MCP server instance */ mcpServer: Server; } diff --git a/tests/actor-server-test.ts b/tests/actor-server-test.ts index 4dd7ec85..2a1d16ee 100644 --- a/tests/actor-server-test.ts +++ b/tests/actor-server-test.ts @@ -7,17 +7,17 @@ import log from '@apify/log'; import { createExpressApp } from '../src/actor/server.js'; import { HelperTools } from '../src/const.js'; -import { ApifyMcpServer } from '../src/mcp-server.js'; +import { ActorsMcpServer } from '../src/mcp-server.js'; describe('ApifyMcpServer initialization', () => { let app: Express; - let server: ApifyMcpServer; + let server: ActorsMcpServer; let httpServer: HttpServer; const testPort = 7357; const testHost = `http://localhost:${testPort}`; beforeEach(async () => { - server = new ApifyMcpServer(); + server = new ActorsMcpServer(); log.setLevel(log.LEVELS.OFF); // Create express app using the proper server setup From af3eb356a3803cc52a388d7e24d63b60143575e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kopeck=C3=BD?= Date: Fri, 11 Apr 2025 10:39:24 +0200 Subject: [PATCH 24/42] Update src/mcp-server.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jiří Spilka --- src/mcp-server.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mcp-server.ts b/src/mcp-server.ts index b183d946..5124d3c0 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -83,6 +83,7 @@ export class ActorsMcpServer { * @returns {object} - The response object containing the tools. */ this.server.setRequestHandler(ListToolsRequestSchema, async () => { + // TODO if there is actor-mcp as a tool, also list the tools from that Actor const tools = Array.from(this.tools.values()).map((tool) => (tool.tool)); return { tools }; }); From b665a2c8ab5123dd81cf44c92ab5df6bb5062278 Mon Sep 17 00:00:00 2001 From: MQ Date: Fri, 11 Apr 2025 10:41:53 +0200 Subject: [PATCH 25/42] remove double updateTools main.ts call, extend ApifyClient with MCP user agent request interceptor --- src/main.ts | 1 - src/tools/actor.ts | 21 +++------------------ src/tools/mcp-apify-client.ts | 26 ++++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 19 deletions(-) create mode 100644 src/tools/mcp-apify-client.ts diff --git a/src/main.ts b/src/main.ts index ba4d9b4b..8f70bc3f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -35,7 +35,6 @@ if (STANDBY_MODE) { const app = createExpressApp(HOST, mcpServer); log.info('Actor is running in the STANDBY mode.'); const tools = [searchTool, actorDefinitionTool]; - mcpServer.updateTools([searchTool, actorDefinitionTool]); if (input.enableActorAutoLoading) { tools.push(addTool, removeTool); } diff --git a/src/tools/actor.ts b/src/tools/actor.ts index b6223dd5..45af401f 100644 --- a/src/tools/actor.ts +++ b/src/tools/actor.ts @@ -1,14 +1,12 @@ import { Ajv } from 'ajv'; -import type { ApifyClientOptions } from 'apify'; import type { ActorCallOptions } from 'apify-client'; -import { ApifyClient } from 'apify-client'; -import type { AxiosRequestConfig } from 'axios'; import log from '@apify/log'; import type { ToolWrap } from '../types.js'; import { getActorDefinition } from './build.js'; -import { ACTOR_ADDITIONAL_INSTRUCTIONS, ACTOR_MAX_MEMORY_MBYTES, USER_AGENT_ORIGIN } from '../const.js'; +import { ACTOR_ADDITIONAL_INSTRUCTIONS, ACTOR_MAX_MEMORY_MBYTES } from '../const.js'; +import { ApifyClient } from './mcp-apify-client.js'; import { actorNameToToolName, addEnumsToDescriptionsWithExamples, @@ -18,18 +16,6 @@ import { shortenProperties, } from './utils.js'; -/** - * Adds a User-Agent header to the request config. - * @param config - * @private - */ -export function addUserAgent(config: AxiosRequestConfig): AxiosRequestConfig { - const updatedConfig = { ...config }; - updatedConfig.headers = updatedConfig.headers ?? {}; - updatedConfig.headers['User-Agent'] = `${updatedConfig.headers['User-Agent'] ?? ''}; ${USER_AGENT_ORIGIN}`; - return updatedConfig; -} - /** * Calls an Apify actor and retrieves the dataset items. * @@ -54,8 +40,7 @@ export async function callActorGetDataset( try { log.info(`Calling Actor ${name} with input: ${JSON.stringify(input)}`); - const options: ApifyClientOptions = { requestInterceptors: [addUserAgent] }; - const client = new ApifyClient({ ...options, token: apifyToken }); + const client = new ApifyClient({ token: apifyToken }); const actorClient = client.actor(name); const results = await actorClient.call(input, callOptions); diff --git a/src/tools/mcp-apify-client.ts b/src/tools/mcp-apify-client.ts new file mode 100644 index 00000000..d7224aee --- /dev/null +++ b/src/tools/mcp-apify-client.ts @@ -0,0 +1,26 @@ +import type { ApifyClientOptions } from 'apify'; +import { ApifyClient as _ApifyClient } from 'apify-client'; +import type { AxiosRequestConfig } from 'axios'; + +import { USER_AGENT_ORIGIN } from '../const.js'; + +/** + * Adds a User-Agent header to the request config. + * @param config + * @private + */ +function addUserAgent(config: AxiosRequestConfig): AxiosRequestConfig { + const updatedConfig = { ...config }; + updatedConfig.headers = updatedConfig.headers ?? {}; + updatedConfig.headers['User-Agent'] = `${updatedConfig.headers['User-Agent'] ?? ''}; ${USER_AGENT_ORIGIN}`; + return updatedConfig; +} + +export class ApifyClient extends _ApifyClient { + constructor(options: ApifyClientOptions) { + super({ + ...options, + requestInterceptors: [addUserAgent], + }); + } +} From 90b87f8edc59a4d3c375ba6344073bafe49a9454 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Fri, 11 Apr 2025 13:38:01 +0200 Subject: [PATCH 26/42] fix: deploy to npm --- .github/workflows/check.yaml | 1 + .github/workflows/pre_release.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 16648e60..97f3d412 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -9,6 +9,7 @@ on: push: branches: - master + - 'feat/decouple' tags-ignore: - "**" # Ignore all tags to prevent duplicate builds when tags are pushed. diff --git a/.github/workflows/pre_release.yaml b/.github/workflows/pre_release.yaml index 321ab834..3d3e8f28 100644 --- a/.github/workflows/pre_release.yaml +++ b/.github/workflows/pre_release.yaml @@ -5,6 +5,7 @@ on: push: branches: - master + - 'feat/decouple' tags-ignore: - "**" # Ignore all tags to prevent duplicate builds when tags are pushed. From 2573e7489961632dcfbe4b4b990d9d42cce2c38f Mon Sep 17 00:00:00 2001 From: MQ Date: Fri, 11 Apr 2025 13:55:48 +0200 Subject: [PATCH 27/42] add default tools and load tools from url MCP server methods --- src/mcp-server.ts | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/mcp-server.ts b/src/mcp-server.ts index 5124d3c0..723dc034 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -15,8 +15,11 @@ import { SERVER_NAME, SERVER_VERSION, } from './const.js'; -import { actorDefinitionTool, callActorGetDataset, searchTool } from './tools/index.js'; +import { actorDefinitionTool, callActorGetDataset, getActorsAsTools, searchTool } from './tools/index.js'; import type { ActorTool, HelperTool, ToolWrap } from './types.js'; +import { defaults } from './actor/const.js'; +import { actorNameToToolName } from './tools/utils.js'; +import { processParamsGetTools } from './actor/utils.js'; /** * Create Apify MCP server @@ -45,6 +48,25 @@ export class ActorsMcpServer { this.updateTools([searchTool, actorDefinitionTool]); } + /** + * Loads missing default tools. + */ + public async loadDefaultTools() { + const missingDefaultTools = defaults.actors.filter(name => !this.tools.has(actorNameToToolName(name))); + const tools = await getActorsAsTools(missingDefaultTools); + if (tools.length > 0) this.updateTools(tools); + } + + /** + * Loads tools from URL params. + * + * Used primarily for SSE. + */ + public async loadToolsFromUrl(url: string) { + const tools = await processParamsGetTools(url); + if (tools.length > 0) this.updateTools(tools); + } + /** * Upsert new tools. * @param tools - Array of tool wrappers. @@ -83,7 +105,7 @@ export class ActorsMcpServer { * @returns {object} - The response object containing the tools. */ this.server.setRequestHandler(ListToolsRequestSchema, async () => { - // TODO if there is actor-mcp as a tool, also list the tools from that Actor + // TODO if there is actor-mcp as a tool, also list the tools from that Actor const tools = Array.from(this.tools.values()).map((tool) => (tool.tool)); return { tools }; }); From 52ccf0c5b8dfbd3dd6e5c17f3fa7f0ed0d2eef01 Mon Sep 17 00:00:00 2001 From: MQ Date: Fri, 11 Apr 2025 14:03:53 +0200 Subject: [PATCH 28/42] fix wf name --- .github/workflows/check.yaml | 2 +- .github/workflows/pre_release.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 97f3d412..a12a6c98 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -15,7 +15,7 @@ on: jobs: lint_and_test: - name: Build & Test + name: Code checks runs-on: ubuntu-latest steps: diff --git a/.github/workflows/pre_release.yaml b/.github/workflows/pre_release.yaml index 3d3e8f28..849c3ba8 100644 --- a/.github/workflows/pre_release.yaml +++ b/.github/workflows/pre_release.yaml @@ -37,7 +37,7 @@ jobs: with: ref: ${{ github.ref }} repo-token: ${{ secrets.GITHUB_TOKEN }} - check-regexp: (Build & Test .*|Lint|Docs build) + check-regexp: (Code checks) wait-interval: 5 update_changelog: From b0b558e6b984c47acad6ae9cdcadd70371ceeafe Mon Sep 17 00:00:00 2001 From: MQ Date: Fri, 11 Apr 2025 15:03:07 +0200 Subject: [PATCH 29/42] bump beta version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 34159360..af1da43b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@apify/actors-mcp-server", - "version": "0.1.21", + "version": "0.1.22", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@apify/actors-mcp-server", - "version": "0.1.21", + "version": "0.1.22", "license": "MIT", "dependencies": { "@apify/log": "^2.5.16", diff --git a/package.json b/package.json index 87acf5ef..d85fc9f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@apify/actors-mcp-server", - "version": "0.1.21", + "version": "0.1.22", "type": "module", "description": "Model Context Protocol Server for Apify", "engines": { From d8e7294f7b6e16a16d92fbcd637952a4f058fb06 Mon Sep 17 00:00:00 2001 From: Apify Release Bot Date: Fri, 11 Apr 2025 13:04:08 +0000 Subject: [PATCH 30/42] chore(release): Update changelog and package version [skip ci] --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b3c40c4..78e6d824 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ All notable changes to this project will be documented in this file. + +## 0.1.22 - **not yet released** + +### 🐛 Bug Fixes + +- Clearly separate Actor related code and rest of the codebase. Only export ApifyMcpServer from the package ([ce711e9](https://github.com/apify/actors-mcp-server/commit/ce711e9096ce2f51f6c3f78c0966a27ef73ad945)) +- Update packages, add TODOs ([b78fb6b](https://github.com/apify/actors-mcp-server/commit/b78fb6b1caadb2ec886eb0b11702bda10b151fdb)) +- Lint issue ([8735e8f](https://github.com/apify/actors-mcp-server/commit/8735e8f99ba2d71db732978a1a5ffaae5dc3f898)) +- Rename server to ActorsMcpServer ([6879923](https://github.com/apify/actors-mcp-server/commit/6879923a946536b8ea422e2bed943f1ed021a458)) +- Deploy to npm ([90b87f8](https://github.com/apify/actors-mcp-server/commit/90b87f8edc59a4d3c375ba6344073bafe49a9454)) + + + ## [0.1.21](https://github.com/apify/actors-mcp-server/releases/tag/v0.1.21) (2025-03-27) ### 🐛 Bug Fixes From 0a836c919d9cdef07d89fbeaee7d98c71ca38fa8 Mon Sep 17 00:00:00 2001 From: MQ Date: Mon, 14 Apr 2025 10:26:15 +0200 Subject: [PATCH 31/42] move defaults from actors/ to consts in src/ --- src/actor/const.ts | 10 ---------- src/const.ts | 10 ++++++++++ src/mcp-server.ts | 2 +- src/stdio.ts | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/actor/const.ts b/src/actor/const.ts index b88eb38c..538e7784 100644 --- a/src/actor/const.ts +++ b/src/actor/const.ts @@ -3,16 +3,6 @@ */ export const HEADER_READINESS_PROBE = 'x-apify-container-server-readiness-probe'; -export const defaults = { - actors: [ - 'apify/instagram-scraper', - 'apify/rag-web-browser', - 'lukaskrivka/google-maps-with-contact-details', - ], - enableActorAutoLoading: false, - maxMemoryMbytes: 4096, -}; - export enum Routes { ROOT = '/', SSE = '/sse', diff --git a/src/const.ts b/src/const.ts index 491884ca..7758896c 100644 --- a/src/const.ts +++ b/src/const.ts @@ -27,3 +27,13 @@ export enum HelperTools { REMOVE_TOOL = 'remove-tool', GET_TOOL_DETAILS = 'get-tool-details', } + +export const defaults = { + actors: [ + 'apify/instagram-scraper', + 'apify/rag-web-browser', + 'lukaskrivka/google-maps-with-contact-details', + ], + enableActorAutoLoading: false, + maxMemoryMbytes: 4096, +}; diff --git a/src/mcp-server.ts b/src/mcp-server.ts index 723dc034..bb54b37a 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -17,7 +17,7 @@ import { } from './const.js'; import { actorDefinitionTool, callActorGetDataset, getActorsAsTools, searchTool } from './tools/index.js'; import type { ActorTool, HelperTool, ToolWrap } from './types.js'; -import { defaults } from './actor/const.js'; +import { defaults } from './const.js'; import { actorNameToToolName } from './tools/utils.js'; import { processParamsGetTools } from './actor/utils.js'; diff --git a/src/stdio.ts b/src/stdio.ts index 2d53a373..d04525a7 100644 --- a/src/stdio.ts +++ b/src/stdio.ts @@ -17,7 +17,7 @@ import minimist from 'minimist'; import log from '@apify/log'; -import { defaults } from './actor/const.js'; +import { defaults } from './const.js'; import { ActorsMcpServer } from './mcp-server.js'; import { addTool, removeTool, getActorsAsTools } from './tools/index.js'; From 581f096030018aa9f1052151c5b628d9b186193b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kopeck=C3=BD?= Date: Tue, 15 Apr 2025 09:22:22 +0200 Subject: [PATCH 32/42] feat: local apify api base url (#71) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: document input schema processing (#58) document Actor input schema processing in README * add VS Code instructions to README (#62) * Add vs code instructions --------- Co-authored-by: mbaiza27 Co-authored-by: Jiří Spilka * ci: run prerelase manually (#70) run prerelase manually * get apify api url base from env var --------- Co-authored-by: Marc Baiza <43151891+mbaiza27@users.noreply.github.com> Co-authored-by: mbaiza27 Co-authored-by: Jiří Spilka --- .github/workflows/pre_release.yaml | 1 + README.md | 67 +++++++++++++++++++++++++++++- src/tools/mcp-apify-client.ts | 2 + 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pre_release.yaml b/.github/workflows/pre_release.yaml index 849c3ba8..7a3bf62f 100644 --- a/.github/workflows/pre_release.yaml +++ b/.github/workflows/pre_release.yaml @@ -1,6 +1,7 @@ name: Create a pre-release on: + workflow_dispatch: # Push to master will deploy a beta version push: branches: diff --git a/README.md b/README.md index 99f0c1da..083247bf 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ For example it can: To interact with the Apify MCP server, you can use MCP clients such as: - [Claude Desktop](https://claude.ai/download) (only Stdio support) +- [Visual Studio Code](https://code.visualstudio.com/) (Stdio and SSE support) - [LibreChat](https://www.librechat.ai/) (stdio and SSE support (yet without Authorization header)) - [Apify Tester MCP Client](https://apify.com/jiri.spilka/tester-mcp-client) (SSE support with Authorization headers) - other clients at [https://modelcontextprotocol.io/clients](https://modelcontextprotocol.io/clients) @@ -275,6 +276,63 @@ To configure Claude Desktop to work with the MCP server, follow these steps. For Find and analyze instagram profile of the Rock. ``` +#### VS Code + +For one-click installation, click one of the install buttons below: + +[![Install with NPX in VS Code](https://img.shields.io/badge/VS_Code-NPM-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=actors-mcp-server&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40apify%2Factors-mcp-server%22%5D%2C%22env%22%3A%7B%22APIFY_TOKEN%22%3A%22%24%7Binput%3Aapify_token%7D%22%7D%7D&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22apify_token%22%2C%22description%22%3A%22Apify+API+Token%22%2C%22password%22%3Atrue%7D%5D) [![Install with NPX in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-NPM-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=actors-mcp-server&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40apify%2Factors-mcp-server%22%5D%2C%22env%22%3A%7B%22APIFY_TOKEN%22%3A%22%24%7Binput%3Aapify_token%7D%22%7D%7D&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22apify_token%22%2C%22description%22%3A%22Apify+API+Token%22%2C%22password%22%3Atrue%7D%5D&quality=insiders) + +##### Manual installation + +You can manually install the Apify MCP Server in VS Code. First, click one of the install buttons at the top of this section for a one-click installation. + +Alternatively, add the following JSON block to your User Settings (JSON) file in VS Code. You can do this by pressing `Ctrl + Shift + P` and typing `Preferences: Open User Settings (JSON)`. + +```json +{ + "mcp": { + "inputs": [ + { + "type": "promptString", + "id": "apify_token", + "description": "Apify API Token", + "password": true + } + ], + "servers": { + "actors-mcp-server": { + "command": "npx", + "args": ["-y", "@apify/actors-mcp-server"], + "env": { + "APIFY_TOKEN": "${input:apify_token}" + } + } + } + } +} +``` + +Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace - just omit the top-level `mcp {}` key. This will allow you to share the configuration with others. + +If you want to specify which Actors to load, you can add the `--actors` argument: + +```json +{ + "servers": { + "actors-mcp-server": { + "command": "npx", + "args": [ + "-y", "@apify/actors-mcp-server", + "--actors", "lukaskrivka/google-maps-with-contact-details,apify/instagram-scraper" + ], + "env": { + "APIFY_TOKEN": "${input:apify_token}" + } + } + } +} +``` + #### Debugging NPM package @apify/actors-mcp-server with @modelcontextprotocol/inspector To debug the server, use the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) tool: @@ -365,8 +423,13 @@ Upon launching, the Inspector will display a URL that you can access in your bro ## ⓘ Limitations and feedback -To limit the context size the properties in the `input schema` are pruned and description is truncated to 500 characters. -Enum fields and titles are truncated to max 50 options. +The Actor input schema is processed to be compatible with most MCP clients while adhering to [JSON Schema](https://json-schema.org/) standards. The processing includes: +- **Descriptions** are truncated to 500 characters (as defined in `MAX_DESCRIPTION_LENGTH`). +- **Enum fields** are truncated to a maximum combined length of 200 characters for all elements (as defined in `ACTOR_ENUM_MAX_LENGTH`). +- **Required fields** are explicitly marked with a "REQUIRED" prefix in their descriptions for compatibility with frameworks that may not handle JSON schema properly. +- **Nested properties** are built for special cases like proxy configuration and request list sources to ensure correct input structure. +- **Array item types** are inferred when not explicitly defined in the schema, using a priority order: explicit type in items > prefill type > default value type > editor type. +- **Enum values and examples** are added to property descriptions to ensure visibility even if the client doesn't fully support JSON schema. Memory for each Actor is limited to 4GB. Free users have an 8GB limit, 128MB needs to be allocated for running `Actors-MCP-Server`. diff --git a/src/tools/mcp-apify-client.ts b/src/tools/mcp-apify-client.ts index d7224aee..0d3dd12b 100644 --- a/src/tools/mcp-apify-client.ts +++ b/src/tools/mcp-apify-client.ts @@ -1,3 +1,4 @@ + import type { ApifyClientOptions } from 'apify'; import { ApifyClient as _ApifyClient } from 'apify-client'; import type { AxiosRequestConfig } from 'axios'; @@ -20,6 +21,7 @@ export class ApifyClient extends _ApifyClient { constructor(options: ApifyClientOptions) { super({ ...options, + baseUrl: process.env.MCP_APIFY_BASE_URL || undefined, requestInterceptors: [addUserAgent], }); } From a9d3b654263017abe83243f7c023b4be5c26350d Mon Sep 17 00:00:00 2001 From: Apify Release Bot Date: Tue, 15 Apr 2025 07:23:05 +0000 Subject: [PATCH 33/42] chore(release): Update changelog and package version [skip ci] --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78e6d824..fa1e1506 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. ## 0.1.22 - **not yet released** +### 🚀 Features + +- Local apify api base url (#71) ([581f096](https://github.com/apify/actors-mcp-server/commit/581f096030018aa9f1052151c5b628d9b186193b)) + ### 🐛 Bug Fixes - Clearly separate Actor related code and rest of the codebase. Only export ApifyMcpServer from the package ([ce711e9](https://github.com/apify/actors-mcp-server/commit/ce711e9096ce2f51f6c3f78c0966a27ef73ad945)) From e918d26f224466cca58fa5ed35063c13054f9480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Spilka?= Date: Wed, 16 Apr 2025 11:02:17 +0200 Subject: [PATCH 34/42] feat: add new specification regarding json response streamable http (#72) * fix: add http request/response, fix lint * feat: add client for /mcp endpoint * fix: fork modelcontextprotocol repository * fix: refactor server.ts --- .github/workflows/check.yaml | 5 +- .nvmrc | 1 + eslint.config.mjs | 6 +- package-lock.json | 375 +++++++++++++-------------- package.json | 12 +- src/actor/const.ts | 8 + src/actor/server.ts | 199 +++++++++----- src/actor/utils.ts | 4 +- src/const.ts | 2 +- src/examples/clientSse.ts | 13 +- src/examples/clientStdio.ts | 8 +- src/examples/clientStdioChat.ts | 14 +- src/examples/clientStreamableHttp.ts | 109 ++++++++ src/index.ts | 3 +- src/main.ts | 10 +- src/mcp-server.ts | 20 +- src/stdio.ts | 2 +- src/tools/actor.ts | 2 +- src/tools/helpers.ts | 2 +- src/tsconfig.json | 7 + tests/actor-server-test.ts | 2 +- tests/actor-test.ts | 2 +- tests/actor-utils-test.ts | 2 +- tsconfig.eslint.json | 19 +- tsconfig.json | 19 +- 25 files changed, 523 insertions(+), 323 deletions(-) create mode 100644 .nvmrc create mode 100644 src/examples/clientStreamableHttp.ts create mode 100644 src/tsconfig.json diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index a12a6c98..d7217b11 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -30,10 +30,13 @@ jobs: run: npm ci - name: Lint - run: npm run lint + run: npm run lint:fix - name: Build run: npm run build - name: Test run: npm run test + + - name: Type checks + run: npm run type-check diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..d5b283a3 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22.13.1 diff --git a/eslint.config.mjs b/eslint.config.mjs index ffe1b904..fb9cd123 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,9 +1,9 @@ -import apify from '@apify/eslint-config'; +import apifyTypeScriptConfig from '@apify/eslint-config/ts.js'; // eslint-disable-next-line import/no-default-export export default [ - { ignores: ['**/dist', '**/.venv'] }, // Ignores need to happen first - ...apify, + { ignores: ['**/dist'] }, // Ignores need to happen first + ...apifyTypeScriptConfig, { languageOptions: { sourceType: 'module', diff --git a/package-lock.json b/package-lock.json index af1da43b..8369b555 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@apify/log": "^2.5.16", - "@modelcontextprotocol/sdk": "^1.9.0", + "@modelcontextprotocol/sdk": "github:jirispilka/mcp-typescript-sdk#fix/add-src-dir", "ajv": "^8.17.1", "apify": "^3.4.0", "apify-client": "^2.12.1", @@ -69,9 +69,9 @@ } }, "node_modules/@apify/consts": { - "version": "2.39.0", - "resolved": "https://registry.npmjs.org/@apify/consts/-/consts-2.39.0.tgz", - "integrity": "sha512-rO9+uzv7kP5XNiz8K+cYcgDmLPK9WZQ9Xlg2VEa2YB5MXDVcBnStdZm4loX/vPPpAGK5ztQnpBnYlA8PURBcXQ==", + "version": "2.40.0", + "resolved": "https://registry.npmjs.org/@apify/consts/-/consts-2.40.0.tgz", + "integrity": "sha512-2coaQ97ddsQ4+QRybqGbPE4irqfmkSaUPlbUPQvIcmT+PLdFT1t1iSU61Yy2T1UW5wN3K6UDAqFWNIqxxb0apg==", "license": "Apache-2.0" }, "node_modules/@apify/datastructures": { @@ -109,23 +109,23 @@ } }, "node_modules/@apify/input_secrets": { - "version": "1.1.70", - "resolved": "https://registry.npmjs.org/@apify/input_secrets/-/input_secrets-1.1.70.tgz", - "integrity": "sha512-fKuNdeYCMIwz5GxBydI+uu/cOnSye1l39SJcZ5FA38knpQHURZxSpYbtLcb98/MOUuzKOSCQvIbPyx1OwO1Pxg==", + "version": "1.1.71", + "resolved": "https://registry.npmjs.org/@apify/input_secrets/-/input_secrets-1.1.71.tgz", + "integrity": "sha512-vLdbRNZkVAOpgqHtho+AGIgWLgaeB4fV2B43oPm9pDbdX+Iv5X+04OM8Gh/X/Gcxs4ssdVXgb/hRy0uDxVa/Uw==", "license": "Apache-2.0", "dependencies": { - "@apify/log": "^2.5.16", - "@apify/utilities": "^2.15.3", + "@apify/log": "^2.5.17", + "@apify/utilities": "^2.15.4", "ow": "^0.28.2" } }, "node_modules/@apify/log": { - "version": "2.5.16", - "resolved": "https://registry.npmjs.org/@apify/log/-/log-2.5.16.tgz", - "integrity": "sha512-/kZJN/vs80yRpihcLxG2kLj45f6stky4RUIC1ea5Vmkb0i6Zv1l/sUqDLBimZMaQkSDCxM1j+Yhc5QaPlgJZrg==", + "version": "2.5.17", + "resolved": "https://registry.npmjs.org/@apify/log/-/log-2.5.17.tgz", + "integrity": "sha512-1L/iiHRxyc8e0EuFfZ1raZLWC4mQYrTPy9JzF1hwwhsRSlyHNFQrfNzF0vbF3/8XpLwEBSasAA3fclQhDbUn/Q==", "license": "Apache-2.0", "dependencies": { - "@apify/consts": "^2.39.0", + "@apify/consts": "^2.40.0", "ansi-colors": "^4.1.1" } }, @@ -145,12 +145,12 @@ } }, "node_modules/@apify/pseudo_url": { - "version": "2.0.57", - "resolved": "https://registry.npmjs.org/@apify/pseudo_url/-/pseudo_url-2.0.57.tgz", - "integrity": "sha512-5LJBocbpBYE9vfb6UDqcqAq3azC0b2PHxRXRz8JFGU4qt7o3Gzi4p16lImZ2qy9/x3plWYWxbYsewX5aH/aamA==", + "version": "2.0.58", + "resolved": "https://registry.npmjs.org/@apify/pseudo_url/-/pseudo_url-2.0.58.tgz", + "integrity": "sha512-a0F94mEhwZJe6UUO3JGVbvufNqSQkP3uc0nLAAJA7Bydwe5PMRrWGLQ8+/EW8pEnrYCFVTuI9+uXtJideQpinA==", "license": "Apache-2.0", "dependencies": { - "@apify/log": "^2.5.16" + "@apify/log": "^2.5.17" } }, "node_modules/@apify/timeout": { @@ -160,20 +160,20 @@ "license": "Apache-2.0" }, "node_modules/@apify/tsconfig": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@apify/tsconfig/-/tsconfig-0.1.0.tgz", - "integrity": "sha512-ba9Y6AMocRucO3AVTb6GM2V+oy1wByNlCDzamK+IC+aqU3pCgJwSN87uNu6iEgu+uetsqYvVbXJYakwiQO1LGA==", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@apify/tsconfig/-/tsconfig-0.1.1.tgz", + "integrity": "sha512-cS7mwN2UW1UXcluGXRDHH0Vr2VsSLkw2DwLTwoSBkcJSe8fvCr3MPryTSq0uod4MashpMURxJ7CsLKxs82VmOQ==", "dev": true, "license": "Apache-2.0" }, "node_modules/@apify/utilities": { - "version": "2.15.3", - "resolved": "https://registry.npmjs.org/@apify/utilities/-/utilities-2.15.3.tgz", - "integrity": "sha512-jCyZ3ZhosjHSnaPND6l9/gPp0y+bH3G+Gz+/eIFRMXE418T9me0X828Ua2sCROcj/TrNg+1DZbdE3GWqOMdyuw==", + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@apify/utilities/-/utilities-2.15.4.tgz", + "integrity": "sha512-uM1kLJAE3bnDApGPqdD0xWeo/2HCE9KyAxeBdtRNaHVOu8DvRf8ioOlif75AZbMCXCo3g0zNmkznjtR6hrglJw==", "license": "Apache-2.0", "dependencies": { - "@apify/consts": "^2.39.0", - "@apify/log": "^2.5.16" + "@apify/consts": "^2.40.0", + "@apify/log": "^2.5.17" } }, "node_modules/@crawlee/core": { @@ -715,9 +715,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", - "integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.0.tgz", + "integrity": "sha512-WhCn7Z7TauhBtmzhvKpoQs0Wwb/kBcy4CwpuI0/eEIr2Lx2auxmulAzLr91wVZJaz47iUZdkXOK7WlAfxGKCnA==", "dev": true, "license": "MIT", "dependencies": { @@ -1064,9 +1064,8 @@ "license": "MIT" }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.9.0.tgz", - "integrity": "sha512-Jq2EUCQpe0iyO5FGpzVYDNFR6oR53AIrwph9yWl7uSc7IWUMsrmpmSaTGra5hQNunXpM+9oit85p924jWuHzUA==", + "version": "1.10.0", + "resolved": "git+ssh://git@github.com/jirispilka/mcp-typescript-sdk.git#4ae4b33f263ec0ab157eb5f541c69ba01cbf2822", "license": "MIT", "dependencies": { "content-type": "^1.0.5", @@ -1391,9 +1390,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.39.0.tgz", - "integrity": "sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", + "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", "cpu": [ "arm" ], @@ -1405,9 +1404,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.39.0.tgz", - "integrity": "sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", + "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", "cpu": [ "arm64" ], @@ -1419,9 +1418,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.39.0.tgz", - "integrity": "sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", + "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", "cpu": [ "arm64" ], @@ -1433,9 +1432,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.39.0.tgz", - "integrity": "sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", + "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", "cpu": [ "x64" ], @@ -1447,9 +1446,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.39.0.tgz", - "integrity": "sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", + "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", "cpu": [ "arm64" ], @@ -1461,9 +1460,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.39.0.tgz", - "integrity": "sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", + "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", "cpu": [ "x64" ], @@ -1475,9 +1474,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.39.0.tgz", - "integrity": "sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", + "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", "cpu": [ "arm" ], @@ -1489,9 +1488,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.39.0.tgz", - "integrity": "sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", + "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", "cpu": [ "arm" ], @@ -1503,9 +1502,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.39.0.tgz", - "integrity": "sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", + "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", "cpu": [ "arm64" ], @@ -1517,9 +1516,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.39.0.tgz", - "integrity": "sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", + "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", "cpu": [ "arm64" ], @@ -1531,9 +1530,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.39.0.tgz", - "integrity": "sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", + "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", "cpu": [ "loong64" ], @@ -1545,9 +1544,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.39.0.tgz", - "integrity": "sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", + "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", "cpu": [ "ppc64" ], @@ -1559,9 +1558,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.39.0.tgz", - "integrity": "sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", + "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", "cpu": [ "riscv64" ], @@ -1573,9 +1572,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.39.0.tgz", - "integrity": "sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", + "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", "cpu": [ "riscv64" ], @@ -1587,9 +1586,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.39.0.tgz", - "integrity": "sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", + "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", "cpu": [ "s390x" ], @@ -1601,9 +1600,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.39.0.tgz", - "integrity": "sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", "cpu": [ "x64" ], @@ -1615,9 +1614,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.39.0.tgz", - "integrity": "sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", "cpu": [ "x64" ], @@ -1629,9 +1628,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.39.0.tgz", - "integrity": "sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", + "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", "cpu": [ "arm64" ], @@ -1643,9 +1642,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.39.0.tgz", - "integrity": "sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", + "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", "cpu": [ "ia32" ], @@ -1657,9 +1656,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.39.0.tgz", - "integrity": "sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", + "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", "cpu": [ "x64" ], @@ -1933,17 +1932,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.1.tgz", - "integrity": "sha512-ba0rr4Wfvg23vERs3eB+P3lfj2E+2g3lhWcCVukUuhtcdUx5lSIFZlGFEBHKr+3zizDa/TvZTptdNHVZWAkSBg==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.30.1.tgz", + "integrity": "sha512-v+VWphxMjn+1t48/jO4t950D6KR8JaJuNXzi33Ve6P8sEmPr5k6CEXjdGwT6+LodVnEa91EQCtwjWNUCPweo+Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.29.1", - "@typescript-eslint/type-utils": "8.29.1", - "@typescript-eslint/utils": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1", + "@typescript-eslint/scope-manager": "8.30.1", + "@typescript-eslint/type-utils": "8.30.1", + "@typescript-eslint/utils": "8.30.1", + "@typescript-eslint/visitor-keys": "8.30.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1963,16 +1962,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.1.tgz", - "integrity": "sha512-zczrHVEqEaTwh12gWBIJWj8nx+ayDcCJs06yoNMY0kwjMWDM6+kppljY+BxWI06d2Ja+h4+WdufDcwMnnMEWmg==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.30.1.tgz", + "integrity": "sha512-H+vqmWwT5xoNrXqWs/fesmssOW70gxFlgcMlYcBaWNPIEWDgLa4W9nkSPmhuOgLnXq9QYgkZ31fhDyLhleCsAg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.29.1", - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/typescript-estree": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1", + "@typescript-eslint/scope-manager": "8.30.1", + "@typescript-eslint/types": "8.30.1", + "@typescript-eslint/typescript-estree": "8.30.1", + "@typescript-eslint/visitor-keys": "8.30.1", "debug": "^4.3.4" }, "engines": { @@ -2006,14 +2005,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.1.tgz", - "integrity": "sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.30.1.tgz", + "integrity": "sha512-+C0B6ChFXZkuaNDl73FJxRYT0G7ufVPOSQkqkpM/U198wUwUFOtgo1k/QzFh1KjpBitaK7R1tgjVz6o9HmsRPg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1" + "@typescript-eslint/types": "8.30.1", + "@typescript-eslint/visitor-keys": "8.30.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2024,14 +2023,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.1.tgz", - "integrity": "sha512-DkDUSDwZVCYN71xA4wzySqqcZsHKic53A4BLqmrWFFpOpNSoxX233lwGu/2135ymTCR04PoKiEEEvN1gFYg4Tw==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.30.1.tgz", + "integrity": "sha512-64uBF76bfQiJyHgZISC7vcNz3adqQKIccVoKubyQcOnNcdJBvYOILV1v22Qhsw3tw3VQu5ll8ND6hycgAR5fEA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.29.1", - "@typescript-eslint/utils": "8.29.1", + "@typescript-eslint/typescript-estree": "8.30.1", + "@typescript-eslint/utils": "8.30.1", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -2066,9 +2065,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.1.tgz", - "integrity": "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.30.1.tgz", + "integrity": "sha512-81KawPfkuulyWo5QdyG/LOKbspyyiW+p4vpn4bYO7DM/hZImlVnFwrpCTnmNMOt8CvLRr5ojI9nU1Ekpw4RcEw==", "dev": true, "license": "MIT", "engines": { @@ -2080,14 +2079,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.1.tgz", - "integrity": "sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.30.1.tgz", + "integrity": "sha512-kQQnxymiUy9tTb1F2uep9W6aBiYODgq5EMSk6Nxh4Z+BDUoYUSa029ISs5zTzKBFnexQEh71KqwjKnRz58lusQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/visitor-keys": "8.29.1", + "@typescript-eslint/types": "8.30.1", + "@typescript-eslint/visitor-keys": "8.30.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2164,16 +2163,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.1.tgz", - "integrity": "sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.30.1.tgz", + "integrity": "sha512-T/8q4R9En2tcEsWPQgB5BQ0XJVOtfARcUvOa8yJP3fh9M/mXraLxZrkCfGb6ChrO/V3W+Xbd04RacUEqk1CFEQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.29.1", - "@typescript-eslint/types": "8.29.1", - "@typescript-eslint/typescript-estree": "8.29.1" + "@typescript-eslint/scope-manager": "8.30.1", + "@typescript-eslint/types": "8.30.1", + "@typescript-eslint/typescript-estree": "8.30.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2188,13 +2187,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.1.tgz", - "integrity": "sha512-RGLh5CRaUEf02viP5c1Vh1cMGffQscyHe7HPAzGpfmfflFg1wUz2rYxd+OZqwpeypYvZ8UxSxuIpF++fmOzEcg==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.30.1.tgz", + "integrity": "sha512-aEhgas7aJ6vZnNFC7K4/vMGDGyOiqWcYZPpIWrTKuTAlsvDNKy2GFDqh9smL+iq069ZvR0YzEeq0B8NJlLzjFA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/types": "8.30.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -2465,12 +2464,12 @@ } }, "node_modules/apify-client": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/apify-client/-/apify-client-2.12.1.tgz", - "integrity": "sha512-5K9VfSwjb2veD0ts1mScI5mVVuw/eTqF3tjyQj/jGyi7o3WIP5NI+3cXWz/aZGIQtPMLSjJ1VAVGu/of+wcmpw==", + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/apify-client/-/apify-client-2.12.2.tgz", + "integrity": "sha512-+eSexDukVso58MQ8pOJj67mnaDkexH80VJs0/stfM8yNSUKMa/BIIdbG3rX8axjpTtT3UzpPgMIz6qh8inxFCQ==", "license": "Apache-2.0", "dependencies": { - "@apify/consts": "^2.25.0", + "@apify/consts": "^2.40.0", "@apify/log": "^2.2.6", "@crawlee/types": "^3.3.0", "agentkeepalive": "^4.2.1", @@ -2915,9 +2914,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001712", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001712.tgz", - "integrity": "sha512-MBqPpGYYdQ7/hfKiet9SCI+nmN5/hp4ZzveOJubl5DTAMa5oggjAuoi0Z4onBpKPFI2ePGnQuQIzF3VxDjDJig==", + "version": "1.0.30001713", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001713.tgz", + "integrity": "sha512-wCIWIg+A4Xr7NfhTuHdX+/FKh3+Op3LBbSp2N5Pfx6T/LhdQy3GTyoTg48BReaW/MyMNZAkTadsBtai3ldWK0Q==", "funding": [ { "type": "opencollective", @@ -3424,9 +3423,9 @@ } }, "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -3463,9 +3462,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.134", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.134.tgz", - "integrity": "sha512-zSwzrLg3jNP3bwsLqWHmS5z2nIOQ5ngMnfMZOWWtXnqqQkPVyOipxK98w+1beLw1TB+EImPNcG8wVP/cLVs2Og==", + "version": "1.5.137", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.137.tgz", + "integrity": "sha512-/QSJaU2JyIuTbbABAo/crOs+SuAZLS+fVVS10PVrIT9hrRkmZl8Hb0xPSkKRUUWHQtYzXHpQUW3Dy5hwMzGZkA==", "license": "ISC" }, "node_modules/encodeurl": { @@ -6607,9 +6606,9 @@ } }, "node_modules/rollup": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.39.0.tgz", - "integrity": "sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", + "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", "dev": true, "license": "MIT", "dependencies": { @@ -6623,26 +6622,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.39.0", - "@rollup/rollup-android-arm64": "4.39.0", - "@rollup/rollup-darwin-arm64": "4.39.0", - "@rollup/rollup-darwin-x64": "4.39.0", - "@rollup/rollup-freebsd-arm64": "4.39.0", - "@rollup/rollup-freebsd-x64": "4.39.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.39.0", - "@rollup/rollup-linux-arm-musleabihf": "4.39.0", - "@rollup/rollup-linux-arm64-gnu": "4.39.0", - "@rollup/rollup-linux-arm64-musl": "4.39.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.39.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.39.0", - "@rollup/rollup-linux-riscv64-gnu": "4.39.0", - "@rollup/rollup-linux-riscv64-musl": "4.39.0", - "@rollup/rollup-linux-s390x-gnu": "4.39.0", - "@rollup/rollup-linux-x64-gnu": "4.39.0", - "@rollup/rollup-linux-x64-musl": "4.39.0", - "@rollup/rollup-win32-arm64-msvc": "4.39.0", - "@rollup/rollup-win32-ia32-msvc": "4.39.0", - "@rollup/rollup-win32-x64-msvc": "4.39.0", + "@rollup/rollup-android-arm-eabi": "4.40.0", + "@rollup/rollup-android-arm64": "4.40.0", + "@rollup/rollup-darwin-arm64": "4.40.0", + "@rollup/rollup-darwin-x64": "4.40.0", + "@rollup/rollup-freebsd-arm64": "4.40.0", + "@rollup/rollup-freebsd-x64": "4.40.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", + "@rollup/rollup-linux-arm-musleabihf": "4.40.0", + "@rollup/rollup-linux-arm64-gnu": "4.40.0", + "@rollup/rollup-linux-arm64-musl": "4.40.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-musl": "4.40.0", + "@rollup/rollup-linux-s390x-gnu": "4.40.0", + "@rollup/rollup-linux-x64-gnu": "4.40.0", + "@rollup/rollup-linux-x64-musl": "4.40.0", + "@rollup/rollup-win32-arm64-msvc": "4.40.0", + "@rollup/rollup-win32-ia32-msvc": "4.40.0", + "@rollup/rollup-win32-x64-msvc": "4.40.0", "fsevents": "~2.3.2" } }, @@ -7285,21 +7284,21 @@ } }, "node_modules/tldts": { - "version": "6.1.85", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.85.tgz", - "integrity": "sha512-gBdZ1RjCSevRPFix/hpaUWeak2/RNUZB4/8frF1r5uYMHjFptkiT0JXIebWvgI/0ZHXvxaUDDJshiA0j6GdL3w==", + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", "license": "MIT", "dependencies": { - "tldts-core": "^6.1.85" + "tldts-core": "^6.1.86" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "6.1.85", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.85.tgz", - "integrity": "sha512-DTjUVvxckL1fIoPSb3KE7ISNtkWSawZdpfxGxwiIrZoO6EbHVDXXUIlIuWympPaeS+BLGyggozX/HTMsRAdsoA==", + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", "license": "MIT" }, "node_modules/to-regex-range": { @@ -7439,9 +7438,9 @@ } }, "node_modules/type-fest": { - "version": "4.39.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.39.1.tgz", - "integrity": "sha512-uW9qzd66uyHYxwyVBYiwS4Oi0qZyUqwjU+Oevr6ZogYiXt99EOYtwvzMSLw1c3lYo2HzJsep/NB23iEVEgjG/w==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.0.tgz", + "integrity": "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw==", "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=16" @@ -7556,15 +7555,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.29.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.29.1.tgz", - "integrity": "sha512-f8cDkvndhbQMPcysk6CUSGBWV+g1utqdn71P5YKwMumVMOG/5k7cHq0KyG4O52nB0oKS4aN2Tp5+wB4APJGC+w==", + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.30.1.tgz", + "integrity": "sha512-D7lC0kcehVH7Mb26MRQi64LMyRJsj3dToJxM1+JVTl53DQSV5/7oUGWQLcKl1C1KnoVHxMMU2FNQMffr7F3Row==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.29.1", - "@typescript-eslint/parser": "8.29.1", - "@typescript-eslint/utils": "8.29.1" + "@typescript-eslint/eslint-plugin": "8.30.1", + "@typescript-eslint/parser": "8.30.1", + "@typescript-eslint/utils": "8.30.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7701,9 +7700,9 @@ } }, "node_modules/vite": { - "version": "6.2.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.5.tgz", - "integrity": "sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==", + "version": "6.2.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz", + "integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index d85fc9f2..988d9350 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ ], "dependencies": { "@apify/log": "^2.5.16", - "@modelcontextprotocol/sdk": "^1.9.0", + "@modelcontextprotocol/sdk": "github:jirispilka/mcp-typescript-sdk#fix/add-src-dir", "ajv": "^8.17.1", "apify": "^3.4.0", "apify-client": "^2.12.1", @@ -59,13 +59,13 @@ "start": "npm run start:dev", "start:prod": "node dist/main.js", "start:dev": "tsx src/main.ts", - "lint": "./node_modules/.bin/eslint .", - "lint:fix": "./node_modules/.bin/eslint . --fix", - "build": "tsc", - "build:watch": "tsc -w", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "build": "tsc -b src", + "build:watch": "tsc -b src -w", + "type-check": "tsc --noEmit", "inspector": "npx @modelcontextprotocol/inspector dist/stdio.js", "test": "vitest run", - "type-check": "tsc --noEmit", "clean": "tsc -b src --clean" }, "author": "Apify", diff --git a/src/actor/const.ts b/src/actor/const.ts index 538e7784..bc9aa114 100644 --- a/src/actor/const.ts +++ b/src/actor/const.ts @@ -5,6 +5,14 @@ export const HEADER_READINESS_PROBE = 'x-apify-container-server-readiness-probe' export enum Routes { ROOT = '/', + MCP = '/mcp', SSE = '/sse', MESSAGE = '/message', } + +export const getHelpMessage = (host: string) => `To interact with the server you can either: +- send request to ${host}${Routes.MCP}?token=YOUR-APIFY-TOKEN and receive a response +or +- connect for Server-Sent Events (SSE) via GET request to: ${host}${Routes.SSE}?token=YOUR-APIFY-TOKEN +- send messages via POST request to: ${host}${Routes.MESSAGE}?token=YOUR-APIFY-TOKEN + (Include your message content in the request body.)`; diff --git a/src/actor/server.ts b/src/actor/server.ts index 85563ff5..29b82ad7 100644 --- a/src/actor/server.ts +++ b/src/actor/server.ts @@ -2,91 +2,170 @@ * Express server implementation used for standby Actor mode. */ +import { randomUUID } from 'node:crypto'; + import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import type { Request, Response } from 'express'; import express from 'express'; import log from '@apify/log'; -import { HEADER_READINESS_PROBE, Routes } from './const.js'; import { type ActorsMcpServer } from '../mcp-server.js'; +import { getHelpMessage, HEADER_READINESS_PROBE, Routes } from './const.js'; import { getActorRunData, processParamsGetTools } from './utils.js'; export function createExpressApp( host: string, mcpServer: ActorsMcpServer, ): express.Express { - const HELP_MESSAGE = `Connect to the server with GET request to ${host}/sse?token=YOUR-APIFY-TOKEN` - + ` and then send POST requests to ${host}/message?token=YOUR-APIFY-TOKEN`; - const app = express(); + app.use(express.json()); + let transportSSE: SSEServerTransport; + const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {}; - let transport: SSEServerTransport; + function respondWithError(res: Response, error: unknown, logMessage: string, statusCode = 500) { + log.error(`${logMessage}: ${error}`); + if (!res.headersSent) { + res.status(statusCode).json({ + jsonrpc: '2.0', + error: { + code: statusCode === 500 ? -32603 : -32000, + message: statusCode === 500 ? 'Internal server error' : 'Bad Request', + }, + id: null, + }); + } + } - app.route(Routes.ROOT) - .get(async (req: Request, res: Response) => { - if (req.headers && req.get(HEADER_READINESS_PROBE) !== undefined) { - log.debug('Received readiness probe'); - res.status(200).json({ message: 'Server is ready' }).end(); - return; + app.get(Routes.ROOT, async (req: Request, res: Response) => { + if (req.headers && req.get(HEADER_READINESS_PROBE) !== undefined) { + log.debug('Received readiness probe'); + res.status(200).json({ message: 'Server is ready' }).end(); + return; + } + try { + log.info(`Received GET message at: ${Routes.ROOT}`); + const tools = await processParamsGetTools(req.url); + if (tools) { + mcpServer.updateTools(tools); } - try { - log.info(`Received GET message at: ${Routes.ROOT}`); - const tools = await processParamsGetTools(req.url); - if (tools) { - mcpServer.updateTools(tools); - } - res.setHeader('Content-Type', 'text/event-stream'); - res.setHeader('Cache-Control', 'no-cache'); - res.setHeader('Connection', 'keep-alive'); - res.status(200).json({ message: `Actor is using Model Context Protocol. ${HELP_MESSAGE}`, data: getActorRunData() }).end(); - } catch (error) { - log.error(`Error in GET ${Routes.ROOT} ${error}`); - res.status(500).json({ message: 'Internal Server Error' }).end(); + res.setHeader('Content-Type', 'text/event-stream'); + res.setHeader('Cache-Control', 'no-cache'); + res.setHeader('Connection', 'keep-alive'); + res.status(200).json({ message: `Actor is using Model Context Protocol. ${getHelpMessage(host)}`, data: getActorRunData() }).end(); + } catch (error) { + respondWithError(res, error, `Error in GET ${Routes.ROOT}`); + } + }); + + app.head(Routes.ROOT, (_req: Request, res: Response) => { + res.status(200).end(); + }); + + app.get(Routes.SSE, async (req: Request, res: Response) => { + try { + log.info(`Received GET message at: ${Routes.SSE}`); + const tools = await processParamsGetTools(req.url); + if (tools) { + mcpServer.updateTools(tools); } - }) - .head((_req: Request, res: Response) => { - res.status(200).end(); - }); - - app.route(Routes.SSE) - .get(async (req: Request, res: Response) => { - try { - log.info(`Received GET message at: ${Routes.SSE}`); - const tools = await processParamsGetTools(req.url); - if (tools) { - mcpServer.updateTools(tools); - } - transport = new SSEServerTransport(Routes.MESSAGE, res); - await mcpServer.connect(transport); - } catch (error) { - log.error(`Error in GET ${Routes.SSE}: ${error}`); - res.status(500).json({ message: 'Internal Server Error' }).end(); + transportSSE = new SSEServerTransport(Routes.MESSAGE, res); + await mcpServer.connect(transportSSE); + } catch (error) { + respondWithError(res, error, `Error in GET ${Routes.SSE}`); + } + }); + + app.post(Routes.MESSAGE, async (req: Request, res: Response) => { + try { + log.info(`Received POST message at: ${Routes.MESSAGE}`); + if (transportSSE) { + await transportSSE.handlePostMessage(req, res); + } else { + log.error('Server is not connected to the client.'); + res.status(400).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: 'Bad Request: Server is not connected to the client. ' + + 'Connect to the server with GET request to /sse endpoint', + }, + id: null, + }); } - }); - - app.route(Routes.MESSAGE) - .post(async (req: Request, res: Response) => { - try { - log.info(`Received POST message at: ${Routes.MESSAGE}`); - if (transport) { - await transport.handlePostMessage(req, res); - } else { - res.status(400).json({ - message: 'Server is not connected to the client. ' - + 'Connect to the server with GET request to /sse endpoint', - }); + } catch (error) { + respondWithError(res, error, `Error in POST ${Routes.MESSAGE}`); + } + }); + + app.post(Routes.MCP, async (req: Request, res: Response) => { + log.info('Received MCP request:', req.body); + try { + // Check for existing session ID + const sessionId = req.headers['mcp-session-id'] as string | undefined; + let transport: StreamableHTTPServerTransport; + + if (sessionId && transports[sessionId]) { + // Reuse existing transport + transport = transports[sessionId]; + } else if (!sessionId && isInitializeRequest(req.body)) { + // New initialization request - use JSON response mode + transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: () => randomUUID(), + enableJsonResponse: true, // Enable JSON response mode + }); + + // Connect the transport to the MCP server BEFORE handling the request + await mcpServer.connect(transport); + + // After handling the request, if we get a session ID back, store the transport + await transport.handleRequest(req, res, req.body); + + // Store the transport by session ID for future requests + if (transport.sessionId) { + transports[transport.sessionId] = transport; } - } catch (error) { - log.error(`Error in POST ${Routes.MESSAGE}: ${error}`); - res.status(500).json({ message: 'Internal Server Error' }).end(); + return; // Already handled + } else { + // Invalid request - no session ID or not initialization request + res.status(400).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: 'Bad Request: No valid session ID provided or not initialization request', + }, + id: null, + }); + return; } - }); + + // Handle the request with existing transport - no need to reconnect + await transport.handleRequest(req, res, req.body); + } catch (error) { + respondWithError(res, error, 'Error handling MCP request'); + } + }); + + // Handle GET requests for SSE streams according to spec + app.get(Routes.MCP, async (_req: Request, res: Response) => { + // We don't support GET requests for this server + // The spec requires returning 405 Method Not Allowed in this case + res.status(405).set('Allow', 'POST').send('Method Not Allowed'); + }); // Catch-all for undefined routes app.use((req: Request, res: Response) => { - res.status(404).json({ message: `There is nothing at route ${req.method} ${req.originalUrl}. ${HELP_MESSAGE}` }).end(); + res.status(404).json({ message: `There is nothing at route ${req.method} ${req.originalUrl}. ${getHelpMessage(host)}` }).end(); }); return app; } + +// Helper function to detect initialize requests +function isInitializeRequest(body: unknown): boolean { + if (Array.isArray(body)) { + return body.some((msg) => typeof msg === 'object' && msg !== null && 'method' in msg && msg.method === 'initialize'); + } + return typeof body === 'object' && body !== null && 'method' in body && body.method === 'initialize'; +} diff --git a/src/actor/utils.ts b/src/actor/utils.ts index 2a826e9e..f31088e6 100644 --- a/src/actor/utils.ts +++ b/src/actor/utils.ts @@ -2,10 +2,10 @@ import { parse } from 'node:querystring'; import { Actor } from 'apify'; -import { processInput } from './input.js'; -import type { ActorRunData, Input } from './types.js'; import { addTool, getActorsAsTools, removeTool } from '../tools/index.js'; import type { ToolWrap } from '../types.js'; +import { processInput } from './input.js'; +import type { ActorRunData, Input } from './types.js'; export function parseInputParamsFromUrl(url: string): Input { const query = url.split('?')[1] || ''; diff --git a/src/const.ts b/src/const.ts index 7758896c..e23c3805 100644 --- a/src/const.ts +++ b/src/const.ts @@ -16,7 +16,7 @@ export const ACTOR_MAX_MEMORY_MBYTES = 4_096; // If the Actor requires 8GB of me // MCP Server export const SERVER_NAME = 'apify-mcp-server'; -export const SERVER_VERSION = '0.1.0'; +export const SERVER_VERSION = '1.0.0'; // User agent headers export const USER_AGENT_ORIGIN = 'Origin/mcp-server'; diff --git a/src/examples/clientSse.ts b/src/examples/clientSse.ts index c77d1fa6..f5f7d930 100644 --- a/src/examples/clientSse.ts +++ b/src/examples/clientSse.ts @@ -6,14 +6,15 @@ * It requires the `APIFY_TOKEN` in the `.env` file. */ -import path from 'path'; -import { fileURLToPath } from 'url'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'; import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js'; -import dotenv from 'dotenv'; -import { EventSource, EventSourceInit } from 'eventsource'; +import dotenv from 'dotenv'; // eslint-disable-line import/no-extraneous-dependencies +import type { EventSourceInit } from 'eventsource'; +import { EventSource } from 'eventsource'; // eslint-disable-line import/no-extraneous-dependencies import { actorNameToToolName } from '../tools/utils.js'; @@ -36,13 +37,15 @@ if (!process.env.APIFY_TOKEN) { // Declare EventSource on globalThis if not available (needed for Node.js environment) declare global { + + // eslint-disable-next-line no-var, vars-on-top var EventSource: { new(url: string, eventSourceInitDict?: EventSourceInit): EventSource; prototype: EventSource; CONNECTING: 0; OPEN: 1; CLOSED: 2; - }; // eslint-disable-line no-var + }; } if (typeof globalThis.EventSource === 'undefined') { diff --git a/src/examples/clientStdio.ts b/src/examples/clientStdio.ts index 8e5627d4..343e380e 100644 --- a/src/examples/clientStdio.ts +++ b/src/examples/clientStdio.ts @@ -6,14 +6,14 @@ * You can choose actors to run in the server, for example: `apify/rag-web-browser`. */ -import { execSync } from 'child_process'; -import path from 'path'; -import { fileURLToPath } from 'url'; +import { execSync } from 'node:child_process'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js'; -import dotenv from 'dotenv'; +import dotenv from 'dotenv'; // eslint-disable-line import/no-extraneous-dependencies import { actorNameToToolName } from '../tools/utils.js'; diff --git a/src/examples/clientStdioChat.ts b/src/examples/clientStdioChat.ts index d7c122e2..bd1118c9 100644 --- a/src/examples/clientStdioChat.ts +++ b/src/examples/clientStdioChat.ts @@ -17,17 +17,17 @@ * Let me summarize the key points: */ -import { execSync } from 'child_process'; -import path from 'path'; -import * as readline from 'readline'; -import { fileURLToPath } from 'url'; +import { execSync } from 'node:child_process'; +import path from 'node:path'; +import * as readline from 'node:readline'; +import { fileURLToPath } from 'node:url'; -import { Anthropic } from '@anthropic-ai/sdk'; -import type { Message, ToolUseBlock, MessageParam } from '@anthropic-ai/sdk/resources/messages'; +import { Anthropic } from '@anthropic-ai/sdk'; // eslint-disable-line import/no-extraneous-dependencies +import type { Message, MessageParam, ToolUseBlock } from '@anthropic-ai/sdk/resources/messages'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js'; -import dotenv from 'dotenv'; +import dotenv from 'dotenv'; // eslint-disable-line import/no-extraneous-dependencies const filename = fileURLToPath(import.meta.url); const dirname = path.dirname(filename); diff --git a/src/examples/clientStreamableHttp.ts b/src/examples/clientStreamableHttp.ts new file mode 100644 index 00000000..2322dc78 --- /dev/null +++ b/src/examples/clientStreamableHttp.ts @@ -0,0 +1,109 @@ +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; +import type { CallToolRequest, ListToolsRequest } from '@modelcontextprotocol/sdk/types.js'; +import { + CallToolResultSchema, + ListToolsResultSchema, + LoggingMessageNotificationSchema, +} from '@modelcontextprotocol/sdk/types.js'; + +import log from '@apify/log'; + +log.setLevel(log.LEVELS.DEBUG); + +async function main(): Promise { + // Create a new client with streamable HTTP transport + const client = new Client({ + name: 'example-client', + version: '1.0.0', + }); + + const transport = new StreamableHTTPClientTransport( + new URL('http://localhost:3000/mcp'), + ); + + // Connect the client using the transport and initialize the server + await client.connect(transport); + log.debug('Connected to MCP server'); + + // Set up notification handlers for server-initiated messages + client.setNotificationHandler(LoggingMessageNotificationSchema, (notification) => { + log.debug(`Notification received: ${notification.params.level} - ${notification.params.data}`); + }); + + // List and call tools + await listTools(client); + + await callSearchTool(client); + await callActor(client); + + // Keep the connection open to receive notifications + log.debug('\nKeeping connection open to receive notifications. Press Ctrl+C to exit.'); +} + +async function listTools(client: Client): Promise { + try { + const toolsRequest: ListToolsRequest = { + method: 'tools/list', + params: {}, + }; + const toolsResult = await client.request(toolsRequest, ListToolsResultSchema); + log.debug(`Tools available, count: ${toolsResult.tools.length}`); + for (const tool of toolsResult.tools) { + log.debug(`Tool: ${tool.name}, Description: ${tool.description}`); + } + if (toolsResult.tools.length === 0) { + log.debug('No tools available from the server'); + } + } catch (error) { + log.error(`Tools not supported by this server (${error})`); + } +} + +async function callSearchTool(client: Client): Promise { + try { + const searchRequest: CallToolRequest = { + method: 'tools/call', + params: { + name: 'search', + arguments: { search: 'rag web browser', limit: 1 }, + }, + }; + const searchResult = await client.request(searchRequest, CallToolResultSchema); + log.debug('Search result:'); + searchResult.content.forEach((item) => { + if (item.type === 'text') { + log.debug(`\t${item.text}`); + } + }); + } catch (error) { + log.error(`Error calling greet tool: ${error}`); + } +} + +async function callActor(client: Client): Promise { + try { + log.debug('\nCalling Actor...'); + const actorRequest: CallToolRequest = { + method: 'tools/call', + params: { + name: 'apify/rag-web-browser', + arguments: { query: 'apify mcp server' }, + }, + }; + const actorResult = await client.request(actorRequest, CallToolResultSchema); + log.debug('Actor results:'); + actorResult.content.forEach((item) => { + if (item.type === 'text') { + log.debug(`- ${item.text}`); + } + }); + } catch (error) { + log.error(`Error calling Actor: ${error}`); + } +} + +main().catch((error: unknown) => { + log.error('Error running MCP client:', error as Error); + process.exit(1); +}); diff --git a/src/index.ts b/src/index.ts index e20c084d..4fb67976 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,4 +4,5 @@ */ import { ActorsMcpServer } from './mcp-server.js'; -export default ActorsMcpServer; + +export { ActorsMcpServer }; diff --git a/src/main.ts b/src/main.ts index 8f70bc3f..d88023fe 100644 --- a/src/main.ts +++ b/src/main.ts @@ -12,14 +12,20 @@ import { processInput } from './actor/input.js'; import { createExpressApp } from './actor/server.js'; import type { Input } from './actor/types'; import { ActorsMcpServer } from './mcp-server.js'; -import { actorDefinitionTool, addTool, removeTool, searchTool, callActorGetDataset } from './tools/index.js'; +import { + actorDefinitionTool, + addTool, + callActorGetDataset, + removeTool, + searchTool, +} from './tools/index.js'; const STANDBY_MODE = Actor.getEnv().metaOrigin === 'STANDBY'; await Actor.init(); const HOST = Actor.isAtHome() ? process.env.ACTOR_STANDBY_URL as string : 'http://localhost'; -const PORT = Actor.isAtHome() ? Number(process.env.ACTOR_STANDBY_PORT) : 3001; +const PORT = Actor.isAtHome() ? Number(process.env.ACTOR_STANDBY_PORT) : 3000; if (!process.env.APIFY_TOKEN) { log.error('APIFY_TOKEN is required but not set in the environment variables.'); diff --git a/src/mcp-server.ts b/src/mcp-server.ts index bb54b37a..cdc03c37 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -9,24 +9,21 @@ import type { ActorCallOptions } from 'apify-client'; import log from '@apify/log'; -import { - ACTOR_OUTPUT_MAX_CHARS_PER_ITEM, +import { processParamsGetTools } from './actor/utils.js'; +import { ACTOR_OUTPUT_MAX_CHARS_PER_ITEM, ACTOR_OUTPUT_TRUNCATED_MESSAGE, - SERVER_NAME, - SERVER_VERSION, -} from './const.js'; + defaults, SERVER_NAME, + SERVER_VERSION } from './const.js'; import { actorDefinitionTool, callActorGetDataset, getActorsAsTools, searchTool } from './tools/index.js'; -import type { ActorTool, HelperTool, ToolWrap } from './types.js'; -import { defaults } from './const.js'; import { actorNameToToolName } from './tools/utils.js'; -import { processParamsGetTools } from './actor/utils.js'; +import type { ActorTool, HelperTool, ToolWrap } from './types.js'; /** * Create Apify MCP server */ export class ActorsMcpServer { - public server: Server; - public tools: Map; + public readonly server: Server; + public readonly tools: Map; constructor() { this.server = new Server( @@ -37,6 +34,7 @@ export class ActorsMcpServer { { capabilities: { tools: { listChanged: true }, + logging: {}, }, }, ); @@ -52,7 +50,7 @@ export class ActorsMcpServer { * Loads missing default tools. */ public async loadDefaultTools() { - const missingDefaultTools = defaults.actors.filter(name => !this.tools.has(actorNameToToolName(name))); + const missingDefaultTools = defaults.actors.filter((name) => !this.tools.has(actorNameToToolName(name))); const tools = await getActorsAsTools(missingDefaultTools); if (tools.length > 0) this.updateTools(tools); } diff --git a/src/stdio.ts b/src/stdio.ts index d04525a7..f5bc2236 100644 --- a/src/stdio.ts +++ b/src/stdio.ts @@ -19,7 +19,7 @@ import log from '@apify/log'; import { defaults } from './const.js'; import { ActorsMcpServer } from './mcp-server.js'; -import { addTool, removeTool, getActorsAsTools } from './tools/index.js'; +import { addTool, getActorsAsTools, removeTool } from './tools/index.js'; // Configure logging, set to ERROR log.setLevel(log.LEVELS.ERROR); diff --git a/src/tools/actor.ts b/src/tools/actor.ts index 45af401f..d8ef7632 100644 --- a/src/tools/actor.ts +++ b/src/tools/actor.ts @@ -3,9 +3,9 @@ import type { ActorCallOptions } from 'apify-client'; import log from '@apify/log'; +import { ACTOR_ADDITIONAL_INSTRUCTIONS, ACTOR_MAX_MEMORY_MBYTES } from '../const.js'; import type { ToolWrap } from '../types.js'; import { getActorDefinition } from './build.js'; -import { ACTOR_ADDITIONAL_INSTRUCTIONS, ACTOR_MAX_MEMORY_MBYTES } from '../const.js'; import { ApifyClient } from './mcp-apify-client.js'; import { actorNameToToolName, diff --git a/src/tools/helpers.ts b/src/tools/helpers.ts index fbe18af4..3fa344f2 100644 --- a/src/tools/helpers.ts +++ b/src/tools/helpers.ts @@ -3,9 +3,9 @@ import { z } from 'zod'; import zodToJsonSchema from 'zod-to-json-schema'; import { HelperTools } from '../const.js'; -import { actorNameToToolName } from './utils.js'; import type { ActorTool, InternalTool, ToolWrap } from '../types'; import { getActorsAsTools } from './actor.js'; +import { actorNameToToolName } from './utils.js'; const ajv = new Ajv({ coerceTypes: 'array', strict: false }); export const AddToolArgsSchema = z.object({ diff --git a/src/tsconfig.json b/src/tsconfig.json new file mode 100644 index 00000000..4a8b9089 --- /dev/null +++ b/src/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "rootDir": "./", + "outDir": "../dist", + } +} diff --git a/tests/actor-server-test.ts b/tests/actor-server-test.ts index 2a1d16ee..4785bd41 100644 --- a/tests/actor-server-test.ts +++ b/tests/actor-server-test.ts @@ -1,4 +1,4 @@ -import type { Server as HttpServer } from 'http'; +import type { Server as HttpServer } from 'node:http'; import type { Express } from 'express'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; diff --git a/tests/actor-test.ts b/tests/actor-test.ts index ba7137ba..2fd26f35 100644 --- a/tests/actor-test.ts +++ b/tests/actor-test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect } from 'vitest'; +import { describe, expect, it } from 'vitest'; import { ACTOR_ENUM_MAX_LENGTH } from '../src/const.js'; import { actorNameToToolName, inferArrayItemType, shortenEnum } from '../src/tools/utils.js'; diff --git a/tests/actor-utils-test.ts b/tests/actor-utils-test.ts index fa77b62f..566c5484 100644 --- a/tests/actor-utils-test.ts +++ b/tests/actor-utils-test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect } from 'vitest'; +import { describe, expect, it } from 'vitest'; import { parseInputParamsFromUrl } from '../src/actor/utils.js'; diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index c5811e85..16c0e4e8 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -1,14 +1,9 @@ { - "extends": "./tsconfig.json", - "include": [ - "src/**/*.ts", - "tests/**/*.ts", - "*.ts", - "*.js", - ".eslintrc.js" - ], - "exclude": [ - "node_modules", - "dist" - ] + "extends": "./tsconfig.json", + "include": [ + "src", + "test", + "tests", + "vitest.config.ts" + ], } diff --git a/tsconfig.json b/tsconfig.json index fb02aa71..8288461e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,16 +1,7 @@ { - "extends": "@apify/tsconfig", - "compilerOptions": { - "module": "ESNext", - "target": "ESNext", - "outDir": "dist", - "moduleResolution": "node", - "noUnusedLocals": false, - "lib": ["ES2022"], - "skipLibCheck": true, - "typeRoots": ["./types", "./node_modules/@types"], - "strict": true - }, - "include": ["./src/**/*"], - "exclude": ["node_modules"] + "extends": "@apify/tsconfig", + "compilerOptions": { + "module": "ES2022", + "skipLibCheck": true, + }, } From 95c311d577bcaeb5aec7e71f6367fb20426826f8 Mon Sep 17 00:00:00 2001 From: Apify Release Bot Date: Wed, 16 Apr 2025 09:03:38 +0000 Subject: [PATCH 35/42] chore(release): Update changelog and package version [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa1e1506..c2749e4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file. ### 🚀 Features - Local apify api base url (#71) ([581f096](https://github.com/apify/actors-mcp-server/commit/581f096030018aa9f1052151c5b628d9b186193b)) +- Add new specification regarding json response streamable http (#72) ([e918d26](https://github.com/apify/actors-mcp-server/commit/e918d26f224466cca58fa5ed35063c13054f9480)) ### 🐛 Bug Fixes From d8514f9ec6f92bd5abdfd12267a53b3eaf769964 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Wed, 16 Apr 2025 11:10:53 +0200 Subject: [PATCH 36/42] fix: lint issues, disable feat/decouple --- .github/workflows/check.yaml | 3 +-- .github/workflows/pre_release.yaml | 1 - src/main.ts | 8 +------- src/mcp-server.ts | 9 ++++++--- src/tools/build.ts | 8 +++++++- src/tools/mcp-apify-client.ts | 1 - 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index d7217b11..6d0364bd 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -9,7 +9,6 @@ on: push: branches: - master - - 'feat/decouple' tags-ignore: - "**" # Ignore all tags to prevent duplicate builds when tags are pushed. @@ -30,7 +29,7 @@ jobs: run: npm ci - name: Lint - run: npm run lint:fix + run: npm run lint - name: Build run: npm run build diff --git a/.github/workflows/pre_release.yaml b/.github/workflows/pre_release.yaml index 7a3bf62f..39235548 100644 --- a/.github/workflows/pre_release.yaml +++ b/.github/workflows/pre_release.yaml @@ -6,7 +6,6 @@ on: push: branches: - master - - 'feat/decouple' tags-ignore: - "**" # Ignore all tags to prevent duplicate builds when tags are pushed. diff --git a/src/main.ts b/src/main.ts index d88023fe..3577f505 100644 --- a/src/main.ts +++ b/src/main.ts @@ -12,13 +12,7 @@ import { processInput } from './actor/input.js'; import { createExpressApp } from './actor/server.js'; import type { Input } from './actor/types'; import { ActorsMcpServer } from './mcp-server.js'; -import { - actorDefinitionTool, - addTool, - callActorGetDataset, - removeTool, - searchTool, -} from './tools/index.js'; +import { actorDefinitionTool, addTool, callActorGetDataset, removeTool, searchTool } from './tools/index.js'; const STANDBY_MODE = Actor.getEnv().metaOrigin === 'STANDBY'; diff --git a/src/mcp-server.ts b/src/mcp-server.ts index cdc03c37..5ddaddf9 100644 --- a/src/mcp-server.ts +++ b/src/mcp-server.ts @@ -10,10 +10,13 @@ import type { ActorCallOptions } from 'apify-client'; import log from '@apify/log'; import { processParamsGetTools } from './actor/utils.js'; -import { ACTOR_OUTPUT_MAX_CHARS_PER_ITEM, +import { + ACTOR_OUTPUT_MAX_CHARS_PER_ITEM, ACTOR_OUTPUT_TRUNCATED_MESSAGE, - defaults, SERVER_NAME, - SERVER_VERSION } from './const.js'; + defaults, + SERVER_NAME, + SERVER_VERSION, +} from './const.js'; import { actorDefinitionTool, callActorGetDataset, getActorsAsTools, searchTool } from './tools/index.js'; import { actorNameToToolName } from './tools/utils.js'; import type { ActorTool, HelperTool, ToolWrap } from './types.js'; diff --git a/src/tools/build.ts b/src/tools/build.ts index 10c5dfb6..dc03441c 100644 --- a/src/tools/build.ts +++ b/src/tools/build.ts @@ -6,7 +6,13 @@ import zodToJsonSchema from 'zod-to-json-schema'; import log from '@apify/log'; import { ACTOR_README_MAX_LENGTH, HelperTools } from '../const.js'; -import type { ActorDefinitionPruned, ActorDefinitionWithDesc, InternalTool, ISchemaProperties, ToolWrap } from '../types.js'; +import type { + ActorDefinitionPruned, + ActorDefinitionWithDesc, + InternalTool, + ISchemaProperties, + ToolWrap, +} from '../types.js'; import { filterSchemaProperties, shortenProperties } from './utils.js'; const ajv = new Ajv({ coerceTypes: 'array', strict: false }); diff --git a/src/tools/mcp-apify-client.ts b/src/tools/mcp-apify-client.ts index 0d3dd12b..0aaacc55 100644 --- a/src/tools/mcp-apify-client.ts +++ b/src/tools/mcp-apify-client.ts @@ -1,4 +1,3 @@ - import type { ApifyClientOptions } from 'apify'; import { ApifyClient as _ApifyClient } from 'apify-client'; import type { AxiosRequestConfig } from 'axios'; From 28d5621f9ceea7482cd43b28e1244addd179044a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kopeck=C3=BD?= Date: Wed, 16 Apr 2025 14:50:29 +0200 Subject: [PATCH 37/42] feat: Actorized MCP servers proxy (#69) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: document input schema processing (#58) document Actor input schema processing in README * add VS Code instructions to README (#62) * Add vs code instructions --------- Co-authored-by: mbaiza27 Co-authored-by: Jiří Spilka * initial working draft of proxy actorized mcp servers * fix tests * fix proxy tool name handling * ci: run prerelase manually (#70) run prerelase manually * organize, fix passing of apify token * fix env var name * fix imports * get standby url from actor id, check if is mcp server based on actor name for now * Actor MCP server load default Actors * get standbyUrlBase from env var * fix standby url base, use dns friendly owner name * get mcp path from definition * Remove unused import from actor.ts * console.log to logger * Remove extra slash from MCP server URL construction * Simplify actor tools loading logic * Refactor actorOwnerDNSFriendly to handle special characters * Add TODO comment for reworking actor definition fetch logic * refactor mcp actor and utils, use real Actor ID as standby URL * fix: get-actor-definition-default-build (#73) * fix get default build in get actor definition * fix tests * fix double import, lint --------- Co-authored-by: Marc Baiza <43151891+mbaiza27@users.noreply.github.com> Co-authored-by: mbaiza27 Co-authored-by: Jiří Spilka Co-authored-by: Jiri Spilka --- src/actor/server.ts | 9 +- src/actor/types.ts | 8 -- src/actor/utils.ts | 30 +------ .../mcp-apify-client.ts => apify-client.ts} | 8 +- src/const.ts | 2 + src/index.ts | 2 +- src/{actor => }/input.ts | 0 src/main.ts | 13 ++- src/mcp/actors.ts | 88 +++++++++++++++++++ src/mcp/client.ts | 41 +++++++++ src/mcp/const.ts | 2 + src/mcp/proxy.ts | 41 +++++++++ src/{mcp-server.ts => mcp/server.ts} | 44 +++++++--- src/mcp/utils.ts | 60 +++++++++++++ src/stdio.ts | 4 +- src/tools/actor.ts | 69 ++++++++++++++- src/tools/build.ts | 15 ++-- src/tools/helpers.ts | 4 +- src/tools/store_collection.ts | 8 +- src/types.ts | 32 ++++++- tests/actor-server-test.ts | 2 +- tests/actor-utils-test.ts | 2 +- 22 files changed, 407 insertions(+), 77 deletions(-) rename src/{tools/mcp-apify-client.ts => apify-client.ts} (78%) rename src/{actor => }/input.ts (100%) create mode 100644 src/mcp/actors.ts create mode 100644 src/mcp/client.ts create mode 100644 src/mcp/const.ts create mode 100644 src/mcp/proxy.ts rename src/{mcp-server.ts => mcp/server.ts} (79%) create mode 100644 src/mcp/utils.ts diff --git a/src/actor/server.ts b/src/actor/server.ts index 29b82ad7..e386b2ac 100644 --- a/src/actor/server.ts +++ b/src/actor/server.ts @@ -11,9 +11,10 @@ import express from 'express'; import log from '@apify/log'; -import { type ActorsMcpServer } from '../mcp-server.js'; +import { type ActorsMcpServer } from '../mcp/server.js'; +import { processParamsGetTools } from '../mcp/utils.js'; import { getHelpMessage, HEADER_READINESS_PROBE, Routes } from './const.js'; -import { getActorRunData, processParamsGetTools } from './utils.js'; +import { getActorRunData } from './utils.js'; export function createExpressApp( host: string, @@ -46,7 +47,7 @@ export function createExpressApp( } try { log.info(`Received GET message at: ${Routes.ROOT}`); - const tools = await processParamsGetTools(req.url); + const tools = await processParamsGetTools(req.url, process.env.APIFY_TOKEN as string); if (tools) { mcpServer.updateTools(tools); } @@ -66,7 +67,7 @@ export function createExpressApp( app.get(Routes.SSE, async (req: Request, res: Response) => { try { log.info(`Received GET message at: ${Routes.SSE}`); - const tools = await processParamsGetTools(req.url); + const tools = await processParamsGetTools(req.url, process.env.APIFY_TOKEN as string); if (tools) { mcpServer.updateTools(tools); } diff --git a/src/actor/types.ts b/src/actor/types.ts index 5e9d3fc8..00ca61d3 100644 --- a/src/actor/types.ts +++ b/src/actor/types.ts @@ -1,11 +1,3 @@ -export type Input = { - actors: string[] | string; - enableActorAutoLoading?: boolean; - maxActorMemoryBytes?: number; - debugActor?: string; - debugActorInput?: unknown; -}; - export interface ActorRunData { id?: string; actId?: string; diff --git a/src/actor/utils.ts b/src/actor/utils.ts index f31088e6..f4a2e80a 100644 --- a/src/actor/utils.ts +++ b/src/actor/utils.ts @@ -1,34 +1,6 @@ -import { parse } from 'node:querystring'; - import { Actor } from 'apify'; -import { addTool, getActorsAsTools, removeTool } from '../tools/index.js'; -import type { ToolWrap } from '../types.js'; -import { processInput } from './input.js'; -import type { ActorRunData, Input } from './types.js'; - -export function parseInputParamsFromUrl(url: string): Input { - const query = url.split('?')[1] || ''; - const params = parse(query) as unknown as Input; - return processInput(params); -} - -/** - * Process input parameters and get tools - * If URL contains query parameter `actors`, return tools from Actors otherwise return null. - * @param url - */ -export async function processParamsGetTools(url: string) { - const input = parseInputParamsFromUrl(url); - let tools: ToolWrap[] = []; - if (input.actors) { - tools = await getActorsAsTools(input.actors as string[]); - } - if (input.enableActorAutoLoading) { - tools.push(addTool, removeTool); - } - return tools; -} +import type { ActorRunData } from './types.js'; export function getActorRunData(): ActorRunData | null { return Actor.isAtHome() ? { diff --git a/src/tools/mcp-apify-client.ts b/src/apify-client.ts similarity index 78% rename from src/tools/mcp-apify-client.ts rename to src/apify-client.ts index 0aaacc55..f3d63e2c 100644 --- a/src/tools/mcp-apify-client.ts +++ b/src/apify-client.ts @@ -2,7 +2,7 @@ import type { ApifyClientOptions } from 'apify'; import { ApifyClient as _ApifyClient } from 'apify-client'; import type { AxiosRequestConfig } from 'axios'; -import { USER_AGENT_ORIGIN } from '../const.js'; +import { USER_AGENT_ORIGIN } from './const.js'; /** * Adds a User-Agent header to the request config. @@ -16,11 +16,15 @@ function addUserAgent(config: AxiosRequestConfig): AxiosRequestConfig { return updatedConfig; } +export function getApifyAPIBaseUrl(): string { + return process.env.APIFY_API_BASE_URL || 'https://api.apify.com'; +} + export class ApifyClient extends _ApifyClient { constructor(options: ApifyClientOptions) { super({ ...options, - baseUrl: process.env.MCP_APIFY_BASE_URL || undefined, + baseUrl: getApifyAPIBaseUrl(), requestInterceptors: [addUserAgent], }); } diff --git a/src/const.ts b/src/const.ts index e23c3805..20decefe 100644 --- a/src/const.ts +++ b/src/const.ts @@ -37,3 +37,5 @@ export const defaults = { enableActorAutoLoading: false, maxMemoryMbytes: 4096, }; + +export const APIFY_USERNAME = 'apify'; diff --git a/src/index.ts b/src/index.ts index 4fb67976..555d3645 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,6 @@ The ActorsMcpServer should be the only class exported from the package */ -import { ActorsMcpServer } from './mcp-server.js'; +import { ActorsMcpServer } from './mcp/server.js'; export { ActorsMcpServer }; diff --git a/src/actor/input.ts b/src/input.ts similarity index 100% rename from src/actor/input.ts rename to src/input.ts diff --git a/src/main.ts b/src/main.ts index 3577f505..28175111 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,11 +8,12 @@ import type { ActorCallOptions } from 'apify-client'; import log from '@apify/log'; -import { processInput } from './actor/input.js'; import { createExpressApp } from './actor/server.js'; -import type { Input } from './actor/types'; -import { ActorsMcpServer } from './mcp-server.js'; -import { actorDefinitionTool, addTool, callActorGetDataset, removeTool, searchTool } from './tools/index.js'; +import { defaults } from './const.js'; +import { processInput } from './input.js'; +import { ActorsMcpServer } from './mcp/server.js'; +import { actorDefinitionTool, addTool, callActorGetDataset, getActorsAsTools, removeTool, searchTool } from './tools/index.js'; +import type { Input } from './types.js'; const STANDBY_MODE = Actor.getEnv().metaOrigin === 'STANDBY'; @@ -38,6 +39,10 @@ if (STANDBY_MODE) { if (input.enableActorAutoLoading) { tools.push(addTool, removeTool); } + const actors = input.actors ?? defaults.actors; + const actorsToLoad = Array.isArray(actors) ? actors : actors.split(','); + const actorTools = await getActorsAsTools(actorsToLoad, process.env.APIFY_TOKEN as string); + tools.push(...actorTools); mcpServer.updateTools(tools); app.listen(PORT, () => { log.info(`The Actor web server is listening for user requests at ${HOST}`); diff --git a/src/mcp/actors.ts b/src/mcp/actors.ts new file mode 100644 index 00000000..b5a45044 --- /dev/null +++ b/src/mcp/actors.ts @@ -0,0 +1,88 @@ +import type { ActorDefinition } from 'apify-client'; + +import { ApifyClient, getApifyAPIBaseUrl } from '../apify-client.js'; + +export async function isActorMCPServer(actorID: string, apifyToken: string): Promise { + const mcpPath = await getActorsMCPServerPath(actorID, apifyToken); + return (mcpPath?.length || 0) > 0; +} + +export async function getActorsMCPServerPath(actorID: string, apifyToken: string): Promise { + const actorDefinition = await getActorDefinition(actorID, apifyToken); + + if ('webServerMcpPath' in actorDefinition && typeof actorDefinition.webServerMcpPath === 'string') { + return actorDefinition.webServerMcpPath; + } + + return undefined; +} + +export async function getActorsMCPServerURL(actorID: string, apifyToken: string): Promise { + // TODO: get from API instead + const standbyBaseUrl = process.env.HOSTNAME === 'mcp-securitybyobscurity.apify.com' + ? 'securitybyobscurity.apify.actor' : 'apify.actor'; + const standbyUrl = await getActorStandbyURL(actorID, apifyToken, standbyBaseUrl); + const mcpPath = await getActorsMCPServerPath(actorID, apifyToken); + return `${standbyUrl}${mcpPath}`; +} + +/** +* Gets Actor ID from the Actor object. +* +* @param actorID +* @param apifyToken +*/ +export async function getRealActorID(actorID: string, apifyToken: string): Promise { + const apifyClient = new ApifyClient({ token: apifyToken }); + + const actor = apifyClient.actor(actorID); + const info = await actor.get(); + if (!info) { + throw new Error(`Actor ${actorID} not found`); + } + return info.id; +} + +/** +* Returns standby URL for given Actor ID. +* +* @param actorID +* @param standbyBaseUrl +* @param apifyToken +* @returns +*/ +export async function getActorStandbyURL(actorID: string, apifyToken: string, standbyBaseUrl = 'apify.actor'): Promise { + const actorRealID = await getRealActorID(actorID, apifyToken); + return `https://${actorRealID}.${standbyBaseUrl}`; +} + +export async function getActorDefinition(actorID: string, apifyToken: string): Promise { + const apifyClient = new ApifyClient({ token: apifyToken }); + const actor = apifyClient.actor(actorID); + const info = await actor.get(); + if (!info) { + throw new Error(`Actor ${actorID} not found`); + } + + const actorObjID = info.id; + const res = await fetch(`${getApifyAPIBaseUrl()}/v2/acts/${actorObjID}/builds/default`, { + headers: { + // This is done so tests can pass with public Actors without token + ...(apifyToken ? { Authorization: `Bearer ${apifyToken}` } : {}), + }, + }); + if (!res.ok) { + throw new Error(`Failed to fetch default build for actor ${actorID}: ${res.statusText}`); + } + const json = await res.json() as any; // eslint-disable-line @typescript-eslint/no-explicit-any + const buildInfo = json.data; + if (!buildInfo) { + throw new Error(`Default build for Actor ${actorID} not found`); + } + const { actorDefinition } = buildInfo; + if (!actorDefinition) { + throw new Error(`Actor default build ${actorID} does not have Actor definition`); + } + + return actorDefinition; +} diff --git a/src/mcp/client.ts b/src/mcp/client.ts new file mode 100644 index 00000000..2f5537c1 --- /dev/null +++ b/src/mcp/client.ts @@ -0,0 +1,41 @@ +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'; + +import { getMCPServerID } from './utils.js'; + +/** + * Creates and connects a ModelContextProtocol client. + */ +export async function createMCPClient( + url: string, token: string, +): Promise { + const transport = new SSEClientTransport( + new URL(url), + { + requestInit: { + headers: { + authorization: `Bearer ${token}`, + }, + }, + eventSourceInit: { + // The EventSource package augments EventSourceInit with a "fetch" parameter. + // You can use this to set additional headers on the outgoing request. + // Based on this example: https://github.com/modelcontextprotocol/typescript-sdk/issues/118 + async fetch(input: Request | URL | string, init?: RequestInit) { + const headers = new Headers(init?.headers || {}); + headers.set('authorization', `Bearer ${token}`); + return fetch(input, { ...init, headers }); + }, + // We have to cast to "any" to use it, since it's non-standard + } as any, // eslint-disable-line @typescript-eslint/no-explicit-any + }); + + const client = new Client({ + name: getMCPServerID(url), + version: '1.0.0', + }); + + await client.connect(transport); + + return client; +} diff --git a/src/mcp/const.ts b/src/mcp/const.ts new file mode 100644 index 00000000..a3cba339 --- /dev/null +++ b/src/mcp/const.ts @@ -0,0 +1,2 @@ +export const MAX_TOOL_NAME_LENGTH = 64; +export const SERVER_ID_LENGTH = 8; diff --git a/src/mcp/proxy.ts b/src/mcp/proxy.ts new file mode 100644 index 00000000..b3f7c410 --- /dev/null +++ b/src/mcp/proxy.ts @@ -0,0 +1,41 @@ +import type { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import Ajv from 'ajv'; + +import type { ActorMCPTool, ToolWrap } from '../types.js'; +import { getMCPServerID, getProxyMCPServerToolName } from './utils.js'; + +export async function getMCPServerTools( + actorID: string, + client: Client, + // Name of the MCP server + serverUrl: string, +): Promise { + const res = await client.listTools(); + const { tools } = res; + + const ajv = new Ajv({ coerceTypes: 'array', strict: false }); + + const compiledTools: ToolWrap[] = []; + for (const tool of tools) { + const mcpTool: ActorMCPTool = { + actorID, + serverId: getMCPServerID(serverUrl), + serverUrl, + originToolName: tool.name, + + name: getProxyMCPServerToolName(serverUrl, tool.name), + description: tool.description || '', + inputSchema: tool.inputSchema, + ajvValidate: ajv.compile(tool.inputSchema), + }; + + const wrap: ToolWrap = { + type: 'actor-mcp', + tool: mcpTool, + }; + + compiledTools.push(wrap); + } + + return compiledTools; +} diff --git a/src/mcp-server.ts b/src/mcp/server.ts similarity index 79% rename from src/mcp-server.ts rename to src/mcp/server.ts index 5ddaddf9..2973402e 100644 --- a/src/mcp-server.ts +++ b/src/mcp/server.ts @@ -2,6 +2,7 @@ * Model Context Protocol (MCP) server for Apify Actors */ +import type { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; @@ -9,17 +10,18 @@ import type { ActorCallOptions } from 'apify-client'; import log from '@apify/log'; -import { processParamsGetTools } from './actor/utils.js'; import { ACTOR_OUTPUT_MAX_CHARS_PER_ITEM, ACTOR_OUTPUT_TRUNCATED_MESSAGE, defaults, SERVER_NAME, SERVER_VERSION, -} from './const.js'; -import { actorDefinitionTool, callActorGetDataset, getActorsAsTools, searchTool } from './tools/index.js'; -import { actorNameToToolName } from './tools/utils.js'; -import type { ActorTool, HelperTool, ToolWrap } from './types.js'; +} from '../const.js'; +import { actorDefinitionTool, callActorGetDataset, getActorsAsTools, searchTool } from '../tools/index.js'; +import { actorNameToToolName } from '../tools/utils.js'; +import type { ActorMCPTool, ActorTool, HelperTool, ToolWrap } from '../types.js'; +import { createMCPClient } from './client.js'; +import { processParamsGetTools } from './utils.js'; /** * Create Apify MCP server @@ -52,19 +54,21 @@ export class ActorsMcpServer { /** * Loads missing default tools. */ - public async loadDefaultTools() { + public async loadDefaultTools(apifyToken: string) { const missingDefaultTools = defaults.actors.filter((name) => !this.tools.has(actorNameToToolName(name))); - const tools = await getActorsAsTools(missingDefaultTools); + const tools = await getActorsAsTools(missingDefaultTools, apifyToken); if (tools.length > 0) this.updateTools(tools); } /** * Loads tools from URL params. * + * This method also handles enabling of Actor auto loading via the processParamsGetTools. + * * Used primarily for SSE. */ - public async loadToolsFromUrl(url: string) { - const tools = await processParamsGetTools(url); + public async loadToolsFromUrl(url: string, apifyToken: string) { + const tools = await processParamsGetTools(url, apifyToken); if (tools.length > 0) this.updateTools(tools); } @@ -118,7 +122,10 @@ export class ActorsMcpServer { */ this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; - const apifyToken = request.params.apifyToken || process.env.APIFY_TOKEN; + const apifyToken = (request.params.apifyToken || process.env.APIFY_TOKEN) as string; + + // Remove apifyToken from request.params just in case + delete request.params.apifyToken; // Validate token if (!apifyToken) { @@ -148,11 +155,28 @@ export class ActorsMcpServer { args, apifyMcpServer: this, mcpServer: this.server, + apifyToken, }) as object; return { ...res }; } + if (tool.type === 'actor-mcp') { + const serverTool = tool.tool as ActorMCPTool; + let client: Client | undefined; + try { + client = await createMCPClient(serverTool.serverUrl, apifyToken); + const res = await client.callTool({ + name: serverTool.originToolName, + arguments: args, + }); + + return { ...res }; + } finally { + if (client) await client.close(); + } + } + // Handle actor tool if (tool.type === 'actor') { const actorTool = tool.tool as ActorTool; diff --git a/src/mcp/utils.ts b/src/mcp/utils.ts new file mode 100644 index 00000000..60a47d72 --- /dev/null +++ b/src/mcp/utils.ts @@ -0,0 +1,60 @@ +import { createHash } from 'node:crypto'; +import { parse } from 'node:querystring'; + +import { processInput } from '../input.js'; +import { addTool, getActorsAsTools, removeTool } from '../tools/index.js'; +import type { Input, ToolWrap } from '../types.js'; +import { MAX_TOOL_NAME_LENGTH, SERVER_ID_LENGTH } from './const.js'; + +/** + * Generates a unique server ID based on the provided URL. + * + * URL is used instead of Actor ID becase one Actor may expose multiple servers - legacy SSE / streamable HTTP. + * + * @param url The URL to generate the server ID from. + * @returns A unique server ID. + */ +export function getMCPServerID(url: string): string { + const serverHashDigest = createHash('sha256').update(url).digest('hex'); + + return serverHashDigest.slice(0, SERVER_ID_LENGTH); +} + +/** + * Generates a unique tool name based on the provided URL and tool name. + * @param url The URL to generate the tool name from. + * @param toolName The tool name to generate the tool name from. + * @returns A unique tool name. + */ +export function getProxyMCPServerToolName(url: string, toolName: string): string { + const prefix = getMCPServerID(url); + + const fullName = `${prefix}-${toolName}`; + return fullName.slice(0, MAX_TOOL_NAME_LENGTH); +} + +/** + * Process input parameters and get tools + * If URL contains query parameter `actors`, return tools from Actors otherwise return null. + * @param url + * @param apifyToken + */ +export async function processParamsGetTools(url: string, apifyToken: string) { + const input = parseInputParamsFromUrl(url); + let tools: ToolWrap[] = []; + if (input.actors) { + const actors = input.actors as string[]; + // Normal Actors as a tool + tools = await getActorsAsTools(actors, apifyToken); + } + if (input.enableActorAutoLoading) { + tools.push(addTool, removeTool); + } + return tools; +} + +export function parseInputParamsFromUrl(url: string): Input { + const query = url.split('?')[1] || ''; + const params = parse(query) as unknown as Input; + return processInput(params); +} diff --git a/src/stdio.ts b/src/stdio.ts index f5bc2236..549bf2ac 100644 --- a/src/stdio.ts +++ b/src/stdio.ts @@ -18,7 +18,7 @@ import minimist from 'minimist'; import log from '@apify/log'; import { defaults } from './const.js'; -import { ActorsMcpServer } from './mcp-server.js'; +import { ActorsMcpServer } from './mcp/server.js'; import { addTool, getActorsAsTools, removeTool } from './tools/index.js'; // Configure logging, set to ERROR @@ -37,7 +37,7 @@ if (!process.env.APIFY_TOKEN) { async function main() { const mcpServer = new ActorsMcpServer(); // Initialize tools - const tools = await getActorsAsTools(actorList.length ? actorList : defaults.actors); + const tools = await getActorsAsTools(actorList.length ? actorList : defaults.actors, process.env.APIFY_TOKEN as string); if (enableActorAutoLoading) { tools.push(addTool, removeTool); } diff --git a/src/tools/actor.ts b/src/tools/actor.ts index d8ef7632..2c4e7744 100644 --- a/src/tools/actor.ts +++ b/src/tools/actor.ts @@ -1,12 +1,16 @@ +import type { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { Ajv } from 'ajv'; import type { ActorCallOptions } from 'apify-client'; import log from '@apify/log'; +import { ApifyClient } from '../apify-client.js'; import { ACTOR_ADDITIONAL_INSTRUCTIONS, ACTOR_MAX_MEMORY_MBYTES } from '../const.js'; +import { getActorsMCPServerURL, isActorMCPServer } from '../mcp/actors.js'; +import { createMCPClient } from '../mcp/client.js'; +import { getMCPServerTools } from '../mcp/proxy.js'; import type { ToolWrap } from '../types.js'; import { getActorDefinition } from './build.js'; -import { ApifyClient } from './mcp-apify-client.js'; import { actorNameToToolName, addEnumsToDescriptionsWithExamples, @@ -55,6 +59,8 @@ export async function callActorGetDataset( } /** + * This function is used to fetch normal non-MCP server Actors as a tool. + * * Fetches actor input schemas by Actor IDs or Actor full names and creates MCP tools. * * This function retrieves the input schemas for the specified actors and compiles them into MCP tools. @@ -72,9 +78,16 @@ export async function callActorGetDataset( * @param {string[]} actors - An array of actor IDs or Actor full names. * @returns {Promise} - A promise that resolves to an array of MCP tools. */ -export async function getActorsAsTools(actors: string[]): Promise { +export async function getNormalActorsAsTools( + actors: string[], + apifyToken: string, +): Promise { const ajv = new Ajv({ coerceTypes: 'array', strict: false }); - const results = await Promise.all(actors.map(getActorDefinition)); + const getActorDefinitionWithToken = async (actorId: string) => { + const actor = await getActorDefinition(actorId, apifyToken); + return actor; + }; + const results = await Promise.all(actors.map(getActorDefinitionWithToken)); const tools: ToolWrap[] = []; for (const result of results) { if (result) { @@ -105,3 +118,53 @@ export async function getActorsAsTools(actors: string[]): Promise { } return tools; } + +async function getMCPServersAsTools( + actors: string[], + apifyToken: string, +): Promise { + const actorsMCPServerTools: ToolWrap[] = []; + for (const actorID of actors) { + const serverUrl = await getActorsMCPServerURL(actorID, apifyToken); + log.info(`ActorID: ${actorID} MCP server URL: ${serverUrl}`); + + let client: Client | undefined; + try { + client = await createMCPClient(serverUrl, apifyToken); + const serverTools = await getMCPServerTools(actorID, client, serverUrl); + actorsMCPServerTools.push(...serverTools); + } finally { + if (client) await client.close(); + } + } + + return actorsMCPServerTools; +} + +export async function getActorsAsTools( + actors: string[], + apifyToken: string, +): Promise { + log.debug(`Fetching actors as tools...`); + log.debug(`Actors: ${actors}`); + // Actorized MCP servers + const actorsMCPServers: string[] = []; + for (const actorID of actors) { + // TODO: rework, we are fetching actor definition from API twice - in the getMCPServerTools + if (await isActorMCPServer(actorID, apifyToken)) { + actorsMCPServers.push(actorID); + } + } + // Normal Actors as a tool + const toolActors = actors.filter((actorID) => !actorsMCPServers.includes(actorID)); + log.debug(`actorsMCPserver: ${actorsMCPServers}`); + log.debug(`toolActors: ${toolActors}`); + + // Normal Actors as a tool + const normalTools = await getNormalActorsAsTools(toolActors, apifyToken); + + // Tools from Actorized MCP servers + const mcpServerTools = await getMCPServersAsTools(actorsMCPServers, apifyToken); + + return [...normalTools, ...mcpServerTools]; +} diff --git a/src/tools/build.ts b/src/tools/build.ts index dc03441c..35ae6483 100644 --- a/src/tools/build.ts +++ b/src/tools/build.ts @@ -1,10 +1,10 @@ import { Ajv } from 'ajv'; -import { ApifyClient } from 'apify-client'; import { z } from 'zod'; import zodToJsonSchema from 'zod-to-json-schema'; import log from '@apify/log'; +import { ApifyClient } from '../apify-client.js'; import { ACTOR_README_MAX_LENGTH, HelperTools } from '../const.js'; import type { ActorDefinitionPruned, @@ -23,10 +23,15 @@ const ajv = new Ajv({ coerceTypes: 'array', strict: false }); * Then, fetch the build details and return actorName, description, and input schema. * @param {string} actorIdOrName - Actor ID or Actor full name. * @param {number} limit - Truncate the README to this limit. + * @param {string} apifyToken * @returns {Promise} - The actor definition with description or null if not found. */ -export async function getActorDefinition(actorIdOrName: string, limit: number = ACTOR_README_MAX_LENGTH): Promise { - const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); +export async function getActorDefinition( + actorIdOrName: string, + apifyToken: string, + limit: number = ACTOR_README_MAX_LENGTH, +): Promise { + const client = new ApifyClient({ token: apifyToken }); const actorClient = client.actor(actorIdOrName); try { // Fetch actor details @@ -120,10 +125,10 @@ export const actorDefinitionTool: ToolWrap = { inputSchema: zodToJsonSchema(GetActorDefinitionArgsSchema), ajvValidate: ajv.compile(zodToJsonSchema(GetActorDefinitionArgsSchema)), call: async (toolArgs) => { - const { args } = toolArgs; + const { args, apifyToken } = toolArgs; const parsed = GetActorDefinitionArgsSchema.parse(args); - const v = await getActorDefinition(parsed.actorName, parsed.limit); + const v = await getActorDefinition(parsed.actorName, apifyToken, parsed.limit); if (v && v.input && 'properties' in v.input && v.input) { const properties = filterSchemaProperties(v.input.properties as { [key: string]: ISchemaProperties }); v.input.properties = shortenProperties(properties); diff --git a/src/tools/helpers.ts b/src/tools/helpers.ts index 3fa344f2..2b85ecc7 100644 --- a/src/tools/helpers.ts +++ b/src/tools/helpers.ts @@ -25,9 +25,9 @@ export const addTool: ToolWrap = { ajvValidate: ajv.compile(zodToJsonSchema(AddToolArgsSchema)), // TODO: I don't like that we are passing apifyMcpServer and mcpServer to the tool call: async (toolArgs) => { - const { apifyMcpServer, mcpServer, args } = toolArgs; + const { apifyMcpServer, mcpServer, apifyToken, args } = toolArgs; const parsed = AddToolArgsSchema.parse(args); - const tools = await getActorsAsTools([parsed.actorName]); + const tools = await getActorsAsTools([parsed.actorName], apifyToken); const toolsAdded = apifyMcpServer.updateTools(tools); await mcpServer.notification({ method: 'notifications/tools/list_changed' }); diff --git a/src/tools/store_collection.ts b/src/tools/store_collection.ts index 5130624d..f8c192bb 100644 --- a/src/tools/store_collection.ts +++ b/src/tools/store_collection.ts @@ -1,9 +1,9 @@ import { Ajv } from 'ajv'; import type { ActorStoreList } from 'apify-client'; -import { ApifyClient } from 'apify-client'; 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, ToolWrap } from '../types.js'; @@ -35,10 +35,11 @@ function pruneActorStoreInfo(response: ActorStoreList): ActorStorePruned { export async function searchActorsByKeywords( search: string, + apifyToken: string, limit: number | undefined = undefined, offset: number | undefined = undefined, ): Promise { - const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); + const client = new ApifyClient({ token: apifyToken }); const results = await client.store().list({ search, limit, offset }); return results.items.map((x) => pruneActorStoreInfo(x)); } @@ -80,10 +81,11 @@ export const searchTool: ToolWrap = { inputSchema: zodToJsonSchema(SearchToolArgsSchema), ajvValidate: ajv.compile(zodToJsonSchema(SearchToolArgsSchema)), call: async (toolArgs) => { - const { args } = toolArgs; + const { args, apifyToken } = toolArgs; const parsed = SearchToolArgsSchema.parse(args); const actors = await searchActorsByKeywords( parsed.search, + apifyToken, parsed.limit, parsed.offset, ); diff --git a/src/types.ts b/src/types.ts index dc8da045..36d2634c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,7 +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 { ActorsMcpServer } from './mcp-server.js'; +import type { ActorsMcpServer } from './mcp/server.js'; export interface ISchemaProperties { type: string; @@ -83,6 +83,8 @@ export type InternalToolArgs = { apifyMcpServer: ActorsMcpServer; /** Reference to the MCP server instance */ mcpServer: Server; + /** Apify API token */ + apifyToken: string; } /** @@ -98,6 +100,24 @@ export interface HelperTool extends ToolBase { call: (toolArgs: InternalToolArgs) => Promise; } +/** +* Actorized MCP server tool where this MCP server acts as a proxy. +* Extends ToolBase with tool associated MCP server. +*/ +export interface ActorMCPTool extends ToolBase { + // Origin MCP server tool name, is needed for the tool call + originToolName: string; + // ID of the Actorized MCP server + actorID: string; + /** + * ID of the Actorized MCP server the tool is associated with. + * See getMCPServerID() + */ + serverId: string; + // Connection URL of the Actorized MCP server + serverUrl: string; +} + /** * Type discriminator for tools - indicates whether a tool is internal or Actor-based. */ @@ -111,7 +131,7 @@ export interface ToolWrap { /** Type of the tool (internal or actor) */ type: ToolType; /** The tool instance */ - tool: ActorTool | HelperTool; + tool: ActorTool | HelperTool | ActorMCPTool; } // ActorStoreList for actor-search tool @@ -152,3 +172,11 @@ export interface InternalTool extends ToolBase { */ call: (toolArgs: InternalToolArgs) => Promise; } + +export type Input = { + actors: string[] | string; + enableActorAutoLoading?: boolean; + maxActorMemoryBytes?: number; + debugActor?: string; + debugActorInput?: unknown; +}; diff --git a/tests/actor-server-test.ts b/tests/actor-server-test.ts index 4785bd41..ddeaa372 100644 --- a/tests/actor-server-test.ts +++ b/tests/actor-server-test.ts @@ -7,7 +7,7 @@ import log from '@apify/log'; import { createExpressApp } from '../src/actor/server.js'; import { HelperTools } from '../src/const.js'; -import { ActorsMcpServer } from '../src/mcp-server.js'; +import { ActorsMcpServer } from '../src/mcp/server.js'; describe('ApifyMcpServer initialization', () => { let app: Express; diff --git a/tests/actor-utils-test.ts b/tests/actor-utils-test.ts index 566c5484..f6f13163 100644 --- a/tests/actor-utils-test.ts +++ b/tests/actor-utils-test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { parseInputParamsFromUrl } from '../src/actor/utils.js'; +import { parseInputParamsFromUrl } from '../src/mcp/utils.js'; describe('parseInputParamsFromUrl', () => { it('should parse actors from URL query params', () => { From 118f0496291f4c9a1a458a659e01bd3608bc55ce Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Wed, 16 Apr 2025 20:43:58 +0200 Subject: [PATCH 38/42] fix: unit-tests --- src/input.ts | 19 +++++++++---------- src/types.ts | 4 ++-- tests/actor-utils-test.ts | 14 +++++++++++++- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/input.ts b/src/input.ts index 7d892faa..88a894e5 100644 --- a/src/input.ts +++ b/src/input.ts @@ -19,16 +19,15 @@ export function processInput(originalInput: Partial): Input { } // enableAddingActors is deprecated, use enableActorAutoLoading instead - if (input.enableActorAutoLoading !== undefined && input.enableAddingActors === undefined) { - log.warning('enableActorAutoLoading is deprecated, use enableAddingActors instead'); - input.enableAddingActors = input.enableActorAutoLoading; + if (input.enableAddingActors === undefined) { + if (input.enableActorAutoLoading !== undefined) { + log.warning('enableActorAutoLoading is deprecated, use enableAddingActors instead'); + input.enableAddingActors = input.enableActorAutoLoading === true || input.enableActorAutoLoading === 'true'; + } else { + input.enableAddingActors = false; + } + } else { + input.enableAddingActors = input.enableAddingActors === true || input.enableAddingActors === 'true'; } - - if (!input.enableAddingActors) { - input.enableAddingActors = false; - } - - input.enableActorAutoLoading = input.enableAddingActors; - return input; } diff --git a/src/types.ts b/src/types.ts index af61669a..becf1fc8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -178,8 +178,8 @@ export type Input = { /** * @deprecated Use `enableAddingActors` instead. */ - enableActorAutoLoading?: boolean; - enableAddingActors?: boolean; + enableActorAutoLoading?: boolean | string; + enableAddingActors?: boolean | string; maxActorMemoryBytes?: number; debugActor?: string; debugActorInput?: unknown; diff --git a/tests/actor-utils-test.ts b/tests/actor-utils-test.ts index f6f13163..6389885b 100644 --- a/tests/actor-utils-test.ts +++ b/tests/actor-utils-test.ts @@ -24,7 +24,19 @@ describe('parseInputParamsFromUrl', () => { it('should parse enableActorAutoLoading flag', () => { const url = 'https://actors-mcp-server.apify.actor?enableActorAutoLoading=true'; const result = parseInputParamsFromUrl(url); - expect(result.enableActorAutoLoading).toBe(true); + expect(result.enableAddingActors).toBe(true); + }); + + it('should parse enableAddingActors flag', () => { + const url = 'https://actors-mcp-server.apify.actor?enableAddingActors=true'; + const result = parseInputParamsFromUrl(url); + expect(result.enableAddingActors).toBe(true); + }); + + it('should parse enableAddingActors flag', () => { + const url = 'https://actors-mcp-server.apify.actor?enableAddingActors=false'; + const result = parseInputParamsFromUrl(url); + expect(result.enableAddingActors).toBe(false); }); it('should handle actors as string parameter', () => { From 76eba1c13376c5eb2fa779f16b59211e808028e8 Mon Sep 17 00:00:00 2001 From: MQ Date: Wed, 16 Apr 2025 23:30:58 +0200 Subject: [PATCH 39/42] fix stream not readable and apify api url base for Actor server --- src/actor/server.ts | 2 +- src/apify-client.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/actor/server.ts b/src/actor/server.ts index e386b2ac..663edf98 100644 --- a/src/actor/server.ts +++ b/src/actor/server.ts @@ -21,7 +21,6 @@ export function createExpressApp( mcpServer: ActorsMcpServer, ): express.Express { const app = express(); - app.use(express.json()); let transportSSE: SSEServerTransport; const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {}; @@ -159,6 +158,7 @@ export function createExpressApp( app.use((req: Request, res: Response) => { res.status(404).json({ message: `There is nothing at route ${req.method} ${req.originalUrl}. ${getHelpMessage(host)}` }).end(); }); + app.use(express.json()); return app; } diff --git a/src/apify-client.ts b/src/apify-client.ts index f3d63e2c..3543108b 100644 --- a/src/apify-client.ts +++ b/src/apify-client.ts @@ -17,6 +17,8 @@ function addUserAgent(config: AxiosRequestConfig): AxiosRequestConfig { } export function getApifyAPIBaseUrl(): string { + // Workaround for Actor server where the platform APIFY_API_BASE_URL did not work with getActorDefinition from actors.ts + if (process.env.APIFY_IS_AT_HOME) return 'https://api.apify.com'; return process.env.APIFY_API_BASE_URL || 'https://api.apify.com'; } From f5310a6d3fea786ce2869ddf91b8540d265ec805 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Thu, 17 Apr 2025 07:51:54 +0200 Subject: [PATCH 40/42] fix: logic when starting server --- src/actor/server.ts | 3 +++ src/main.ts | 10 +++------- src/mcp/utils.ts | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/actor/server.ts b/src/actor/server.ts index 663edf98..fe0f7d8e 100644 --- a/src/actor/server.ts +++ b/src/actor/server.ts @@ -69,6 +69,9 @@ export function createExpressApp( const tools = await processParamsGetTools(req.url, process.env.APIFY_TOKEN as string); if (tools) { mcpServer.updateTools(tools); + } else { + // We are loading default Actors (if not specified otherwise), so that we don't have "empty" tools + await mcpServer.loadDefaultTools(process.env.APIFY_TOKEN as string); } transportSSE = new SSEServerTransport(Routes.MESSAGE, res); await mcpServer.connect(transportSSE); diff --git a/src/main.ts b/src/main.ts index 28175111..33a5f4a4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,10 +9,9 @@ import type { ActorCallOptions } from 'apify-client'; import log from '@apify/log'; import { createExpressApp } from './actor/server.js'; -import { defaults } from './const.js'; import { processInput } from './input.js'; import { ActorsMcpServer } from './mcp/server.js'; -import { actorDefinitionTool, addTool, callActorGetDataset, getActorsAsTools, removeTool, searchTool } from './tools/index.js'; +import { actorDefinitionTool, addTool, callActorGetDataset, removeTool, searchTool } from './tools/index.js'; import type { Input } from './types.js'; const STANDBY_MODE = Actor.getEnv().metaOrigin === 'STANDBY'; @@ -35,14 +34,11 @@ log.info(`Loaded input: ${JSON.stringify(input)} `); if (STANDBY_MODE) { const app = createExpressApp(HOST, mcpServer); log.info('Actor is running in the STANDBY mode.'); + // Do not load default Actors here, for mastra.ai template we need to start without default Actors const tools = [searchTool, actorDefinitionTool]; - if (input.enableActorAutoLoading) { + if (input.enableAddingActors) { tools.push(addTool, removeTool); } - const actors = input.actors ?? defaults.actors; - const actorsToLoad = Array.isArray(actors) ? actors : actors.split(','); - const actorTools = await getActorsAsTools(actorsToLoad, process.env.APIFY_TOKEN as string); - tools.push(...actorTools); mcpServer.updateTools(tools); app.listen(PORT, () => { log.info(`The Actor web server is listening for user requests at ${HOST}`); diff --git a/src/mcp/utils.ts b/src/mcp/utils.ts index 60a47d72..e7b8b3d7 100644 --- a/src/mcp/utils.ts +++ b/src/mcp/utils.ts @@ -47,7 +47,7 @@ export async function processParamsGetTools(url: string, apifyToken: string) { // Normal Actors as a tool tools = await getActorsAsTools(actors, apifyToken); } - if (input.enableActorAutoLoading) { + if (input.enableAddingActors) { tools.push(addTool, removeTool); } return tools; From aadd4a035cab8520069671471b23ba1159e79d91 Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Thu, 17 Apr 2025 08:05:44 +0200 Subject: [PATCH 41/42] fix: server.ts --- src/actor/server.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/actor/server.ts b/src/actor/server.ts index fe0f7d8e..b71140ae 100644 --- a/src/actor/server.ts +++ b/src/actor/server.ts @@ -67,7 +67,7 @@ export function createExpressApp( try { log.info(`Received GET message at: ${Routes.SSE}`); const tools = await processParamsGetTools(req.url, process.env.APIFY_TOKEN as string); - if (tools) { + if (tools.length > 0) { mcpServer.updateTools(tools); } else { // We are loading default Actors (if not specified otherwise), so that we don't have "empty" tools @@ -161,7 +161,6 @@ export function createExpressApp( app.use((req: Request, res: Response) => { res.status(404).json({ message: `There is nothing at route ${req.method} ${req.originalUrl}. ${getHelpMessage(host)}` }).end(); }); - app.use(express.json()); return app; } From 5d0a84a4188adafec1c3e34aaee431648d8a4ecd Mon Sep 17 00:00:00 2001 From: Jiri Spilka Date: Thu, 17 Apr 2025 08:07:16 +0200 Subject: [PATCH 42/42] fix: change port to not collide with inspector --- src/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.ts b/src/main.ts index 33a5f4a4..ede9f9ac 100644 --- a/src/main.ts +++ b/src/main.ts @@ -19,7 +19,7 @@ const STANDBY_MODE = Actor.getEnv().metaOrigin === 'STANDBY'; await Actor.init(); const HOST = Actor.isAtHome() ? process.env.ACTOR_STANDBY_URL as string : 'http://localhost'; -const PORT = Actor.isAtHome() ? Number(process.env.ACTOR_STANDBY_PORT) : 3000; +const PORT = Actor.isAtHome() ? Number(process.env.ACTOR_STANDBY_PORT) : 3001; if (!process.env.APIFY_TOKEN) { log.error('APIFY_TOKEN is required but not set in the environment variables.');