Spaces:
Sleeping
Sleeping
Upload 3 files
Browse files- Dockerfile +1 -2
- entrypoint.py +55 -7
- mcp_server.py +16 -7
Dockerfile
CHANGED
|
@@ -12,8 +12,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
|
|
| 12 |
PIP_DISABLE_PIP_VERSION_CHECK=1 \
|
| 13 |
GNOME_DATA_DIR=/app/gnome_data \
|
| 14 |
GNOME_MODEL_DIR=/app/models \
|
| 15 |
-
HF_HOME=/app/huggingface
|
| 16 |
-
MCP_TRANSPORT_SECURITY_DISABLE_HOST_VALIDATION=1
|
| 17 |
|
| 18 |
# Install system dependencies
|
| 19 |
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
|
| 12 |
PIP_DISABLE_PIP_VERSION_CHECK=1 \
|
| 13 |
GNOME_DATA_DIR=/app/gnome_data \
|
| 14 |
GNOME_MODEL_DIR=/app/models \
|
| 15 |
+
HF_HOME=/app/huggingface
|
|
|
|
| 16 |
|
| 17 |
# Install system dependencies
|
| 18 |
RUN apt-get update && apt-get install -y --no-install-recommends \
|
entrypoint.py
CHANGED
|
@@ -10,10 +10,6 @@ import os
|
|
| 10 |
import sys
|
| 11 |
import logging
|
| 12 |
|
| 13 |
-
# Disable MCP host validation for HuggingFace Spaces
|
| 14 |
-
# This allows connections from *.hf.space domains
|
| 15 |
-
os.environ["MCP_TRANSPORT_SECURITY_DISABLE_HOST_VALIDATION"] = "1"
|
| 16 |
-
|
| 17 |
# Add current directory to path
|
| 18 |
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 19 |
|
|
@@ -111,17 +107,69 @@ def main():
|
|
| 111 |
import uvicorn
|
| 112 |
from starlette.applications import Starlette
|
| 113 |
from starlette.routing import Mount, Route
|
| 114 |
-
from starlette.responses import JSONResponse
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
|
| 116 |
async def health_check(request):
|
| 117 |
"""Health check endpoint."""
|
| 118 |
return JSONResponse({"status": "healthy", "service": "GNoME MCP Server"})
|
| 119 |
|
| 120 |
-
# Create Starlette app
|
|
|
|
| 121 |
app = Starlette(
|
| 122 |
routes=[
|
|
|
|
| 123 |
Route("/health", health_check),
|
| 124 |
-
Mount("/", app=create_sse_app()),
|
| 125 |
]
|
| 126 |
)
|
| 127 |
|
|
|
|
| 10 |
import sys
|
| 11 |
import logging
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
# Add current directory to path
|
| 14 |
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 15 |
|
|
|
|
| 107 |
import uvicorn
|
| 108 |
from starlette.applications import Starlette
|
| 109 |
from starlette.routing import Mount, Route
|
| 110 |
+
from starlette.responses import JSONResponse, HTMLResponse
|
| 111 |
+
|
| 112 |
+
async def homepage(request):
|
| 113 |
+
"""Homepage with service info."""
|
| 114 |
+
html_content = """
|
| 115 |
+
<!DOCTYPE html>
|
| 116 |
+
<html>
|
| 117 |
+
<head>
|
| 118 |
+
<title>GNoME Materials Discovery MCP Server</title>
|
| 119 |
+
<style>
|
| 120 |
+
body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
|
| 121 |
+
h1 { color: #2c3e50; }
|
| 122 |
+
.status { color: #27ae60; font-weight: bold; }
|
| 123 |
+
.endpoint { background: #f5f5f5; padding: 10px; border-radius: 5px; margin: 10px 0; }
|
| 124 |
+
code { background: #e8e8e8; padding: 2px 6px; border-radius: 3px; }
|
| 125 |
+
</style>
|
| 126 |
+
</head>
|
| 127 |
+
<body>
|
| 128 |
+
<h1>🔬 GNoME Materials Discovery MCP Server</h1>
|
| 129 |
+
<p class="status">✅ Service is running</p>
|
| 130 |
+
<h2>Endpoints</h2>
|
| 131 |
+
<div class="endpoint">
|
| 132 |
+
<strong>SSE Endpoint:</strong> <code>/sse</code><br>
|
| 133 |
+
<small>Connect your MCP client (Cursor, Claude Desktop) to this endpoint</small>
|
| 134 |
+
</div>
|
| 135 |
+
<div class="endpoint">
|
| 136 |
+
<strong>Health Check:</strong> <code>/health</code>
|
| 137 |
+
</div>
|
| 138 |
+
<h2>Connection Example</h2>
|
| 139 |
+
<p>Add to your Cursor MCP settings:</p>
|
| 140 |
+
<pre><code>{
|
| 141 |
+
"mcpServers": {
|
| 142 |
+
"gnome-materials": {
|
| 143 |
+
"url": "https://YOUR-SPACE.hf.space/sse"
|
| 144 |
+
}
|
| 145 |
+
}
|
| 146 |
+
}</code></pre>
|
| 147 |
+
<h2>Available Tools</h2>
|
| 148 |
+
<ul>
|
| 149 |
+
<li>query_materials - Search GNoME materials database</li>
|
| 150 |
+
<li>get_dataset_statistics - Get dataset overview</li>
|
| 151 |
+
<li>calculate_decomposition_energy - Calculate stability</li>
|
| 152 |
+
<li>get_phase_diagram - Build phase diagrams</li>
|
| 153 |
+
<li>calculate_air_stability - Analyze air stability</li>
|
| 154 |
+
<li>get_structure - Load crystal structures</li>
|
| 155 |
+
<li>And more...</li>
|
| 156 |
+
</ul>
|
| 157 |
+
</body>
|
| 158 |
+
</html>
|
| 159 |
+
"""
|
| 160 |
+
return HTMLResponse(content=html_content)
|
| 161 |
|
| 162 |
async def health_check(request):
|
| 163 |
"""Health check endpoint."""
|
| 164 |
return JSONResponse({"status": "healthy", "service": "GNoME MCP Server"})
|
| 165 |
|
| 166 |
+
# Create Starlette app with proper routing
|
| 167 |
+
# SSE app is mounted at /sse path, root serves info page
|
| 168 |
app = Starlette(
|
| 169 |
routes=[
|
| 170 |
+
Route("/", homepage),
|
| 171 |
Route("/health", health_check),
|
| 172 |
+
Mount("/sse", app=create_sse_app()),
|
| 173 |
]
|
| 174 |
)
|
| 175 |
|
mcp_server.py
CHANGED
|
@@ -25,11 +25,8 @@ from typing import Optional, List, Dict, Any
|
|
| 25 |
from contextlib import asynccontextmanager
|
| 26 |
from collections.abc import AsyncIterator
|
| 27 |
|
| 28 |
-
# Disable host validation for HuggingFace Spaces deployment BEFORE importing MCP
|
| 29 |
-
# This is needed because HuggingFace uses custom domain names like *.hf.space
|
| 30 |
-
os.environ["MCP_TRANSPORT_SECURITY_DISABLE_HOST_VALIDATION"] = "1"
|
| 31 |
-
|
| 32 |
from mcp.server.fastmcp import FastMCP
|
|
|
|
| 33 |
|
| 34 |
# Import local modules
|
| 35 |
from data_utils import DataManager, get_data_manager
|
|
@@ -87,8 +84,16 @@ async def app_lifespan(server: FastMCP) -> AsyncIterator[Dict[str, Any]]:
|
|
| 87 |
data_manager.close()
|
| 88 |
|
| 89 |
|
| 90 |
-
# Create FastMCP server
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
|
| 93 |
|
| 94 |
# ============================================================================
|
|
@@ -915,7 +920,11 @@ async def check_data_status() -> Dict[str, Any]:
|
|
| 915 |
# ============================================================================
|
| 916 |
|
| 917 |
def create_sse_app():
|
| 918 |
-
"""Create SSE app for mounting in Starlette.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 919 |
return mcp.sse_app()
|
| 920 |
|
| 921 |
|
|
|
|
| 25 |
from contextlib import asynccontextmanager
|
| 26 |
from collections.abc import AsyncIterator
|
| 27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
from mcp.server.fastmcp import FastMCP
|
| 29 |
+
from mcp.server.transport_security import TransportSecuritySettings
|
| 30 |
|
| 31 |
# Import local modules
|
| 32 |
from data_utils import DataManager, get_data_manager
|
|
|
|
| 84 |
data_manager.close()
|
| 85 |
|
| 86 |
|
| 87 |
+
# Create FastMCP server with DNS rebinding protection disabled for HuggingFace Spaces
|
| 88 |
+
# This allows connections from *.hf.space domains
|
| 89 |
+
mcp = FastMCP(
|
| 90 |
+
"GNoME Materials Discovery",
|
| 91 |
+
host="0.0.0.0",
|
| 92 |
+
port=7860,
|
| 93 |
+
transport_security=TransportSecuritySettings(
|
| 94 |
+
enable_dns_rebinding_protection=False
|
| 95 |
+
)
|
| 96 |
+
)
|
| 97 |
|
| 98 |
|
| 99 |
# ============================================================================
|
|
|
|
| 920 |
# ============================================================================
|
| 921 |
|
| 922 |
def create_sse_app():
|
| 923 |
+
"""Create SSE app for mounting in Starlette.
|
| 924 |
+
|
| 925 |
+
Note: DNS rebinding protection is disabled in FastMCP initialization
|
| 926 |
+
to allow connections from HuggingFace Spaces domains.
|
| 927 |
+
"""
|
| 928 |
return mcp.sse_app()
|
| 929 |
|
| 930 |
|