Skip to content

Commit 273127e

Browse files
Add environment configuration and health check for remote deployment
- Introduced .env.example file to outline environment variables for remote deployment. - Updated Dockerfile to include environment variables and health check for container orchestration. - Enhanced server.py to support API key retrieval from HTTP headers and added a health check endpoint for remote deployments. - Updated main function to handle different transport modes for local and remote execution.
1 parent a92f270 commit 273127e

File tree

4 files changed

+142
-63
lines changed

4 files changed

+142
-63
lines changed

.env.example

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# ScrapeGraph MCP Server Environment Variables
2+
3+
# ===========================================
4+
# FOR REMOTE DEPLOYMENT (http mode on Render, etc.)
5+
# ===========================================
6+
7+
# Transport mode: "http" for remote deployment
8+
MCP_TRANSPORT=http
9+
10+
# Server host - use 0.0.0.0 for Docker/remote
11+
HOST=0.0.0.0
12+
13+
# Server port - Render automatically sets this via PORT env var
14+
PORT=8000
15+
16+
# ===========================================
17+
# FOR USERS CONNECTING TO REMOTE SERVER
18+
# ===========================================
19+
# Pass your ScrapeGraph API key via the X-API-Key header using mcp-remote.
20+
#
21+
# Example Claude Desktop config:
22+
# {
23+
# "mcpServers": {
24+
# "scrapegraph": {
25+
# "command": "npx",
26+
# "args": [
27+
# "-y",
28+
# "mcp-remote",
29+
# "https://your-server.onrender.com/mcp",
30+
# "--header",
31+
# "X-API-Key:${SGAI_API_KEY}"
32+
# ],
33+
# "env": {
34+
# "SGAI_API_KEY": "your-api-key-here"
35+
# }
36+
# }
37+
# }
38+
# }

Dockerfile

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ FROM python:3.11-slim
44
# Set working directory
55
WORKDIR /app
66

7-
# Set Python unbuffered mode
7+
# Set Python unbuffered mode for real-time logs
88
ENV PYTHONUNBUFFERED=1
99

1010
# Copy pyproject.toml and README.md first for better caching
@@ -16,13 +16,25 @@ COPY src/ ./src/
1616
# Install the package and its dependencies from pyproject.toml
1717
RUN pip install --no-cache-dir .
1818

19-
# Create non-root user
19+
# Create non-root user for security
2020
RUN useradd -m -u 1000 mcpuser && \
2121
chown -R mcpuser:mcpuser /app
2222

2323
# Switch to non-root user
2424
USER mcpuser
2525

26+
# Environment variables for remote deployment
27+
# MCP_TRANSPORT: "http" for remote, "stdio" for local (default)
28+
# PORT: Server port (Render sets this automatically)
29+
ENV MCP_TRANSPORT=http
30+
ENV PORT=8000
31+
32+
# Expose the port
33+
EXPOSE 8000
34+
35+
# Health check for container orchestration
36+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
37+
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:${PORT}/health')" || exit 1
38+
2639
# Run the server
2740
CMD ["python", "-m", "scrapegraph_mcp.server"]
28-

render.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Render Blueprint for ScrapeGraph MCP Server
2+
# Deploy with: https://render.com/deploy
3+
# Or connect your GitHub repo to Render and it will auto-detect this file
4+
5+
services:
6+
- type: web
7+
name: scrapegraph-mcp
8+
runtime: docker
9+
plan: starter # Change to 'free' for testing, 'starter' or higher for production
10+
11+
# Docker configuration
12+
dockerfilePath: ./Dockerfile
13+
14+
# Health check endpoint
15+
healthCheckPath: /health
16+
17+
# Auto-deploy on push to main branch
18+
autoDeploy: true
19+
20+
# Environment variables
21+
envVars:
22+
# Transport mode (http for remote deployment)
23+
- key: MCP_TRANSPORT
24+
value: http
25+
26+
# Note: SGAI_API_KEY is NOT set here.
27+
# Each user passes their own API key via mcp-remote from their local environment.

src/scrapegraph_mcp/server.py

Lines changed: 62 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -395,74 +395,59 @@ class ConfigSchema(BaseModel):
395395

