Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
147 changes: 147 additions & 0 deletions apify-mcp-server-sdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# Apify MCP Server SDK

A comprehensive Python SDK for building Apify MCP (Model Context Protocol) server implementations with built-in support for charging, tool authorization, and multiple transport protocols.

## Features

- **Multi-Transport Support**: Connect to stdio, SSE, or HTTP-based MCP servers
- **Tool Authorization**: Whitelist-based tool authorization with flexible charging
- **HTML Browser Support**: Automatic detection and serving of HTML pages for browser requests
- **OAuth Integration**: Built-in OAuth authorization server support
- **Event Store**: In-memory event store for resumability functionality
- **Flexible Charging**: Configurable charging for different MCP operations
- **Easy Configuration**: Simple configuration utilities for common setups

## Installation

```bash
pip install apify-mcp-server-sdk
```

## Usage

### Simple Calculator Server

```python
import os
from enum import Enum
from apify import Actor
from apify_mcp_server_sdk import ServerType, create_stdio_config, run_mcp_server

class ChargeEvents(str, Enum):
ACTOR_START = 'actor-start'
TOOL_CALL = 'tool-call'

async def main():
# Configure the MCP server
mcp_server_params = create_stdio_config(
command='uv',
args=['run', 'mcp-server-calculator'],
)

# Run the server with charging enabled
await run_mcp_server(
server_name='calculator-mcp-server',
mcp_server_params=mcp_server_params,
server_type=ServerType.STDIO,
actor_charge_function=Actor.charge,
)

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

### Remote Server with Authentication

```python
import os
from enum import Enum
from apify import Actor
from apify_mcp_server_sdk import ServerType, create_remote_config, run_mcp_server

class ChargeEvents(str, Enum):
ACTOR_START = 'actor-start'
TOOL_CALL = 'tool-call'

async def main():
# Configure remote MCP server with authentication
mcp_server_params = create_remote_config(
url='https://your-remote-mcp-server.com/mcp',
headers={'Authorization': f'Bearer {os.getenv("API_KEY")}'},
timeout=60.0,
)

# Run the server
await run_mcp_server(
server_name='remote-mcp-server',
mcp_server_params=mcp_server_params,
server_type=ServerType.HTTP,
actor_charge_function=Actor.charge,
)
```

### Advanced: Tool Whitelisting and Custom Charging

```python
import os
from enum import Enum
from apify import Actor
from apify_mcp_server_sdk import ProxyServer, ServerType, create_stdio_config, get_actor_config

class ChargeEvents(str, Enum):
ACTOR_START = 'actor-start'
GENERATE_SLIDE = 'generate-slide'
GET_TEMPLATES = 'get-templates'

async def main():
# Configure MCP server
mcp_server_params = create_stdio_config(
command='npx',
args=['mcp-remote', 'https://mcp.slidespeak.co/mcp', '--header', f'Authorization: Bearer {os.getenv("API_KEY")}'],
)

# Define tool whitelist with custom charging
tool_whitelist = {
'generatePowerpoint': (ChargeEvents.GENERATE_SLIDE.value, 1),
'getAvailableTemplates': (ChargeEvents.GET_TEMPLATES.value, 1),
}

# Get actor configuration
host, port, standby_mode = get_actor_config()

async with Actor:
await Actor.charge(ChargeEvents.ACTOR_START.value)

# Create proxy server with custom configuration
proxy_server = ProxyServer(
server_name='slidespeak-mcp-server',
config=mcp_server_params,
host=host,
port=port,
server_type=ServerType.STDIO,
actor_charge_function=Actor.charge,
)
await proxy_server.start()
```

## Development

```bash
# Install in development mode
pip install -e .

# Run tests
pytest

# Format code
black src/
isort src/

