File size: 18,501 Bytes
0d555a0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
"""
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
    )