Skip to content

Commit 9eb5937

Browse files
committed
Updating PR #29 with required changes
2 parents 49beaec + b0b4952 commit 9eb5937

File tree

15 files changed

+385
-78
lines changed

15 files changed

+385
-78
lines changed

.actor/input_schema.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,19 @@
1414
"lukaskrivka/google-maps-with-contact-details"
1515
]
1616
},
17+
"enableActorAutoLoading": {
18+
"title": "Enable automatic loading of Actors based on context and use-case (experimental, check if it supported by your client)",
19+
"type": "boolean",
20+
"description": "When enabled, the server can dynamically add Actors as tools based on user requests and context. \n\nNote: Not all MCP clients support this feature. To try it, you can use the [Tester MCP Client](https://apify.com/jiri.spilka/tester-mcp-client). This is an experimental feature and may require client-specific support.",
21+
"default": false
22+
},
23+
"maxActorMemoryBytes": {
24+
"title": "Limit the maximum memory used by an Actor",
25+
"type": "integer",
26+
"description": "Limit the maximum memory used by an Actor in bytes. This is important setting for Free plan users to avoid exceeding the memory limit.",
27+
"prefill": 4096,
28+
"default": 4096
29+
},
1730
"debugActor": {
1831
"title": "Debug Actor",
1932
"type": "string",
@@ -28,7 +41,7 @@
2841
"description": "Specify the input for the Actor that will be used for debugging in normal mode",
2942
"editor": "json",
3043
"prefill": {
31-
"query": "hello world"
44+
"query": "hello world"
3245
}
3346
}
3447
}

