Skip to content

chore: add tests #103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
May 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@
"build:watch": "tsc -b src -w",
"type-check": "tsc --noEmit",
"inspector": "npm run build && npx @modelcontextprotocol/inspector dist/stdio.js",
"test": "vitest run",
"test": "npm run test:unit",
"test:unit": "vitest run tests/unit",
"test:integration": "npm run build && vitest run tests/integration",
"clean": "tsc -b src --clean"
},
"author": "Apify",
Expand Down
14 changes: 10 additions & 4 deletions src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,22 @@ export enum HelperTools {
ADD_ACTOR = 'add-actor',
REMOVE_ACTOR = 'remove-actor',
GET_ACTOR_DETAILS = 'get-actor-details',
HELP_TOOL = 'help-tool',
}

export const defaults = {
actors: [
'apify/instagram-scraper',
'apify/rag-web-browser',
'lukaskrivka/google-maps-with-contact-details',
],
enableActorAutoLoading: false,
maxMemoryMbytes: 4096,
helperTools: [
HelperTools.SEARCH_ACTORS,
HelperTools.GET_ACTOR_DETAILS,
HelperTools.HELP_TOOL,
],
actorAddingTools: [
HelperTools.ADD_ACTOR,
HelperTools.REMOVE_ACTOR,
],
};

