Skip to content

Commit 08c62be

Browse files
authored
feat: prepare for dockerhub integration, prepare dockerfile, add support for reading config from env vars for stdio (#224)
* feat: prepare for dockerhub integration, prepare dockerfile, add support for reading config from env vars for stdio * format
1 parent eb38160 commit 08c62be

File tree

4 files changed

+90
-26
lines changed

4 files changed

+90
-26
lines changed

Dockerfile

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
2-
# Stage 1: Build the TypeScript project
3-
FROM node:18-alpine AS builder
1+
# Stage 1: Build the project
2+
FROM node:24-alpine AS builder
43

54
# Set working directory
65
WORKDIR /app
@@ -17,7 +16,7 @@ COPY tsconfig.json ./
1716
RUN npm run build
1817

1918
# Stage 2: Set up the runtime environment
20-
FROM node:18-alpine
19+
FROM node:24-alpine
2120

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

32-
# Expose any necessary ports (example: 3000)
33-
EXPOSE 3000
34-
35-
# Set the environment variable for the Apify token
36-
ENV APIFY_TOKEN=<your-apify-token>
37-
3831
# Set the entry point for the container
39-
ENTRYPOINT ["node", "dist/main.js"]
32+
ENTRYPOINT ["node", "dist/stdio.js"]

src/stdio.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,16 @@ log.setLevel(log.LEVELS.ERROR);
4747
// Parse command line arguments using yargs
4848
const argv = yargs(hideBin(process.argv))
4949
.usage('Usage: $0 [options]')
50+
.env()
5051
.option('actors', {
5152
type: 'string',
52-
describe: 'Comma-separated list of Actor full names to add to the server.',
53+
describe: 'Comma-separated list of Actor full names to add to the server. Can also be set via ACTORS environment variable.',
5354
example: 'apify/google-search-scraper,apify/instagram-scraper',
5455
})
5556
.option('enable-adding-actors', {
5657
type: 'boolean',
5758
default: true,
58-
describe: 'Enable dynamically adding Actors as tools based on user requests.',
59+
describe: 'Enable dynamically adding Actors as tools based on user requests. Can also be set via ENABLE_ADDING_ACTORS environment variable.',
5960
})
6061
.option('enableActorAutoLoading', {
6162
type: 'boolean',
@@ -65,7 +66,7 @@ const argv = yargs(hideBin(process.argv))
6566
})
6667
.options('tools', {
6768
type: 'string',
68-
describe: `Comma-separated list of specific tool categories to enable.
69+
describe: `Comma-separated list of specific tool categories to enable. Can also be set via TOOLS environment variable.
6970
7071
Available choices: ${Object.keys(toolCategories).join(', ')}
7172

tests/helpers.ts

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface McpClientOptions {
1111
actors?: string[];
1212
enableAddingActors?: boolean;
1313
tools?: ToolCategory[]; // Tool categories to include
14+
useEnv?: boolean; // Use environment variables instead of command line arguments (stdio only)
1415
}
1516

1617
export async function createMcpSseClient(
@@ -97,24 +98,40 @@ export async function createMcpStdioClient(
9798
if (!process.env.APIFY_TOKEN) {
9899
throw new Error('APIFY_TOKEN environment variable is not set.');
99100
}
100-
const { actors, enableAddingActors, tools } = options || {};
101+
const { actors, enableAddingActors, tools, useEnv } = options || {};
101102
const args = ['dist/stdio.js'];
102-
if (actors) {
103-
args.push('--actors', actors.join(','));
104-
}
105-
if (enableAddingActors !== undefined) {
106-
args.push('--enable-adding-actors', enableAddingActors.toString());
107-
}
108-
if (tools && tools.length > 0) {
109-
args.push('--tools', tools.join(','));
103+
const env: Record<string, string> = {
104+
APIFY_TOKEN: process.env.APIFY_TOKEN as string,
105+
};
106+
107+
// Set environment variables instead of command line arguments when useEnv is true
108+
if (useEnv) {
109+
if (actors) {
110+
env.ACTORS = actors.join(',');
111+
}
112+
if (enableAddingActors !== undefined) {
113+
env.ENABLE_ADDING_ACTORS = enableAddingActors.toString();
114+
}
115+
if (tools && tools.length > 0) {
116+
env.TOOLS = tools.join(',');
117+
}
118+
} else {
119+
// Use command line arguments as before
120+
if (actors) {
121+
args.push('--actors', actors.join(','));
122+
}
123+
if (enableAddingActors !== undefined) {
124+
args.push('--enable-adding-actors', enableAddingActors.toString());
125+
}
126+
if (tools && tools.length > 0) {
127+
args.push('--tools', tools.join(','));
128+
}
110129
}
111130

112131
const transport = new StdioClientTransport({
113132
command: 'node',
114133
args,
115-
env: {
116-
APIFY_TOKEN: process.env.APIFY_TOKEN as string,
117-
},
134+
env,
118135
});
119136
const client = new Client({
120137
name: 'stdio-client',

tests/integration/suite.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,5 +493,58 @@ export function createIntegrationTestsSuite(
493493
await (client.transport as StreamableHTTPClientTransport).terminateSession();
494494
await client.close();
495495
});
496+
497+
// Environment variable tests - only applicable to stdio transport
498+
it.runIf(options.transport === 'stdio')('should load actors from ACTORS environment variable', async () => {
499+
const actors = ['apify/python-example', 'apify/rag-web-browser'];
500+
const client = await createClientFn({ actors, useEnv: true });
501+
const names = getToolNames(await client.listTools());
502+
expect(names.length).toEqual(defaultTools.length + actors.length + addRemoveTools.length);
503+
expectToolNamesToContain(names, DEFAULT_TOOL_NAMES);
504+
expectToolNamesToContain(names, actors.map((actor) => actorNameToToolName(actor)));
505+
expectToolNamesToContain(names, addRemoveTools.map((tool) => tool.tool.name));
506+
507+
await client.close();
508+
});
509+
510+
it.runIf(options.transport === 'stdio')('should respect ENABLE_ADDING_ACTORS environment variable', async () => {
511+
// Test with enableAddingActors = false via env var
512+
const client = await createClientFn({ enableAddingActors: false, useEnv: true });
513+
const names = getToolNames(await client.listTools());
514+
expect(names.length).toEqual(defaultTools.length + defaults.actors.length);
515+
516+
expectToolNamesToContain(names, DEFAULT_TOOL_NAMES);
517+
expectToolNamesToContain(names, DEFAULT_ACTOR_NAMES);
518+
await client.close();
519+
});
520+
521+
it.runIf(options.transport === 'stdio')('should load tool categories from TOOLS environment variable', async () => {
522+
const categories = ['docs', 'runs'] as ToolCategory[];
523+
const client = await createClientFn({ tools: categories, useEnv: true });
524+
525+
const loadedTools = await client.listTools();
526+
const toolNames = getToolNames(loadedTools);
527+
528+
const expectedTools = [
529+
...toolCategories.docs,
530+
...toolCategories.runs,
531+
];
532+
const expectedToolNames = expectedTools.map((tool) => tool.tool.name);
533+
534+
// Handle case where tools are enabled by default
535+
const selectedCategoriesInDefault = categories.filter((key) => toolCategoriesEnabledByDefault.includes(key));
536+
const numberOfToolsFromCategoriesInDefault = selectedCategoriesInDefault
537+
.flatMap((key) => toolCategories[key]).length;
538+
539+
const numberOfToolsExpected = defaultTools.length + defaults.actors.length + addRemoveTools.length
540+
// Tools from tool categories minus the ones already in default tools
541+
+ (expectedTools.length - numberOfToolsFromCategoriesInDefault);
542+
expect(toolNames.length).toEqual(numberOfToolsExpected);
543+
for (const expectedToolName of expectedToolNames) {
544+
expect(toolNames).toContain(expectedToolName);
545+
}
546+
547+
await client.close();
548+
});
496549
});
497550
}

0 commit comments

Comments
 (0)