Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
21 changes: 10 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
# Stage 1: Build the project
FROM node:24-alpine AS builder
FROM node:22.12-alpine AS builder

# Set working directory
WORKDIR /app

# Copy package files and install dependencies
COPY package.json package-lock.json ./
RUN npm install

# Copy source files
# Install all dependencies (including devDependencies for build)
RUN npm ci --ignore-scripts

COPY src ./src
COPY tsconfig.json ./

# Build the project
RUN npm run build

# Stage 2: Set up the runtime environment
FROM node:24-alpine
FROM node:22-alpine AS release

# Set working directory
WORKDIR /app

# Copy only the necessary files from the build stage
COPY --from=builder /app/dist ./dist
COPY package.json package-lock.json ./

ENV NODE_ENV=production
ENV APIFY_TOKEN=your-api-key-here

# Install production dependencies only
RUN npm ci --omit=dev
RUN npm ci --ignore-scripts --omit=dev

# Set the entry point for the container
ENTRYPOINT ["node", "dist/stdio.js"]
ENTRYPOINT ["node", "dist/index.js"]
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"node": ">=18.0.0"
},
"main": "dist/index.js",
"module": "./src/index.ts",
"exports": {
".": "./dist/index.js",
"./internals": "./dist/index-internals.js",
Expand Down
18 changes: 1 addition & 17 deletions smithery.yaml
Original file line number Diff line number Diff line change
@@ -1,17 +1 @@
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml

startCommand:
type: stdio
configSchema:
# JSON Schema defining the configuration options for the MCP.
type: object
required:
- apifyToken
properties:
apifyToken:
type: string
description: The API token for accessing Apify's services.
commandFunction:
# A function that produces the CLI command to start the MCP on stdio.
|-
(config) => ({ command: 'node', args: ['dist/main.js'], env: { APIFY_TOKEN: config.apifyToken } })
runtime: typescript
62 changes: 62 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,70 @@
#!/usr/bin/env node
/*
This file provides essential functions and tools for MCP servers, serving as a library.
The ActorsMcpServer should be the only class exported from the package
Also, it serves as the main entry point for smithery deployment.
*/
import type { z } from 'zod';

import { ActorsMcpServer } from './mcp/server.js';
import type { Input, ToolCategory } from './types';
import { serverConfigSchemaSmithery as configSchema } from './types.js';
import { loadToolsFromInput } from './utils/tools-loader.js';

// Export the config schema for Smithery. The export must be named configSchema
export { configSchema };

export { ActorsMcpServer };

/**
* Main entrypoint for Smithery deployment, do not change signature of this function.
* @param param0
* @returns
*/
// eslint-disable-next-line import/no-default-export
export default function ({ config: _config }: { config: z.infer<typeof configSchema> }) {
try {
const apifyToken = _config.apifyToken || process.env.APIFY_TOKEN || '';
const enableAddingActors = _config.enableAddingActors ?? true;
const actors = _config.actors || '';
const actorList = actors ? actors.split(',').map((a: string) => a.trim()) : [];
const toolCategoryKeys = _config.tools ? _config.tools.split(',').map((t: string) => t.trim()) : [];

// Validate environment
if (!apifyToken) {
// eslint-disable-next-line no-console
console.warn('APIFY_TOKEN is required but not set in the environment variables or config. Some tools may not work properly.');
} else {
process.env.APIFY_TOKEN = apifyToken; // Ensure token is set in the environment
}

const server = new ActorsMcpServer({ enableAddingActors, enableDefaultActors: false });

const input: Input = {
actors: actorList.length ? actorList : [],
enableAddingActors,
tools: toolCategoryKeys as ToolCategory[],
};

// NOTE: This is a workaround for Smithery's requirement of a synchronous function
// We load tools asynchronously and attach the promise to the server
// However, this approach is NOT 100% reliable - the external library may still
// try to use the server before tools are fully loaded
loadToolsFromInput(input, apifyToken, actorList.length === 0)
.then((tools) => {
server.upsertTools(tools);
return true;
})
.catch((error) => {
// eslint-disable-next-line no-console
console.error('Failed to load tools:', error);
return false;
});
return server.server;
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
throw e;
}
}
18 changes: 2 additions & 16 deletions src/stdio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,9 @@ import log from '@apify/log';

import { ActorsMcpServer } from './mcp/server.js';
import { toolCategories } from './tools/index.js';
import type { Input, ToolCategory } from './types.js';
import type { Input, ServerConfigCli, ToolCategory } from './types.js';
import { loadToolsFromInput } from './utils/tools-loader.js';

// Keeping this interface here and not types.ts since
// it is only relevant to the CLI/STDIO transport in this file
/**
* Interface for command line arguments
*/
interface CliArgs {
actors?: string;
enableAddingActors: boolean;
/** @deprecated */
enableActorAutoLoading: boolean;
/** Tool categories to include */
tools?: string;
}

// Configure logging, set to ERROR
log.setLevel(log.LEVELS.ERROR);

Expand Down Expand Up @@ -88,7 +74,7 @@ Note: Tools that enable you to search Actors from the Apify Store and get their
+ ' and set the environment variable `APIFY_TOKEN` to your Apify API token.\n',
)
.epilogue('For more information, visit https://mcp.apify.com or https://github.com/apify/actors-mcp-server')
.parseSync() as CliArgs;
.parseSync() as ServerConfigCli;

const enableAddingActors = argv.enableAddingActors && argv.enableActorAutoLoading;
const actors = argv.actors as string || '';
Expand Down
29 changes: 29 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,41 @@ import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/proto
import type { Notification, Prompt, Request } from '@modelcontextprotocol/sdk/types.js';
import type { ValidateFunction } from 'ajv';
import type { ActorDefaultRunOptions, ActorDefinition, ActorStoreList, PricingInfo } from 'apify-client';
import { z } from 'zod';

import type { ACTOR_PRICING_MODEL } from './const.js';
import type { ActorsMcpServer } from './mcp/server.js';
import type { toolCategories } from './tools/index.js';
import type { ProgressTracker } from './utils/progress.js';

export const serverConfigSchemaCli = z.object({
actors: z
.string()
.optional()
.describe('Comma-separated list of Actor full names to add to the server'),
enableAddingActors: z
.boolean()
.default(true)
.describe('Enable dynamically adding Actors as tools based on user requests'),
tools: z
.string()
.optional()
.describe('Comma-separated list of specific tool categories to enable (docs,runs,storage,preview)'),
/** @deprecated */
enableActorAutoLoading: z
.boolean()
.default(true)
.describe('Deprecated: use enable-adding-actors instead.'),
});

export const serverConfigSchemaSmithery = serverConfigSchemaCli.extend({
apifyToken: z
.string()
.describe('Apify token, learn more: https://docs.apify.com/platform/integrations/api#api-token'),
});

export type ServerConfigCli = z.infer<typeof serverConfigSchemaCli>;

export interface ISchemaProperties {
type: string;

Expand Down