Skip to content
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
15 changes: 4 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
# Stage 1: Build the TypeScript project
FROM node:18-alpine AS builder
# Stage 1: Build the project
FROM node:24-alpine AS builder

# Set working directory
WORKDIR /app
Expand All @@ -17,7 +16,7 @@ COPY tsconfig.json ./
RUN npm run build

# Stage 2: Set up the runtime environment
FROM node:18-alpine
FROM node:24-alpine

# Set working directory
WORKDIR /app
Expand All @@ -29,11 +28,5 @@ COPY package.json package-lock.json ./
# Install production dependencies only
RUN npm ci --omit=dev

# Expose any necessary ports (example: 3000)
EXPOSE 3000

# Set the environment variable for the Apify token
ENV APIFY_TOKEN=<your-apify-token>

# Set the entry point for the container
ENTRYPOINT ["node", "dist/main.js"]
ENTRYPOINT ["node", "dist/stdio.js"]
7 changes: 4 additions & 3 deletions src/stdio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,16 @@ log.setLevel(log.LEVELS.ERROR);
// Parse command line arguments using yargs
const argv = yargs(hideBin(process.argv))
.usage('Usage: $0 [options]')
.env()
.option('actors', {
type: 'string',
describe: 'Comma-separated list of Actor full names to add to the server.',
describe: 'Comma-separated list of Actor full names to add to the server. Can also be set via ACTORS environment variable.',
example: 'apify/google-search-scraper,apify/instagram-scraper',
})
.option('enable-adding-actors', {
type: 'boolean',
default: true,
describe: 'Enable dynamically adding Actors as tools based on user requests.',
describe: 'Enable dynamically adding Actors as tools based on user requests. Can also be set via ENABLE_ADDING_ACTORS environment variable.',
})
.option('enableActorAutoLoading', {
type: 'boolean',
Expand All @@ -65,7 +66,7 @@ const argv = yargs(hideBin(process.argv))
})
.options('tools', {
type: 'string',
describe: `Comma-separated list of specific tool categories to enable.
describe: `Comma-separated list of specific tool categories to enable. Can also be set via TOOLS environment variable.

Available choices: ${Object.keys(toolCategories).join(', ')}

Expand Down
41 changes: 29 additions & 12 deletions tests/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface McpClientOptions {
actors?: string[];
enableAddingActors?: boolean;
tools?: ToolCategory[]; // Tool categories to include
useEnv?: boolean; // Use environment variables instead of command line arguments (stdio only)
}

export async function createMcpSseClient(
Expand Down Expand Up @@ -97,24 +98,40 @@ export async function createMcpStdioClient(
if (!process.env.APIFY_TOKEN) {
throw new Error('APIFY_TOKEN environment variable is not set.');
}
const { actors, enableAddingActors, tools } = options || {};
const { actors, enableAddingActors, tools, useEnv } = options || {};
const args = ['dist/stdio.js'];
if (actors) {
args.push('--actors', actors.join(','));
}
if (enableAddingActors !== undefined) {
args.push('--enable-adding-actors', enableAddingActors.toString());
}
if (tools && tools.length > 0) {
args.push('--tools', tools.join(','));
const env: Record<string, string> = {
APIFY_TOKEN: process.env.APIFY_TOKEN as string,
};

// Set environment variables instead of command line arguments when useEnv is true
if (useEnv) {
if (actors) {
env.ACTORS = actors.join(',');
}
if (enableAddingActors !== undefined) {
env.ENABLE_ADDING_ACTORS = enableAddingActors.toString();
}
if (tools && tools.length > 0) {
env.TOOLS = tools.join(',');
}
} else {
// Use command line arguments as before
if (actors) {
args.push('--actors', actors.join(','));
}
if (enableAddingActors !== undefined) {
args.push('--enable-adding-actors', enableAddingActors.toString());
}
if (tools && tools.length > 0) {
args.push('--tools', tools.join(','));
}
}

const transport = new StdioClientTransport({
command: 'node',
args,
env: {
APIFY_TOKEN: process.env.APIFY_TOKEN as string,
},
env,
});
const client = new Client({
name: 'stdio-client',
Expand Down
53 changes: 53 additions & 0 deletions tests/integration/suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,5 +493,58 @@ export function createIntegrationTestsSuite(
await (client.transport as StreamableHTTPClientTransport).terminateSession();
await client.close();
});

// Environment variable tests - only applicable to stdio transport
it.runIf(options.transport === 'stdio')('should load actors from ACTORS environment variable', async () => {
const actors = ['apify/python-example', 'apify/rag-web-browser'];
const client = await createClientFn({ actors, useEnv: true });
const names = getToolNames(await client.listTools());
expect(names.length).toEqual(defaultTools.length + actors.length + addRemoveTools.length);
expectToolNamesToContain(names, DEFAULT_TOOL_NAMES);
expectToolNamesToContain(names, actors.map((actor) => actorNameToToolName(actor)));
expectToolNamesToContain(names, addRemoveTools.map((tool) => tool.tool.name));

await client.close();
});

it.runIf(options.transport === 'stdio')('should respect ENABLE_ADDING_ACTORS environment variable', async () => {
// Test with enableAddingActors = false via env var
const client = await createClientFn({ enableAddingActors: false, useEnv: true });
const names = getToolNames(await client.listTools());
expect(names.length).toEqual(defaultTools.length + defaults.actors.length);

expectToolNamesToContain(names, DEFAULT_TOOL_NAMES);
expectToolNamesToContain(names, DEFAULT_ACTOR_NAMES);
await client.close();
});

it.runIf(options.transport === 'stdio')('should load tool categories from TOOLS environment variable', async () => {
const categories = ['docs', 'runs'] as ToolCategory[];
const client = await createClientFn({ tools: categories, useEnv: true });

const loadedTools = await client.listTools();
const toolNames = getToolNames(loadedTools);

const expectedTools = [
...toolCategories.docs,
...toolCategories.runs,
];
const expectedToolNames = expectedTools.map((tool) => tool.tool.name);

// Handle case where tools are enabled by default
const selectedCategoriesInDefault = categories.filter((key) => toolCategoriesEnabledByDefault.includes(key));
const numberOfToolsFromCategoriesInDefault = selectedCategoriesInDefault
.flatMap((key) => toolCategories[key]).length;

const numberOfToolsExpected = defaultTools.length + defaults.actors.length + addRemoveTools.length
// Tools from tool categories minus the ones already in default tools
+ (expectedTools.length - numberOfToolsFromCategoriesInDefault);
expect(toolNames.length).toEqual(numberOfToolsExpected);
for (const expectedToolName of expectedToolNames) {
expect(toolNames).toContain(expectedToolName);
}

await client.close();
});
});
}