Skip to content

Commit 8f996bf

Browse files
committed
docs: mirror MCP servers guide into versioned docs 3.4
1 parent 821195e commit 8f996bf

6 files changed

Lines changed: 286 additions & 4 deletions

File tree

website/versioned_docs/version-3.4/01_introduction/index.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ Almost any Python project can become an Actor, including projects for:
4242
- **Browser automation** - Drive a real browser with [Playwright](./guides/playwright) or [Selenium](./guides/selenium), or with higher-level tools such as [Browser Use](./guides/browser-use).
4343
- **Web servers and APIs** - Run a [web server](./guides/running-webserver) inside an Actor to serve HTTP requests, for example to expose your scraper as a live API.
4444
- **AI agents** - Host agents built with your framework of choice. Ready-made Actor templates cover [PydanticAI](https://apify.com/templates/python-pydanticai), [CrewAI](https://apify.com/templates/python-crewai), [LangGraph](https://apify.com/templates/python-langgraph), [LlamaIndex](https://apify.com/templates/python-llamaindex-agent), and [Smolagents](https://apify.com/templates/python-smolagents).
45-
- **MCP servers** - Deploy a Python MCP server as an Actor and make its tools available to any MCP client. See the [MCP server](https://apify.com/templates/python-mcp-empty) and [MCP proxy](https://apify.com/templates/python-mcp-proxy) templates.
45+
- **MCP servers** - Deploy a Python MCP server as an Actor and make its tools available to any MCP client (see the [MCP servers guide](./guides/mcp-servers)). Ready-made Actor templates cover the [MCP server](https://apify.com/templates/python-mcp-empty) and [MCP proxy](https://apify.com/templates/python-mcp-proxy).
4646

4747
Whatever you build, the Apify SDK doesn't lock you into a particular framework. Bring the libraries you already use, and let Apify run your project in the cloud.
4848

website/versioned_docs/version-3.4/01_introduction/quick-start.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,4 @@ For other aspects of Actor development, explore these guides:
117117
- [Project management with uv](./guides/uv)
118118
- [Input validation with Pydantic](./guides/input-validation)
119119
- [Running a web server](./guides/running-webserver)
120+
- [Building MCP servers](./guides/mcp-servers)

website/versioned_docs/version-3.4/03_guides/12_running_webserver.mdx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ The URL is available in the following places:
2323

2424
The web server running inside the container must listen at the port defined by the `Actor.configuration.web_server_port` property. When running Actors locally, the port defaults to `4321`, so the web server will be accessible at `http://localhost:4321`.
2525

26-
## Example Actor
26+
## Using the standard library
2727

2828
The following example shows how to start a simple web server in your Actor, which will respond to every GET request with the number of items that the Actor has processed so far:
2929

@@ -52,14 +52,31 @@ Note that:
5252

5353
- `uvicorn.Server(...).serve()` is a coroutine. It runs as an `asyncio` task alongside the Actor's own work instead of blocking it. Setting `server.should_exit = True` triggers a graceful shutdown once the work is done.
5454
- The server binds to `0.0.0.0` (all interfaces) rather than `localhost`. This makes it reachable through the container URL, not only from inside the container.
55-
- The same pattern powers an [Actor Standby](#actor-standby) service. Swap the one-off work loop for an Actor that keeps serving requests.
55+
- The same pattern powers an [Actor Standby](#exposing-it-over-standby) service. Swap the one-off work loop for an Actor that keeps serving requests.
5656

57-
## Actor Standby
57+
## Exposing it over Standby
5858

5959
The example runs a web server for the duration of a single Actor run. With [Actor Standby](https://docs.apify.com/platform/actors/development/programming-interface/standby), you can instead expose your Actor as an always-ready HTTP API: the platform keeps the Actor running in the background and routes incoming HTTP requests to the web server inside it, spinning up additional instances as the load grows.
6060

6161
From the SDK's perspective, a Standby Actor is built the same way as the web server above. You start an HTTP server listening on the port from `Actor.configuration.web_server_port`. The difference is operational: instead of doing its work once and exiting, a Standby Actor stays up and serves requests. This makes it a good fit for low-latency, on-demand use cases, such as serving scraped data or acting as a microservice.
6262

63+
To enable Standby, set `usesStandbyMode` in the Actor's `.actor/actor.json`:
64+
65+
```json
66+
{
67+
"actorSpecification": 1,
68+
"name": "my-standby-server",
69+
"usesStandbyMode": true
70+
}
71+
```
72+
73+
Deploy the Actor with `apify push`. Once it's running, a client reaches the web server at the Actor's Standby URL, passing an [Apify API token](https://console.apify.com/account/integrations) as a bearer token:
74+
75+
```bash
76+
curl "https://me--my-standby-server.apify.actor" \
77+
-H "Authorization: Bearer <YOUR_APIFY_API_TOKEN>"
78+
```
79+
6380
To get started, use the [Standby Python template](https://apify.com/templates/python-standby). For details on enabling Standby, request routing, and readiness probes, see the [Actor Standby documentation](https://docs.apify.com/platform/actors/development/programming-interface/standby).
6481

6582
## Conclusion
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
---
2+
id: mcp-servers
3+
title: Deploying MCP servers
4+
description: Deploy a Python MCP server as an Apify Actor and make its tools available to any MCP client.
5+
---
6+
7+
import RunnableCodeBlock from '@site/src/components/RunnableCodeBlock';
8+
9+
import McpServerExample from '!!raw-loader!roa-loader!./code/13_mcp_server.py';
10+
import McpProxyExample from '!!raw-loader!roa-loader!./code/13_mcp_proxy.py';
11+
12+
In this guide, you'll learn how to deploy a [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server as an Apify Actor.
13+
14+
## Introduction
15+
16+
The [Model Context Protocol](https://modelcontextprotocol.io) 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.
17+
18+
Apify Actors are a good fit for MCP servers:
19+
20+
- With [Actor Standby](https://docs.apify.com/platform/actors/development/programming-interface/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.
21+
- The platform scales instances with demand, keeps logs, and handles the network, so you don't operate any infrastructure.
22+
- Every request carries an Apify API token, so the platform authenticates clients for you.
23+
- Pay-per-event charging lets you monetize the server, for example per tool call.
24+
25+
## Apify templates
26+
27+
Apify provides two templates for building MCP servers:
28+
29+
- Write a server from scratch with [FastMCP](https://gofastmcp.com/), starting from the [`python-mcp-empty`](https://apify.com/templates/python-mcp-empty) template. For details, see [MCP server](#mcp-server).
30+
- Wrap an existing MCP server (stdio, HTTP, or SSE) with a proxy, starting from the [`python-mcp-proxy`](https://apify.com/templates/python-mcp-proxy) template. For details, see [MCP proxy](#mcp-proxy).
31+
32+
Both templates live in the [actor-templates repository](https://github.com/apify/actor-templates). To create a new project, use the [Apify CLI](https://docs.apify.com/cli), for example:
33+
34+
```bash
35+
apify create my-mcp-server --template python-mcp-empty
36+
```
37+
38+
## Before you start
39+
40+
To follow along, install the [Apify CLI](https://docs.apify.com/cli/docs/installation) and log in with `apify login`. Both examples build on [FastMCP](https://gofastmcp.com/) and are served by [uvicorn](https://www.uvicorn.org/), so declare them in your Actor's `requirements.txt` next to the SDK:
41+
42+
```text
43+
apify
44+
fastmcp
45+
uvicorn
46+
```
47+
48+
## MCP server
49+
50+
Build your own server when you want to expose your own tools and resources. The following Actor runs a small [FastMCP](https://gofastmcp.com/) server that exposes a single `add` tool and an informational resource. It serves the MCP protocol over the [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http) transport on the Actor's web server port:
51+
52+
<RunnableCodeBlock className="language-python" language="python">
53+
{McpServerExample}
54+
</RunnableCodeBlock>
55+
56+
Note that:
57+
58+
- A `build_server` helper registers the tools and resources. Add your own with the `@server.tool()` and `@server.resource()` decorators.
59+
- `server.http_app(transport='streamable-http')` returns an ASGI app that speaks MCP over Streamable HTTP, which [uvicorn](https://www.uvicorn.org/) then serves.
60+
- 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.
61+
- 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](#exposing-it-over-standby), shutdown happens automatically once an instance has been idle for a while.
62+
63+
## MCP proxy
64+
65+
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:
66+
67+
<RunnableCodeBlock className="language-python" language="python">
68+
{McpProxyExample}
69+
</RunnableCodeBlock>
70+
71+
Note that:
72+
73+
- `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.
74+
- The example wraps a remote URL. To spawn and wrap a local stdio server, pass an `mcpServers` config instead:
75+
76+
```python
77+
proxy = create_proxy(
78+
{'mcpServers': {'arxiv': {'command': 'uv', 'args': ['run', 'arxiv-mcp-server']}}},
79+
name='my-mcp-proxy',
80+
)
81+
```
82+
83+
- Serving works the same way as the server. You expose it over [Standby](#exposing-it-over-standby) the same way, so clients connect to `<actor-url>/mcp` with a bearer token.
84+
- To control which tools clients may call, or to charge per call, add a FastMCP [middleware](https://gofastmcp.com/servers/middleware) that hooks `on_list_tools` and `on_call_tool`.
85+
86+
For a gateway with a tool whitelist and per-operation charging, start from the [`python-mcp-proxy`](https://apify.com/templates/python-mcp-proxy) template.
87+
88+
## Exposing it over Standby
89+
90+
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](https://docs.apify.com/platform/actors/development/programming-interface/standby) and tell the platform where the MCP endpoint lives. Set both in the Actor's `.actor/actor.json`:
91+
92+
```json
93+
{
94+
"actorSpecification": 1,
95+
"name": "my-mcp-server",
96+
"usesStandbyMode": true,
97+
"webServerMcpPath": "/mcp"
98+
}
99+
```
100+
101+
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](https://console.apify.com/account/integrations) as a bearer token:
102+
103+
```json
104+
{
105+
"mcpServers": {
106+
"my-mcp-server": {
107+
"url": "https://me--my-mcp-server.apify.actor/mcp",
108+
"headers": {
109+
"Authorization": "Bearer <YOUR_APIFY_API_TOKEN>"
110+
}
111+
}
112+
}
113+
}
114+
```
115+
116+
## Monetizing with pay-per-event
117+
118+
Both approaches support [pay-per-event charging](../concepts/pay-per-event), so you can cover the cost of running the server or turn it into a paid product.
119+
120+
1. Define the events in the Actor's `.actor/pay_per_event.json`:
121+
122+
```json
123+
{
124+
"tool-call": {
125+
"eventTitle": "Price for completing a tool call",
126+
"eventDescription": "Flat fee for completing a tool call.",
127+
"eventPriceUsd": 0.05
128+
}
129+
}
130+
```
131+
132+
2. Charge the event from your code when a client calls a tool. The `add` tool from the [server example](#mcp-server) becomes `async` so it can `await` the charge:
133+
134+
```python
135+
@server.tool()
136+
async def add(a: float, b: float) -> float:
137+
"""Add two numbers and return the sum."""
138+
await Actor.charge(event_name='tool-call')
139+
return a + b
140+
```
141+
142+
You can charge per tool call, per resource read, or per any other operation. For the full setup, see the [pay-per-event](../concepts/pay-per-event) guide.
143+
144+
## Conclusion
145+
146+
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. To get started, see the [Actor templates](https://apify.com/templates/categories/python). If you have questions or need assistance, feel free to reach out on our [GitHub](https://github.com/apify/apify-sdk-python) or join our [Discord community](https://discord.com/invite/jyEM2PRvMU). Happy building!
147+
148+
## Additional resources
149+
150+
- [Apify templates: MCP server](https://apify.com/templates/python-mcp-empty)
151+
- [Apify templates: MCP proxy](https://apify.com/templates/python-mcp-proxy)
152+
- [Apify: actor-templates repository](https://github.com/apify/actor-templates)
153+
- [Apify: MCP server documentation](https://docs.apify.com/platform/integrations/mcp)
154+
- [Model Context Protocol: Official documentation](https://modelcontextprotocol.io)
155+
- [FastMCP: Official documentation](https://gofastmcp.com/)
156+
- [Apify blog: What is the Model Context Protocol](https://blog.apify.com/what-is-model-context-protocol/)
157+
- [Apify blog: How to use MCP with Apify Actors](https://blog.apify.com/how-to-use-mcp/)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import asyncio
2+
3+
import uvicorn
4+
from fastmcp.server import create_proxy
5+
6+
from apify import Actor
7+
8+
# The upstream MCP server to expose. Point this at any remote Streamable HTTP or
9+
# SSE endpoint. To wrap a local stdio server instead, pass `create_proxy` an
10+
# `mcpServers` config mapping instead of a URL, for example
11+
# `{'mcpServers': {'my-server': {'command': ..., 'args': [...]}}}`.
12+
UPSTREAM_URL = 'https://mcp.example.com/mcp'
13+
14+
15+
async def main() -> None:
16+
async with Actor:
17+
# Connect to the upstream server and re-expose it over Streamable HTTP.
18+
proxy = create_proxy(UPSTREAM_URL, name='my-mcp-proxy')
19+
app = proxy.http_app(transport='streamable-http')
20+
21+
# Serve it on the platform's web server port, exactly like a server you
22+
# build yourself. Binding to 0.0.0.0 makes it reachable through the
23+
# Actor's container URL.
24+
config = uvicorn.Config(
25+
app,
26+
host='0.0.0.0', # noqa: S104
27+
port=Actor.configuration.web_server_port,
28+
)
29+
web_server = uvicorn.Server(config)
30+
31+
# Run the server in the background.
32+
server_task = asyncio.create_task(web_server.serve())
33+
34+
url = Actor.configuration.web_server_url
35+
Actor.log.info(f'MCP proxy is available at {url}/mcp')
36+
37+
# In production the server runs until the platform shuts the Actor down.
38+
# This runnable example instead serves for a short window so the run
39+
# finishes on its own.
40+
await asyncio.sleep(60)
41+
42+
# Signal the server to shut down and wait.
43+
web_server.should_exit = True
44+
await server_task
45+
46+
47+
if __name__ == '__main__':
48+
asyncio.run(main())
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import asyncio
2+
3+
import uvicorn
4+
from fastmcp import FastMCP
5+
6+
from apify import Actor
7+
8+
9+
def build_server() -> FastMCP:
10+
"""Create a FastMCP server exposing one tool and one resource."""
11+
server = FastMCP(name='calculator')
12+
13+
@server.tool()
14+
def add(a: float, b: float) -> float:
15+
"""Add two numbers and return the sum."""
16+
return a + b
17+
18+
@server.resource(uri='resource://calculator/info', name='calculator-info')
19+
def info() -> str:
20+
"""Describe what this MCP server does."""
21+
return 'A simple calculator MCP server that adds two numbers.'
22+
23+
return server
24+
25+
26+
async def main() -> None:
27+
async with Actor:
28+
# Build the server and expose it over the Streamable HTTP transport.
29+
server = build_server()
30+
app = server.http_app(transport='streamable-http')
31+
32+
# Serve it on the platform's web server port. Binding to 0.0.0.0 makes
33+
# the server reachable through the Actor's container URL.
34+
config = uvicorn.Config(
35+
app,
36+
host='0.0.0.0', # noqa: S104
37+
port=Actor.configuration.web_server_port,
38+
)
39+
web_server = uvicorn.Server(config)
40+
41+
# Run the server in the background.
42+
server_task = asyncio.create_task(web_server.serve())
43+
44+
url = Actor.configuration.web_server_url
45+
Actor.log.info(f'MCP server is available at {url}/mcp')
46+
47+
# In production the server runs until the platform shuts the Actor down,
48+
# for example when a Standby instance has been idle past its timeout. This
49+
# runnable example instead serves for a short window so the run finishes
50+
# on its own.
51+
await asyncio.sleep(60)
52+
53+
# Signal the server to shut down and wait.
54+
web_server.should_exit = True
55+
await server_task
56+
57+
58+
if __name__ == '__main__':
59+
asyncio.run(main())

0 commit comments

Comments
 (0)