export const APIFY_USERNAME = 'apify';
23 changes: 22 additions & 1 deletion src/mcp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
SERVER_NAME,
SERVER_VERSION,
} from '../const.js';
import { helpTool } from '../tools/helpers.js';
import {
actorDefinitionTool,
addTool,
Expand Down Expand Up @@ -66,7 +67,7 @@ export class ActorsMcpServer {
this.setupToolHandlers();

// Add default tools
this.updateTools([searchTool, actorDefinitionTool]);
this.updateTools([searchTool, actorDefinitionTool, helpTool]);

// Add tools to dynamically load Actors
if (this.options.enableAddingActors) {
Expand All @@ -79,6 +80,22 @@ export class ActorsMcpServer {
});
}

/**
* Resets the server to the default state.
* This method clears all tools and loads the default tools.
* Used primarily for testing purposes.
*/
public async reset(): Promise<void> {
this.tools.clear();
this.updateTools([searchTool, actorDefinitionTool, helpTool]);
if (this.options.enableAddingActors) {
this.loadToolsToAddActors();
}

// Initialize automatically for backward compatibility
await this.initialize();
}

/**
* Initialize the server with default tools if enabled
*/
Expand Down Expand Up @@ -295,4 +312,8 @@ export class ActorsMcpServer {
async connect(transport: Transport): Promise<void> {
await this.server.connect(transport);
}

async close(): Promise<void> {
await this.server.close();
}
}
69 changes: 69 additions & 0 deletions src/tools/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,58 @@ import { getActorsAsTools } from './actor.js';
import { actorNameToToolName } from './utils.js';

const ajv = new Ajv({ coerceTypes: 'array', strict: false });

const HELP_TOOL_TEXT = `Apify MCP server help:

Note: "MCP" stands for "Model Context Protocol". The user can use the "RAG Web Browser" tool to get the content of the links mentioned in this help and present it to the user.

This MCP server can be used in the following ways:
- Locally over "STDIO".
- Remotely over "SSE" or streamable "HTTP" transport with the "Actors MCP Server Apify Actor".
- Remotely over "SSE" or streamable "HTTP" transport with "https://mcp.apify.com".

# Usage
## Locally over "STDIO"
1. The user should install the "@apify/actors-mcp-server" NPM package.
2. The user should configure the MCP client to use the MCP server. Refer to "https://github.com/apify/actors-mcp-server" or the MCP client documentation for more details (the user can specify which MCP client is being used).
The user needs to set the following environment variables:
- "APIFY_TOKEN": Apify token to authenticate with the MCP server.
If the user wants to load an Actor outside the default ones, the user needs to pass it as a CLI argument:
- "--actors <actor1,actor2,...>" // comma-separated list of Actor names, for example, "apify/rag-web-browser,apify/instagram-scraper".
If the user wants to enable the dynamic addition of Actors to the MCP server, the user needs to pass the following CLI argument:
- "--enable-adding-actors".

## Remotely over "SSE" or streamable "HTTP" transport with "Actors MCP Server Apify Actor"
1. The user should configure the MCP client to use the "Actors MCP Server Apify Actor" with:
- "SSE" transport URL: "https://actors-mcp-server.apify.actor/sse".
- Streamable "HTTP" transport URL: "https://actors-mcp-server.apify.actor/mcp".
2. The user needs to pass an "APIFY_TOKEN" as a URL query parameter "?token=<APIFY_TOKEN>" or set the following headers: "Authorization: Bearer <APIFY_TOKEN>".
If the user wants to load an Actor outside the default ones, the user needs to pass it as a URL query parameter:
- "?actors=<actor1,actor2,...>" // comma-separated list of Actor names, for example, "apify/rag-web-browser,apify/instagram-scraper".
If the user wants to enable the addition of Actors to the MCP server dynamically, the user needs to pass the following URL query parameter:
- "?enable-adding-actors=true".

## Remotely over "SSE" or streamable "HTTP" transport with "https://mcp.apify.com"
1. The user should configure the MCP client to use "https://mcp.apify.com" with:
- "SSE" transport URL: "https://mcp.apify.com/sse".
- Streamable "HTTP" transport URL: "https://mcp.apify.com/".
2. The user needs to pass an "APIFY_TOKEN" as a URL query parameter "?token=<APIFY_TOKEN>" or set the following headers: "Authorization: Bearer <APIFY_TOKEN>".
If the user wants to load an Actor outside the default ones, the user needs to pass it as a URL query parameter:
- "?actors=<actor1,actor2,...>" // comma-separated list of Actor names, for example, "apify/rag-web-browser,apify/instagram-scraper".
If the user wants to enable the addition of Actors to the MCP server dynamically, the user needs to pass the following URL query parameter:
- "?enable-adding-actors=true".

# Features
## Dynamic adding of Actors
THIS FEATURE MAY NOT BE SUPPORTED BY ALL MCP CLIENTS. THE USER MUST ENSURE THAT THE CLIENT SUPPORTS IT!
To enable this feature, see the usage section. Once dynamic adding is enabled, tools will be added that allow the user to add or remove Actors from the MCP server.
Tools related:
- "add-actor".
- "remove-actor".
If the user is using these tools and it seems like the tools have been added but cannot be called, the issue may be that the client does not support dynamic adding of Actors.
In that case, the user should check the MCP client documentation to see if the client supports this feature.
`;

export const AddToolArgsSchema = z.object({
actorName: z.string()
.describe('Add a tool, Actor or MCP-Server to available tools by Actor ID or tool full name.'
Expand Down Expand Up @@ -64,3 +116,20 @@ export const removeTool: ToolWrap = {
},
} as InternalTool,
};

// Tool takes no arguments
export const HelpToolArgsSchema = z.object({});
export const helpTool: ToolWrap = {
type: 'internal',
tool: {
name: HelperTools.HELP_TOOL,
description: 'Helper tool to get information on how to use and troubleshoot the Apify MCP server. '
+ 'This tool always returns the same help message with information about the server and how to use it. '
+ 'Call this tool in case of any problems or uncertainties with the server. ',
inputSchema: zodToJsonSchema(HelpToolArgsSchema),
ajvValidate: ajv.compile(zodToJsonSchema(HelpToolArgsSchema)),
call: async () => {
return { content: [{ type: 'text', text: HELP_TOOL_TEXT }] };
},
} as InternalTool,
};
29 changes: 29 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Tests

This directory contains **unit** and **integration** tests for the `actors-mcp-server` project.

# Unit Tests

Unit tests are located in the `tests/unit` directory.

To run the unit tests, you can use the following command:
```bash
npm run test:unit
```

# Integration Tests

Integration tests are located in the `tests/integration` directory.
In order to run the integration tests, you need to have the `APIFY_TOKEN` environment variable set.
Also following Actors need to exist on the target execution Apify platform:
```
ALL DEFAULT ONES DEFINED IN consts.ts AND ALSO EXPLICITLY:
apify/rag-web-browser
apify/instagram-scraper
apify/python-example
```

To run the integration tests, you can use the following command:
```bash
APIFY_TOKEN=your_token npm run test:integration
```
68 changes: 0 additions & 68 deletions tests/actor-server-test.ts

This file was deleted.

111 changes: 111 additions & 0 deletions tests/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

export interface MCPClientOptions {
actors?: string[];
enableAddingActors?: boolean;
}

export async function createMCPSSEClient(
serverUrl: string,
options?: MCPClientOptions,
): Promise<Client> {
if (!process.env.APIFY_TOKEN) {
throw new Error('APIFY_TOKEN environment variable is not set.');
}
const url = new URL(serverUrl);
const { actors, enableAddingActors } = options || {};
if (actors) {
url.searchParams.append('actors', actors.join(','));
}
if (enableAddingActors) {
url.searchParams.append('enableAddingActors', 'true');
}

const transport = new SSEClientTransport(
url,
{
requestInit: {
headers: {
authorization: `Bearer ${process.env.APIFY_TOKEN}`,
},
},
},
);

const client = new Client({
name: 'sse-client',
version: '1.0.0',
});
await client.connect(transport);

return client;
}

export async function createMCPStreamableClient(
serverUrl: string,
options?: MCPClientOptions,
): Promise<Client> {
if (!process.env.APIFY_TOKEN) {
throw new Error('APIFY_TOKEN environment variable is not set.');
}
const url = new URL(serverUrl);
const { actors, enableAddingActors } = options || {};
if (actors) {
url.searchParams.append('actors', actors.join(','));
}
if (enableAddingActors) {
url.searchParams.append('enableAddingActors', 'true');
}

const transport = new StreamableHTTPClientTransport(
url,
{
requestInit: {
headers: {
authorization: `Bearer ${process.env.APIFY_TOKEN}`,
},
},
},
);

const client = new Client({
name: 'streamable-http-client',
version: '1.0.0',
});
await client.connect(transport);

return client;
}

export async function createMCPStdioClient(
options?: MCPClientOptions,
): Promise<Client> {
if (!process.env.APIFY_TOKEN) {
throw new Error('APIFY_TOKEN environment variable is not set.');
}
const { actors, enableAddingActors } = options || {};
const args = ['dist/stdio.js'];
if (actors) {
args.push('--actors', actors.join(','));
}
if (enableAddingActors) {
args.push('--enable-adding-actors');
}
const transport = new StdioClientTransport({
command: 'node',
args,
env: {
APIFY_TOKEN: process.env.APIFY_TOKEN as string,
},
});
const client = new Client({
name: 'stdio-client',
version: '1.0.0',
});
await client.connect(transport);

return client;
}
Loading