Azure Functions Python extension for MCP STDIO adapter - converts STDIO MCP servers to streamable HTTP endpoints
Project description
Azure Functions MCP STDIO Adapter
A Python extension for Azure Functions that acts as an adapter between MCP (Model Context Protocol) servers running on STDIO and HTTP clients. This adapter surfaces STDIO-based MCP servers as streamable HTTP endpoints without modifying the underlying MCP server behavior.
Overview
The Azure Functions MCP STDIO Adapter enables seamless integration between:
- Input: Python MCP servers that communicate via STDIO (JSON-RPC over stdin/stdout with Content-Length framing)
- Output: Azure Functions HTTP endpoints that expose streamable HTTP responses
- Transport: Maintains protocol parity without modifying customer MCP servers
Data Flow Diagram
Client ⇄ HTTP Stream ⇄ Azure Function Adapter ⇄ STDIO ⇄ MCP Server
│ │ │ │
│ │ │ └─ Customer's MCP Server
│ │ └─ JSON-RPC over STDIO
│ └─ Process Management & Forwarding
└─ Streamable HTTP Responses
Features
- Multi-format Configuration Support: Supports various JSON configuration formats
- Process Lifecycle Management: Automatic start, monitor, and graceful shutdown of MCP servers
- Streaming HTTP Integration: Uses MCP SDK's StreamableHTTPSessionManager for real-time communication
- UVX Integration: Supports
uvx(uv tool run) for running MCP servers without global installation - Error Recovery: Handles MCP server crashes and reconnection scenarios
- Environment Variable Support: Passes through environment variables to MCP servers
- Well-known Configuration Files: Supports loading from standard file locations
Session Isolation & Multi-Tenancy
The Azure Functions MCP STDIO Adapter provides best-effort session isolation for multi-tenant scenarios while maintaining optimal resource utilization.
Session-to-Process Mapping
Each unique session gets its own dedicated MCP server process, ensuring proper isolation between different clients:
Session ID → Adapter Instance → Process Manager → MCP Server Process
session-1 → adapter_1 → process_1 → PID 1001
session-2 → adapter_2 → process_2 → PID 1002
session-3 → adapter_3 → process_3 → PID 1003
How It Works
Session Lifecycle
# Client A connects with session-1
GET /mcp HTTP/1.1
mcp-session-id: session-1
# → Creates new MCPStdioAdapter instance → Spawns new MCP server process
# Client A makes multiple calls - all use the same process
POST /mcp HTTP/1.1
mcp-session-id: session-1
{"method": "initialize", ...} # → process_1
POST /mcp HTTP/1.1
mcp-session-id: session-1
{"method": "tools/list", ...} # → process_1
POST /mcp HTTP/1.1
mcp-session-id: session-1
{"method": "tools/call", ...} # → process_1
# Client B connects with session-2 (concurrent)
GET /mcp HTTP/1.1
mcp-session-id: session-2
# → Creates SEPARATE MCPStdioAdapter → Spawns SEPARATE MCP server process
POST /mcp HTTP/1.1
mcp-session-id: session-2
{"method": "initialize", ...} # → process_2 (ISOLATED!)
Session Isolation Guarantees
-
Process Isolation: Each session runs in its own MCP server subprocess
# Each session maintains separate process state session_adapters = { "session-1": MCPStdioAdapter(process_1), # PID 1001 "session-2": MCPStdioAdapter(process_2), # PID 1002 "session-3": MCPStdioAdapter(process_3), # PID 1003 }
-
Memory Isolation: Each adapter has independent buffers and state
# Session 1's adapter state adapter_1._read_buffer = b"session_1_data" adapter_1._session_state = "initialized" # Session 2's adapter state (completely separate) adapter_2._read_buffer = b"session_2_data" adapter_2._session_state = "uninitialized"
-
Communication Isolation: Each session has dedicated STDIO channels
# No cross-session message contamination adapter_1.process_manager.stdin # → process_1 stdin adapter_2.process_manager.stdin # → process_2 stdin adapter_1.process_manager.stdout # ← process_1 stdout adapter_2.process_manager.stdout # ← process_2 stdout
Session Management
Session Headers
Sessions are identified via HTTP headers:
POST /mcp HTTP/1.1
mcp-session-id: client-unique-session-id
Content-Type: application/json
{"jsonrpc": "2.0", "method": "tools/list", "id": 1}
Automatic Session Creation
If no session ID is provided, the adapter generates one:
# Client request without session ID
session_id = req.headers.get("mcp-session-id")
if not session_id:
session_id = str(uuid.uuid4()).replace("-", "") # Generate new session
# New adapter and process created automatically
Session Persistence
Sessions persist across multiple HTTP requests:
# Session state maintained in memory
class MCPSessionState:
session_id: str
is_initialized: bool = False
initialization_response: Optional[Dict[str, Any]] = None
last_activity: float = 0.0 # Auto-cleanup after timeout
Best-Effort Multi-Tenancy
What "Best-Effort" Means
✅ Guaranteed Isolation:
- Each session has its own MCP server process
- Memory spaces are completely separate
- STDIO communication channels are isolated
- Session state is tracked independently
⚠️ Azure Functions Shared Environment:
- Sessions share the same Azure Functions runtime
- Sessions share the same file system
- Sessions share network and system resources
- No cryptographic isolation between sessions
Resource Management
# Automatic resource cleanup
class MCPSessionManager:
def __init__(self, session_timeout_seconds: float = 3600): # 1 hour default
self._sessions: Dict[str, MCPSessionState] = {}
async def _cleanup_expired_sessions(self):
"""Remove expired sessions and their processes"""
current_time = time.time()
expired_sessions = [
sid for sid, session in self._sessions.items()
if current_time - session.last_activity > self._session_timeout
]
for session_id in expired_sessions:
# Clean up adapter and terminate process
await self._cleanup_session(session_id)
Recommended Usage Patterns
-
Trusted Multi-Tenancy: Use for scenarios where tenants are trusted (e.g., different teams in same organization)
-
Development/Testing: Ideal for development environments with multiple concurrent users
-
Microservice Integration: Perfect for multiple services calling the same MCP functionality
-
Session-Aware Applications: Applications that maintain client state across multiple MCP calls
Limitations & Considerations
Security Considerations
- File System Access: MCP servers can access the same file system
- Environment Variables: Shared environment between sessions
- Network Access: Sessions share network interfaces
- Process Visibility: Processes may be visible to each other
Resource Limits
- Memory Usage: Multiple processes increase memory consumption
- Process Limits: Azure Functions has process count limitations
- Connection Limits: Each session maintains persistent connections
Best Practices
# 1. Use unique session IDs per client
session_id = f"client-{client_id}-{timestamp}"
# 2. Implement proper session cleanup
@app.function_name("cleanup_sessions")
@app.timer_trigger(schedule="0 */30 * * * *") # Every 30 minutes
async def cleanup_expired_sessions(timer: func.TimerRequest):
await session_manager.cleanup_expired_sessions()
# 3. Monitor resource usage
@app.function_name("session_metrics")
@app.http_trigger(methods=["GET"])
async def get_session_metrics(req: func.HttpRequest):
return {
"active_sessions": len(session_adapters),
"total_processes": sum(1 for adapter in session_adapters.values()
if adapter.is_connected)
}
Configuration for Multi-Tenancy
{
"mcpServers": {
"shared-mcp-server": {
"command": "uvx",
"args": ["your-mcp-server"],
"env": {
"MAX_CONCURRENT_SESSIONS": "10",
"SESSION_TIMEOUT_SECONDS": "3600"
},
"timeout_seconds": 30,
"restart_on_failure": true,
"max_restarts": 3
}
}
}
This architecture provides robust session isolation suitable for most multi-tenant Azure Functions scenarios while maintaining the flexibility and performance benefits of the MCP protocol.
Authentication & Authorization
The Azure Functions MCP STDIO Adapter provides comprehensive authentication support for both Azure and non-Azure MCP servers, enabling secure remote access with proper token handling and On-Behalf-Of (OBO) flows.
Authentication Architecture
Client Request → Azure Functions → Auth Provider → MCP Server Process
↓ ↓ ↓ ↓
Bearer Token → Token Validation → Env Variables → Authenticated SDK
Supported Authentication Methods
1. No Authentication (none)
For development, testing, or internal-only MCP servers:
{
"mcpServers": {
"internal-tools": {
"command": "uvx",
"args": ["internal-mcp-server"],
"auth": {
"method": "none"
}
}
}
}
2. Azure Default Credentials (azure_default)
Uses Azure Managed Identity when deployed, DefaultAzureCredential locally:
{
"mcpServers": {
"azure-resources": {
"command": "uvx",
"args": ["azure-resource-mcp"],
"auth": {
"method": "azure_default",
"azure_scopes": [
"https://management.azure.com/.default"
],
"forward_user_token": false
}
}
}
}
Use Cases:
- MCP servers that need to access Azure resources with the function's identity
- Scenarios where the MCP server itself needs Azure permissions
- Backend services that don't need user context
3. Azure On-Behalf-Of (azure_obo)
Perfect for Fabric RTI and similar scenarios - forwards user tokens to Azure services:
{
"mcpServers": {
"fabric-rti-mcp": {
"command": "uvx",
"args": ["microsoft-fabric-rti-mcp"],
"env": {
"KUSTO_SERVICE_URI": "https://help.kusto.windows.net/",
"KUSTO_SERVICE_DEFAULT_DB": "Samples"
},
"auth": {
"method": "azure_obo",
"azure_client_id": "${AZURE_CLIENT_ID}",
"azure_client_secret": "${AZURE_CLIENT_SECRET}",
"azure_scopes": [
"https://management.azure.com/.default",
"https://fabric.microsoft.com/.default"
],
"forward_user_token": true
}
}
}
}
Environment Variables Set for MCP Server:
AZURE_CLIENT_ID=your-app-registration-id
AZURE_CLIENT_SECRET=your-client-secret
AZURE_TENANT_ID=extracted-from-user-token
AZURE_USER_ASSERTION=original-user-token
AZURE_USE_OBO=true
Use Cases:
- Fabric RTI MCP servers accessing user's Fabric workspaces
- Any Azure service requiring user context (SharePoint, Graph, etc.)
- Multi-tenant applications with user-specific data access
4. Generic OAuth2 Bearer (oauth2_bearer)
For non-Azure OAuth2 providers (Google, GitHub, custom identity providers):
{
"mcpServers": {
"github-tools": {
"command": "uvx",
"args": ["github-mcp-server"],
"auth": {
"method": "oauth2_bearer",
"oauth2_required_scopes": [
"repo:read",
"user:read"
],
"oauth2_issuer": "https://github.com",
"forward_user_token": true
}
}
}
}
Environment Variables Set for MCP Server:
OAUTH_ACCESS_TOKEN=user-provided-token
OAUTH_USER_ID=extracted-user-id
OAUTH_SCOPES=repo:read user:read
Client-Side Authentication
HTTP Headers Required
All authenticated requests must include the Authorization header:
POST /api/mcp HTTP/1.1
Host: your-function-app.azurewebsites.net
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
mcp-session-id: session-12345
{
"jsonrpc": "2.0",
"method": "tools/list",
"id": 1
}
MCP Client Configuration
When using the MCP SDK, configure HTTP transport with authentication:
from mcp import ClientSession
from mcp.client.stdio import StdioServerParameters
from mcp.client.sse import SseServerParameters
# For HTTP streaming with auth
server = SseServerParameters(
url="https://your-function-app.azurewebsites.net/api/mcp",
headers={
"Authorization": f"Bearer {user_token}",
"Content-Type": "application/json"
}
)
async with ClientSession(server) as session:
# Use authenticated session
result = await session.call_tool("list_files", {"path": "/"})
Azure Active Directory Integration
App Registration Setup
- Create App Registration in Azure Portal
- Configure API Permissions for target services:
- Microsoft Graph: User.Read - Azure Service Management: user_impersonation - Power BI Service: Dataset.Read.All (for Fabric)
- Generate Client Secret
- Configure Application Settings:
AZURE_CLIENT_ID=12345678-1234-1234-1234-123456789012 AZURE_CLIENT_SECRET=your-secret-value
Token Acquisition Flow
// Client-side token acquisition (JavaScript example)
import { PublicClientApplication } from "@azure/msal-browser";
const msalConfig = {
auth: {
clientId: "your-client-id",
authority: "https://login.microsoftonline.com/your-tenant"
}
};
const pca = new PublicClientApplication(msalConfig);
// Get token for MCP server access
const tokenRequest = {
scopes: [
"https://management.azure.com/.default",
"https://fabric.microsoft.com/.default"
]
};
const response = await pca.acquireTokenSilent(tokenRequest);
const accessToken = response.accessToken;
// Use token with MCP client
const mcpClient = new McpClient({
url: "https://your-function.azurewebsites.net/api/mcp",
headers: {
"Authorization": `Bearer ${accessToken}`
}
});
Error Handling
Authentication Errors
The adapter returns standard HTTP status codes for auth failures:
// 401 Unauthorized - Missing or invalid token
{
"error": {
"code": "authentication_required",
"message": "Missing or invalid Authorization header"
}
}
// 403 Forbidden - Insufficient scopes
{
"error": {
"code": "insufficient_scopes",
"message": "Token missing required scopes: repo:write"
}
}
Debugging Authentication
Enable debug logging to troubleshoot auth issues:
import logging
logging.getLogger("azurefunctions.extensions.mcp_server.auth").setLevel(logging.DEBUG)
Security Considerations
Token Validation
- Bearer tokens are parsed for claims extraction
- Production deployments should implement proper JWT signature verification
- Tokens are validated for required scopes before processing
Token Storage
- User tokens are only stored in memory during request processing
- Tokens are passed to MCP servers via environment variables
- No persistent token storage in the adapter
Environment Isolation
- Each session gets isolated environment variables
- Authentication credentials are scoped to individual MCP server processes
- No credential sharing between sessions
Best Practices
For Azure MCP Servers
{
"auth": {
"method": "azure_obo",
"azure_scopes": [
"https://management.azure.com/.default" // Be specific about scopes
],
"forward_user_token": true // Enable for user context
}
}
For Non-Azure MCP Servers
{
"auth": {
"method": "oauth2_bearer",
"oauth2_required_scopes": ["read:data"], // Validate required scopes
"forward_user_token": true
}
}
Environment Variables
# Use Azure App Settings for secrets
AZURE_CLIENT_SECRET="@Microsoft.KeyVault(SecretUri=https://vault.vault.azure.net/secrets/client-secret/)"
# Reference environment variables in config
"azure_client_id": "${AZURE_CLIENT_ID}"
This authentication architecture ensures secure, scalable access to MCP servers while supporting both Azure-native and generic OAuth2 authentication patterns.
Installation
# Install with UV (recommended)
uv add azurefunctions-mcp-stdio-adapter
# Or with pip
pip install azurefunctions-mcp-stdio-adapter
Quick Start
Configuration-Only Usage
- Create a configuration file
mcp_config.json:
{
"mcpServers": {
"git-tools": {
"command": "uvx",
"args": ["mcp-server-git"],
"env": {
"GIT_REPO_PATH": "/path/to/your/repo"
}
}
}
}
- Create your Azure Function app:
from azurefunctions.extensions.mcp_server import MCPFunctionApp, MCPMode
# Load configuration from file
app = MCPFunctionApp(
mode=MCPMode.STDIO,
config_file="mcp_config.json"
)
Programmatic Usage
import azure.functions as func
from azurefunctions.extensions.mcp_server import (
MCPFunctionApp,
MCPMode,
MCPStdioConfiguration,
MCPServerStdioParams
)
# Define MCP server configuration
git_mcp = MCPStdioConfiguration(
name="git-tools",
params=MCPServerStdioParams(
command="uvx",
args=["mcp-server-git"],
env={"GIT_REPO_PATH": "/path/to/repo"}
)
)
# Create MCP Function App
app = MCPFunctionApp(
mode=MCPMode.STDIO,
mcp_server=git_mcp
)
Configuration Formats
The adapter supports JSON configuration files with the following format:
Standard Configuration Format
{
"mcpServers": {
"mssql": {
"command": "python",
"args": ["server.py"],
"env": {
"MSSQL_SERVER": "your_server",
"MSSQL_DATABASE": "your_database"
}
}
}
}
Example: MySQL Configuration
{
"mcpServers": {
"mysql": {
"command": "uvx",
"args": ["--from", "mysql-mcp-server", "mysql_mcp_server"],
"env": {
"MYSQL_HOST": "localhost",
"MYSQL_PORT": "3306",
"MYSQL_USER": "your_username",
"MYSQL_PASSWORD": "your_password",
"MYSQL_DATABASE": "your_database"
}
}
}
}
Example: Fabric RTI Configuration
{
"mcpServers": {
"fabric-rti-mcp": {
"command": "uvx",
"args": ["microsoft-fabric-rti-mcp"],
"env": {
"KUSTO_SERVICE_URI": "https://help.kusto.windows.net/",
"KUSTO_SERVICE_DEFAULT_DB": "Samples"
}
}
}
}
API Reference
MCPFunctionApp
The main class for creating Azure Function apps with MCP STDIO adapter functionality.
class MCPFunctionApp:
def __init__(
self,
mode: MCPMode = MCPMode.STDIO,
mcp_server: Optional[MCPStdioConfiguration] = None,
config_file: Optional[str] = None,
auth_level: Union[AuthLevel, str] = AuthLevel.FUNCTION,
**kwargs
):
"""
Initialize MCP Function App
Args:
mode: Operating mode (currently only STDIO supported)
mcp_server: Programmatic MCP server configuration
config_file: Path to JSON configuration file
auth_level: Azure Functions authorization level
"""
MCPStdioConfiguration
Configuration container for MCP STDIO servers.
class MCPStdioConfiguration:
def __init__(
self,
name: str,
params: MCPServerStdioParams
):
"""
MCP STDIO server configuration
Args:
name: Unique name for the MCP server
params: Server execution parameters
"""
MCPServerStdioParams
Parameters for STDIO server execution.
class MCPServerStdioParams:
def __init__(
self,
command: str,
args: List[str] = None,
env: Dict[str, str] = None,
working_dir: Optional[str] = None
):
"""
STDIO server execution parameters
Args:
command: Command to execute (e.g., "uvx", "python")
args: Command arguments
env: Environment variables
working_dir: Working directory for the process
"""
Deployment
Local Development
# Clone and setup
git clone <your-repo>
cd azurefunctions-mcp-stdio-adapter
# Install dependencies
uv sync
# Run tests
uv run pytest
# Format code
uv run black .
uv run isort .
Azure Deployment
- Ensure your
function_app.pyuses the MCP adapter:
from azurefunctions.extensions.mcp_server import MCPFunctionApp, MCPMode
app = MCPFunctionApp(
mode=MCPMode.STDIO,
config_file="mcp_config.json"
)
- Deploy using Azure Functions Core Tools:
func azure functionapp publish <your-function-app-name>
Error Handling
The adapter includes comprehensive error handling:
- UVX Detection: Automatically detects missing
uvxand provides helpful error messages - Process Recovery: Handles MCP server crashes with automatic restart
- Connection Management: Manages STDIO connections with proper cleanup
- Timeout Handling: Configurable timeouts for process startup and communication
Logging and Monitoring
Enable detailed logging by setting the log level:
import logging
logging.basicConfig(level=logging.INFO)
app = MCPFunctionApp(
mode=MCPMode.STDIO,
config_file="mcp_config.json"
)
Troubleshooting
Common Issues
- UVX not found: Ensure
uvxis installed and available in PATH - Process startup timeout: Increase timeout in configuration or check MCP server startup time
- STDIO communication errors: Verify MCP server implements proper Content-Length framing
Debug Mode
Enable debug logging for detailed troubleshooting:
import logging
logging.basicConfig(level=logging.DEBUG)
References
- MCP Specification - Official MCP transport specification
- MCP Python SDK - STDIO and Streamable HTTP support
- Azure Functions Python Streaming - Azure Functions HTTP streaming documentation
- UVX Documentation - UV tool runner documentation
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Run the test suite:
uv run pytest - Format code:
uv run black . && uv run isort . - Submit a pull request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
For issues and questions:
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file azurefunctions_mcp_stdio_adapter-0.1.0a11.tar.gz.
File metadata
- Download URL: azurefunctions_mcp_stdio_adapter-0.1.0a11.tar.gz
- Upload date:
- Size: 51.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fa39de34f0576c690dd599cd06442556b101f31a5f0397ab32f771c22b7d29c8
|
|
| MD5 |
2b392fc2f883110362c4d7cf140583c7
|
|
| BLAKE2b-256 |
c30038ae21b1325e0f6ba220d4aa6c1e9255a9448201af17a497210a856f3404
|
File details
Details for the file azurefunctions_mcp_stdio_adapter-0.1.0a11-py3-none-any.whl.
File metadata
- Download URL: azurefunctions_mcp_stdio_adapter-0.1.0a11-py3-none-any.whl
- Upload date:
- Size: 50.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f9c371724e12ff7c6577796bb654c0aed5996da1eadf2e174a5274e09a3d08ef
|
|
| MD5 |
d2c6cd8fa24648e36372fb159cea7769
|
|
| BLAKE2b-256 |
650807c03b7c11d05c1f0b6e48e373070d3e111acb8a04e18a29d8a4355c9824
|