Skip to main content
Version: 3.4

Deploying MCP servers

In this guide, you'll learn how to deploy a Model Context Protocol (MCP) server as an Apify Actor.

Introduction

The Model Context Protocol is an open standard that lets AI applications connect to external tools and data. An MCP server exposes tools, resources, and prompts, and any MCP client (such as Claude or an IDE assistant) can call them. Hosting that server as an Apify Actor turns it into a remote, always-ready service.

Apify Actors are a good fit for MCP servers:

  • With Actor Standby, the platform keeps the Actor running in the background and routes incoming HTTP requests to it, so your server is always ready to answer an MCP client.
  • The platform scales instances with demand, keeps logs, and handles the network, so you don't operate any infrastructure.
  • Every request carries an Apify API token, so the platform authenticates clients for you.
  • Pay-per-event charging lets you monetize the server, for example per tool call.

Before you start

To follow along, install the Apify CLI and log in with apify login. Both examples build on FastMCP and are served by uvicorn, so declare them in your Actor's requirements.txt next to the SDK:

apify
fastmcp
uvicorn

MCP server

Build your own server when you want to expose your own tools and resources. The following Actor runs a small FastMCP server that exposes a single add tool and an informational resource. It serves the MCP protocol over the Streamable HTTP transport on the Actor's web server port:

Run on
import asyncio

import uvicorn
from fastmcp import FastMCP

from apify import Actor


def build_server() -> FastMCP:
"""Create a FastMCP server exposing one tool and one resource."""
server = FastMCP(name='calculator')

@server.tool()
def add(a: float, b: float) -> float:
"""Add two numbers and return the sum."""
return a + b

@server.resource(uri='resource://calculator/info', name='calculator-info')
def info() -> str:
"""Describe what this MCP server does."""
return 'A simple calculator MCP server that adds two numbers.'

return server


async def main() -> None:
async with Actor:
# Build the server and expose it over the Streamable HTTP transport.
server = build_server()
app = server.http_app(transport='streamable-http')

# Serve it on the platform's web server port. Binding to 0.0.0.0 makes
# the server reachable through the Actor's container URL.
config = uvicorn.Config(
app,
host='0.0.0.0', # noqa: S104
port=Actor.configuration.web_server_port,
)
web_server = uvicorn.Server(config)

# Run the server in the background.
server_task = asyncio.create_task(web_server.serve())

url = Actor.configuration.web_server_url
Actor.log.info(f'MCP server is available at {url}/mcp')

# In production the server runs until the platform shuts the Actor down,
# for example when a Standby instance has been idle past its timeout. This
# runnable example instead serves for a short window so the run finishes
# on its own.
await asyncio.sleep(60)

# Signal the server to shut down and wait.
web_server.should_exit = True
await server_task


if __name__ == '__main__':
asyncio.run(main())

Note that:

  • A build_server helper registers the tools and resources. Add your own with the @server.tool() and @server.resource() decorators.
  • server.http_app(transport='streamable-http') returns an ASGI app that speaks MCP over Streamable HTTP, which uvicorn then serves.
  • The server binds to Actor.configuration.web_server_port and 0.0.0.0, so the platform can route the Actor's container URL to it.
  • The example serves in the background for a short window so the run finishes on its own. A production Actor keeps serving until the platform shuts it down. With Standby, shutdown happens automatically once an instance has been idle for a while.

MCP proxy

If you already have an MCP server, or want to expose a third-party one, you don't need to rewrite it. FastMCP can wrap an existing server and re-expose it over Streamable HTTP, so you put the same always-ready Apify endpoint in front of a server you didn't write. The following Actor proxies a remote MCP server, serving it on its web server port:

Run on
import asyncio

import uvicorn
from fastmcp.server import create_proxy

from apify import Actor

# The upstream MCP server to expose. Point this at any remote Streamable HTTP or
# SSE endpoint. To wrap a local stdio server instead, pass `create_proxy` an
# `mcpServers` config mapping instead of a URL, for example
# `{'mcpServers': {'my-server': {'command': ..., 'args': [...]}}}`.
UPSTREAM_URL = 'https://mcp.example.com/mcp'


