""" 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": result.get('api_name', 'Generated API'), "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 )