Skip to content

Commit 42e32bd

Browse files
committed
feat(input): allow empty tools and actors to allow greater control of exposed tools
1 parent 2bd1ad7 commit 42e32bd

File tree

13 files changed

+146
-88
lines changed

13 files changed

+146
-88
lines changed

src/actor/server.ts

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import express from 'express';
1212
import log from '@apify/log';
1313

1414
import { ActorsMcpServer } from '../mcp/server.js';
15-
import { parseInputParamsFromUrl } from '../mcp/utils.js';
1615
import { getHelpMessage, HEADER_READINESS_PROBE, Routes, TransportType } from './const.js';
1716
import { getActorRunData } from './utils.js';
1817

@@ -80,16 +79,8 @@ export function createExpressApp(
8079

8180
// Load MCP server tools
8281
const apifyToken = process.env.APIFY_TOKEN as string;
83-
const input = parseInputParamsFromUrl(req.url);
84-
if (input.actors || input.enableAddingActors || input.tools) {
85-
log.debug('Loading tools from URL', { sessionId: transport.sessionId, tr: TransportType.SSE });
86-
await mcpServer.loadToolsFromUrl(req.url, apifyToken);
87-
}
88-
// Load default tools if no actors are specified
89-
if (!input.actors) {
90-
log.debug('Loading default tools', { sessionId: transport.sessionId, tr: TransportType.SSE });
91-
await mcpServer.loadDefaultActors(apifyToken);
92-
}
82+
log.debug('Loading tools from URL', { sessionId: transport.sessionId, tr: TransportType.SSE });
83+
await mcpServer.loadToolsFromUrl(req.url, apifyToken);
9384

9485
transportsSSE[transport.sessionId] = transport;
9586
mcpServers[transport.sessionId] = mcpServer;
@@ -170,16 +161,8 @@ export function createExpressApp(
170161

171162
// Load MCP server tools
172163
const apifyToken = process.env.APIFY_TOKEN as string;
173-
const input = parseInputParamsFromUrl(req.url);
174-
if (input.actors || input.enableAddingActors || input.tools) {
175-
log.debug('Loading tools from URL', { sessionId: transport.sessionId, tr: TransportType.HTTP });
176-
await mcpServer.loadToolsFromUrl(req.url, apifyToken);
177-
}
178-
// Load default tools if no actors are specified
179-
if (!input.actors) {
180-
log.debug('Loading default tools', { sessionId: transport.sessionId, tr: TransportType.HTTP });
181-
await mcpServer.loadDefaultActors(apifyToken);
182-
}
164+
log.debug('Loading tools from URL', { sessionId: transport.sessionId, tr: TransportType.HTTP });
165+
await mcpServer.loadToolsFromUrl(req.url, apifyToken);
183166

184167
// Connect the transport to the MCP server BEFORE handling the request
185168
await mcpServer.connect(transport);

src/index-internals.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import { defaults, HelperTools } from './const.js';
66
import { parseInputParamsFromUrl, processParamsGetTools } from './mcp/utils.js';
7-
import { addRemoveTools, defaultTools, getActorsAsTools, toolCategories, toolCategoriesEnabledByDefault } from './tools/index.js';
7+
import { defaultTools, getActorsAsTools, toolCategories, toolCategoriesEnabledByDefault } from './tools/index.js';
88
import { actorNameToToolName } from './tools/utils.js';
99
import type { ToolCategory } from './types.js';
1010
import { getToolPublicFieldOnly } from './utils/tools.js';
@@ -15,7 +15,6 @@ export {
1515
HelperTools,
1616
defaults,
1717
defaultTools,
18-
addRemoveTools,
1918
toolCategories,
2019
toolCategoriesEnabledByDefault,
2120
type ToolCategory,

src/input.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ export function processInput(originalInput: Partial<Input>): Input {
1717
if (input.actors && typeof input.actors === 'string') {
1818
input.actors = input.actors.split(',').map((format: string) => format.trim()) as string[];
1919
}
20+
/**
21+
* Replace empty string with empty array to prevent invalid Actor API error.
22+
*/
23+
if (input.actors === '') {
24+
input.actors = [];
25+
}
2026

2127
// enableAddingActors is deprecated, use enableActorAutoLoading instead
2228
if (input.enableAddingActors === undefined) {

src/mcp/server.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,6 @@ export class ActorsMcpServer {
7575
this.setupToolHandlers();
7676
this.setupPromptHandlers();
7777

78-
// Add default tools
79-
this.upsertTools(defaultTools);
80-
8178
// Add tools to dynamically load Actors
8279
if (this.options.enableAddingActors) {
8380
this.enableDynamicActorTools();
@@ -213,7 +210,6 @@ export class ActorsMcpServer {
213210
if (this.toolsChangedHandler) {
214211
this.unregisterToolsChangedHandler();
215212
}
216-
this.upsertTools(defaultTools);
217213
if (this.options.enableAddingActors) {
218214
this.enableDynamicActorTools();
219215
}
@@ -244,14 +240,6 @@ export class ActorsMcpServer {
244240
}
245241
}
246242

247-
/**
248-
* @deprecated Use `loadDefaultActors` instead.
249-
* Loads default tools if not already loaded.
250-
*/
251-
public async loadDefaultTools(apifyToken: string) {
252-
await this.loadDefaultActors(apifyToken);
253-
}
254-
255243
/**
256244
* Loads tools from URL params.
257245
*

src/stdio.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,14 @@ Note: Tools that enable you to search Actors from the Apify Store and get their
9090
.parseSync() as CliArgs;
9191

9292
const enableAddingActors = argv.enableAddingActors && argv.enableActorAutoLoading;
93-
const actors = argv.actors as string || '';
94-
const actorList = actors ? actors.split(',').map((a: string) => a.trim()) : [];
95-
// Keys of the tool categories to enable
96-
const toolCategoryKeys = argv.tools ? argv.tools.split(',').map((t: string) => t.trim()) : [];
93+
// Split actors argument, trim whitespace, and filter out empty strings
94+
const actorList = argv.actors !== undefined
95+
? argv.actors.split(',').map((a: string) => a.trim()).filter((a: string) => a.length > 0)
96+
: undefined;
97+
// Split tools argument, trim whitespace, and filter out empty strings
98+
const toolCategoryKeys = argv.tools !== undefined
99+
? argv.tools.split(',').map((t: string) => t.trim()).filter((t: string) => t.length > 0)
100+
: undefined;
97101

98102
// Propagate log.error to console.error for easier debugging
99103
const originalError = log.error.bind(log);
@@ -114,13 +118,13 @@ async function main() {
114118

115119
// Create an Input object from CLI arguments
116120
const input: Input = {
117-
actors: actorList.length ? actorList : [],
121+
actors: actorList,
118122
enableAddingActors,
119123
tools: toolCategoryKeys as ToolCategory[],
120124
};
121125

122126
// Use the shared tools loading logic
123-
const tools = await loadToolsFromInput(input, process.env.APIFY_TOKEN as string, actorList.length === 0);
127+
const tools = await loadToolsFromInput(input, process.env.APIFY_TOKEN as string);
124128

125129
mcpServer.upsertTools(tools);
126130

src/tools/index.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Import specific tools that are being used
22
import type { ToolCategory } from '../types.js';
3+
import { getExpectedToolsByCategories } from '../utils/tools.js';
34
import { callActor, callActorGetDataset, getActorsAsTools } from './actor.js';
45
import { getDataset, getDatasetItems, getDatasetSchema } from './dataset.js';
56
import { getUserDatasetsList } from './dataset_collection.js';
@@ -14,6 +15,14 @@ import { searchApifyDocsTool } from './search-apify-docs.js';
1415
import { searchActors } from './store_collection.js';
1516

1617
export const toolCategories = {
18+
'actor-discovery': [
19+
getActorDetailsTool,
20+
searchActors,
21+
/**
22+
* TODO: we should add the add-actor tool here but we would need to change the configuraton
23+
* interface around the ?enableAddingActors
24+
*/
25+
],
1726
docs: [
1827
searchApifyDocsTool,
1928
fetchApifyDocsTool,
@@ -38,16 +47,15 @@ export const toolCategories = {
3847
],
3948
};
4049
export const toolCategoriesEnabledByDefault: ToolCategory[] = [
50+
'actor-discovery',
4151
'docs',
4252
];
4353

44-
export const defaultTools = [
45-
getActorDetailsTool,
46-
searchActors,
47-
// Add the tools from the enabled categories
48-
...toolCategoriesEnabledByDefault.map((key) => toolCategories[key]).flat(),
49-
];
54+
export const defaultTools = getExpectedToolsByCategories(toolCategoriesEnabledByDefault);
5055

56+
/**
57+
* Tools related to `enableAddingActors` param for dynamic Actor adding.
58+
*/
5159
export const addRemoveTools = [
5260
addTool,
5361
];

src/types.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,12 @@ export interface InternalTool extends ToolBase {
213213
export type ToolCategory = keyof typeof toolCategories;
214214

215215
export type Input = {
216-
actors: string[] | string;
216+
/**
217+
* When `actors` is undefined that means the default Actors should be loaded.
218+
* If it as empty string or empty array then no Actors should be loaded.
219+
* Otherwise the specified Actors should be loaded.
220+
*/
221+
actors?: string[] | string;
217222
/**
218223
* @deprecated Use `enableAddingActors` instead.
219224
*/
@@ -222,7 +227,12 @@ export type Input = {
222227
maxActorMemoryBytes?: number;
223228
debugActor?: string;
224229
debugActorInput?: unknown;
225-
/** Tool categories to include */
230+
/**
231+
* Tool categories to include
232+
* When `tools` is undefined that means the default tools categories should be loaded.
233+
* If it as empty string or empty array then no tools should be loaded.
234+
* Otherwise the specified tools categories should be loaded.
235+
*/
226236
tools?: ToolCategory[] | string;
227237
};
228238

src/utils/tools-loader.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,30 @@
44
*/
55

66
import { defaults } from '../const.js';
7-
import { addRemoveTools, getActorsAsTools, toolCategories } from '../tools/index.js';
7+
import { addRemoveTools, getActorsAsTools, toolCategories, toolCategoriesEnabledByDefault } from '../tools/index.js';
88
import type { Input, ToolCategory, ToolEntry } from '../types.js';
9+
import { getExpectedToolsByCategories } from './tools.js';
910

1011
/**
1112
* Load tools based on the provided Input object.
1213
* This function is used by both the stdio.ts and the processParamsGetTools function.
1314
*
1415
* @param input The processed Input object
1516
* @param apifyToken The Apify API token
16-
* @param useDefaultActors Whether to use default actors if no actors are specified
1717
* @returns An array of tool entries
1818
*/
1919
export async function loadToolsFromInput(
2020
input: Input,
2121
apifyToken: string,
22-
useDefaultActors = false,
2322
): Promise<ToolEntry[]> {
2423
let tools: ToolEntry[] = [];
2524

2625
// Load actors as tools
27-
if (input.actors && (Array.isArray(input.actors) ? input.actors.length > 0 : input.actors)) {
26+
if (input.actors !== undefined) {
2827
const actors = Array.isArray(input.actors) ? input.actors : [input.actors];
2928
tools = await getActorsAsTools(actors, apifyToken);
30-
} else if (useDefaultActors) {
31-
// Use default actors if no actors are specified and useDefaultActors is true
29+
} else {
30+
// Use default actors if no actors are specified
3231
tools = await getActorsAsTools(defaults.actors, apifyToken);
3332
}
3433

@@ -38,12 +37,14 @@ export async function loadToolsFromInput(
3837
}
3938

4039
// Add tools from enabled categories
41-
if (input.tools) {
40+
if (input.tools !== undefined) {
4241
const toolKeys = Array.isArray(input.tools) ? input.tools : [input.tools];
4342
for (const toolKey of toolKeys) {
4443
const keyTools = toolCategories[toolKey as ToolCategory] || [];
4544
tools.push(...keyTools);
4645
}
46+
} else {
47+
tools.push(...getExpectedToolsByCategories(toolCategoriesEnabledByDefault));
4748
}
4849

4950
return tools;

src/utils/tools.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import type { ToolBase } from '../types.js';
1+
import { toolCategories } from '../tools/index.js';
2+
import type { ToolBase, ToolCategory, ToolEntry } from '../types.js';
23

34
/**
45
* Returns a public version of the tool containing only fields that should be exposed publicly.
@@ -11,3 +12,18 @@ export function getToolPublicFieldOnly(tool: ToolBase) {
1112
inputSchema: tool.inputSchema,
1213
};
1314
}
15+
16+
/**
17+
* Returns the tool objects for the given category names using toolCategories.
18+
*/
19+
export function getExpectedToolsByCategories(categories: ToolCategory[]): ToolEntry[] {
20+
return categories
21+
.flatMap((category) => toolCategories[category] || []);
22+
}
23+
24+
/**
25+
* Returns the tool names for the given category names using getExpectedToolsByCategories.
26+
*/
27+
export function getExpectedToolNamesByCategories(categories: ToolCategory[]): string[] {
28+
return getExpectedToolsByCategories(categories).map((tool) => tool.tool.name);
29+
}

tests/const.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import { toolCategoriesEnabledByDefault } from '../dist/index-internals.js';
12
import { defaults } from '../src/const.js';
2-
import { defaultTools } from '../src/tools/index.js';
33
import { actorNameToToolName } from '../src/tools/utils.js';
4+
import { getExpectedToolNamesByCategories } from '../src/utils/tools.js';
45

56
export const ACTOR_PYTHON_EXAMPLE = 'apify/python-example';
67
export const ACTOR_MCP_SERVER_ACTOR_NAME = 'apify/actors-mcp-server';
7-
export const DEFAULT_TOOL_NAMES = defaultTools.map((tool) => tool.tool.name);
8+
export const DEFAULT_TOOL_NAMES = getExpectedToolNamesByCategories(toolCategoriesEnabledByDefault);
89
export const DEFAULT_ACTOR_NAMES = defaults.actors.map((tool) => actorNameToToolName(tool));

0 commit comments

Comments
 (0)