async def main() -> None:
async with Actor:
# Connect to the upstream server and re-expose it over Streamable HTTP.
proxy = create_proxy(UPSTREAM_URL, name='my-mcp-proxy')
app = proxy.http_app(transport='streamable-http')

# Serve it on the platform's web server port, exactly like a server you
# build yourself. Binding to 0.0.0.0 makes it reachable through the
# Actor's container URL.
config = uvicorn.Config(
app,
host='0.0.0.0', # noqa: S104
port=Actor.configuration.web_server_port,
)
web_server = uvicorn.Server(config)

# Run the server in the background.
server_task = asyncio.create_task(web_server.serve())

url = Actor.configuration.web_server_url
Actor.log.info(f'MCP proxy is available at {url}/mcp')

# In production the server runs until the platform shuts the Actor down.
# This runnable example instead serves for a short window so the run
# finishes on its own.
await asyncio.sleep(60)

# Signal the server to shut down and wait.
web_server.should_exit = True
await server_task


if __name__ == '__main__':
asyncio.run(main())

Note that:

  • create_proxy connects to the upstream server and returns a FastMCP instance, so the rest of the Actor is identical to a server you build yourself.

  • The example wraps a remote URL. To spawn and wrap a local stdio server, pass an mcpServers config instead:

    proxy = create_proxy(
    {'mcpServers': {'arxiv': {'command': 'uv', 'args': ['run', 'arxiv-mcp-server']}}},
    name='my-mcp-proxy',
    )
  • Serving works the same way as the server. You expose it over Standby the same way, so clients connect to <actor-url>/mcp with a bearer token.

  • To control which tools clients may call, or to charge per call, add a FastMCP middleware that hooks on_list_tools and on_call_tool.

For a gateway with a tool whitelist and per-operation charging, start from the Python MCP proxy template.

Exposing it over Standby

Both the server and the proxy listen on the web server port, so you expose either one the same way. To make it an always-ready HTTP API, enable Actor Standby and tell the platform where the MCP endpoint lives. Set both in the Actor's .actor/actor.json:

{
"actorSpecification": 1,
"name": "my-mcp-server",
"usesStandbyMode": true,
"webServerMcpPath": "/mcp"
}

Deploy the Actor with apify push. Once it's running, an MCP client connects to the Actor's URL using the Streamable HTTP transport, passing an Apify API token as a bearer token:

{
"mcpServers": {
"my-mcp-server": {
"url": "https://me--my-mcp-server.apify.actor/mcp",
"headers": {
"Authorization": "Bearer <YOUR_APIFY_API_TOKEN>"
}
}
}
}

Monetizing with pay-per-event

Both approaches support pay-per-event charging, so you can cover the cost of running the server or turn it into a paid product.

  1. Define the events in the Actor's .actor/pay_per_event.json:

    {
    "tool-call": {
    "eventTitle": "Price for completing a tool call",
    "eventDescription": "Flat fee for completing a tool call.",
    "eventPriceUsd": 0.05
    }
    }
  2. Charge the event from your code when a client calls a tool. The add tool from the MCP server example becomes async so it can await the charge:

    @server.tool()
    async def add(a: float, b: float) -> float:
    """Add two numbers and return the sum."""
    await Actor.charge(event_name='tool-call')
    return a + b

You can charge per tool call, per resource read, or per any other operation. For the full setup, see the pay-per-event guide.

Starting from a template

Instead of wiring up an Actor by hand, you can scaffold one from a ready-made template. Apify provides two, one for each approach in this guide:

  • Build a server from scratch with the Python MCP server template, which uses FastMCP like the MCP server example.
  • Wrap an existing MCP server (stdio, HTTP, or SSE) with the Python MCP proxy template, like the MCP proxy example.

Both templates live in the actor-templates repository. To create a new project, use the Apify CLI, for example:

apify create my-mcp-server --template python-mcp-empty

Conclusion

In this guide, you learned how to deploy an MCP server as an Apify Actor. You can now write a server with FastMCP or wrap an existing one with a proxy, expose it over Actor Standby, connect MCP clients to it, and monetize it with pay-per-event. If you have questions or need assistance, feel free to reach out on our GitHub or join our Discord community. Happy building!

Additional resources