visproj commited on
Commit
22014bf
Β·
verified Β·
1 Parent(s): 9ca41c1

deploy to huggingface spoace with hf token

Browse files
Files changed (4) hide show
  1. app.py +578 -450
  2. requirements.txt +1 -0
  3. src/hf_deployer.py +190 -0
  4. src/mcp_http_host.py +1 -15
app.py CHANGED
@@ -1,450 +1,578 @@
1
- """
2
- MCP Generator - Gradio Frontend
3
- Turn any API into an MCP server in seconds!
4
- """
5
-
6
- import asyncio
7
- import gradio as gr
8
- from pathlib import Path
9
- import zipfile
10
- import io
11
- import os
12
-
13
- from src.agents.factory import AgentFactory
14
- from src.mcp_host import mcp_host
15
- from src.mcp_http_host import mcp_http_host
16
- from src.mcp_registry import mcp_registry
17
- from src.config import LLM_PROVIDER, ANTHROPIC_API_KEY, OPENAI_API_KEY, HOSTED_MCPS_DIR
18
-
19
-
20
- # Initialize agent factory
21
- try:
22
- agent_factory = AgentFactory()
23
- print(f"βœ… Using {LLM_PROVIDER.upper()} for code generation")
24
- except ValueError as e:
25
- print(f"❌ Error: {e}")
26
- print(f"Please set {'ANTHROPIC_API_KEY' if LLM_PROVIDER == 'anthropic' else 'OPENAI_API_KEY'} in .env file")
27
- agent_factory = None
28
-
29
-
30
- async def generate_and_host_mcp(api_url: str, api_key: str = None, force_regenerate: bool = False, progress=gr.Progress()):
31
- """Generate and host an MCP server
32
-
33
- Args:
34
- api_url: The API URL to analyze
35
- api_key: Optional API key for the target API
36
- force_regenerate: If True, regenerate even if exists
37
- progress: Gradio progress tracker
38
-
39
- Returns:
40
- Tuple of (status_text, code, download_file, readme, connection_config)
41
- """
42
- if not agent_factory:
43
- api_key_name = "ANTHROPIC_API_KEY" if LLM_PROVIDER == "anthropic" else "OPENAI_API_KEY"
44
- return (
45
- f"❌ Error: {api_key_name} not configured. Please set it in your .env file.",
46
- "",
47
- None,
48
- "",
49
- ""
50
- )
51
-
52
- try:
53
- # Check if MCP already exists for this URL
54
- existing_mcp = mcp_registry.find_by_url(api_url)
55
-
56
- if existing_mcp and not force_regenerate:
57
- progress(0.5, desc="Found existing MCP, reusing...")
58
-
59
- # Reuse existing MCP
60
- mcp_id = existing_mcp['mcp_id']
61
- mcp_path = HOSTED_MCPS_DIR / mcp_id
62
-
63
- # Update last used timestamp
64
- mcp_registry.update_last_used(api_url)
65
-
66
- # Load existing files
67
- server_code = (mcp_path / "server.py").read_text()
68
- readme = (mcp_path / "README.md").read_text()
69
-
70
- # Get hosted URLs
71
- base_url = os.getenv("SPACE_HOST", "http://localhost:7860")
72
- hosted_base_url = f"{base_url}/{mcp_id}"
73
- hosted_info_url = f"{base_url}/{mcp_id}/info"
74
-
75
- status_text = f"""♻️ **Reusing Existing MCP!**
76
-
77
- **MCP ID:** `{mcp_id}`
78
- **Originally Created:** {existing_mcp['created_at']}
79
- **Last Used:** {existing_mcp['last_used']}
80
-
81
- This MCP was already generated for this API URL. Using existing version to save time and API calls!
82
-
83
- **🌐 HTTP Transport (MCP 2025-03-26):**
84
- - **Base URL:** `{hosted_base_url}`
85
- - **Info:** `{hosted_info_url}`
86
- - **Protocol:** JSON-RPC 2.0 over HTTP Streamable
87
- - **Note:** Claude Desktop automatically appends `/mcp` to the base URL
88
-
89
- πŸ’‘ **Tip:** To regenerate from scratch, check "Force Regenerate" below.
90
- """
91
-
92
- # Create ZIP file
93
- zip_buffer = io.BytesIO()
94
- with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zipf:
95
- for file_path in mcp_path.rglob('*'):
96
- if file_path.is_file():
97
- arcname = file_path.relative_to(mcp_path.parent)
98
- zipf.write(file_path, arcname)
99
-
100
- zip_buffer.seek(0)
101
- temp_zip = f"/tmp/{mcp_id}.zip"
102
- with open(temp_zip, 'wb') as f:
103
- f.write(zip_buffer.read())
104
-
105
- connection_config = f"""# Local stdio transport:
106
- {{
107
- "mcpServers": {{
108
- "{mcp_id}": {{
109
- "command": "python",
110
- "args": ["server.py"]
111
- }}
112
- }}
113
- }}
114
-
115
- # HTTP transport (hosted):
116
- # Claude Desktop will automatically append /mcp to this URL
117
- {{
118
- "mcpServers": {{
119
- "{mcp_id}": {{
120
- "url": "{hosted_base_url}"
121
- }}
122
- }}
123
- }}"""
124
-
125
- return (
126
- status_text,
127
- server_code,
128
- temp_zip,
129
- readme,
130
- connection_config
131
- )
132
-
133
- # Generate new MCP
134
- if existing_mcp:
135
- progress(0.1, desc="Regenerating MCP (forced)...")
136
- else:
137
- progress(0.1, desc="Analyzing API...")
138
-
139
- # Generate the MCP
140
- result = await agent_factory.generate_mcp(api_url, api_key)
141
-
142
- if result["status"] == "error":
143
- return (
144
- f"❌ Error: {result['error']}",
145
- "",
146
- None,
147
- "",
148
- ""
149
- )
150
-
151
- progress(0.6, desc="Generating code...")
152
-
153
- # Get the generated files
154
- mcp_id = result["mcp_id"]
155
- server_code = result["server_code"]
156
- readme = result["readme_content"]
157
-
158
- progress(0.8, desc="Starting MCP server...")
159
-
160
- # Start the MCP server
161
- start_result = await mcp_host.start_mcp(mcp_id)
162
-
163
- # Get the hosted URL (will be updated when deployed to HuggingFace)
164
- base_url = os.getenv("SPACE_HOST", "http://localhost:7860")
165
- hosted_base_url = f"{base_url}/{mcp_id}"
166
- hosted_info_url = f"{base_url}/{mcp_id}/info"
167
-
168
- if not start_result["success"]:
169
- status_text = f"⚠️ MCP generated but failed to start locally: {start_result.get('error')}\n\n**HTTP Base URL:** {hosted_base_url}"
170
- else:
171
- status_text = f"""βœ… **MCP Server Generated & Hosted!**
172
-
173
- **MCP ID:** `{mcp_id}`
174
- **Status:** {start_result['status']}
175
-
176
- **🌐 HTTP Transport (MCP 2025-03-26):**
177
- - **Base URL:** `{hosted_base_url}`
178
- - **Info:** `{hosted_info_url}`
179
- - **Protocol:** JSON-RPC 2.0 over HTTP Streamable
180
- - **Note:** Claude Desktop automatically appends `/mcp` to the base URL
181
-
182
- **πŸ“¦ Local Use (stdio):**
183
- - Download ZIP below and extract
184
- - Configure in Claude Desktop (see Connection Config tab)
185
-
186
- Your MCP server is live and accessible via HTTP! πŸŽ‰
187
- """
188
-
189
- progress(0.9, desc="Creating download package...")
190
-
191
- # Create ZIP file for download
192
- zip_buffer = io.BytesIO()
193
- mcp_path = Path(result["download_path"])
194
-
195
- with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zipf:
196
- for file_path in mcp_path.rglob('*'):
197
- if file_path.is_file():
198
- arcname = file_path.relative_to(mcp_path.parent)
199
- zipf.write(file_path, arcname)
200
-
201
- zip_buffer.seek(0)
202
-
203
- # Save to temp file for Gradio
204
- temp_zip = f"/tmp/{mcp_id}.zip"
205
- with open(temp_zip, 'wb') as f:
206
- f.write(zip_buffer.read())
207
-
208
- # Create connection configs for both transports
209
- connection_config = f"""# Local stdio transport (extract ZIP first):
210
- {{
211
- "mcpServers": {{
212
- "{mcp_id}": {{
213
- "command": "python",
214
- "args": ["server.py"]
215
- }}
216
- }}
217
- }}
218
-
219
- # HTTP transport (use hosted endpoint):
220
- # Claude Desktop will automatically append /mcp to this URL
221
- {{
222
- "mcpServers": {{
223
- "{mcp_id}": {{
224
- "url": "{hosted_base_url}"
225
- }}
226
- }}
227
- }}"""
228
-
229
- progress(1.0, desc="Done!")
230
-
231
- return (
232
- status_text,
233
- server_code,
234
- temp_zip,
235
- readme,
236
- connection_config
237
- )
238
-
239
- except Exception as e:
240
- return (
241
- f"❌ Unexpected error: {str(e)}",
242
- "",
243
- None,
244
- "",
245
- ""
246
- )
247
-
248
-
249
- # Build Gradio Interface
250
- with gr.Blocks(
251
- title="MCP Generator"
252
- ) as app:
253
-
254
- gr.Markdown("""
255
- # πŸ€– MCP Generator
256
- ## Turn Any API into an MCP Server in Seconds!
257
-
258
- Simply enter an API URL and we'll generate a complete, working MCP server with:
259
- - βœ… Automatically analyzed endpoints
260
- - βœ… Generated MCP tools
261
- - βœ… Complete documentation
262
- - βœ… Ready to use immediately!
263
-
264
- **Built for the MCP 1st Birthday Hackathon** πŸŽ‰
265
- """)
266
-
267
- with gr.Accordion("ℹ️ API URL Guidelines & Tips", open=False):
268
- gr.Markdown("""
269
- ### βœ… What Works Best:
270
- - **Base API URLs** (e.g., `https://api.example.com`)
271
- - **REST APIs** with clear endpoints
272
- - **OpenAPI/Swagger** documented APIs
273
- - **Public APIs** (no complex auth)
274
-
275
- ### 🎯 Try These Free APIs:
276
-
277
- **No Auth Required (Perfect for Testing!):**
278
- - `https://jsonplaceholder.typicode.com` - Fake REST API (best for testing!)
279
- - `https://api.github.com` - GitHub public API
280
- - `https://dog.ceo/api` - Random dog images
281
- - `https://catfact.ninja` - Random cat facts
282
- - `https://api.coindesk.com/v1/bpi` - Bitcoin prices
283
- - `https://api.ipify.org` - Get IP address
284
-
285
- **With API Key (Free Tier):**
286
- - `https://api.openweathermap.org/data/2.5` - Weather data
287
- - `https://newsapi.org/v2` - News articles
288
- - `https://api.stripe.com` - Payment processing (test mode)
289
-
290
- ### πŸ’‘ Tips:
291
- - **Start with jsonplaceholder.typicode.com** - always works!
292
- - Paste the **base URL** (not a specific endpoint)
293
- - If API needs a key, add it in the "API Key" field below
294
- - Cached URLs generate instantly (try the same URL twice!)
295
-
296
- ### ⚠️ May Not Work Well:
297
- - GraphQL APIs (REST only for now)
298
- - APIs requiring OAuth flows
299
- - WebSocket-only APIs
300
- - APIs with very complex authentication
301
- """)
302
-
303
- with gr.Row():
304
- with gr.Column(scale=2):
305
- gr.Markdown("### πŸ“ Input")
306
-
307
- api_url = gr.Textbox(
308
- label="API URL or Documentation URL",
309
- placeholder="https://api.example.com",
310
- info="Enter the base URL or documentation URL of the API"
311
- )
312
-
313
- api_key = gr.Textbox(
314
- label="API Key (Optional)",
315
- placeholder="sk-...",
316
- type="password",
317
- info="If the API requires authentication"
318
- )
319
-
320
- force_regenerate = gr.Checkbox(
321
- label="Force Regenerate",
322
- value=False,
323
- info="Regenerate even if MCP already exists for this URL (saves API calls when unchecked)"
324
- )
325
-
326
- generate_btn = gr.Button(
327
- "πŸš€ Generate & Host MCP Server",
328
- variant="primary",
329
- size="lg"
330
- )
331
-
332
- with gr.Accordion("πŸš€ Quick Start Examples", open=True):
333
- gr.Markdown("""
334
- **Click to copy and paste:**
335
-
336
- ```
337
- https://jsonplaceholder.typicode.com
338
- ```
339
- ⭐ **Recommended first try!** Always works, no API key needed.
340
-
341
- ---
342
-
343
- **More examples:**
344
- - `https://api.github.com` - GitHub API (no auth)
345
- - `https://dog.ceo/api` - Dog images (fun!)
346
- - `https://catfact.ninja` - Cat facts (simple)
347
-
348
- πŸ’‘ **Tip:** MCPs are cached - try the same URL twice to see instant results!
349
- """)
350
-
351
- gr.Markdown("---")
352
-
353
- with gr.Row():
354
- with gr.Column():
355
- gr.Markdown("### πŸ“Š Results")
356
-
357
- status_output = gr.Markdown(label="Status")
358
-
359
- with gr.Tab("Generated Code"):
360
- code_output = gr.Code(
361
- label="server.py",
362
- language="python",
363
- lines=20
364
- )
365
-
366
- with gr.Tab("README"):
367
- readme_output = gr.Markdown()
368
-
369
- with gr.Tab("Connection Config"):
370
- connection_output = gr.Code(
371
- label="Claude Desktop Config",
372
- language="json"
373
- )
374
-
375
- download_output = gr.File(
376
- label="πŸ“¦ Download Complete Package (ZIP)"
377
- )
378
-
379
- # Wire up the button
380
- generate_btn.click(
381
- fn=generate_and_host_mcp,
382
- inputs=[api_url, api_key, force_regenerate],
383
- outputs=[
384
- status_output,
385
- code_output,
386
- download_output,
387
- readme_output,
388
- connection_output
389
- ]
390
- )
391
-
392
- with gr.Accordion("πŸ“‹ Previously Generated MCPs", open=False):
393
- def get_existing_mcps():
394
- """Get list of existing MCPs for display"""
395
- mcps = mcp_registry.list_all()
396
- if not mcps:
397
- return "No MCPs generated yet. Generate your first one above! πŸ‘†"
398
-
399
- output = "| API Name | URL | Created | Last Used |\n"
400
- output += "|----------|-----|---------|----------|\n"
401
- for mcp in mcps[:10]: # Show last 10
402
- api_name = mcp['api_name']
403
- api_url = mcp['api_url'][:40] + "..." if len(mcp['api_url']) > 40 else mcp['api_url']
404
- created = mcp['created_at'].split('T')[0]
405
- last_used = mcp['last_used'].split('T')[0]
406
- output += f"| {api_name} | {api_url} | {created} | {last_used} |\n"
407
-
408
- return output
409
-
410
- existing_mcps_display = gr.Markdown(get_existing_mcps())
411
- refresh_btn = gr.Button("πŸ”„ Refresh List", size="sm")
412
- refresh_btn.click(fn=get_existing_mcps, outputs=existing_mcps_display)
413
-
414
- gr.Markdown("""
415
- ---
416
- ### 🎯 How to Use Your Generated MCP
417
-
418
- 1. **Download** the ZIP file above
419
- 2. **Extract** it to a folder
420
- 3. **Add** the connection config to your Claude Desktop settings
421
- 4. **Restart** Claude Desktop
422
-
423
- Your MCP server is ready to use! πŸŽ‰
424
-
425
- ### πŸš€ About This Project
426
-
427
- This is a meta-MCP: an MCP server that generates other MCP servers!
428
-
429
- - Built with [Gradio](https://gradio.app)
430
- - Powered by [LangGraph](https://github.com/langchain-ai/langgraph) agents
431
- - Uses [Anthropic's Claude](https://anthropic.com) for code generation
432
- - Integrates with [MCP Fetch Server](https://github.com/modelcontextprotocol/servers)
433
-
434
- **For MCP 1st Birthday Hackathon - Track 2: MCP in Action** πŸŽ‚
435
- """)
436
-
437
-
438
- if __name__ == "__main__":
439
- # Check for API key
440
- if not ANTHROPIC_API_KEY and not OPENAI_API_KEY:
441
- print("⚠️ WARNING: No API key set!")
442
- print("Please set either OPENAI_API_KEY or ANTHROPIC_API_KEY in your .env file")
443
- print(f"Example: echo 'OPENAI_API_KEY=your_key_here' > .env")
444
-
445
- # Launch Gradio UI
446
- app.launch(
447
- server_name="0.0.0.0",
448
- server_port=7860,
449
- share=False
450
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ MCP Generator - Gradio Frontend
3
+ Turn any API into an MCP server in seconds!
4
+ """
5
+
6
+ import asyncio
7
+ import gradio as gr
8
+ from pathlib import Path
9
+ import zipfile
10
+ import io
11
+ import os
12
+
13
+ from src.agents.factory import AgentFactory
14
+ from src.mcp_host import mcp_host
15
+ from src.mcp_http_host import mcp_http_host
16
+ from src.mcp_registry import mcp_registry
17
+ from src.hf_deployer import deploy_to_huggingface
18
+ from src.config import LLM_PROVIDER, ANTHROPIC_API_KEY, OPENAI_API_KEY, HOSTED_MCPS_DIR
19
+
20
+
21
+ # Initialize agent factory
22
+ try:
23
+ agent_factory = AgentFactory()
24
+ print(f"βœ… Using {LLM_PROVIDER.upper()} for code generation")
25
+ except ValueError as e:
26
+ print(f"❌ Error: {e}")
27
+ print(f"Please set {'ANTHROPIC_API_KEY' if LLM_PROVIDER == 'anthropic' else 'OPENAI_API_KEY'} in .env file")
28
+ agent_factory = None
29
+
30
+
31
+ async def generate_and_host_mcp(api_url: str, api_key: str = None, force_regenerate: bool = False, progress=gr.Progress()):
32
+ """Generate and host an MCP server
33
+
34
+ Args:
35
+ api_url: The API URL to analyze
36
+ api_key: Optional API key for the target API
37
+ force_regenerate: If True, regenerate even if exists
38
+ progress: Gradio progress tracker
39
+
40
+ Returns:
41
+ Tuple of (status_text, code, download_file, readme, connection_config)
42
+ """
43
+ if not agent_factory:
44
+ api_key_name = "ANTHROPIC_API_KEY" if LLM_PROVIDER == "anthropic" else "OPENAI_API_KEY"
45
+ return (
46
+ f"❌ Error: {api_key_name} not configured. Please set it in your .env file.",
47
+ "",
48
+ None,
49
+ "",
50
+ ""
51
+ )
52
+
53
+ try:
54
+ # Check if MCP already exists for this URL
55
+ existing_mcp = mcp_registry.find_by_url(api_url)
56
+
57
+ if existing_mcp and not force_regenerate:
58
+ progress(0.5, desc="Found existing MCP, reusing...")
59
+
60
+ # Reuse existing MCP
61
+ mcp_id = existing_mcp['mcp_id']
62
+ mcp_path = HOSTED_MCPS_DIR / mcp_id
63
+
64
+ # Update last used timestamp
65
+ mcp_registry.update_last_used(api_url)
66
+
67
+ # Load existing files
68
+ server_code = (mcp_path / "server.py").read_text()
69
+ readme = (mcp_path / "README.md").read_text()
70
+
71
+ # Get hosted URLs
72
+ base_url = os.getenv("SPACE_HOST", "http://localhost:7860")
73
+ hosted_base_url = f"{base_url}/{mcp_id}"
74
+ hosted_info_url = f"{base_url}/{mcp_id}/info"
75
+
76
+ status_text = f"""♻️ **Reusing Existing MCP!**
77
+
78
+ **MCP ID:** `{mcp_id}`
79
+ **Originally Created:** {existing_mcp['created_at']}
80
+ **Last Used:** {existing_mcp['last_used']}
81
+
82
+ This MCP was already generated for this API URL. Using existing version to save time and API calls!
83
+
84
+ **🌐 HTTP Transport (MCP 2025-03-26):**
85
+ - **Base URL:** `{hosted_base_url}`
86
+ - **Info:** `{hosted_info_url}`
87
+ - **Protocol:** JSON-RPC 2.0 over HTTP Streamable
88
+ - **Note:** Claude Desktop automatically appends `/mcp` to the base URL
89
+
90
+ πŸ’‘ **Tip:** To regenerate from scratch, check "Force Regenerate" below.
91
+ """
92
+
93
+ # Create ZIP file
94
+ zip_buffer = io.BytesIO()
95
+ with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zipf:
96
+ for file_path in mcp_path.rglob('*'):
97
+ if file_path.is_file():
98
+ arcname = file_path.relative_to(mcp_path.parent)
99
+ zipf.write(file_path, arcname)
100
+
101
+ zip_buffer.seek(0)
102
+ temp_zip = f"/tmp/{mcp_id}.zip"
103
+ with open(temp_zip, 'wb') as f:
104
+ f.write(zip_buffer.read())
105
+
106
+ connection_config = f"""# Local stdio transport:
107
+ {{
108
+ "mcpServers": {{
109
+ "{mcp_id}": {{
110
+ "command": "python",
111
+ "args": ["server.py"]
112
+ }}
113
+ }}
114
+ }}
115
+
116
+ # HTTP transport (hosted):
117
+ # Claude Desktop will automatically append /mcp to this URL
118
+ {{
119
+ "mcpServers": {{
120
+ "{mcp_id}": {{
121
+ "url": "{hosted_base_url}"
122
+ }}
123
+ }}
124
+ }}"""
125
+
126
+ # Prepare MCP info for deployment
127
+ mcp_info = {
128
+ "api_name": existing_mcp['api_name'],
129
+ "mcp_id": mcp_id
130
+ }
131
+
132
+ return (
133
+ status_text,
134
+ server_code,
135
+ temp_zip,
136
+ readme,
137
+ connection_config,
138
+ mcp_info # For HF deployment
139
+ )
140
+
141
+ # Generate new MCP
142
+ if existing_mcp:
143
+ progress(0.1, desc="Regenerating MCP (forced)...")
144
+ else:
145
+ progress(0.1, desc="Analyzing API...")
146
+
147
+ # Generate the MCP
148
+ result = await agent_factory.generate_mcp(api_url, api_key)
149
+
150
+ if result["status"] == "error":
151
+ return (
152
+ f"❌ Error: {result['error']}",
153
+ "",
154
+ None,
155
+ "",
156
+ "",
157
+ None # mcp_info for state
158
+ )
159
+
160
+ progress(0.6, desc="Generating code...")
161
+
162
+ # Get the generated files
163
+ mcp_id = result["mcp_id"]
164
+ server_code = result["server_code"]
165
+ readme = result["readme_content"]
166
+
167
+ progress(0.8, desc="Starting MCP server...")
168
+
169
+ # Start the MCP server
170
+ start_result = await mcp_host.start_mcp(mcp_id)
171
+
172
+ # Get the hosted URL (will be updated when deployed to HuggingFace)
173
+ base_url = os.getenv("SPACE_HOST", "http://localhost:7860")
174
+ hosted_base_url = f"{base_url}/{mcp_id}"
175
+ hosted_info_url = f"{base_url}/{mcp_id}/info"
176
+
177
+ if not start_result["success"]:
178
+ status_text = f"⚠️ MCP generated but failed to start locally: {start_result.get('error')}\n\n**HTTP Base URL:** {hosted_base_url}"
179
+ else:
180
+ status_text = f"""βœ… **MCP Server Generated & Hosted!**
181
+
182
+ **MCP ID:** `{mcp_id}`
183
+ **Status:** {start_result['status']}
184
+
185
+ **🌐 HTTP Transport (MCP 2025-03-26):**
186
+ - **Base URL:** `{hosted_base_url}`
187
+ - **Info:** `{hosted_info_url}`
188
+ - **Protocol:** JSON-RPC 2.0 over HTTP Streamable
189
+ - **Note:** Claude Desktop automatically appends `/mcp` to the base URL
190
+
191
+ **πŸ“¦ Local Use (stdio):**
192
+ - Download ZIP below and extract
193
+ - Configure in Claude Desktop (see Connection Config tab)
194
+
195
+ Your MCP server is live and accessible via HTTP! πŸŽ‰
196
+ """
197
+
198
+ progress(0.9, desc="Creating download package...")
199
+
200
+ # Create ZIP file for download
201
+ zip_buffer = io.BytesIO()
202
+ mcp_path = Path(result["download_path"])
203
+
204
+ with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zipf:
205
+ for file_path in mcp_path.rglob('*'):
206
+ if file_path.is_file():
207
+ arcname = file_path.relative_to(mcp_path.parent)
208
+ zipf.write(file_path, arcname)
209
+
210
+ zip_buffer.seek(0)
211
+
212
+ # Save to temp file for Gradio
213
+ temp_zip = f"/tmp/{mcp_id}.zip"
214
+ with open(temp_zip, 'wb') as f:
215
+ f.write(zip_buffer.read())
216
+
217
+ # Create connection configs for both transports
218
+ connection_config = f"""# Local stdio transport (extract ZIP first):
219
+ {{
220
+ "mcpServers": {{
221
+ "{mcp_id}": {{
222
+ "command": "python",
223
+ "args": ["server.py"]
224
+ }}
225
+ }}
226
+ }}
227
+
228
+ # HTTP transport (use hosted endpoint):
229
+ # Claude Desktop will automatically append /mcp to this URL
230
+ {{
231
+ "mcpServers": {{
232
+ "{mcp_id}": {{
233
+ "url": "{hosted_base_url}"
234
+ }}
235
+ }}
236
+ }}"""
237
+
238
+ progress(1.0, desc="Done!")
239
+
240
+ # Prepare MCP info for deployment
241
+ mcp_info = {
242
+ "api_name": state['api_name'],
243
+ "mcp_id": mcp_id
244
+ }
245
+
246
+ return (
247
+ status_text,
248
+ server_code,
249
+ temp_zip,
250
+ readme,
251
+ connection_config,
252
+ mcp_info # For HF deployment
253
+ )
254
+
255
+ except Exception as e:
256
+ return (
257
+ f"❌ Unexpected error: {str(e)}",
258
+ "",
259
+ None,
260
+ "",
261
+ "",
262
+ None # mcp_info for state
263
+ )
264
+
265
+
266
+ def deploy_to_hf_space(hf_token: str, mcp_info: dict) -> str:
267
+ """Deploy generated MCP to HuggingFace Space
268
+
269
+ Args:
270
+ hf_token: HuggingFace API token
271
+ mcp_info: Dict with api_name and mcp_id
272
+
273
+ Returns:
274
+ Deployment status message
275
+ """
276
+ if not hf_token:
277
+ return "❌ Please enter your HuggingFace token above"
278
+
279
+ if not mcp_info or not mcp_info.get("mcp_id"):
280
+ return "❌ Please generate an MCP first before deploying"
281
+
282
+ try:
283
+ api_name = mcp_info["api_name"]
284
+ mcp_id = mcp_info["mcp_id"]
285
+ mcp_dir = HOSTED_MCPS_DIR / mcp_id
286
+
287
+ if not mcp_dir.exists():
288
+ return f"❌ MCP directory not found: {mcp_id}"
289
+
290
+ # Deploy to HuggingFace
291
+ result = deploy_to_huggingface(
292
+ mcp_dir=mcp_dir,
293
+ api_name=api_name,
294
+ mcp_id=mcp_id,
295
+ hf_token=hf_token
296
+ )
297
+
298
+ if result["success"]:
299
+ return f"""βœ… **Deployed Successfully!**
300
+
301
+ **Space URL:** [{result['repo_id']}]({result['space_url']})
302
+
303
+ **MCP Endpoint:** `{result['mcp_endpoint']}`
304
+
305
+ **Next Steps:**
306
+ 1. Visit your Space (may take 1-2 min to build)
307
+ 2. Copy the MCP endpoint URL
308
+ 3. Add to Claude Desktop config:
309
+ ```json
310
+ {{
311
+ "mcpServers": {{
312
+ "{mcp_id}": {{
313
+ "url": "{result['mcp_endpoint'].replace('/mcp', '')}"
314
+ }}
315
+ }}
316
+ }}
317
+ ```
318
+
319
+ πŸŽ‰ Your MCP is now live and accessible!
320
+ """
321
+ else:
322
+ return f"❌ **Deployment Failed**\n\nError: {result['error']}"
323
+
324
+ except Exception as e:
325
+ return f"❌ **Deployment Error**\n\n{str(e)}"
326
+
327
+
328
+ # Build Gradio Interface
329
+ with gr.Blocks(
330
+ title="MCP Generator"
331
+ ) as app:
332
+
333
+ gr.Markdown("""
334
+ # πŸ€– MCP Generator
335
+ ## Turn Any API into an MCP Server in Seconds!
336
+
337
+ Simply enter an API URL and we'll generate a complete, working MCP server with:
338
+ - βœ… Automatically analyzed endpoints
339
+ - βœ… Generated MCP tools
340
+ - βœ… Complete documentation
341
+ - βœ… Ready to use immediately!
342
+
343
+ **Built for the MCP 1st Birthday Hackathon** πŸŽ‰
344
+ """)
345
+
346
+ with gr.Accordion("ℹ️ API URL Guidelines & Tips", open=False):
347
+ gr.Markdown("""
348
+ ### βœ… What Works Best:
349
+ - **Base API URLs** (e.g., `https://api.example.com`)
350
+ - **REST APIs** with clear endpoints
351
+ - **OpenAPI/Swagger** documented APIs
352
+ - **Public APIs** (no complex auth)
353
+
354
+ ### 🎯 Try These Free APIs:
355
+
356
+ **No Auth Required (Perfect for Testing!):**
357
+ - `https://jsonplaceholder.typicode.com` - Fake REST API (best for testing!)
358
+ - `https://api.github.com` - GitHub public API
359
+ - `https://dog.ceo/api` - Random dog images
360
+ - `https://catfact.ninja` - Random cat facts
361
+ - `https://api.coindesk.com/v1/bpi` - Bitcoin prices
362
+ - `https://api.ipify.org` - Get IP address
363
+
364
+ **With API Key (Free Tier):**
365
+ - `https://api.openweathermap.org/data/2.5` - Weather data
366
+ - `https://newsapi.org/v2` - News articles
367
+ - `https://api.stripe.com` - Payment processing (test mode)
368
+
369
+ ### πŸ’‘ Tips:
370
+ - **Start with jsonplaceholder.typicode.com** - always works!
371
+ - Paste the **base URL** (not a specific endpoint)
372
+ - If API needs a key, add it in the "API Key" field below
373
+ - Cached URLs generate instantly (try the same URL twice!)
374
+
375
+ ### ⚠️ May Not Work Well:
376
+ - GraphQL APIs (REST only for now)
377
+ - APIs requiring OAuth flows
378
+ - WebSocket-only APIs
379
+ - APIs with very complex authentication
380
+ """)
381
+
382
+ with gr.Row():
383
+ with gr.Column(scale=2):
384
+ gr.Markdown("### πŸ“ Input")
385
+
386
+ api_url = gr.Textbox(
387
+ label="API URL or Documentation URL",
388
+ placeholder="https://api.example.com",
389
+ info="Enter the base URL or documentation URL of the API"
390
+ )
391
+
392
+ api_key = gr.Textbox(
393
+ label="API Key (Optional)",
394
+ placeholder="sk-...",
395
+ type="password",
396
+ info="If the API requires authentication"
397
+ )
398
+
399
+ force_regenerate = gr.Checkbox(
400
+ label="Force Regenerate",
401
+ value=False,
402
+ info="Regenerate even if MCP already exists for this URL (saves API calls when unchecked)"
403
+ )
404
+
405
+ generate_btn = gr.Button(
406
+ "πŸš€ Generate & Host MCP Server",
407
+ variant="primary",
408
+ size="lg"
409
+ )
410
+
411
+ with gr.Accordion("πŸš€ Quick Start Examples", open=True):
412
+ gr.Markdown("""
413
+ **Click to copy and paste:**
414
+
415
+ ```
416
+ https://jsonplaceholder.typicode.com
417
+ ```
418
+ ⭐ **Recommended first try!** Always works, no API key needed.
419
+
420
+ ---
421
+
422
+ **More examples:**
423
+ - `https://api.github.com` - GitHub API (no auth)
424
+ - `https://dog.ceo/api` - Dog images (fun!)
425
+ - `https://catfact.ninja` - Cat facts (simple)
426
+
427
+ πŸ’‘ **Tip:** MCPs are cached - try the same URL twice to see instant results!
428
+ """)
429
+
430
+ gr.Markdown("---")
431
+
432
+ with gr.Row():
433
+ with gr.Column():
434
+ gr.Markdown("### πŸ“Š Results")
435
+
436
+ status_output = gr.Markdown(label="Status")
437
+
438
+ with gr.Tab("Generated Code"):
439
+ code_output = gr.Code(
440
+ label="server.py",
441
+ language="python",
442
+ lines=20
443
+ )
444
+
445
+ with gr.Tab("README"):
446
+ readme_output = gr.Markdown()
447
+
448
+ with gr.Tab("Connection Config"):
449
+ connection_output = gr.Code(
450
+ label="Claude Desktop Config",
451
+ language="json"
452
+ )
453
+
454
+ download_output = gr.File(
455
+ label="πŸ“¦ Download Complete Package (ZIP)"
456
+ )
457
+
458
+ # HuggingFace Deployment Section
459
+ gr.Markdown("---")
460
+ gr.Markdown("### πŸš€ One-Click Deploy to HuggingFace")
461
+
462
+ gr.Markdown("""
463
+ ⚠️ **Security Best Practices:**
464
+ - Use a **temporary, disposable token** that you'll delete immediately after deployment
465
+ - **Required permissions:** `write` access (to create Spaces)
466
+ - **Recommended flow:** Create token β†’ Deploy β†’ Delete token
467
+ - Get tokens from: [https://huggingface.co/settings/tokens](https://huggingface.co/settings/tokens)
468
+
469
+ πŸ’‘ **How to create a safe token:**
470
+ 1. Go to HuggingFace β†’ Settings β†’ Access Tokens
471
+ 2. Click "New token"
472
+ 3. Name: `mcp-temp-deploy` (or similar)
473
+ 4. Type: **Write** (required to create Spaces)
474
+ 5. Click "Generate" β†’ Copy the token
475
+ 6. Paste below and deploy
476
+ 7. **⚠️ DELETE THE TOKEN** immediately after deployment succeeds!
477
+ """)
478
+
479
+ with gr.Row():
480
+ hf_token_input = gr.Textbox(
481
+ label="HuggingFace Token (hidden with β€’β€’β€’β€’β€’β€’)",
482
+ placeholder="hf_β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’β€’",
483
+ type="password",
484
+ info="⚠️ Use a TEMPORARY token with 'write' permission. Delete it after deploying!"
485
+ )
486
+
487
+ with gr.Row():
488
+ deploy_hf_btn = gr.Button(
489
+ "πŸš€ Deploy to HuggingFace Space",
490
+ variant="secondary",
491
+ size="lg"
492
+ )
493
+
494
+ hf_deploy_status = gr.Markdown(label="Deployment Status")
495
+
496
+ # Store the last generated MCP info for deployment
497
+ last_mcp_state = gr.State(value=None)
498
+
499
+ # Wire up the generation button
500
+ generate_btn.click(
501
+ fn=generate_and_host_mcp,
502
+ inputs=[api_url, api_key, force_regenerate],
503
+ outputs=[
504
+ status_output,
505
+ code_output,
506
+ download_output,
507
+ readme_output,
508
+ connection_output,
509
+ last_mcp_state # Store MCP info for deployment
510
+ ]
511
+ )
512
+
513
+ # Wire up the HuggingFace deployment button
514
+ deploy_hf_btn.click(
515
+ fn=deploy_to_hf_space,
516
+ inputs=[hf_token_input, last_mcp_state],
517
+ outputs=[hf_deploy_status]
518
+ )
519
+
520
+ with gr.Accordion("πŸ“‹ Previously Generated MCPs", open=False):
521
+ def get_existing_mcps():
522
+ """Get list of existing MCPs for display"""
523
+ mcps = mcp_registry.list_all()
524
+ if not mcps:
525
+ return "No MCPs generated yet. Generate your first one above! πŸ‘†"
526
+
527
+ output = "| API Name | URL | Created | Last Used |\n"
528
+ output += "|----------|-----|---------|----------|\n"
529
+ for mcp in mcps[:10]: # Show last 10
530
+ api_name = mcp['api_name']
531
+ api_url = mcp['api_url'][:40] + "..." if len(mcp['api_url']) > 40 else mcp['api_url']
532
+ created = mcp['created_at'].split('T')[0]
533
+ last_used = mcp['last_used'].split('T')[0]
534
+ output += f"| {api_name} | {api_url} | {created} | {last_used} |\n"
535
+
536
+ return output
537
+
538
+ existing_mcps_display = gr.Markdown(get_existing_mcps())
539
+ refresh_btn = gr.Button("πŸ”„ Refresh List", size="sm")
540
+ refresh_btn.click(fn=get_existing_mcps, outputs=existing_mcps_display)
541
+
542
+ gr.Markdown("""
543
+ ---
544
+ ### 🎯 How to Use Your Generated MCP
545
+
546
+ 1. **Download** the ZIP file above
547
+ 2. **Extract** it to a folder
548
+ 3. **Add** the connection config to your Claude Desktop settings
549
+ 4. **Restart** Claude Desktop
550
+
551
+ Your MCP server is ready to use! πŸŽ‰
552
+
553
+ ### πŸš€ About This Project
554
+
555
+ This is a meta-MCP: an MCP server that generates other MCP servers!
556
+
557
+ - Built with [Gradio](https://gradio.app)
558
+ - Powered by [LangGraph](https://github.com/langchain-ai/langgraph) agents
559
+ - Uses [Anthropic's Claude](https://anthropic.com) for code generation
560
+ - Integrates with [MCP Fetch Server](https://github.com/modelcontextprotocol/servers)
561
+
562
+ **For MCP 1st Birthday Hackathon - Track 2: MCP in Action** πŸŽ‚
563
+ """)
564
+
565
+
566
+ if __name__ == "__main__":
567
+ # Check for API key
568
+ if not ANTHROPIC_API_KEY and not OPENAI_API_KEY:
569
+ print("⚠️ WARNING: No API key set!")
570
+ print("Please set either OPENAI_API_KEY or ANTHROPIC_API_KEY in your .env file")
571
+ print(f"Example: echo 'OPENAI_API_KEY=your_key_here' > .env")
572
+
573
+ # Launch Gradio UI
574
+ app.launch(
575
+ server_name="0.0.0.0",
576
+ server_port=7860,
577
+ share=False
578
+ )
requirements.txt CHANGED
@@ -10,3 +10,4 @@ aiohttp
10
  fastapi
11
  uvicorn
12
  httpx
 
 
10
  fastapi
11
  uvicorn
12
  httpx
13
+ huggingface_hub>=0.20.0
src/hf_deployer.py ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """HuggingFace Space Auto-Deployer - One-click MCP deployment"""
2
+
3
+ from huggingface_hub import HfApi, create_repo, upload_file
4
+ from pathlib import Path
5
+ import re
6
+ from typing import Optional
7
+
8
+
9
+ class HFSpaceDeployer:
10
+ """Deploys generated MCPs to HuggingFace Spaces"""
11
+
12
+ def __init__(self, token: str):
13
+ """Initialize with HuggingFace token
14
+
15
+ Args:
16
+ token: HuggingFace API token
17
+ """
18
+ self.api = HfApi(token=token)
19
+ self.token = token
20
+
21
+ def deploy_mcp(self, mcp_dir: Path, api_name: str, mcp_id: str) -> dict:
22
+ """Deploy an MCP to a new HuggingFace Space
23
+
24
+ Args:
25
+ mcp_dir: Path to the MCP directory
26
+ api_name: Name of the API (for Space name)
27
+ mcp_id: MCP ID
28
+
29
+ Returns:
30
+ dict with deployment info (url, repo_id, etc.)
31
+ """
32
+ try:
33
+ # Get username
34
+ user_info = self.api.whoami()
35
+ username = user_info['name']
36
+
37
+ # Create Space name (URL-safe)
38
+ space_name = f"mcp-{api_name.lower().replace(' ', '-')}"
39
+ repo_id = f"{username}/{space_name}"
40
+
41
+ # Create the Space
42
+ print(f"Creating Space: {repo_id}...")
43
+ create_repo(
44
+ repo_id=repo_id,
45
+ repo_type="space",
46
+ space_sdk="gradio",
47
+ exist_ok=True,
48
+ token=self.token
49
+ )
50
+
51
+ # Create app.py wrapper for the Space
52
+ app_py_content = self._generate_space_app_py(mcp_id)
53
+ app_py_path = mcp_dir / "space_app.py"
54
+ app_py_path.write_text(app_py_content)
55
+
56
+ # Update README with Space frontmatter
57
+ self._update_readme_for_space(mcp_dir / "README.md", api_name, space_name)
58
+
59
+ # Upload files
60
+ print(f"Uploading files to {repo_id}...")
61
+ files_to_upload = [
62
+ ("space_app.py", "app.py"), # Rename to app.py for Space
63
+ ("server_http.py", "server_http.py"),
64
+ ("requirements.txt", "requirements.txt"),
65
+ ("README.md", "README.md"),
66
+ ]
67
+
68
+ for local_name, remote_name in files_to_upload:
69
+ local_path = mcp_dir / local_name
70
+ if local_path.exists():
71
+ upload_file(
72
+ path_or_fileobj=str(local_path),
73
+ path_in_repo=remote_name,
74
+ repo_id=repo_id,
75
+ repo_type="space",
76
+ token=self.token
77
+ )
78
+
79
+ space_url = f"https://huggingface.co/spaces/{repo_id}"
80
+
81
+ return {
82
+ "success": True,
83
+ "space_url": space_url,
84
+ "repo_id": repo_id,
85
+ "mcp_endpoint": f"https://{username}-{space_name}.hf.space/mcp",
86
+ "message": f"βœ… Deployed to {space_url}"
87
+ }
88
+
89
+ except Exception as e:
90
+ return {
91
+ "success": False,
92
+ "error": str(e),
93
+ "message": f"❌ Deployment failed: {str(e)}"
94
+ }
95
+
96
+ def _generate_space_app_py(self, mcp_id: str) -> str:
97
+ """Generate app.py for HuggingFace Space"""
98
+ return f'''"""
99
+ MCP Server Space - Auto-generated by MCP Generator
100
+ This Space runs an MCP server via HTTP transport.
101
+ """
102
+
103
+ import gradio as gr
104
+ from server_http import app as fastapi_app
105
+
106
+ # Create a simple Gradio interface for the Space
107
+ with gr.Blocks() as demo:
108
+ gr.Markdown("""
109
+ # πŸ€– MCP Server Running!
110
+
111
+ This Space is hosting an MCP server with HTTP transport.
112
+
113
+ ## πŸ”Œ How to Connect:
114
+
115
+ Add this to your Claude Desktop config:
116
+
117
+ ```json
118
+ {{
119
+ "mcpServers": {{
120
+ "{mcp_id}": {{
121
+ "url": "https://YOUR-USERNAME-SPACE-NAME.hf.space"
122
+ }}
123
+ }}
124
+ }}
125
+ ```
126
+
127
+ **Note:** Replace `YOUR-USERNAME-SPACE-NAME` with this Space's actual URL.
128
+
129
+ ## πŸ“‘ Endpoints:
130
+
131
+ - `/mcp` - Main MCP endpoint (JSON-RPC 2.0)
132
+ - `/health` - Health check
133
+ - `/info` - Server information
134
+
135
+ ## πŸŽ‰ Your MCP is live and ready to use!
136
+
137
+ Generated by [MCP Generator](https://huggingface.co/spaces/MCP-1st-Birthday/mcp-generator)
138
+ """)
139
+
140
+ # Mount Gradio to FastAPI
141
+ app = gr.routes.App.create_app(demo)
142
+ fastapi_app.mount("/ui", app)
143
+
144
+ # Export the FastAPI app for HuggingFace Spaces
145
+ # Spaces will run this with: uvicorn app:fastapi_app
146
+ '''
147
+
148
+ def _update_readme_for_space(self, readme_path: Path, api_name: str, space_name: str):
149
+ """Update README.md with Space-specific YAML frontmatter"""
150
+ content = readme_path.read_text()
151
+
152
+ # Add YAML frontmatter
153
+ frontmatter = f"""---
154
+ title: {api_name} MCP Server
155
+ emoji: πŸ€–
156
+ colorFrom: blue
157
+ colorTo: purple
158
+ sdk: gradio
159
+ sdk_version: 5.9.1
160
+ app_file: app.py
161
+ pinned: false
162
+ license: mit
163
+ tags:
164
+ - mcp
165
+ - model-context-protocol
166
+ - api
167
+ - mcp-server
168
+ ---
169
+
170
+ """
171
+
172
+ # Prepend frontmatter to existing README
173
+ updated_content = frontmatter + content
174
+ readme_path.write_text(updated_content)
175
+
176
+
177
+ def deploy_to_huggingface(mcp_dir: Path, api_name: str, mcp_id: str, hf_token: str) -> dict:
178
+ """Helper function to deploy an MCP to HuggingFace Spaces
179
+
180
+ Args:
181
+ mcp_dir: Path to MCP directory
182
+ api_name: API name
183
+ mcp_id: MCP ID
184
+ hf_token: HuggingFace API token
185
+
186
+ Returns:
187
+ dict with deployment result
188
+ """
189
+ deployer = HFSpaceDeployer(token=hf_token)
190
+ return deployer.deploy_mcp(mcp_dir, api_name, mcp_id)
src/mcp_http_host.py CHANGED
@@ -17,21 +17,7 @@ class MCPHTTPHost:
17
  self.app = FastAPI(title="MCP Generator - Hosted MCPs")
18
  self.loaded_mcps: Dict[str, Any] = {}
19
 
20
- # Add root endpoint
21
- @self.app.get("/")
22
- async def root():
23
- return {
24
- "service": "MCP Generator",
25
- "hosted_mcps": list(self.loaded_mcps.keys()),
26
- "endpoints": {
27
- "list_all": "/mcps",
28
- "mcp_endpoint": "/{mcp_id}/mcp",
29
- "mcp_info": "/{mcp_id}/info",
30
- "mcp_health": "/{mcp_id}/health"
31
- },
32
- "usage": "Configure in Claude Desktop with: {\"url\": \"https://your-space.hf.space/{mcp_id}\"}"
33
- }
34
-
35
  @self.app.get("/mcps")
36
  async def list_mcps():
37
  """List all hosted MCPs"""
 
17
  self.app = FastAPI(title="MCP Generator - Hosted MCPs")
18
  self.loaded_mcps: Dict[str, Any] = {}
19
 
20
+ # List all hosted MCPs endpoint
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  @self.app.get("/mcps")
22
  async def list_mcps():
23
  """List all hosted MCPs"""