mcp-generator / app.py
visproj's picture
deploy to huggingface spoace with hf token
22014bf verified
raw
history blame
19.1 kB
"""
MCP Generator - Gradio Frontend
Turn any API into an MCP server in seconds!
"""
import asyncio
import gradio as gr
from pathlib import Path
import zipfile
import io
import os
from src.agents.factory import AgentFactory
from src.mcp_host import mcp_host
from src.mcp_http_host import mcp_http_host
from src.mcp_registry import mcp_registry
from src.hf_deployer import deploy_to_huggingface
from src.config import LLM_PROVIDER, ANTHROPIC_API_KEY, OPENAI_API_KEY, HOSTED_MCPS_DIR
# Initialize agent factory
try:
agent_factory = AgentFactory()
print(f"βœ… Using {LLM_PROVIDER.upper()} for code generation")
except ValueError as e:
print(f"❌ Error: {e}")
print(f"Please set {'ANTHROPIC_API_KEY' if LLM_PROVIDER == 'anthropic' else 'OPENAI_API_KEY'} in .env file")
agent_factory = None
async def generate_and_host_mcp(api_url: str, api_key: str = None, force_regenerate: bool = False, progress=gr.Progress()):
"""Generate and host an MCP server
Args:
api_url: The API URL to analyze
api_key: Optional API key for the target API
force_regenerate: If True, regenerate even if exists
progress: Gradio progress tracker
Returns:
Tuple of (status_text, code, download_file, readme, connection_config)
"""
if not agent_factory:
api_key_name = "ANTHROPIC_API_KEY" if LLM_PROVIDER == "anthropic" else "OPENAI_API_KEY"
return (
f"❌ Error: {api_key_name} not configured. Please set it in your .env file.",
"",
None,
"",
""
)
try:
# Check if MCP already exists for this URL
existing_mcp = mcp_registry.find_by_url(api_url)
if existing_mcp and not force_regenerate:
progress(0.5, desc="Found existing MCP, reusing...")
# Reuse existing MCP
mcp_id = existing_mcp['mcp_id']
mcp_path = HOSTED_MCPS_DIR / mcp_id
# Update last used timestamp
mcp_registry.update_last_used(api_url)
# Load existing files
server_code = (mcp_path / "server.py").read_text()
readme = (mcp_path / "README.md").read_text()
# Get hosted URLs
base_url = os.getenv("SPACE_HOST", "http://localhost:7860")
hosted_base_url = f"{base_url}/{mcp_id}"
hosted_info_url = f"{base_url}/{mcp_id}/info"
status_text = f"""♻️ **Reusing Existing MCP!**
**MCP ID:** `{mcp_id}`
**Originally Created:** {existing_mcp['created_at']}
**Last Used:** {existing_mcp['last_used']}
This MCP was already generated for this API URL. Using existing version to save time and API calls!
**🌐 HTTP Transport (MCP 2025-03-26):**
- **Base URL:** `{hosted_base_url}`
- **Info:** `{hosted_info_url}`
- **Protocol:** JSON-RPC 2.0 over HTTP Streamable
- **Note:** Claude Desktop automatically appends `/mcp` to the base URL
πŸ’‘ **Tip:** To regenerate from scratch, check "Force Regenerate" below.
"""
# Create ZIP file
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zipf:
for file_path in mcp_path.rglob('*'):
if file_path.is_file():
arcname = file_path.relative_to(mcp_path.parent)
zipf.write(file_path, arcname)
zip_buffer.seek(0)
temp_zip = f"/tmp/{mcp_id}.zip"
with open(temp_zip, 'wb') as f:
f.write(zip_buffer.read())
connection_config = f"""# Local stdio transport:
{{
"mcpServers": {{
"{mcp_id}": {{
"command": "python",
"args": ["server.py"]
}}
}}
}}
# HTTP transport (hosted):
# Claude Desktop will automatically append /mcp to this URL
{{
"mcpServers": {{
"{mcp_id}": {{
"url": "{hosted_base_url}"
}}
}}
}}"""
# Prepare MCP info for deployment
mcp_info = {
"api_name": existing_mcp['api_name'],
"mcp_id": mcp_id
}
return (
status_text,
server_code,
temp_zip,
readme,
connection_config,
mcp_info # For HF deployment
)
# Generate new MCP
if existing_mcp:
progress(0.1, desc="Regenerating MCP (forced)...")
else:
progress(0.1, desc="Analyzing API...")
# Generate the MCP
result = await agent_factory.generate_mcp(api_url, api_key)
if result["status"] == "error":
return (
f"❌ Error: {result['error']}",
"",
None,
"",
"",
None # mcp_info for state
)
progress(0.6, desc="Generating code...")
# Get the generated files
mcp_id = result["mcp_id"]
server_code = result["server_code"]
readme = result["readme_content"]
progress(0.8, desc="Starting MCP server...")
# Start the MCP server
start_result = await mcp_host.start_mcp(mcp_id)
# Get the hosted URL (will be updated when deployed to HuggingFace)
base_url = os.getenv("SPACE_HOST", "http://localhost:7860")
hosted_base_url = f"{base_url}/{mcp_id}"
hosted_info_url = f"{base_url}/{mcp_id}/info"
if not start_result["success"]:
status_text = f"⚠️ MCP generated but failed to start locally: {start_result.get('error')}\n\n**HTTP Base URL:** {hosted_base_url}"
else:
status_text = f"""βœ… **MCP Server Generated & Hosted!**
**MCP ID:** `{mcp_id}`
**Status:** {start_result['status']}
**🌐 HTTP Transport (MCP 2025-03-26):**
- **Base URL:** `{hosted_base_url}`
- **Info:** `{hosted_info_url}`
- **Protocol:** JSON-RPC 2.0 over HTTP Streamable
- **Note:** Claude Desktop automatically appends `/mcp` to the base URL
**πŸ“¦ Local Use (stdio):**
- Download ZIP below and extract
- Configure in Claude Desktop (see Connection Config tab)
Your MCP server is live and accessible via HTTP! πŸŽ‰
"""
progress(0.9, desc="Creating download package...")
# Create ZIP file for download
zip_buffer = io.BytesIO()
mcp_path = Path(result["download_path"])
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zipf:
for file_path in mcp_path.rglob('*'):
if file_path.is_file():
arcname = file_path.relative_to(mcp_path.parent)
zipf.write(file_path, arcname)
zip_buffer.seek(0)
# Save to temp file for Gradio
temp_zip = f"/tmp/{mcp_id}.zip"
with open(temp_zip, 'wb') as f:
f.write(zip_buffer.read())
# Create connection configs for both transports
connection_config = f"""# Local stdio transport (extract ZIP first):
{{
"mcpServers": {{
"{mcp_id}": {{
"command": "python",
"args": ["server.py"]
}}
}}
}}
# HTTP transport (use hosted endpoint):
# Claude Desktop will automatically append /mcp to this URL
{{
"mcpServers": {{
"{mcp_id}": {{
"url": "{hosted_base_url}"
}}
}}
}}"""
progress(1.0, desc="Done!")
# Prepare MCP info for deployment
mcp_info = {
"api_name": state['api_name'],
"mcp_id": mcp_id
}
return (
status_text,
server_code,
temp_zip,
readme,
connection_config,
mcp_info # For HF deployment
)
except Exception as e:
return (
f"❌ Unexpected error: {str(e)}",
"",
None,
"",
"",
None # mcp_info for state
)
def deploy_to_hf_space(hf_token: str, mcp_info: dict) -> str:
"""Deploy generated MCP to HuggingFace Space
Args:
hf_token: HuggingFace API token
mcp_info: Dict with api_name and mcp_id
Returns:
Deployment status message
"""
if not hf_token:
return "❌ Please enter your HuggingFace token above"
if not mcp_info or not mcp_info.get("mcp_id"):
return "❌ Please generate an MCP first before deploying"
try:
api_name = mcp_info["api_name"]
mcp_id = mcp_info["mcp_id"]
mcp_dir = HOSTED_MCPS_DIR / mcp_id
if not mcp_dir.exists():
return f"❌ MCP directory not found: {mcp_id}"
# Deploy to HuggingFace
result = deploy_to_huggingface(
mcp_dir=mcp_dir,
api_name=api_name,
mcp_id=mcp_id,
hf_token=hf_token
)
if result["success"]:
return f"""βœ… **Deployed Successfully!**
**Space URL:** [{result['repo_id']}]({result['space_url']})
**MCP Endpoint:** `{result['mcp_endpoint']}`
**Next Steps:**
1. Visit your Space (may take 1-2 min to build)
2. Copy the MCP endpoint URL
3. Add to Claude Desktop config:
```json
{{
"mcpServers": {{
"{mcp_id}": {{
"url": "{result['mcp_endpoint'].replace('/mcp', '')}"
}}
}}
}}
```
πŸŽ‰ Your MCP is now live and accessible!
"""
else:
return f"❌ **Deployment Failed**\n\nError: {result['error']}"
except Exception as e:
return f"❌ **Deployment Error**\n\n{str(e)}"
# Build Gradio Interface
with gr.Blocks(
title="MCP Generator"
) as app:
gr.Markdown("""
# πŸ€– MCP Generator
## Turn Any API into an MCP Server in Seconds!
Simply enter an API URL and we'll generate a complete, working MCP server with:
- βœ… Automatically analyzed endpoints
- βœ… Generated MCP tools
- βœ… Complete documentation
- βœ… Ready to use immediately!
**Built for the MCP 1st Birthday Hackathon** πŸŽ‰
""")
with gr.Accordion("ℹ️ API URL Guidelines & Tips", open=False):
gr.Markdown("""
### βœ… What Works Best:
- **Base API URLs** (e.g., `https://api.example.com`)
- **REST APIs** with clear endpoints
- **OpenAPI/Swagger** documented APIs
- **Public APIs** (no complex auth)
### 🎯 Try These Free APIs:
**No Auth Required (Perfect for Testing!):**
- `https://jsonplaceholder.typicode.com` - Fake REST API (best for testing!)
- `https://api.github.com` - GitHub public API
- `https://dog.ceo/api` - Random dog images
- `https://catfact.ninja` - Random cat facts
- `https://api.coindesk.com/v1/bpi` - Bitcoin prices
- `https://api.ipify.org` - Get IP address
**With API Key (Free Tier):**
- `https://api.openweathermap.org/data/2.5` - Weather data
- `https://newsapi.org/v2` - News articles
- `https://api.stripe.com` - Payment processing (test mode)
### πŸ’‘ Tips:
- **Start with jsonplaceholder.typicode.com** - always works!
- Paste the **base URL** (not a specific endpoint)
- If API needs a key, add it in the "API Key" field below
- Cached URLs generate instantly (try the same URL twice!)
### ⚠️ May Not Work Well:
- GraphQL APIs (REST only for now)
- APIs requiring OAuth flows
- WebSocket-only APIs
- APIs with very complex authentication
""")
with gr.Row():
with gr.Column(scale=2):
gr.Markdown("### πŸ“ Input")
api_url = gr.Textbox(
label="API URL or Documentation URL",
placeholder="https://api.example.com",
info="Enter the base URL or documentation URL of the API"
)
api_key = gr.Textbox(
label="API Key (Optional)",
placeholder="sk-...",
type="password",
info="If the API requires authentication"
)
force_regenerate = gr.Checkbox(
label="Force Regenerate",
value=False,
info="Regenerate even if MCP already exists for this URL (saves API calls when unchecked)"
)
generate_btn = gr.Button(
"πŸš€ Generate & Host MCP Server",
variant="primary",
size="lg"
)
with gr.Accordion("πŸš€ Quick Start Examples", open=True):
gr.Markdown("""
**Click to copy and paste:**
```
https://jsonplaceholder.typicode.com
```
⭐ **Recommended first try!** Always works, no API key needed.
---
**More examples:**
- `https://api.github.com` - GitHub API (no auth)
- `https://dog.ceo/api` - Dog images (fun!)
- `https://catfact.ninja` - Cat facts (simple)
πŸ’‘ **Tip:** MCPs are cached - try the same URL twice to see instant results!
""")
gr.Markdown("---")
with gr.Row():
with gr.Column():
gr.Markdown("### πŸ“Š Results")
status_output = gr.Markdown(label="Status")
with gr.Tab("Generated Code"):
code_output = gr.Code(
label="server.py",
language="python",
lines=20
)
with gr.Tab("README"):
readme_output = gr.Markdown()
with gr.Tab("Connection Config"):
connection_output = gr.Code(
label="Claude Desktop Config",
language="json"
)
download_output = gr.File(
label="πŸ“¦ Download Complete Package (ZIP)"
)
# HuggingFace Deployment Section
gr.Markdown("---")
gr.Markdown("### πŸš€ One-Click Deploy to HuggingFace")
gr.Markdown("""
⚠️ **Security Best Practices:**
- Use a **temporary, disposable token** that you'll delete immediately after deployment
- **Required permissions:** `write` access (to create Spaces)
- **Recommended flow:** Create token β†’ Deploy β†’ Delete token
- Get tokens from: [https://huggingface.co/settings/tokens](https://huggingface.co/settings/tokens)
πŸ’‘ **How to create a safe token:**
1. Go to HuggingFace β†’ Settings β†’ Access Tokens
2. Click "New token"
3. Name: `mcp-temp-deploy` (or similar)
4. Type: **Write** (required to create Spaces)
5. Click "Generate" β†’ Copy the token
6. Paste below and deploy
7. **⚠️ DELETE THE TOKEN** immediately after deployment succeeds!
""")
with gr.Row():
hf_token_input = gr.Textbox(
label="HuggingFace Token (hidden with β€’β€’β€’β€’β€’β€’)",
placeholder="hf_β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’",
type="password",
info="⚠️ Use a TEMPORARY token with 'write' permission. Delete it after deploying!"
)
with gr.Row():
deploy_hf_btn = gr.Button(
"πŸš€ Deploy to HuggingFace Space",
variant="secondary",
size="lg"
)
hf_deploy_status = gr.Markdown(label="Deployment Status")
# Store the last generated MCP info for deployment
last_mcp_state = gr.State(value=None)
# Wire up the generation button
generate_btn.click(
fn=generate_and_host_mcp,
inputs=[api_url, api_key, force_regenerate],
outputs=[
status_output,
code_output,
download_output,
readme_output,
connection_output,
last_mcp_state # Store MCP info for deployment
]
)
# Wire up the HuggingFace deployment button
deploy_hf_btn.click(
fn=deploy_to_hf_space,
inputs=[hf_token_input, last_mcp_state],
outputs=[hf_deploy_status]
)
with gr.Accordion("πŸ“‹ Previously Generated MCPs", open=False):
def get_existing_mcps():
"""Get list of existing MCPs for display"""
mcps = mcp_registry.list_all()
if not mcps:
return "No MCPs generated yet. Generate your first one above! πŸ‘†"
output = "| API Name | URL | Created | Last Used |\n"
output += "|----------|-----|---------|----------|\n"
for mcp in mcps[:10]: # Show last 10
api_name = mcp['api_name']
api_url = mcp['api_url'][:40] + "..." if len(mcp['api_url']) > 40 else mcp['api_url']
created = mcp['created_at'].split('T')[0]
last_used = mcp['last_used'].split('T')[0]
output += f"| {api_name} | {api_url} | {created} | {last_used} |\n"
return output
existing_mcps_display = gr.Markdown(get_existing_mcps())
refresh_btn = gr.Button("πŸ”„ Refresh List", size="sm")
refresh_btn.click(fn=get_existing_mcps, outputs=existing_mcps_display)
gr.Markdown("""
---
### 🎯 How to Use Your Generated MCP
1. **Download** the ZIP file above
2. **Extract** it to a folder
3. **Add** the connection config to your Claude Desktop settings
4. **Restart** Claude Desktop
Your MCP server is ready to use! πŸŽ‰
### πŸš€ About This Project
This is a meta-MCP: an MCP server that generates other MCP servers!
- Built with [Gradio](https://gradio.app)
- Powered by [LangGraph](https://github.com/langchain-ai/langgraph) agents
- Uses [Anthropic's Claude](https://anthropic.com) for code generation
- Integrates with [MCP Fetch Server](https://github.com/modelcontextprotocol/servers)
**For MCP 1st Birthday Hackathon - Track 2: MCP in Action** πŸŽ‚
""")
if __name__ == "__main__":
# Check for API key
if not ANTHROPIC_API_KEY and not OPENAI_API_KEY:
print("⚠️ WARNING: No API key set!")
print("Please set either OPENAI_API_KEY or ANTHROPIC_API_KEY in your .env file")
print(f"Example: echo 'OPENAI_API_KEY=your_key_here' > .env")
# Launch Gradio UI
app.launch(
server_name="0.0.0.0",
server_port=7860,
share=False
)