-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathdeploy_mcp.py
More file actions
272 lines (222 loc) Β· 8.91 KB
/
deploy_mcp.py
File metadata and controls
272 lines (222 loc) Β· 8.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
"""
CLI command for deploying MCP servers to Google Cloud Run.
"""
import importlib
import os
import sys
import tempfile
from pathlib import Path
from typing import Dict, Optional
from eval_protocol.gcp_tools import (
build_and_push_docker_image,
deploy_to_cloud_run,
ensure_artifact_registry_repo_exists,
)
from .common import check_environment
def _generate_mcp_dockerfile_content(
mcp_server_module: str,
python_version: str = "3.11",
service_port: int = 8000,
additional_requirements: Optional[str] = None,
) -> str:
"""
Generate Dockerfile content for MCP server deployment.
Args:
mcp_server_module: The Python module containing the MCP server (e.g., 'frozen_lake_mcp_server')
python_version: Python version to use in the container
service_port: Port the MCP server will listen on
additional_requirements: Additional pip requirements
Returns:
Dockerfile content as string
"""
# Base requirements for MCP servers - matching setup.py dependencies
base_requirements = [
"fastmcp>=0.1.0",
# Core Eval Protocol dependencies from setup.py
"requests>=2.25.0",
"pydantic>=2.0.0",
"dataclasses-json>=0.5.7",
"fastapi>=0.68.0",
"uvicorn>=0.15.0",
"python-dotenv>=0.19.0",
"openai==1.78.1",
"aiosqlite",
"aiohttp",
"mcp>=1.9.2",
"PyYAML>=5.0",
"datasets==3.6.0",
"fsspec==2025.3.0",
"hydra-core>=1.3.2",
"omegaconf>=2.3.0",
"gymnasium>=0.29.0",
"httpx>=0.24.0",
"fireworks-ai>=0.17.19",
]
if additional_requirements:
requirements = base_requirements + [req.strip() for req in additional_requirements.split("\n") if req.strip()]
else:
requirements = base_requirements
# Generate pip install lines with proper escaping
pip_install_lines = []
for req in requirements[:-1]:
pip_install_lines.append(f" {req} \\")
pip_install_lines.append(f" {requirements[-1]}")
pip_install_section = "\n".join(pip_install_lines)
dockerfile_content = f"""# Multi-stage build for MCP server deployment
FROM python:{python_version}-slim as builder
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \\
build-essential \\
&& rm -rf /var/lib/apt/lists/*
# Copy requirements and install Python dependencies
RUN pip install --no-cache-dir --upgrade pip
# Install MCP server requirements
RUN pip install --no-cache-dir \\
{pip_install_section}
# Production stage
FROM python:{python_version}-slim
WORKDIR /app
# Install runtime dependencies
RUN apt-get update && apt-get install -y \\
&& rm -rf /var/lib/apt/lists/*
# Copy Python packages from builder
COPY --from=builder /usr/local/lib/python{python_version}/site-packages /usr/local/lib/python{python_version}/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
# Copy the MCP server code
COPY . .
# Set environment variables for Cloud Run
# FastMCP uses HOST and PORT environment variables for streamable-http transport
ENV HOST=0.0.0.0
ENV PORT={service_port}
ENV PYTHONPATH=/app
ENV PYTHONUNBUFFERED=1
# Expose the port
EXPOSE {service_port}
# Run the MCP server with proper host and port for Cloud Run
CMD ["python", "-m", "{mcp_server_module}", "--host", "0.0.0.0", "--port", "{service_port}"]
"""
return dockerfile_content
def _deploy_mcp_to_gcp_cloud_run(args):
"""Deploy MCP server to GCP Cloud Run."""
print(f"Starting MCP server deployment to GCP Cloud Run for '{args.id}'...")
# Validate required arguments - either dockerfile or mcp-server-module must be provided
if not args.dockerfile and not args.mcp_server_module:
print("Error: Either --dockerfile or --mcp-server-module is required for MCP server deployment.")
return None
# Resolve GCP configuration
gcp_project_id = args.gcp_project
if not gcp_project_id:
print("Error: GCP Project ID must be provided via --gcp-project argument.")
return None
gcp_region = args.gcp_region
if not gcp_region:
print("Error: GCP Region must be provided via --gcp-region argument.")
return None
gcp_ar_repo_name = args.gcp_ar_repo
if not gcp_ar_repo_name:
gcp_ar_repo_name = "eval-protocol-mcp-servers"
print(f"Using GCP Project: {gcp_project_id}, Region: {gcp_region}, AR Repo: {gcp_ar_repo_name}")
# Ensure Artifact Registry repository exists
if not ensure_artifact_registry_repo_exists(
project_id=gcp_project_id, region=gcp_region, repo_name=gcp_ar_repo_name
):
print(f"Failed to ensure Artifact Registry repository '{gcp_ar_repo_name}' exists. Aborting.")
return None
# Determine Dockerfile content - use provided file or generate
dockerfile_content = None
if hasattr(args, "dockerfile") and args.dockerfile:
# Use provided Dockerfile
dockerfile_path = Path(args.dockerfile)
if not dockerfile_path.exists():
print(f"Error: Dockerfile not found at {dockerfile_path}")
return None
print(f"Using provided Dockerfile: {dockerfile_path}")
try:
with open(dockerfile_path, "r") as f:
dockerfile_content = f.read()
except Exception as e:
print(f"Error reading Dockerfile at {dockerfile_path}: {e}")
return None
else:
# Generate Dockerfile content (legacy approach)
print("Generating Dockerfile content from mcp-server-module...")
dockerfile_content = _generate_mcp_dockerfile_content(
mcp_server_module=args.mcp_server_module,
python_version=getattr(args, "python_version", "3.11"),
service_port=getattr(args, "port", 8000),
additional_requirements=getattr(args, "requirements", None),
)
if not dockerfile_content:
print("Failed to obtain Dockerfile content. Aborting.")
return None
# Build and push Docker image
image_tag = "latest"
image_name_tag = f"{gcp_region}-docker.pkg.dev/{gcp_project_id}/{gcp_ar_repo_name}/{args.id}:{image_tag}"
build_context_dir = os.getcwd()
if not build_and_push_docker_image(
image_name_tag=image_name_tag,
dockerfile_content=dockerfile_content,
build_context_dir=build_context_dir,
gcp_project_id=gcp_project_id,
):
print(f"Failed to build and push Docker image {image_name_tag}. Aborting.")
return None
print(f"Successfully built and pushed Docker image: {image_name_tag}")
# Deploy to Cloud Run
service_port = getattr(args, "port", 8000)
env_vars = {}
# Add any custom environment variables
if hasattr(args, "env_vars") and args.env_vars:
for env_pair in args.env_vars:
if "=" in env_pair:
key, value = env_pair.split("=", 1)
env_vars[key] = value
cloud_run_service_url = deploy_to_cloud_run(
service_name=args.id,
image_name_tag=image_name_tag,
gcp_project_id=gcp_project_id,
gcp_region=gcp_region,
allow_unauthenticated=True, # MCP servers typically need to be publicly accessible
env_vars=env_vars if env_vars else None,
service_port=service_port,
)
if not cloud_run_service_url:
print("Failed to deploy to Cloud Run or retrieve service URL. Aborting.")
return None
print("π Successfully deployed MCP server to Cloud Run!")
print(f"π Service URL: {cloud_run_service_url}")
print(f"π MCP Connection URL: {cloud_run_service_url}")
print(f"π Service Name: {args.id}")
deployment_method = (
"local Dockerfile" if (hasattr(args, "dockerfile") and args.dockerfile) else "auto-generated Dockerfile"
)
print(f"π³ Deployment Method: {deployment_method}")
print()
print("π― Next steps:")
print(f" 1. Test your MCP server: curl {cloud_run_service_url}/health")
print(f" 2. Connect MCP clients to: {cloud_run_service_url}")
print(
f" 3. Monitor logs: gcloud logging read 'resource.type=cloud_run_revision AND resource.labels.service_name={args.id}' --project {gcp_project_id}"
)
return cloud_run_service_url
def deploy_mcp_command(args):
"""Main entry point for MCP server deployment command."""
# Check environment (similar to existing deploy command)
if not check_environment():
print("Environment check failed. Please resolve the issues above before deploying.")
return False
try:
# Deploy to GCP Cloud Run
service_url = _deploy_mcp_to_gcp_cloud_run(args)
if service_url:
print(f"β
MCP server '{args.id}' successfully deployed!")
return True
else:
print(f"β Failed to deploy MCP server '{args.id}'")
return False
except Exception as e:
print(f"Error during MCP server deployment: {e}")
import traceback
traceback.print_exc()
return False