396396
def get_api_key(ctx: Context) -> str:
397397
"""
398-
Get the API key from config or environment variable.
399-
398+
Get the API key from HTTP header or MCP session config.
399+
400+
Supports two modes:
401+
- HTTP mode (Render): API key from 'X-API-Key' header via mcp-remote
402+
- Stdio mode (Smithery): API key from session_config.scrapegraph_api_key
403+
400404
Args:
401405
ctx: FastMCP context
402-
406+
403407
Returns:
404408
API key string
405-
409+
406410
Raises:
407411
ValueError: If no API key is found
408412
"""
413+
from fastmcp.server.dependencies import get_http_headers
414+
415+
# Try HTTP header first (for remote/Render deployments)
409416
try:
410-
logger.info(f"Getting API key. Context type: {type(ctx)}")
411-
logger.info(f"Context has session_config: {hasattr(ctx, 'session_config')}")
412-
413-
# Try to get from config first, but handle cases where session_config might be None
414-
api_key = None
415-
if hasattr(ctx, 'session_config') and ctx.session_config is not None:
416-
logger.info(f"Session config type: {type(ctx.session_config)}")
417-
api_key = getattr(ctx.session_config, 'scrapegraph_api_key', None)
418-
logger.info(f"API key from config: {'***' if api_key else 'None'}")
419-
else:
420-
logger.info("No session_config available or session_config is None")
421-
422-
# If not in config, try environment variable
423-
if not api_key:
424-
api_key = os.getenv('SGAI_API_KEY')
425-
logger.info(f"API key from env: {'***' if api_key else 'None'}")
426-
427-
# If still no API key found, raise error
428-
if not api_key:
429-
logger.error("No API key found in config or environment")
430-
raise ValueError(
431-
"ScapeGraph API key is required. Please provide it either:\n"
432-
"1. In the MCP server configuration as 'scrapegraph_api_key'\n"
433-
"2. As an environment variable 'SGAI_API_KEY'"
434-
)
435-
436-
logger.info("API key successfully retrieved")
437-
return api_key
438-
439-
except Exception as e:
440-
logger.warning(f"Error getting API key from context: {e}. Falling back to cached method.")
441-
# Fallback to cached method if context handling fails
442-
return get_cached_api_key()
417+
headers = get_http_headers()
418+
api_key = headers.get('x-api-key')
419+
if api_key:
420+
logger.info("API key retrieved from X-API-Key header")
421+
return api_key
422+
except LookupError:
423+
# Not in HTTP context, try session config (Smithery/stdio mode)
424+
pass
425+
426+
# Try session config (for Smithery/stdio deployments)
427+
if hasattr(ctx, 'session_config') and ctx.session_config is not None:
428+
api_key = getattr(ctx.session_config, 'scrapegraph_api_key', None)
429+
if api_key:
430+
logger.info("API key retrieved from session config")
431+
return api_key
432+
433+
logger.error("No API key found in header or session config")
434+
raise ValueError(
435+
"ScapeGraph API key is required. Please provide it via:\n"
436+
"- HTTP header 'X-API-Key' (for remote server via mcp-remote)\n"
437+
"- MCP config 'scrapegraphApiKey' (for Smithery/local stdio)"
438+
)
443439

444440

445441
# Create MCP server instance
446442
mcp = FastMCP("ScapeGraph API MCP Server")
447443

448-
# Global API key cache to handle session issues
449-
_api_key_cache: Optional[str] = None
450444

451-
def get_cached_api_key() -> str:
452-
"""Get API key from cache or environment, bypassing session config issues."""
453-
global _api_key_cache
454-
455-
if _api_key_cache is None:
456-
_api_key_cache = os.getenv('SGAI_API_KEY')
457-
if _api_key_cache:
458-
logger.info("API key loaded from environment variable")
459-
else:
460-
logger.error("No API key found in environment variable SGAI_API_KEY")
461-
raise ValueError(
462-
"ScapeGraph API key is required. Please set the SGAI_API_KEY environment variable."
463-
)
464-
465-
return _api_key_cache
445+
# Health check endpoint for remote deployments (Render, etc.)
446+
@mcp.custom_route("/health", methods=["GET"])
447+
async def health_check(request):
448+
"""Health check endpoint for container orchestration and load balancers."""
449+
from starlette.responses import JSONResponse
450+
return JSONResponse({"status": "healthy", "service": "scrapegraph-mcp"})
466451

467452

468453
# Add prompts to help users interact with the server
@@ -2298,13 +2283,30 @@ def create_server() -> FastMCP:
22982283

22992284

23002285
def main() -> None:
2301-
"""Run the ScapeGraph MCP server."""
2286+
"""Run the ScapeGraph MCP server.
2287+
2288+
Supports two transport modes:
2289+
- stdio (default): For local use with Claude Desktop, Cursor, etc.
2290+
- http: For remote deployment on Render, Koyeb, etc.
2291+
2292+
Set MCP_TRANSPORT=http environment variable for remote deployment.
2293+
"""
2294+
transport = os.getenv("MCP_TRANSPORT", "stdio").lower()
2295+
23022296
try:
2303-
# Verify we're running from local codebase
2304-
server_path = os.path.abspath(__file__)
2305-
logger.info(f"Starting ScapeGraph MCP server from local codebase: {server_path}")
2306-
print(f"Starting ScapeGraph MCP server (local codebase)")
2307-
mcp.run(transport="stdio")
2297+
if transport == "http":
2298+
# Remote deployment mode (Render, Koyeb, etc.)
2299+
host = os.getenv("HOST", "0.0.0.0")
2300+
port = int(os.getenv("PORT", "8000"))
2301+
logger.info(f"Starting ScapeGraph MCP server in HTTP mode on {host}:{port}")
2302+
print(f"Starting ScapeGraph MCP server in HTTP mode on {host}:{port}")
2303+
mcp.run(transport="http", host=host, port=port)
2304+
else:
2305+
# Local stdio mode (Claude Desktop, Cursor, etc.)
2306+
server_path = os.path.abspath(__file__)
2307+
logger.info(f"Starting ScapeGraph MCP server from local codebase: {server_path}")
2308+
print("Starting ScapeGraph MCP server (local codebase)")
2309+
mcp.run(transport="stdio")
23082310
except Exception as e:
23092311
logger.error(f"Failed to start MCP server: {e}")
23102312
print(f"Error starting server: {e}")

0 commit comments

Comments
 (0)