Skip to content

Commit 5e2c9f0

Browse files
jirispilkafnesveda
andauthored
feat: MCP server implementation (#1)
* Implementation of MCP server for Apify Actors --------- Co-authored-by: František Nesveda <fnesveda@users.noreply.github.com>
1 parent 77bd2ac commit 5e2c9f0

28 files changed

Lines changed: 8295 additions & 53 deletions

.actor/Dockerfile

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Specify the base Docker image. You can read more about
2+
# the available images at https://docs.apify.com/sdk/js/docs/guides/docker-images
3+
# You can also use any other image from Docker Hub.
4+
FROM apify/actor-node:20 AS builder
5+
6+
# Check preinstalled packages
7+
RUN npm ls crawlee apify puppeteer playwright
8+
9+
# Copy just package.json and package-lock.json
10+
# to speed up the build using Docker layer cache.
11+
COPY package*.json ./
12+
13+
# Install all dependencies. Don't audit to speed up the installation.
14+
RUN npm install --include=dev --audit=false
15+
16+
# Next, copy the source files using the user set
17+
# in the base image.
18+
COPY . ./
19+
20+
# Install all dependencies and build the project.
21+
# Don't audit to speed up the installation.
22+
RUN npm run build
23+
24+
# Create final image
25+
FROM apify/actor-node:20
26+
27+
# Check preinstalled packages
28+
RUN npm ls crawlee apify puppeteer playwright
29+
30+
# Copy just package.json and package-lock.json
31+
# to speed up the build using Docker layer cache.
32+
COPY package*.json ./
33+
34+
# Install NPM packages, skip optional and development dependencies to
35+
# keep the image small. Avoid logging too much and print the dependency
36+
# tree for debugging
37+
RUN npm --quiet set progress=false \
38+
&& npm install --omit=dev --omit=optional \
39+
&& echo "Installed NPM packages:" \
40+
&& (npm list --omit=dev --all || true) \
41+
&& echo "Node.js version:" \
42+
&& node --version \
43+
&& echo "NPM version:" \
44+
&& npm --version \
45+
&& rm -r ~/.npm
46+
47+
# Copy built JS files from builder image
48+
COPY --from=builder /usr/src/app/dist ./dist
49+
50+
# Next, copy the remaining files and directories with the source code.
51+
# Since we do this after NPM install, quick build will be really fast
52+
# for most source file changes.
53+
COPY . ./
54+
55+
56+
# Run the image.
57+
CMD npm run start:prod --silent

.actor/actor.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"actorSpecification": 1,
3+
"name": "apify-mcp-server",
4+
"title": "Model Context Protocol Server for Apify Actors",
5+
"description": "Implementation of a Model Context Protocol (MCP) Server for Apify Actors that enables AI applications (and AI agents) to interact with Apify Actors",
6+
"version": "0.0",
7+
"input": "./input_schema.json",
8+
"dockerfile": "./Dockerfile"
9+
}

.actor/input_schema.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"title": "Apify MCP Server",
3+
"type": "object",
4+
"schemaVersion": 1,
5+
"properties": {
6+
"actors": {
7+
"title": "Actors to be exposed for an AI application (AI agent)",
8+
"type": "array",
9+
"description": "List Actors to be exposed to an AI application (AI agent) for communication via the MCP protocol. \n\n Ensure the Actor definitions fit within the LLM context by limiting the number of used Actors.",
10+
"editor": "stringList",
11+
"prefill": [
12+
"apify/instagram-scraper",
13+
"apify/rag-web-browser",
14+
"lukaskrivka/google-maps-with-contact-details"
15+
]
16+
},
17+
"debugActor": {
18+
"title": "Debug Actor",
19+
"type": "string",
20+
"description": "Specify the name of the Actor that will be used for debugging in normal mode",
21+
"editor": "textfield",
22+
"prefill": "apify/rag-web-browser",
23+
"sectionCaption": "Debugging settings (normal mode)"
24+
},
25+
"debugActorInput": {
26+
"title": "Debug Actor input",
27+
"type": "object",
28+
"description": "Specify the input for the Actor that will be used for debugging in normal mode",
29+
"editor": "json",
30+
"prefill": {
31+
"query": "hello world"
32+
}
33+
}
34+
}
35+
}

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ node_modules
1616
data
1717
src/storage
1818
dist
19+
.env

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
APIFY_TOKEN=
2+
# ANTHROPIC_API_KEY is only required when you want to run examples/clientStdioChat.js
3+
ANTHROPIC_API_KEY=
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const { execSync } = require('child_process');
2+
const fs = require('fs');
3+
const path = require('path');
4+
5+
const PKG_JSON_PATH = path.join(__dirname, '..', '..', 'package.json');
6+
7+
const pkgJson = require(PKG_JSON_PATH); // eslint-disable-line import/no-dynamic-require
8+
9+
const PACKAGE_NAME = pkgJson.name;
10+
const VERSION = pkgJson.version;
11+
12+
const nextVersion = getNextVersion(VERSION);
13+
console.log(`before-deploy: Setting version to ${nextVersion}`); // eslint-disable-line no-console
14+
pkgJson.version = nextVersion;
15+
16+
fs.writeFileSync(PKG_JSON_PATH, `${JSON.stringify(pkgJson, null, 2)}\n`);
17+
18+
function getNextVersion(version) {
19+
const versionString = execSync(`npm show ${PACKAGE_NAME} versions --json`, { encoding: 'utf8' });
20+
const versions = JSON.parse(versionString);
21+
22+
if (versions.some((v) => v === VERSION)) {
23+
console.error(`before-deploy: A release with version ${VERSION} already exists. Please increment version accordingly.`); // eslint-disable-line no-console
24+
process.exit(1);
25+
}
26+
27+
const prereleaseNumbers = versions
28+
.filter((v) => (v.startsWith(VERSION) && v.includes('-')))
29+
.map((v) => Number(v.match(/\.(\d+)$/)[1]));
30+
const lastPrereleaseNumber = Math.max(-1, ...prereleaseNumbers);
31+
return `${version}-beta.${lastPrereleaseNumber + 1}`;
32+
}

