Skip to content

Commit f70825f

Browse files
MQ37Copilotjirispilka
authored
chore: add tests (#103)
* add default tools stdio test * run unit only by default, add more stdio tests, fix concurrency in actor server tests * vibe unit tests * fix default tool loading for Actors MCP server, more integration tests * organize tests * add streamable Actor server tests, improve consistency with real setup * decouple actor mcp server helper funcs and actors mcp server main MCPServer class creation * add tests readme * rename streamable client * unify default tools loading * feat: add help tool (#111) * add help tool * Update src/tools/helpers.ts Co-authored-by: Copilot <[email protected]> * fix: move tool text into a constant * fix: helpTool text * Update src/tools/helpers.ts Co-authored-by: Jiří Spilka <[email protected]> --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: Jiri Spilka <[email protected]> Co-authored-by: Jiří Spilka <[email protected]> * removed createActorMCPServer wrapper - using ActorsMCPServer class directly, removed duplicate tools.actor tests and renamed actor.test.ts, removed old TODOs * use single http server insteance for each describe clock and reset state for each state to fix MaxListenersExceededWarning * Update src/mcp/server.ts Co-authored-by: Copilot <[email protected]> * fix: actor -> Actor * refactor tests and create a shared test suite --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: Jiri Spilka <[email protected]> Co-authored-by: Jiří Spilka <[email protected]>
1 parent 7d0ac9f commit f70825f

16 files changed

+869
-83
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@
6666
"build:watch": "tsc -b src -w",
6767
"type-check": "tsc --noEmit",
6868
"inspector": "npm run build && npx @modelcontextprotocol/inspector dist/stdio.js",
69-
"test": "vitest run",
69+
"test": "npm run test:unit",
70+
"test:unit": "vitest run tests/unit",
71+
"test:integration": "npm run build && vitest run tests/integration",
7072
"clean": "tsc -b src --clean"
7173
},
7274
"author": "Apify",

src/const.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,22 @@ export enum HelperTools {
2626
ADD_ACTOR = 'add-actor',
2727
REMOVE_ACTOR = 'remove-actor',
2828
GET_ACTOR_DETAILS = 'get-actor-details',
29+
HELP_TOOL = 'help-tool',
2930
}
3031

3132
export const defaults = {
3233
actors: [
33-
'apify/instagram-scraper',
3434
'apify/rag-web-browser',
35-
'lukaskrivka/google-maps-with-contact-details',
3635
],
37-
enableActorAutoLoading: false,
38-
maxMemoryMbytes: 4096,
36+
helperTools: [
37+
HelperTools.SEARCH_ACTORS,
38+
HelperTools.GET_ACTOR_DETAILS,
39+
HelperTools.HELP_TOOL,
40+
],
41+
actorAddingTools: [
42+
HelperTools.ADD_ACTOR,
43+
HelperTools.REMOVE_ACTOR,
44+
],
3945
};
4046

4147
export const APIFY_USERNAME = 'apify';

src/mcp/server.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
SERVER_NAME,
1818
SERVER_VERSION,
1919
} from '../const.js';
20+
import { helpTool } from '../tools/helpers.js';
2021
import {
2122
actorDefinitionTool,
2223
addTool,
@@ -66,7 +67,7 @@ export class ActorsMcpServer {
6667
this.setupToolHandlers();
6768

6869
// Add default tools
69-
this.updateTools([searchTool, actorDefinitionTool]);
70+
this.updateTools([searchTool, actorDefinitionTool, helpTool]);
7071

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

83+
/**
84+
* Resets the server to the default state.
85+
* This method clears all tools and loads the default tools.
86+
* Used primarily for testing purposes.
87+
*/
88+
public async reset(): Promise<void> {
89+
this.tools.clear();
90+
this.updateTools([searchTool, actorDefinitionTool, helpTool]);
91+
if (this.options.enableAddingActors) {
92+
this.loadToolsToAddActors();
93+
}
94+
95+
// Initialize automatically for backward compatibility
96+
await this.initialize();
97+
}
98+
8299
/**
83100
* Initialize the server with default tools if enabled
84101
*/
@@ -295,4 +312,8 @@ export class ActorsMcpServer {
295312
async connect(transport: Transport): Promise<void> {
296313
await this.server.connect(transport);
297314
}
315+
316+
async close(): Promise<void> {
317+
await this.server.close();
318+
}
298319
}

src/tools/helpers.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,58 @@ import { getActorsAsTools } from './actor.js';
88
import { actorNameToToolName } from './utils.js';
99

1010
const ajv = new Ajv({ coerceTypes: 'array', strict: false });
11+
12+
const HELP_TOOL_TEXT = `Apify MCP server help:
13+
14+
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.
15+
16+
This MCP server can be used in the following ways:
17+
- Locally over "STDIO".
18+
- Remotely over "SSE" or streamable "HTTP" transport with the "Actors MCP Server Apify Actor".
19+
- Remotely over "SSE" or streamable "HTTP" transport with "https://mcp.apify.com".
20+
21+
# Usage
22+
## Locally over "STDIO"
23+
1. The user should install the "@apify/actors-mcp-server" NPM package.
24+
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).
25+
The user needs to set the following environment variables:
26+
- "APIFY_TOKEN": Apify token to authenticate with the MCP server.
27+
If the user wants to load an Actor outside the default ones, the user needs to pass it as a CLI argument:
28+
- "--actors <actor1,actor2,...>" // comma-separated list of Actor names, for example, "apify/rag-web-browser,apify/instagram-scraper".
29+
If the user wants to enable the dynamic addition of Actors to the MCP server, the user needs to pass the following CLI argument:
30+
- "--enable-adding-actors".
31+
32+
## Remotely over "SSE" or streamable "HTTP" transport with "Actors MCP Server Apify Actor"
33+
1. The user should configure the MCP client to use the "Actors MCP Server Apify Actor" with:
34+
- "SSE" transport URL: "https://actors-mcp-server.apify.actor/sse".
35+
- Streamable "HTTP" transport URL: "https://actors-mcp-server.apify.actor/mcp".
36+
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>".
37+
If the user wants to load an Actor outside the default ones, the user needs to pass it as a URL query parameter:
38+
- "?actors=<actor1,actor2,...>" // comma-separated list of Actor names, for example, "apify/rag-web-browser,apify/instagram-scraper".
39+
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:
40+
- "?enable-adding-actors=true".
41+
42+
## Remotely over "SSE" or streamable "HTTP" transport with "https://mcp.apify.com"
43+
1. The user should configure the MCP client to use "https://mcp.apify.com" with:
44+
- "SSE" transport URL: "https://mcp.apify.com/sse".
45+
- Streamable "HTTP" transport URL: "https://mcp.apify.com/".
46+
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>".
47+
If the user wants to load an Actor outside the default ones, the user needs to pass it as a URL query parameter:
48+
- "?actors=<actor1,actor2,...>" // comma-separated list of Actor names, for example, "apify/rag-web-browser,apify/instagram-scraper".
49+
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:
50+
- "?enable-adding-actors=true".
51+
52+
# Features
53+
## Dynamic adding of Actors
54+
THIS FEATURE MAY NOT BE SUPPORTED BY ALL MCP CLIENTS. THE USER MUST ENSURE THAT THE CLIENT SUPPORTS IT!
55+
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.
56+
Tools related:
57+
- "add-actor".
58+
- "remove-actor".
59+
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.
60+
In that case, the user should check the MCP client documentation to see if the client supports this feature.
61+
`;
62+
1163
export const AddToolArgsSchema = z.object({
1264
actorName: z.string()
1365
.describe('Add a tool, Actor or MCP-Server to available tools by Actor ID or tool full name.'
@@ -64,3 +116,20 @@ export const removeTool: ToolWrap = {
64116
},
65117
} as InternalTool,
66118
};
119+
120+
// Tool takes no arguments
121+
export const HelpToolArgsSchema = z.object({});
122+
export const helpTool: ToolWrap = {
123+
type: 'internal',
124+
tool: {
125+
name: HelperTools.HELP_TOOL,
126+
description: 'Helper tool to get information on how to use and troubleshoot the Apify MCP server. '
127+
+ 'This tool always returns the same help message with information about the server and how to use it. '
128+
+ 'Call this tool in case of any problems or uncertainties with the server. ',
129+
inputSchema: zodToJsonSchema(HelpToolArgsSchema),
130+
ajvValidate: ajv.compile(zodToJsonSchema(HelpToolArgsSchema)),
131+
call: async () => {
132+
return { content: [{ type: 'text', text: HELP_TOOL_TEXT }] };
133+
},
134+
} as InternalTool,
135+
};

tests/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Tests
2+
3+
This directory contains **unit** and **integration** tests for the `actors-mcp-server` project.
4+
5+
# Unit Tests
6+
7+
Unit tests are located in the `tests/unit` directory.
8+
9+
To run the unit tests, you can use the following command:
10+
```bash
11+
npm run test:unit
12+
```
13+
14+
# Integration Tests
15+
16+
Integration tests are located in the `tests/integration` directory.
17+
In order to run the integration tests, you need to have the `APIFY_TOKEN` environment variable set.
18+
Also following Actors need to exist on the target execution Apify platform:
19+
```
20+
ALL DEFAULT ONES DEFINED IN consts.ts AND ALSO EXPLICITLY:
21+
apify/rag-web-browser
22+
apify/instagram-scraper
23+
apify/python-example
24+
```
25+
26+
To run the integration tests, you can use the following command:
27+
```bash
28+
APIFY_TOKEN=your_token npm run test:integration
29+
```

tests/actor-server-test.ts

Lines changed: 0 additions & 68 deletions
This file was deleted.

tests/helpers.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2+
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
3+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
4+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
5+
6+
export interface MCPClientOptions {
7+
actors?: string[];
8+
enableAddingActors?: boolean;
9+
}
10+
11+
export async function createMCPSSEClient(
12+
serverUrl: string,
13+
options?: MCPClientOptions,
14+
): Promise<Client> {
15+
if (!process.env.APIFY_TOKEN) {
16+
throw new Error('APIFY_TOKEN environment variable is not set.');
17+
}
18+
const url = new URL(serverUrl);
19+
const { actors, enableAddingActors } = options || {};
20+
if (actors) {
21+
url.searchParams.append('actors', actors.join(','));
22+
}
23+
if (enableAddingActors) {
24+
url.searchParams.append('enableAddingActors', 'true');
25+
}
26+
27+
const transport = new SSEClientTransport(
28+
url,
29+
{
30+
requestInit: {
31+
headers: {
32+
authorization: `Bearer ${process.env.APIFY_TOKEN}`,
33+
},
34+
},
35+
},
36+
);
37+
38+
const client = new Client({
39+
name: 'sse-client',
40+
version: '1.0.0',
41+
});
42+
await client.connect(transport);
43+
44+
return client;
45+
}
46+
47+
export async function createMCPStreamableClient(
48+
serverUrl: string,
49+
options?: MCPClientOptions,
50+
): Promise<Client> {
51+
if (!process.env.APIFY_TOKEN) {
52+
throw new Error('APIFY_TOKEN environment variable is not set.');
53+
}
54+
const url = new URL(serverUrl);
55+
const { actors, enableAddingActors } = options || {};
56+
if (actors) {
57+
url.searchParams.append('actors', actors.join(','));
58+
}
59+
if (enableAddingActors) {
60+
url.searchParams.append('enableAddingActors', 'true');
61+
}
62+
63+
const transport = new StreamableHTTPClientTransport(
64+
url,
65+
{
66+
requestInit: {
67+
headers: {
68+
authorization: `Bearer ${process.env.APIFY_TOKEN}`,
69+
},
70+
},
71+
},
72+
);
73+
74+
const client = new Client({
75+
name: 'streamable-http-client',
76+
version: '1.0.0',
77+
});
78+
await client.connect(transport);
79+
80+
return client;
81+
}
82+
83+
export async function createMCPStdioClient(
84+
options?: MCPClientOptions,
85+
): Promise<Client> {
86+
if (!process.env.APIFY_TOKEN) {
87+
throw new Error('APIFY_TOKEN environment variable is not set.');
88+
}
89+
const { actors, enableAddingActors } = options || {};
90+
const args = ['dist/stdio.js'];
91+
if (actors) {
92+
args.push('--actors', actors.join(','));
93+
}
94+
if (enableAddingActors) {
95+
args.push('--enable-adding-actors');
96+
}
97+
const transport = new StdioClientTransport({
98+
command: 'node',
99+
args,
100+
env: {
101+
APIFY_TOKEN: process.env.APIFY_TOKEN as string,
102+
},
103+
});
104+
const client = new Client({
105+
name: 'stdio-client',
106+
version: '1.0.0',
107+
});
108+
await client.connect(transport);
109+
110+
return client;
111+
}

0 commit comments

Comments
 (0)