# Type checking
mypy src/
```

## License

MIT
46 changes: 46 additions & 0 deletions apify-mcp-server-sdk/examples/remote_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""Example: Remote MCP Server using the SDK."""

import os
from enum import Enum

from apify import Actor
from apify_mcp_server_sdk import (
ServerType,
create_remote_config,
run_mcp_server,
)


class ChargeEvents(str, Enum):
"""Event types for charging MCP operations."""
ACTOR_START = 'actor-start'
TOOL_CALL = 'tool-call'
RESOURCE_READ = 'resource-read'


async def main() -> None:
"""Run a remote MCP server."""
# Configure the remote MCP server
mcp_server_params = create_remote_config(
url='https://your-remote-mcp-server.com/mcp',
headers={'Authorization': f'Bearer {os.getenv("API_KEY", "your-api-key")}'},
timeout=60.0,
)

async with Actor:
# Initialize and charge for Actor startup
Actor.log.info('Starting Remote MCP Server Actor')
await Actor.charge(ChargeEvents.ACTOR_START.value)

# Run the server with charging enabled
await run_mcp_server(
server_name='remote-mcp-server',
mcp_server_params=mcp_server_params,
server_type=ServerType.HTTP, # or ServerType.SSE
actor_charge_function=Actor.charge,
)


if __name__ == '__main__':
import asyncio
asyncio.run(main())
45 changes: 45 additions & 0 deletions apify-mcp-server-sdk/examples/simple_calculator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Example: Simple Calculator MCP Server using the SDK."""

import os
from enum import Enum

from apify import Actor
from apify_mcp_server_sdk import (
ServerType,
create_stdio_config,
run_mcp_server,
)


class ChargeEvents(str, Enum):
"""Event types for charging MCP operations."""
ACTOR_START = 'actor-start'
TOOL_CALL = 'tool-call'


async def main() -> None:
"""Run the calculator MCP server."""
# Configure the MCP server to run calculator
mcp_server_params = create_stdio_config(
command='uv',
args=['run', 'mcp-server-calculator'],
env={'YOUR-ENV_VAR': os.getenv('YOUR-ENV-VAR') or ''},
)

async with Actor:
# Initialize and charge for Actor startup
Actor.log.info('Starting Calculator MCP Server Actor')
await Actor.charge(ChargeEvents.ACTOR_START.value)

# Run the server with charging enabled
await run_mcp_server(
server_name='calculator-mcp-server',
mcp_server_params=mcp_server_params,
server_type=ServerType.STDIO,
actor_charge_function=Actor.charge,
)


if __name__ == '__main__':
import asyncio
asyncio.run(main())
86 changes: 86 additions & 0 deletions apify-mcp-server-sdk/examples/slidespeak_with_whitelist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""Example: SlideSpeak MCP Server with tool whitelisting using the SDK."""

import os
from enum import Enum

from apify import Actor
from apify_mcp_server_sdk import (
ProxyServer,
ServerType,
create_stdio_config,
get_actor_config,
)


class ChargeEvents(str, Enum):
"""Event types for charging MCP operations."""
ACTOR_START = 'actor-start'
GET_TEMPLATES = 'get-templates'
GENERATE_SLIDE = 'generate-slide'
GENERATE_SLIDE_BY_SLIDE = 'generate-slide-by-slide'
GET_TASK_STATUS = 'get-task-status'


async def main() -> None:
"""Run the SlideSpeak MCP server with tool whitelisting."""
# Get the API key from environment
slidespeak_api_key = os.getenv('SLIDESPEAK_API_KEY')
if not slidespeak_api_key:
raise ValueError("SLIDESPEAK_API_KEY environment variable not set!")

# Configure the MCP server
mcp_server_params = create_stdio_config(
command='uv',
args=[
'run',
'mcp-remote',
'https://mcp.slidespeak.co/mcp',
'--header',
f'Authorization: Bearer {slidespeak_api_key}',
],
)

# Define tool whitelist with charging events
tool_whitelist = {
'generatePowerpoint': (ChargeEvents.GENERATE_SLIDE.value, 1),
'getAvailableTemplates': (ChargeEvents.GET_TEMPLATES.value, 1),
'generateSlideBySlide': (ChargeEvents.GENERATE_SLIDE_BY_SLIDE.value, 1),
'getTaskStatus': (ChargeEvents.GET_TASK_STATUS.value, 1),
}

# Get actor configuration
host, port, standby_mode = get_actor_config()

async with Actor:
# Initialize and charge for Actor startup
Actor.log.info('Starting SlideSpeak MCP Server Actor')
await Actor.charge(ChargeEvents.ACTOR_START.value)

if not standby_mode:
msg = 'This Actor is not meant to be run directly. It should be run in standby mode.'
Actor.log.error(msg)
await Actor.exit(status_message=msg)
return

try:
# Create and start the server with tool whitelisting
proxy_server = ProxyServer(
server_name='slidespeak-mcp-server',
config=mcp_server_params,
host=host,
port=port,
server_type=ServerType.STDIO,
actor_charge_function=Actor.charge,
tool_whitelist=tool_whitelist,
)

await proxy_server.start()
except Exception as e:
Actor.log.exception(f'Server failed to start: {e}')
await Actor.exit()
raise


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