.github/workflows/check.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# This workflow runs for every pull request to lint and test the proposed changes.
2+
3+
name: Check
4+
5+
on:
6+
pull_request:
7+
8+
# Push to master will trigger code checks
9+
push:
10+
branches:
11+
- master
12+
tags-ignore:
13+
- "**" # Ignore all tags to prevent duplicate builds when tags are pushed.
14+
15+
jobs:
16+
lint:
17+
name: Lint
18+
runs-on: ubuntu-latest
19+
20+
steps:
21+
- uses: actions/checkout@v4
22+
- name: Use Node.js 22
23+
uses: actions/setup-node@v4
24+
with:
25+
node-version: 22
26+
cache: 'npm'
27+
cache-dependency-path: 'package-lock.json'
28+
- name: Install Dependencies
29+
run: npm ci
30+
- run: npm run lint

.github/workflows/pre_release.yaml

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
name: Create a pre-release
2+
3+
on:
4+
# Push to master will deploy a beta version
5+
push:
6+
branches:
7+
- master
8+
tags-ignore:
9+
- "**" # Ignore all tags to prevent duplicate builds when tags are pushed.
10+
11+
concurrency:
12+
group: release
13+
cancel-in-progress: false
14+
15+
jobs:
16+
release_metadata:
17+
if: "!startsWith(github.event.head_commit.message, 'docs') && !startsWith(github.event.head_commit.message, 'ci') && startsWith(github.repository, 'apify/')"
18+
name: Prepare release metadata
19+
runs-on: ubuntu-latest
20+
outputs:
21+
version_number: ${{ steps.release_metadata.outputs.version_number }}
22+
changelog: ${{ steps.release_metadata.outputs.changelog }}
23+
steps:
24+
- uses: apify/workflows/git-cliff-release@main
25+
name: Prepare release metadata
26+
id: release_metadata
27+
with:
28+
release_type: prerelease
29+
existing_changelog_path: CHANGELOG.md
30+
31+
wait_for_checks:
32+
name: Wait for code checks to pass
33+
runs-on: ubuntu-latest
34+
steps:
35+
- uses: lewagon/wait-on-check-action@v1.3.4
36+
with:
37+
ref: ${{ github.ref }}
38+
repo-token: ${{ secrets.GITHUB_TOKEN }}
39+
check-name: 'Lint'
40+
wait-interval: 5
41+
42+
update_changelog:
43+
needs: [ release_metadata ]
44+
name: Update changelog
45+
runs-on: ubuntu-latest
46+
outputs:
47+
changelog_commitish: ${{ steps.commit.outputs.commit_long_sha || github.sha }}
48+
49+
steps:
50+
- name: Checkout repository
51+
uses: actions/checkout@v4
52+
with:
53+
token: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }}
54+
55+
- name: Use Node.js 22
56+
uses: actions/setup-node@v4
57+
with:
58+
node-version: 22
59+
60+
- name: Update package version in package.json
61+
run: npm version --no-git-tag-version --allow-same-version ${{ needs.release_metadata.outputs.version_number }}
62+
63+
- name: Update CHANGELOG.md
64+
uses: DamianReeves/write-file-action@master
65+
with:
66+
path: CHANGELOG.md
67+
write-mode: overwrite
68+
contents: ${{ needs.release_metadata.outputs.changelog }}
69+
70+
- name: Commit changes
71+
id: commit
72+
uses: EndBug/add-and-commit@v9
73+
with:
74+
author_name: Apify Release Bot
75+
author_email: noreply@apify.com
76+
message: "chore(release): Update changelog and package version [skip ci]"
77+
78+
publish_to_npm:
79+
name: Publish to NPM
80+
needs: [ release_metadata ]
81+
runs-on: ubuntu-latest
82+
steps:
83+
- uses: actions/checkout@v4
84+
with:
85+
ref: ${{ needs.update_changelog.changelog_commitish }}
86+
- name: Use Node.js 22
87+
uses: actions/setup-node@v4
88+
with:
89+
node-version: 22
90+
- name: Install dependencies
91+
run: |
92+
echo "access=public" >> .npmrc
93+
echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> .npmrc
94+
npm install
95+
- # Check version consistency and increment pre-release version number for beta only.
96+
name: Bump pre-release version
97+
run: node ./.github/scripts/before-beta-release.js
98+
- name: Publish to NPM
99+
run: npm publish --tag beta
100+
101+
env:
102+
NODE_AUTH_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_NPM_TOKEN }}
103+
NPM_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_NPM_TOKEN }}

0 commit comments

Comments
 (0)