CHANGELOG.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,41 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
<!-- git-cliff-unreleased-start -->
6+
## 0.1.10 - **not yet released**
7+
8+
### 🚀 Features
9+
10+
- Update README with a link to relevant blogposts ([#34](https://github.com/apify/actors-mcp-server/pull/34)) ([a7c8ea2](https://github.com/apify/actors-mcp-server/commit/a7c8ea24da243283195822d16b56f135786866f4)) by [@jirispilka](https://github.com/jirispilka)
11+
12+
### 🐛 Bug Fixes
13+
14+
- Update README.md ([#33](https://github.com/apify/actors-mcp-server/pull/33)) ([d053c63](https://github.com/apify/actors-mcp-server/commit/d053c6381939e46da7edce409a529fd1581a8143)) by [@RVCA212](https://github.com/RVCA212)
15+
16+
17+
<!-- git-cliff-unreleased-end -->
18+
## [0.1.9](https://github.com/apify/actors-mcp-server/releases/tag/v0.1.9) (2025-02-07)
19+
20+
### 🐛 Bug Fixes
21+
22+
- Stdio and SSE example, improve logging ([#32](https://github.com/apify/actors-mcp-server/pull/32)) ([1b1852c](https://github.com/apify/actors-mcp-server/commit/1b1852cdb49c5de3f8dd48a1d9abc5fd28c58b3a)) by [@jirispilka](https://github.com/jirispilka)
23+
24+
25+
## [0.1.8](https://github.com/apify/actors-mcp-server/releases/tag/v0.1.8) (2025-01-31)
26+
27+
### 🐛 Bug Fixes
28+
29+
- Actor auto loading (corret tool-&gt;Actor name conversion) ([#31](https://github.com/apify/actors-mcp-server/pull/31)) ([45073ea](https://github.com/apify/actors-mcp-server/commit/45073ea49f56784cc4e11bed84c01bcb136b2d8e)) by [@jirispilka](https://github.com/jirispilka)
30+
31+
32+
## [0.1.7](https://github.com/apify/actors-mcp-server/releases/tag/v0.1.7) (2025-01-30)
33+
34+
### 🐛 Bug Fixes
35+
36+
- Add internal tools for Actor discovery ([#28](https://github.com/apify/actors-mcp-server/pull/28)) ([193f098](https://github.com/apify/actors-mcp-server/commit/193f0983255d8170c90109d162589e62ec10e340)) by [@jirispilka](https://github.com/jirispilka)
37+
- Update README.md ([#30](https://github.com/apify/actors-mcp-server/pull/30)) ([23bb32e](https://github.com/apify/actors-mcp-server/commit/23bb32e1f2d5b10d3d557de87cb2d97b5e81921b)) by [@jirispilka](https://github.com/jirispilka)
38+
39+
540
## [0.1.6](https://github.com/apify/actors-mcp-server/releases/tag/v0.1.6) (2025-01-23)
641

742
### 🐛 Bug Fixes

docs/actors-mcp-server.png

79.9 KB
Loading

package-lock.json

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@apify/actors-mcp-server",
3-
"version": "0.1.6",
3+
"version": "0.1.10",
44
"type": "module",
55
"description": "Model Context Protocol Server for Apify Actors",
66
"engines": {
@@ -35,7 +35,9 @@
3535
"apify": "^3.2.6",
3636
"apify-client": "^2.11.1",
3737
"express": "^4.21.2",
38-
"minimist": "^1.2.8"
38+
"minimist": "^1.2.8",
39+
"zod": "^3.24.1",
40+
"zod-to-json-schema": "^3.24.1"
3941
},
4042
"devDependencies": {
4143
"@anthropic-ai/sdk": "^0.33.1",

src/actorDefinition.ts renamed to src/actors.ts

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
import { Ajv } from 'ajv';
22
import { ApifyClient } from 'apify-client';
33

4-
import { MAX_DESCRIPTION_LENGTH, MAX_ENUM_LENGTH, MAX_MEMORY_MBYTES } from './const.js';
4+
import { ACTOR_ADDITIONAL_INSTRUCTIONS, defaults, MAX_DESCRIPTION_LENGTH } from './const.js';
55
import { log } from './logger.js';
6-
import type { ActorDefinitionWithDesc, SchemaProperties, Tool } from './types.js';
6+
import type { ActorDefinitionPruned, ActorDefinitionWithDesc, SchemaProperties, Tool } from './types.js';
7+
8+
export function actorNameToToolName(actorName: string): string {
9+
return actorName.replace('/', '--');
10+
}
11+
12+
export function toolNameToActorName(toolName: string): string {
13+
return toolName.replace('--', '/');
14+
}
715

816
/**
917
* Get actor input schema by actor name.
@@ -12,11 +20,7 @@ import type { ActorDefinitionWithDesc, SchemaProperties, Tool } from './types.js
1220
* @param {string} actorFullName - The full name of the actor.
1321
* @returns {Promise<ActorDefinitionWithDesc | null>} - The actor definition with description or null if not found.
1422
*/
15-
async function fetchActorDefinition(actorFullName: string): Promise<ActorDefinitionWithDesc | null> {
16-
if (!process.env.APIFY_TOKEN) {
17-
log.error('APIFY_TOKEN is required but not set. Please set it as an environment variable');
18-
return null;
19-
}
23+
export async function getActorDefinition(actorFullName: string): Promise<ActorDefinitionPruned | null> {
2024
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
2125
const actorClient = client.actor(actorFullName);
2226

@@ -43,32 +47,37 @@ async function fetchActorDefinition(actorFullName: string): Promise<ActorDefinit
4347
if (buildDetails?.actorDefinition) {
4448
const actorDefinitions = buildDetails?.actorDefinition as ActorDefinitionWithDesc;
4549
actorDefinitions.description = actor.description || '';
46-
actorDefinitions.name = actorFullName;
50+
actorDefinitions.actorFullName = actorFullName;
4751
actorDefinitions.defaultRunOptions = actor.defaultRunOptions;
48-
return actorDefinitions;
52+
return pruneActorDefinition(actorDefinitions);
4953
}
5054
return null;
5155
} catch (error) {
5256
log.error(`Failed to fetch input schema for actor: ${actorFullName} with error ${error}.`);
53-
return null;
57+
throw new Error(`Failed to fetch input schema for actor: ${actorFullName} with error ${error}.`);
5458
}
5559
}
5660

61+
function pruneActorDefinition(response: ActorDefinitionWithDesc): ActorDefinitionPruned {
62+
return {
63+
actorFullName: response.actorFullName || '',
64+
buildTag: response?.buildTag || '',
65+
readme: response?.readme || '',
66+
input: response?.input || null,
67+
description: response.description,
68+
defaultRunOptions: response.defaultRunOptions,
69+
};
70+
}
71+
5772
/**
5873
* Shortens the description and enum values of schema properties.
5974
* @param properties
6075
*/
61-
function shortenProperties(properties: { [key: string]: SchemaProperties}): { [key: string]: SchemaProperties } {
76+
export function shortenProperties(properties: { [key: string]: SchemaProperties}): { [key: string]: SchemaProperties } {
6277
for (const property of Object.values(properties)) {
6378
if (property.description.length > MAX_DESCRIPTION_LENGTH) {
6479
property.description = `${property.description.slice(0, MAX_DESCRIPTION_LENGTH)}...`;
6580
}
66-
if (property.enum) {
67-
property.enum = property.enum.slice(0, MAX_ENUM_LENGTH);
68-
}
69-
if (property.enumTitles) {
70-
property.enumTitles = property.enumTitles.slice(0, MAX_ENUM_LENGTH);
71-
}
7281
}
7382
return properties;
7483
}
@@ -77,11 +86,11 @@ function shortenProperties(properties: { [key: string]: SchemaProperties}): { [k
7786
* Filters schema properties to include only the necessary fields.
7887
* @param properties
7988
*/
80-
function filterSchemaProperties(properties: { [key: string]: SchemaProperties }): { [key: string]: SchemaProperties } {
89+
export function filterSchemaProperties(properties: { [key: string]: SchemaProperties }): { [key: string]: SchemaProperties } {
8190
const filteredProperties: { [key: string]: SchemaProperties } = {};
8291
for (const [key, property] of Object.entries(properties)) {
83-
const { title, description, enum: enumValues, enumTitles, type, default: defaultValue, prefill } = property;
84-
filteredProperties[key] = { title, description, enum: enumValues, enumTitles, type, default: defaultValue, prefill };
92+
const { title, description, enum: enumValues, type, default: defaultValue, prefill } = property;
93+
filteredProperties[key] = { title, description, enum: enumValues, type, default: defaultValue, prefill };
8594
}
8695
return filteredProperties;
8796
}
@@ -98,9 +107,8 @@ function filterSchemaProperties(properties: { [key: string]: SchemaProperties })
98107
* @returns {Promise<Tool[]>} - A promise that resolves to an array of MCP tools.
99108
*/
100109
export async function getActorsAsTools(actors: string[]): Promise<Tool[]> {
101-
// Fetch input schemas in parallel
102110
const ajv = new Ajv({ coerceTypes: 'array', strict: false });
103-
const results = await Promise.all(actors.map(fetchActorDefinition));
111+
const results = await Promise.all(actors.map(getActorDefinition));
104112
const tools = [];
105113
for (const result of results) {
106114
if (result) {
@@ -109,17 +117,17 @@ export async function getActorsAsTools(actors: string[]): Promise<Tool[]> {
109117
result.input.properties = shortenProperties(properties);
110118
}
111119
try {
112-
const memoryMbytes = result.defaultRunOptions?.memoryMbytes || MAX_MEMORY_MBYTES;
120+
const memoryMbytes = result.defaultRunOptions?.memoryMbytes || defaults.maxMemoryMbytes;
113121
tools.push({
114-
name: result.name.replace('/', '_'),
115-
actorName: result.name,
116-
description: result.description,
122+
name: actorNameToToolName(result.actorFullName),
123+
actorFullName: result.actorFullName,
124+
description: `${result.description} Instructions: ${ACTOR_ADDITIONAL_INSTRUCTIONS}`,
117125
inputSchema: result.input || {},
118126
ajvValidate: ajv.compile(result.input || {}),
119-
memoryMbytes: memoryMbytes > MAX_MEMORY_MBYTES ? MAX_MEMORY_MBYTES : memoryMbytes,
127+
memoryMbytes: memoryMbytes > defaults.maxMemoryMbytes ? defaults.maxMemoryMbytes : memoryMbytes,
120128
});
121129
} catch (validationError) {
122-
log.error(`Failed to compile AJV schema for actor: ${result.name}. Error: ${validationError}`);
130+
log.error(`Failed to compile AJV schema for actor: ${result.actorFullName}. Error: ${validationError}`);
123131
}
124132
}
125133
}

src/const.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,7 @@ export const SERVER_NAME = 'apify-mcp-server';
22
export const SERVER_VERSION = '0.1.0';
33

44
export const HEADER_READINESS_PROBE = 'x-apify-container-server-readiness-probe';
5-
6-
export const MAX_ENUM_LENGTH = 50;
7-
export const MAX_DESCRIPTION_LENGTH = 200;
8-
// Limit memory to 4GB for Actors. Free users have 8 GB limit, but we need to reserve some memory for Actors-MCP-Server too
9-
export const MAX_MEMORY_MBYTES = 4096;
10-
5+
export const MAX_DESCRIPTION_LENGTH = 500;
116
export const USER_AGENT_ORIGIN = 'Origin/mcp-server';
127

138
export const defaults = {
@@ -16,11 +11,22 @@ export const defaults = {
1611
'apify/rag-web-browser',
1712
'lukaskrivka/google-maps-with-contact-details',
1813
],
14+
enableActorAutoLoading: false,
15+
maxMemoryMbytes: 4096,
1916
};
2017

21-
export const ACTOR_OUTPUT_MAX_CHARS_PER_ITEM = 2_000;
18+
export const ACTOR_OUTPUT_MAX_CHARS_PER_ITEM = 5_000;
2219
export const ACTOR_OUTPUT_TRUNCATED_MESSAGE = `Output was truncated because it will not fit into context.`
23-
+ ` There is no reason to call this tool again!`;
20+
+ `There is no reason to call this tool again!`;
21+
export const ACTOR_ADDITIONAL_INSTRUCTIONS = 'Never call/execute tool/Actor unless confirmed by the user. '
22+
+ 'Always limit the number of results in the call arguments.';
23+
24+
export enum InternalTools {
25+
DISCOVER_ACTORS = 'discover-actors',
26+
ADD_ACTOR_TO_TOOLS = 'add-actor-to-tools',
27+
REMOVE_ACTOR_FROM_TOOLS = 'remove-actor-from-tools',
28+
GET_ACTOR_DETAILS = 'get-actor-details',
29+
}
2430

2531
export enum Routes {
2632
ROOT = '/',

src/examples/clientSse.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Connect to the MCP server using SSE transport and call a tool.
44
* The Actors MCP Server will load default Actors.
55
*
6+
* It requires the `APIFY_TOKEN` in the `.env` file.
67
*/
78

89
import path from 'path';
@@ -15,15 +16,14 @@ import dotenv from 'dotenv';
1516
import { EventSource } from 'eventsource';
1617

1718
const REQUEST_TIMEOUT = 120_000; // 2 minutes
18-
// Resolve dirname equivalent in ES module
1919
const filename = fileURLToPath(import.meta.url);
2020
const dirname = path.dirname(filename);
2121

2222
dotenv.config({ path: path.resolve(dirname, '../../.env') });
2323

2424
const SERVER_URL = 'https://actors-mcp-server.apify.actor/sse';
25-
// We need to change forward slash / to underscore _ in the tool name as Anthropic does not allow forward slashes in the tool name
26-
const SELECTED_TOOL = 'apify_rag-web-browser';
25+
// We need to change forward slash / to underscore -- in the tool name as Anthropic does not allow forward slashes in the tool name
26+
const SELECTED_TOOL = 'apify--rag-web-browser';
2727
const QUERY = 'web browser for Anthropic';
2828

2929
if (!process.env.APIFY_TOKEN) {

src/examples/clientStdio.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const SERVER_PATH = path.resolve(dirname, '../../dist/index.js');
2424
const NODE_PATH = execSync(process.platform === 'win32' ? 'where node' : 'which node').toString().trim();
2525

2626
const TOOLS = 'apify/rag-web-browser,lukaskrivka/google-maps-with-contact-details';
27-
const SELECTED_TOOL = 'apify_rag-web-browser'; // We need to change / to _ in the tool name
27+
const SELECTED_TOOL = 'apify--rag-web-browser'; // We need to change / to _ in the tool name
2828

2929
if (!process.env.APIFY_TOKEN) {
3030
console.error('APIFY_TOKEN is required but not set in the environment variables.');

src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ log.setLevel(log.LEVELS.ERROR);
2323
const argv = minimist(process.argv.slice(2));
2424
const argActors = argv.actors?.split(',').map((actor: string) => actor.trim()) || [];
2525

26+
if (!process.env.APIFY_TOKEN) {
27+
log.error('APIFY_TOKEN is required but not set in the environment variables.');
28+
process.exit(1);
29+
}
30+
2631
async function main() {
2732
const server = new ApifyMcpServer();
2833
await (argActors.length !== 0

0 commit comments

Comments
 (0)