visproj commited on
Commit
b68dc30
·
verified ·
1 Parent(s): 14d54ee

update to auto deploy

Browse files
app.py CHANGED
@@ -1,401 +1,471 @@
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_registry import mcp_registry
16
- from src.config import LLM_PROVIDER, ANTHROPIC_API_KEY, OPENAI_API_KEY, HOSTED_MCPS_DIR
17
-
18
-
19
- # Initialize agent factory
20
- try:
21
- agent_factory = AgentFactory()
22
- print(f"✅ Using {LLM_PROVIDER.upper()} for code generation")
23
- except ValueError as e:
24
- print(f"❌ Error: {e}")
25
- print(f"Please set {'ANTHROPIC_API_KEY' if LLM_PROVIDER == 'anthropic' else 'OPENAI_API_KEY'} in .env file")
26
- agent_factory = None
27
-
28
-
29
- async def generate_and_host_mcp(api_url: str, api_key: str = None, force_regenerate: bool = False, progress=gr.Progress()):
30
- """Generate and host an MCP server
31
-
32
- Args:
33
- api_url: The API URL to analyze
34
- api_key: Optional API key for the target API
35
- force_regenerate: If True, regenerate even if exists
36
- progress: Gradio progress tracker
37
-
38
- Returns:
39
- Tuple of (status_text, code, download_file, readme, connection_config)
40
- """
41
- if not agent_factory:
42
- api_key_name = "ANTHROPIC_API_KEY" if LLM_PROVIDER == "anthropic" else "OPENAI_API_KEY"
43
- return (
44
- f"❌ Error: {api_key_name} not configured. Please set it in your .env file.",
45
- "",
46
- None,
47
- "",
48
- ""
49
- )
50
-
51
- try:
52
- # Check if MCP already exists for this URL
53
- existing_mcp = mcp_registry.find_by_url(api_url)
54
-
55
- if existing_mcp and not force_regenerate:
56
- progress(0.5, desc="Found existing MCP, reusing...")
57
-
58
- # Reuse existing MCP
59
- mcp_id = existing_mcp['mcp_id']
60
- mcp_path = HOSTED_MCPS_DIR / mcp_id
61
-
62
- # Update last used timestamp
63
- mcp_registry.update_last_used(api_url)
64
-
65
- # Load existing files
66
- server_code = (mcp_path / "server.py").read_text()
67
- readme = (mcp_path / "README.md").read_text()
68
-
69
- status_text = f"""♻️ **Reusing Existing MCP!**
70
-
71
- **MCP ID:** `{mcp_id}`
72
- **Originally Created:** {existing_mcp['created_at']}
73
- **Last Used:** {existing_mcp['last_used']}
74
-
75
- This MCP was already generated for this API URL. Using existing version to save time and API calls!
76
-
77
- 💡 **Tip:** To regenerate from scratch, check "Force Regenerate" below.
78
- """
79
-
80
- # Create ZIP file
81
- zip_buffer = io.BytesIO()
82
- with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zipf:
83
- for file_path in mcp_path.rglob('*'):
84
- if file_path.is_file():
85
- arcname = file_path.relative_to(mcp_path.parent)
86
- zipf.write(file_path, arcname)
87
-
88
- zip_buffer.seek(0)
89
- temp_zip = f"/tmp/{mcp_id}.zip"
90
- with open(temp_zip, 'wb') as f:
91
- f.write(zip_buffer.read())
92
-
93
- connection_config = f"""{{
94
- "mcpServers": {{
95
- "{mcp_id}": {{
96
- "command": "python",
97
- "args": ["server.py"]
98
- }}
99
- }}
100
- }}"""
101
-
102
- return (
103
- status_text,
104
- server_code,
105
- temp_zip,
106
- readme,
107
- connection_config
108
- )
109
-
110
- # Generate new MCP
111
- if existing_mcp:
112
- progress(0.1, desc="Regenerating MCP (forced)...")
113
- else:
114
- progress(0.1, desc="Analyzing API...")
115
-
116
- # Generate the MCP
117
- result = await agent_factory.generate_mcp(api_url, api_key)
118
-
119
- if result["status"] == "error":
120
- return (
121
- f"❌ Error: {result['error']}",
122
- "",
123
- None,
124
- "",
125
- ""
126
- )
127
-
128
- progress(0.6, desc="Generating code...")
129
-
130
- # Get the generated files
131
- mcp_id = result["mcp_id"]
132
- server_code = result["server_code"]
133
- readme = result["readme_content"]
134
-
135
- progress(0.8, desc="Starting MCP server...")
136
-
137
- # Start the MCP server
138
- start_result = await mcp_host.start_mcp(mcp_id)
139
-
140
- if not start_result["success"]:
141
- status_text = f"⚠️ MCP generated but failed to start: {start_result.get('error')}"
142
- else:
143
- status_text = f"""✅ **MCP Server Running!**
144
-
145
- **MCP ID:** `{mcp_id}`
146
- **Status:** {start_result['status']}
147
- **Connection:** stdio (local)
148
-
149
- Your MCP server is generated and ready to use!
150
- """
151
-
152
- progress(0.9, desc="Creating download package...")
153
-
154
- # Create ZIP file for download
155
- zip_buffer = io.BytesIO()
156
- mcp_path = Path(result["download_path"])
157
-
158
- with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zipf:
159
- for file_path in mcp_path.rglob('*'):
160
- if file_path.is_file():
161
- arcname = file_path.relative_to(mcp_path.parent)
162
- zipf.write(file_path, arcname)
163
-
164
- zip_buffer.seek(0)
165
-
166
- # Save to temp file for Gradio
167
- temp_zip = f"/tmp/{mcp_id}.zip"
168
- with open(temp_zip, 'wb') as f:
169
- f.write(zip_buffer.read())
170
-
171
- # Create connection config
172
- connection_config = f"""{{
173
- "mcpServers": {{
174
- "{mcp_id}": {{
175
- "command": "python",
176
- "args": ["server.py"]
177
- }}
178
- }}
179
- }}"""
180
-
181
- progress(1.0, desc="Done!")
182
-
183
- return (
184
- status_text,
185
- server_code,
186
- temp_zip,
187
- readme,
188
- connection_config
189
- )
190
-
191
- except Exception as e:
192
- return (
193
- f"❌ Unexpected error: {str(e)}",
194
- "",
195
- None,
196
- "",
197
- ""
198
- )
199
-
200
-
201
- # Build Gradio Interface
202
- with gr.Blocks(
203
- title="MCP Generator"
204
- ) as app:
205
-
206
- gr.Markdown("""
207
- # 🤖 MCP Generator
208
- ## Turn Any API into an MCP Server in Seconds!
209
-
210
- Simply enter an API URL and we'll generate a complete, working MCP server with:
211
- - ✅ Automatically analyzed endpoints
212
- - ✅ Generated MCP tools
213
- - ✅ Complete documentation
214
- - ✅ Ready to use immediately!
215
-
216
- **Built for the MCP 1st Birthday Hackathon** 🎉
217
- """)
218
-
219
- with gr.Accordion("ℹ️ API URL Guidelines & Tips", open=False):
220
- gr.Markdown("""
221
- ### ✅ What Works Best:
222
- - **Base API URLs** (e.g., `https://api.example.com`)
223
- - **REST APIs** with clear endpoints
224
- - **OpenAPI/Swagger** documented APIs
225
- - **Public APIs** (no complex auth)
226
-
227
- ### 🎯 Try These Free APIs:
228
-
229
- **No Auth Required (Perfect for Testing!):**
230
- - `https://jsonplaceholder.typicode.com` - Fake REST API (best for testing!)
231
- - `https://api.github.com` - GitHub public API
232
- - `https://dog.ceo/api` - Random dog images
233
- - `https://catfact.ninja` - Random cat facts
234
- - `https://api.coindesk.com/v1/bpi` - Bitcoin prices
235
- - `https://api.ipify.org` - Get IP address
236
-
237
- **With API Key (Free Tier):**
238
- - `https://api.openweathermap.org/data/2.5` - Weather data
239
- - `https://newsapi.org/v2` - News articles
240
- - `https://api.stripe.com` - Payment processing (test mode)
241
-
242
- ### 💡 Tips:
243
- - **Start with jsonplaceholder.typicode.com** - always works!
244
- - Paste the **base URL** (not a specific endpoint)
245
- - If API needs a key, add it in the "API Key" field below
246
- - Cached URLs generate instantly (try the same URL twice!)
247
-
248
- ### ⚠️ May Not Work Well:
249
- - GraphQL APIs (REST only for now)
250
- - APIs requiring OAuth flows
251
- - WebSocket-only APIs
252
- - APIs with very complex authentication
253
- """)
254
-
255
- with gr.Row():
256
- with gr.Column(scale=2):
257
- gr.Markdown("### 📝 Input")
258
-
259
- api_url = gr.Textbox(
260
- label="API URL or Documentation URL",
261
- placeholder="https://api.example.com",
262
- info="Enter the base URL or documentation URL of the API"
263
- )
264
-
265
- api_key = gr.Textbox(
266
- label="API Key (Optional)",
267
- placeholder="sk-...",
268
- type="password",
269
- info="If the API requires authentication"
270
- )
271
-
272
- force_regenerate = gr.Checkbox(
273
- label="Force Regenerate",
274
- value=False,
275
- info="Regenerate even if MCP already exists for this URL (saves API calls when unchecked)"
276
- )
277
-
278
- generate_btn = gr.Button(
279
- "🚀 Generate & Host MCP Server",
280
- variant="primary",
281
- size="lg"
282
- )
283
-
284
- with gr.Accordion("🚀 Quick Start Examples", open=True):
285
- gr.Markdown("""
286
- **Click to copy and paste:**
287
-
288
- ```
289
- https://jsonplaceholder.typicode.com
290
- ```
291
- ⭐ **Recommended first try!** Always works, no API key needed.
292
-
293
- ---
294
-
295
- **More examples:**
296
- - `https://api.github.com` - GitHub API (no auth)
297
- - `https://dog.ceo/api` - Dog images (fun!)
298
- - `https://catfact.ninja` - Cat facts (simple)
299
-
300
- 💡 **Tip:** MCPs are cached - try the same URL twice to see instant results!
301
- """)
302
-
303
- gr.Markdown("---")
304
-
305
- with gr.Row():
306
- with gr.Column():
307
- gr.Markdown("### 📊 Results")
308
-
309
- status_output = gr.Markdown(label="Status")
310
-
311
- with gr.Tab("Generated Code"):
312
- code_output = gr.Code(
313
- label="server.py",
314
- language="python",
315
- lines=20
316
- )
317
-
318
- with gr.Tab("README"):
319
- readme_output = gr.Markdown()
320
-
321
- with gr.Tab("Connection Config"):
322
- connection_output = gr.Code(
323
- label="Claude Desktop Config",
324
- language="json"
325
- )
326
-
327
- download_output = gr.File(
328
- label="📦 Download Complete Package (ZIP)"
329
- )
330
-
331
- # Wire up the button
332
- generate_btn.click(
333
- fn=generate_and_host_mcp,
334
- inputs=[api_url, api_key, force_regenerate],
335
- outputs=[
336
- status_output,
337
- code_output,
338
- download_output,
339
- readme_output,
340
- connection_output
341
- ]
342
- )
343
-
344
- with gr.Accordion("📋 Previously Generated MCPs", open=False):
345
- def get_existing_mcps():
346
- """Get list of existing MCPs for display"""
347
- mcps = mcp_registry.list_all()
348
- if not mcps:
349
- return "No MCPs generated yet. Generate your first one above! 👆"
350
-
351
- output = "| API Name | URL | Created | Last Used |\n"
352
- output += "|----------|-----|---------|----------|\n"
353
- for mcp in mcps[:10]: # Show last 10
354
- api_name = mcp['api_name']
355
- api_url = mcp['api_url'][:40] + "..." if len(mcp['api_url']) > 40 else mcp['api_url']
356
- created = mcp['created_at'].split('T')[0]
357
- last_used = mcp['last_used'].split('T')[0]
358
- output += f"| {api_name} | {api_url} | {created} | {last_used} |\n"
359
-
360
- return output
361
-
362
- existing_mcps_display = gr.Markdown(get_existing_mcps())
363
- refresh_btn = gr.Button("🔄 Refresh List", size="sm")
364
- refresh_btn.click(fn=get_existing_mcps, outputs=existing_mcps_display)
365
-
366
- gr.Markdown("""
367
- ---
368
- ### 🎯 How to Use Your Generated MCP
369
-
370
- 1. **Download** the ZIP file above
371
- 2. **Extract** it to a folder
372
- 3. **Add** the connection config to your Claude Desktop settings
373
- 4. **Restart** Claude Desktop
374
-
375
- Your MCP server is ready to use! 🎉
376
-
377
- ### 🚀 About This Project
378
-
379
- This is a meta-MCP: an MCP server that generates other MCP servers!
380
-
381
- - Built with [Gradio](https://gradio.app)
382
- - Powered by [LangGraph](https://github.com/langchain-ai/langgraph) agents
383
- - Uses [Anthropic's Claude](https://anthropic.com) for code generation
384
- - Integrates with [MCP Fetch Server](https://github.com/modelcontextprotocol/servers)
385
-
386
- **For MCP 1st Birthday Hackathon - Track 2: MCP in Action** 🎂
387
- """)
388
-
389
-
390
- if __name__ == "__main__":
391
- # Check for API key
392
- if not ANTHROPIC_API_KEY:
393
- print("⚠️ WARNING: ANTHROPIC_API_KEY not set!")
394
- print("Please create a .env file with your API key")
395
- print("Example: echo 'ANTHROPIC_API_KEY=your_key_here' > .env")
396
-
397
- app.launch(
398
- server_name="0.0.0.0",
399
- server_port=7860,
400
- share=False
401
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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_mcp_url = f"{base_url}/mcp/{mcp_id}/messages"
73
+ hosted_info_url = f"{base_url}/mcp/{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
+ - **Endpoint:** `{hosted_mcp_url}`
85
+ - **Info:** `{hosted_info_url}`
86
+ - **Protocol:** JSON-RPC 2.0 over HTTP Streamable
87
+
88
+ 💡 **Tip:** To regenerate from scratch, check "Force Regenerate" below.
89
+ """
90
+
91
+ # Create ZIP file
92
+ zip_buffer = io.BytesIO()
93
+ with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zipf:
94
+ for file_path in mcp_path.rglob('*'):
95
+ if file_path.is_file():
96
+ arcname = file_path.relative_to(mcp_path.parent)
97
+ zipf.write(file_path, arcname)
98
+
99
+ zip_buffer.seek(0)
100
+ temp_zip = f"/tmp/{mcp_id}.zip"
101
+ with open(temp_zip, 'wb') as f:
102
+ f.write(zip_buffer.read())
103
+
104
+ connection_config = f"""# Local stdio transport:
105
+ {{
106
+ "mcpServers": {{
107
+ "{mcp_id}": {{
108
+ "command": "python",
109
+ "args": ["server.py"]
110
+ }}
111
+ }}
112
+ }}
113
+
114
+ # HTTP transport (hosted):
115
+ {{
116
+ "mcpServers": {{
117
+ "{mcp_id}": {{
118
+ "url": "{hosted_mcp_url}",
119
+ "transport": "http"
120
+ }}
121
+ }}
122
+ }}"""
123
+
124
+ return (
125
+ status_text,
126
+ server_code,
127
+ temp_zip,
128
+ readme,
129
+ connection_config
130
+ )
131
+
132
+ # Generate new MCP
133
+ if existing_mcp:
134
+ progress(0.1, desc="Regenerating MCP (forced)...")
135
+ else:
136
+ progress(0.1, desc="Analyzing API...")
137
+
138
+ # Generate the MCP
139
+ result = await agent_factory.generate_mcp(api_url, api_key)
140
+
141
+ if result["status"] == "error":
142
+ return (
143
+ f" Error: {result['error']}",
144
+ "",
145
+ None,
146
+ "",
147
+ ""
148
+ )
149
+
150
+ progress(0.6, desc="Generating code...")
151
+
152
+ # Get the generated files
153
+ mcp_id = result["mcp_id"]
154
+ server_code = result["server_code"]
155
+ readme = result["readme_content"]
156
+
157
+ progress(0.8, desc="Starting MCP server...")
158
+
159
+ # Start the MCP server
160
+ start_result = await mcp_host.start_mcp(mcp_id)
161
+
162
+ # Get the hosted URL (will be updated when deployed to HuggingFace)
163
+ base_url = os.getenv("SPACE_HOST", "http://localhost:7860")
164
+ hosted_mcp_url = f"{base_url}/mcp/{mcp_id}/messages"
165
+ hosted_info_url = f"{base_url}/mcp/{mcp_id}/info"
166
+
167
+ if not start_result["success"]:
168
+ status_text = f"⚠️ MCP generated but failed to start locally: {start_result.get('error')}\n\n**HTTP Endpoint:** {hosted_mcp_url}"
169
+ else:
170
+ status_text = f"""✅ **MCP Server Generated & Hosted!**
171
+
172
+ **MCP ID:** `{mcp_id}`
173
+ **Status:** {start_result['status']}
174
+
175
+ **🌐 HTTP Transport (MCP 2025-03-26):**
176
+ - **Endpoint:** `{hosted_mcp_url}`
177
+ - **Info:** `{hosted_info_url}`
178
+ - **Protocol:** JSON-RPC 2.0 over HTTP Streamable
179
+
180
+ **📦 Local Use (stdio):**
181
+ - Download ZIP below and extract
182
+ - Configure in Claude Desktop (see Connection Config tab)
183
+
184
+ Your MCP server is live and accessible via HTTP! 🎉
185
+ """
186
+
187
+ progress(0.9, desc="Creating download package...")
188
+
189
+ # Create ZIP file for download
190
+ zip_buffer = io.BytesIO()
191
+ mcp_path = Path(result["download_path"])
192
+
193
+ with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zipf:
194
+ for file_path in mcp_path.rglob('*'):
195
+ if file_path.is_file():
196
+ arcname = file_path.relative_to(mcp_path.parent)
197
+ zipf.write(file_path, arcname)
198
+
199
+ zip_buffer.seek(0)
200
+
201
+ # Save to temp file for Gradio
202
+ temp_zip = f"/tmp/{mcp_id}.zip"
203
+ with open(temp_zip, 'wb') as f:
204
+ f.write(zip_buffer.read())
205
+
206
+ # Create connection configs for both transports
207
+ connection_config = f"""# Local stdio transport (extract ZIP first):
208
+ {{
209
+ "mcpServers": {{
210
+ "{mcp_id}": {{
211
+ "command": "python",
212
+ "args": ["server.py"]
213
+ }}
214
+ }}
215
+ }}
216
+
217
+ # HTTP transport (use hosted endpoint):
218
+ {{
219
+ "mcpServers": {{
220
+ "{mcp_id}": {{
221
+ "url": "{hosted_mcp_url}",
222
+ "transport": "http"
223
+ }}
224
+ }}
225
+ }}"""
226
+
227
+ progress(1.0, desc="Done!")
228
+
229
+ return (
230
+ status_text,
231
+ server_code,
232
+ temp_zip,
233
+ readme,
234
+ connection_config
235
+ )
236
+
237
+ except Exception as e:
238
+ return (
239
+ f"❌ Unexpected error: {str(e)}",
240
+ "",
241
+ None,
242
+ "",
243
+ ""
244
+ )
245
+
246
+
247
+ # Build Gradio Interface
248
+ with gr.Blocks(
249
+ title="MCP Generator",
250
+ css="""
251
+ .gradio-container {max-width: 1200px !important}
252
+ .output-box {border: 2px solid #4CAF50; border-radius: 8px; padding: 16px;}
253
+ """
254
+ ) as app:
255
+
256
+ gr.Markdown("""
257
+ # 🤖 MCP Generator
258
+ ## Turn Any API into an MCP Server in Seconds!
259
+
260
+ Simply enter an API URL and we'll generate a complete, working MCP server with:
261
+ - ✅ Automatically analyzed endpoints
262
+ - Generated MCP tools
263
+ - ✅ Complete documentation
264
+ - ✅ Ready to use immediately!
265
+
266
+ **Built for the MCP 1st Birthday Hackathon** 🎉
267
+ """)
268
+
269
+ with gr.Accordion("ℹ️ API URL Guidelines & Tips", open=False):
270
+ gr.Markdown("""
271
+ ### ✅ What Works Best:
272
+ - **Base API URLs** (e.g., `https://api.example.com`)
273
+ - **REST APIs** with clear endpoints
274
+ - **OpenAPI/Swagger** documented APIs
275
+ - **Public APIs** (no complex auth)
276
+
277
+ ### 🎯 Try These Free APIs:
278
+
279
+ **No Auth Required (Perfect for Testing!):**
280
+ - `https://jsonplaceholder.typicode.com` - Fake REST API (best for testing!)
281
+ - `https://api.github.com` - GitHub public API
282
+ - `https://dog.ceo/api` - Random dog images
283
+ - `https://catfact.ninja` - Random cat facts
284
+ - `https://api.coindesk.com/v1/bpi` - Bitcoin prices
285
+ - `https://api.ipify.org` - Get IP address
286
+
287
+ **With API Key (Free Tier):**
288
+ - `https://api.openweathermap.org/data/2.5` - Weather data
289
+ - `https://newsapi.org/v2` - News articles
290
+ - `https://api.stripe.com` - Payment processing (test mode)
291
+
292
+ ### 💡 Tips:
293
+ - **Start with jsonplaceholder.typicode.com** - always works!
294
+ - Paste the **base URL** (not a specific endpoint)
295
+ - If API needs a key, add it in the "API Key" field below
296
+ - Cached URLs generate instantly (try the same URL twice!)
297
+
298
+ ### ⚠️ May Not Work Well:
299
+ - GraphQL APIs (REST only for now)
300
+ - APIs requiring OAuth flows
301
+ - WebSocket-only APIs
302
+ - APIs with very complex authentication
303
+ """)
304
+
305
+ with gr.Row():
306
+ with gr.Column(scale=2):
307
+ gr.Markdown("### 📝 Input")
308
+
309
+ api_url = gr.Textbox(
310
+ label="API URL or Documentation URL",
311
+ placeholder="https://api.example.com",
312
+ info="Enter the base URL or documentation URL of the API"
313
+ )
314
+
315
+ api_key = gr.Textbox(
316
+ label="API Key (Optional)",
317
+ placeholder="sk-...",
318
+ type="password",
319
+ info="If the API requires authentication"
320
+ )
321
+
322
+ force_regenerate = gr.Checkbox(
323
+ label="Force Regenerate",
324
+ value=False,
325
+ info="Regenerate even if MCP already exists for this URL (saves API calls when unchecked)"
326
+ )
327
+
328
+ generate_btn = gr.Button(
329
+ "🚀 Generate & Host MCP Server",
330
+ variant="primary",
331
+ size="lg"
332
+ )
333
+
334
+ with gr.Accordion("🚀 Quick Start Examples", open=True):
335
+ gr.Markdown("""
336
+ **Click to copy and paste:**
337
+
338
+ ```
339
+ https://jsonplaceholder.typicode.com
340
+ ```
341
+ ⭐ **Recommended first try!** Always works, no API key needed.
342
+
343
+ ---
344
+
345
+ **More examples:**
346
+ - `https://api.github.com` - GitHub API (no auth)
347
+ - `https://dog.ceo/api` - Dog images (fun!)
348
+ - `https://catfact.ninja` - Cat facts (simple)
349
+
350
+ 💡 **Tip:** MCPs are cached - try the same URL twice to see instant results!
351
+ """)
352
+
353
+ gr.Markdown("---")
354
+
355
+ with gr.Row():
356
+ with gr.Column():
357
+ gr.Markdown("### 📊 Results")
358
+
359
+ status_output = gr.Markdown(label="Status")
360
+
361
+ with gr.Tab("Generated Code"):
362
+ code_output = gr.Code(
363
+ label="server.py",
364
+ language="python",
365
+ lines=20
366
+ )
367
+
368
+ with gr.Tab("README"):
369
+ readme_output = gr.Markdown()
370
+
371
+ with gr.Tab("Connection Config"):
372
+ connection_output = gr.Code(
373
+ label="Claude Desktop Config",
374
+ language="json"
375
+ )
376
+
377
+ download_output = gr.File(
378
+ label="📦 Download Complete Package (ZIP)"
379
+ )
380
+
381
+ # Wire up the button
382
+ generate_btn.click(
383
+ fn=generate_and_host_mcp,
384
+ inputs=[api_url, api_key, force_regenerate],
385
+ outputs=[
386
+ status_output,
387
+ code_output,
388
+ download_output,
389
+ readme_output,
390
+ connection_output
391
+ ]
392
+ )
393
+
394
+ with gr.Accordion("📋 Previously Generated MCPs", open=False):
395
+ def get_existing_mcps():
396
+ """Get list of existing MCPs for display"""
397
+ mcps = mcp_registry.list_all()
398
+ if not mcps:
399
+ return "No MCPs generated yet. Generate your first one above! 👆"
400
+
401
+ output = "| API Name | URL | Created | Last Used |\n"
402
+ output += "|----------|-----|---------|----------|\n"
403
+ for mcp in mcps[:10]: # Show last 10
404
+ api_name = mcp['api_name']
405
+ api_url = mcp['api_url'][:40] + "..." if len(mcp['api_url']) > 40 else mcp['api_url']
406
+ created = mcp['created_at'].split('T')[0]
407
+ last_used = mcp['last_used'].split('T')[0]
408
+ output += f"| {api_name} | {api_url} | {created} | {last_used} |\n"
409
+
410
+ return output
411
+
412
+ existing_mcps_display = gr.Markdown(get_existing_mcps())
413
+ refresh_btn = gr.Button("🔄 Refresh List", size="sm")
414
+ refresh_btn.click(fn=get_existing_mcps, outputs=existing_mcps_display)
415
+
416
+ gr.Markdown("""
417
+ ---
418
+ ### 🎯 How to Use Your Generated MCP
419
+
420
+ 1. **Download** the ZIP file above
421
+ 2. **Extract** it to a folder
422
+ 3. **Add** the connection config to your Claude Desktop settings
423
+ 4. **Restart** Claude Desktop
424
+
425
+ Your MCP server is ready to use! 🎉
426
+
427
+ ### 🚀 About This Project
428
+
429
+ This is a meta-MCP: an MCP server that generates other MCP servers!
430
+
431
+ - Built with [Gradio](https://gradio.app)
432
+ - Powered by [LangGraph](https://github.com/langchain-ai/langgraph) agents
433
+ - Uses [Anthropic's Claude](https://anthropic.com) for code generation
434
+ - Integrates with [MCP Fetch Server](https://github.com/modelcontextprotocol/servers)
435
+
436
+ **For MCP 1st Birthday Hackathon - Track 2: MCP in Action** 🎂
437
+ """)
438
+
439
+
440
+ if __name__ == "__main__":
441
+ # Check for API key
442
+ if not ANTHROPIC_API_KEY and not OPENAI_API_KEY:
443
+ print("⚠️ WARNING: No API key set!")
444
+ print("Please set either OPENAI_API_KEY or ANTHROPIC_API_KEY in your .env file")
445
+ print(f"Example: echo 'OPENAI_API_KEY=your_key_here' > .env")
446
+
447
+ # Get the MCP HTTP host FastAPI app
448
+ mcp_fastapi_app = mcp_http_host.get_app()
449
+
450
+ # Mount MCP routes to Gradio's FastAPI instance
451
+ # Gradio 5.x exposes its FastAPI app via app.fastapi_app after launch
452
+ demo_app, local_url, share_url = app.launch(
453
+ server_name="0.0.0.0",
454
+ server_port=7860,
455
+ share=False,
456
+ prevent_thread_lock=True
457
+ )
458
+
459
+ # Mount the MCP FastAPI app to serve MCPs
460
+ if hasattr(app, 'fastapi_app'):
461
+ # Mount all MCP routes onto the Gradio FastAPI app
462
+ app.fastapi_app.mount("/mcp", mcp_fastapi_app)
463
+
464
+ print("✅ MCP Generator is running!")
465
+ print(f"📊 Gradio UI: {local_url}")
466
+ print(f"🔧 MCP Endpoints: {local_url}/mcps")
467
+ print(f"🌐 Generated MCPs will be accessible at: {local_url}/mcp/{{mcp_id}}/messages")
468
+
469
+ # Keep the server running
470
+ import threading
471
+ threading.Event().wait()
requirements.txt CHANGED
@@ -1,11 +1,12 @@
1
- gradio
2
- anthropic
3
- openai
4
- mcp
5
- langgraph
6
- jinja2
7
- pydantic
8
- python-dotenv
9
- aiohttp
10
- uvicorn
11
- fastapi
 
 
1
+ gradio
2
+ anthropic
3
+ openai
4
+ mcp
5
+ langgraph
6
+ jinja2
7
+ pydantic
8
+ python-dotenv
9
+ aiohttp
10
+ fastapi
11
+ uvicorn
12
+ httpx
src/agents/code_generator.py CHANGED
@@ -79,7 +79,7 @@ Only include JSON, no other text."""
79
  # Setup Jinja2 environment
80
  env = Environment(loader=FileSystemLoader(TEMPLATES_DIR))
81
 
82
- # Generate server code
83
  server_template = env.get_template("mcp_server_template.py.jinja")
84
  server_code = server_template.render(
85
  api_name=state['api_name'],
@@ -89,6 +89,16 @@ Only include JSON, no other text."""
89
  tools=tools
90
  )
91
 
 
 
 
 
 
 
 
 
 
 
92
  # Generate README
93
  readme_template = env.get_template("readme_template.md.jinja")
94
  readme_content = readme_template.render(
@@ -97,7 +107,7 @@ Only include JSON, no other text."""
97
  server_name=mcp_id,
98
  timestamp=datetime.now().isoformat(),
99
  tools=tools,
100
- hosted_url=f"http://localhost:8000/mcps/{mcp_id}", # Will be updated when hosted
101
  auth_type=state['auth_type'],
102
  rate_limits="Check API documentation"
103
  )
@@ -105,7 +115,9 @@ Only include JSON, no other text."""
105
  # Generate requirements.txt
106
  requirements = """mcp>=1.1.0
107
  httpx>=0.27.0
108
- asyncio
 
 
109
  """
110
 
111
  # Save to hosted_mcps directory
@@ -113,6 +125,7 @@ asyncio
113
  mcp_dir.mkdir(exist_ok=True)
114
 
115
  (mcp_dir / "server.py").write_text(server_code)
 
116
  (mcp_dir / "README.md").write_text(readme_content)
117
  (mcp_dir / "requirements.txt").write_text(requirements)
118
 
 
79
  # Setup Jinja2 environment
80
  env = Environment(loader=FileSystemLoader(TEMPLATES_DIR))
81
 
82
+ # Generate stdio server code (for local use/download)
83
  server_template = env.get_template("mcp_server_template.py.jinja")
84
  server_code = server_template.render(
85
  api_name=state['api_name'],
 
89
  tools=tools
90
  )
91
 
92
+ # Generate HTTP server code (for hosting in Space)
93
+ http_template = env.get_template("mcp_server_http_template.py.jinja")
94
+ http_server_code = http_template.render(
95
+ api_name=state['api_name'],
96
+ api_url=state['api_url'],
97
+ mcp_id=mcp_id,
98
+ timestamp=datetime.now().isoformat(),
99
+ tools=tools
100
+ )
101
+
102
  # Generate README
103
  readme_template = env.get_template("readme_template.md.jinja")
104
  readme_content = readme_template.render(
 
107
  server_name=mcp_id,
108
  timestamp=datetime.now().isoformat(),
109
  tools=tools,
110
+ hosted_url=f"https://your-space.hf.space/mcp/{mcp_id}", # Will be updated when deployed
111
  auth_type=state['auth_type'],
112
  rate_limits="Check API documentation"
113
  )
 
115
  # Generate requirements.txt
116
  requirements = """mcp>=1.1.0
117
  httpx>=0.27.0
118
+ fastapi>=0.104.0
119
+ uvicorn>=0.24.0
120
+ pydantic>=2.0.0
121
  """
122
 
123
  # Save to hosted_mcps directory
 
125
  mcp_dir.mkdir(exist_ok=True)
126
 
127
  (mcp_dir / "server.py").write_text(server_code)
128
+ (mcp_dir / "server_http.py").write_text(http_server_code)
129
  (mcp_dir / "README.md").write_text(readme_content)
130
  (mcp_dir / "requirements.txt").write_text(requirements)
131
 
src/mcp_host.py CHANGED
@@ -5,7 +5,6 @@ import subprocess
5
  import signal
6
  from pathlib import Path
7
  from typing import Dict, Optional
8
- import psutil
9
 
10
  from .config import HOSTED_MCPS_DIR
11
 
 
5
  import signal
6
  from pathlib import Path
7
  from typing import Dict, Optional
 
8
 
9
  from .config import HOSTED_MCPS_DIR
10
 
src/mcp_http_host.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """HTTP Host for Generated MCPs - Makes them accessible via web endpoints"""
2
+
3
+ import asyncio
4
+ from fastapi import FastAPI, HTTPException
5
+ from fastapi.responses import JSONResponse
6
+ from pathlib import Path
7
+ import importlib.util
8
+ import sys
9
+ from typing import Dict, Any
10
+
11
+ from .config import HOSTED_MCPS_DIR
12
+
13
+ class MCPHTTPHost:
14
+ """Hosts multiple MCP servers via HTTP endpoints"""
15
+
16
+ def __init__(self):
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_tools": "/mcp/{mcp_id}/tools",
29
+ "mcp_call": "/mcp/{mcp_id}/call",
30
+ "mcp_health": "/mcp/{mcp_id}/health"
31
+ }
32
+ }
33
+
34
+ @self.app.get("/mcps")
35
+ async def list_mcps():
36
+ """List all hosted MCPs"""
37
+ return {
38
+ "mcps": [
39
+ {
40
+ "mcp_id": mcp_id,
41
+ "endpoints": {
42
+ "tools": f"/mcp/{mcp_id}/tools",
43
+ "call": f"/mcp/{mcp_id}/call",
44
+ "health": f"/mcp/{mcp_id}/health"
45
+ }
46
+ }
47
+ for mcp_id in self.loaded_mcps.keys()
48
+ ]
49
+ }
50
+
51
+ self._setup_dynamic_routes()
52
+
53
+ def _setup_dynamic_routes(self):
54
+ """Set up dynamic routes for all MCPs"""
55
+
56
+ @self.app.get("/mcp/{mcp_id}/tools")
57
+ async def get_tools(mcp_id: str):
58
+ """Get tools for a specific MCP"""
59
+ if mcp_id not in self.loaded_mcps:
60
+ # Try to load the MCP
61
+ if not self.load_mcp(mcp_id):
62
+ raise HTTPException(status_code=404, detail=f"MCP {mcp_id} not found")
63
+
64
+ mcp_module = self.loaded_mcps[mcp_id]
65
+ if hasattr(mcp_module, 'get_tools'):
66
+ return {"tools": await mcp_module.get_tools()}
67
+ raise HTTPException(status_code=500, detail="MCP does not expose tools")
68
+
69
+ @self.app.post("/mcp/{mcp_id}/call")
70
+ async def call_tool(mcp_id: str, tool_call: dict):
71
+ """Call a tool in a specific MCP"""
72
+ if mcp_id not in self.loaded_mcps:
73
+ if not self.load_mcp(mcp_id):
74
+ raise HTTPException(status_code=404, detail=f"MCP {mcp_id} not found")
75
+
76
+ mcp_module = self.loaded_mcps[mcp_id]
77
+ if hasattr(mcp_module, 'call_tool'):
78
+ result = await mcp_module.call_tool(
79
+ tool_call.get('name'),
80
+ tool_call.get('arguments', {})
81
+ )
82
+ return {"content": result}
83
+ raise HTTPException(status_code=500, detail="MCP does not support tool calls")
84
+
85
+ @self.app.get("/mcp/{mcp_id}/health")
86
+ async def health_check(mcp_id: str):
87
+ """Health check for a specific MCP"""
88
+ if mcp_id in self.loaded_mcps:
89
+ return {"status": "healthy", "mcp_id": mcp_id, "loaded": True}
90
+
91
+ # Try to load
92
+ if self.load_mcp(mcp_id):
93
+ return {"status": "healthy", "mcp_id": mcp_id, "loaded": True}
94
+
95
+ return {"status": "not_found", "mcp_id": mcp_id, "loaded": False}
96
+
97
+ def load_mcp(self, mcp_id: str) -> bool:
98
+ """Dynamically load an MCP server module"""
99
+ mcp_path = HOSTED_MCPS_DIR / mcp_id / "server_http.py"
100
+
101
+ if not mcp_path.exists():
102
+ return False
103
+
104
+ try:
105
+ # Load the module dynamically
106
+ spec = importlib.util.spec_from_file_location(f"mcp_{mcp_id}", mcp_path)
107
+ if spec and spec.loader:
108
+ module = importlib.util.module_from_spec(spec)
109
+ sys.modules[f"mcp_{mcp_id}"] = module
110
+ spec.loader.exec_module(module)
111
+
112
+ self.loaded_mcps[mcp_id] = module
113
+ return True
114
+ except Exception as e:
115
+ print(f"Failed to load MCP {mcp_id}: {e}")
116
+ return False
117
+
118
+ return False
119
+
120
+ def load_all_mcps(self):
121
+ """Load all available MCPs from the hosted_mcps directory"""
122
+ if not HOSTED_MCPS_DIR.exists():
123
+ return
124
+
125
+ for mcp_dir in HOSTED_MCPS_DIR.iterdir():
126
+ if mcp_dir.is_dir():
127
+ mcp_id = mcp_dir.name
128
+ self.load_mcp(mcp_id)
129
+
130
+ print(f"✅ Loaded {len(self.loaded_mcps)} MCPs")
131
+
132
+ def get_app(self) -> FastAPI:
133
+ """Get the FastAPI app instance"""
134
+ self.load_all_mcps()
135
+ return self.app
136
+
137
+
138
+ # Global instance
139
+ mcp_http_host = MCPHTTPHost()
src/templates/mcp_server_http_template.py.jinja ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Generated MCP Server for {{ api_name }} (HTTP Streamable Transport - MCP 2025-03-26)
3
+ Auto-generated by MCP Generator
4
+
5
+ API URL: {{ api_url }}
6
+ Generated: {{ timestamp }}
7
+ Hosted at: /mcp/{{ mcp_id }}/
8
+
9
+ This server implements MCP specification 2025-03-26 with HTTP streamable transport.
10
+ Uses JSON-RPC 2.0 protocol over HTTP as specified in the MCP standard.
11
+ """
12
+
13
+ import asyncio
14
+ import json
15
+ from typing import Any
16
+ from mcp.server import Server
17
+ from mcp.types import Tool, TextContent
18
+ import httpx
19
+ from fastapi import FastAPI, Request
20
+ from fastapi.responses import StreamingResponse
21
+ import uvicorn
22
+
23
+ # Initialize MCP server
24
+ mcp_server = Server("{{ mcp_id }}")
25
+
26
+ # Initialize FastAPI app for HTTP transport
27
+ app = FastAPI(title="{{ api_name }} MCP Server")
28
+
29
+ @mcp_server.list_tools()
30
+ async def list_tools() -> list[Tool]:
31
+ """List available MCP tools"""
32
+ return [
33
+ {% for tool in tools %}
34
+ Tool(
35
+ name="{{ tool.name }}",
36
+ description="{{ tool.description }}",
37
+ inputSchema={
38
+ "type": "object",
39
+ "properties": {{ tool.parameters | tojson }},
40
+ "required": {{ tool.required | tojson }}
41
+ }
42
+ ){% if not loop.last %},{% endif %}
43
+ {% endfor %}
44
+ ]
45
+
46
+ @mcp_server.call_tool()
47
+ async def call_tool(name: str, arguments: Any) -> list[TextContent]:
48
+ """Handle MCP tool calls"""
49
+ {% for tool in tools %}
50
+ {% if loop.first %}if{% else %}elif{% endif %} name == "{{ tool.name }}":
51
+ return await {{ tool.function_name }}(arguments)
52
+ {% endfor %}
53
+ else:
54
+ raise ValueError(f"Unknown tool: {name}")
55
+
56
+ {% for tool in tools %}
57
+ async def {{ tool.function_name }}(args: dict) -> list[TextContent]:
58
+ """{{ tool.description }}"""
59
+ try:
60
+ async with httpx.AsyncClient(timeout=30.0) as client:
61
+ {% if tool.method == "GET" %}
62
+ response = await client.get(
63
+ "{{ tool.endpoint }}",
64
+ params=args,
65
+ headers={{ tool.headers | tojson }}
66
+ )
67
+ {% elif tool.method == "POST" %}
68
+ response = await client.post(
69
+ "{{ tool.endpoint }}",
70
+ json=args,
71
+ headers={{ tool.headers | tojson }}
72
+ )
73
+ {% elif tool.method == "PUT" %}
74
+ response = await client.put(
75
+ "{{ tool.endpoint }}",
76
+ json=args,
77
+ headers={{ tool.headers | tojson }}
78
+ )
79
+ {% elif tool.method == "DELETE" %}
80
+ response = await client.delete(
81
+ "{{ tool.endpoint }}",
82
+ params=args,
83
+ headers={{ tool.headers | tojson }}
84
+ )
85
+ {% endif %}
86
+
87
+ response.raise_for_status()
88
+
89
+ # Format response
90
+ content = response.json() if response.headers.get("content-type", "").startswith("application/json") else response.text
91
+
92
+ return [TextContent(
93
+ type="text",
94
+ text=json.dumps(content, indent=2) if isinstance(content, dict) else str(content)
95
+ )]
96
+
97
+ except httpx.HTTPError as e:
98
+ return [TextContent(
99
+ type="text",
100
+ text=f"Error calling API: {str(e)}"
101
+ )]
102
+ except Exception as e:
103
+ return [TextContent(
104
+ type="text",
105
+ text=f"Unexpected error: {str(e)}"
106
+ )]
107
+
108
+ {% endfor %}
109
+
110
+ # HTTP Streamable Transport Implementation (MCP 2025-03-26 spec)
111
+ class MCPMessageHandler:
112
+ """Handles MCP JSON-RPC messages over HTTP"""
113
+
114
+ async def handle_message(self, message: dict) -> dict:
115
+ """Process MCP JSON-RPC 2.0 message"""
116
+ method = message.get("method")
117
+ params = message.get("params", {})
118
+ msg_id = message.get("id")
119
+
120
+ try:
121
+ if method == "tools/list":
122
+ tools = await list_tools()
123
+ return {
124
+ "jsonrpc": "2.0",
125
+ "id": msg_id,
126
+ "result": {
127
+ "tools": [
128
+ {
129
+ "name": tool.name,
130
+ "description": tool.description,
131
+ "inputSchema": tool.inputSchema
132
+ }
133
+ for tool in tools
134
+ ]
135
+ }
136
+ }
137
+
138
+ elif method == "tools/call":
139
+ tool_name = params.get("name")
140
+ arguments = params.get("arguments", {})
141
+
142
+ result = await call_tool(tool_name, arguments)
143
+
144
+ return {
145
+ "jsonrpc": "2.0",
146
+ "id": msg_id,
147
+ "result": {
148
+ "content": [
149
+ {"type": item.type, "text": item.text}
150
+ for item in result
151
+ ]
152
+ }
153
+ }
154
+
155
+ elif method == "initialize":
156
+ return {
157
+ "jsonrpc": "2.0",
158
+ "id": msg_id,
159
+ "result": {
160
+ "protocolVersion": "2025-03-26",
161
+ "serverInfo": {
162
+ "name": "{{ mcp_id }}",
163
+ "version": "1.0.0"
164
+ },
165
+ "capabilities": {
166
+ "tools": {}
167
+ }
168
+ }
169
+ }
170
+
171
+ else:
172
+ return {
173
+ "jsonrpc": "2.0",
174
+ "id": msg_id,
175
+ "error": {
176
+ "code": -32601,
177
+ "message": f"Method not found: {method}"
178
+ }
179
+ }
180
+
181
+ except Exception as e:
182
+ return {
183
+ "jsonrpc": "2.0",
184
+ "id": msg_id,
185
+ "error": {
186
+ "code": -32603,
187
+ "message": f"Internal error: {str(e)}"
188
+ }
189
+ }
190
+
191
+ message_handler = MCPMessageHandler()
192
+
193
+ @app.post("/mcp/{{ mcp_id }}/messages")
194
+ async def handle_mcp_messages(request: Request):
195
+ """
196
+ MCP HTTP Streamable Transport endpoint (MCP 2025-03-26)
197
+ Accepts JSON-RPC 2.0 messages and returns streaming responses
198
+ """
199
+ try:
200
+ body = await request.json()
201
+
202
+ # Handle single message or batch
203
+ messages = body if isinstance(body, list) else [body]
204
+
205
+ async def generate_responses():
206
+ for message in messages:
207
+ response = await message_handler.handle_message(message)
208
+ yield json.dumps(response) + "\n"
209
+
210
+ return StreamingResponse(
211
+ generate_responses(),
212
+ media_type="application/jsonrpc+json",
213
+ headers={"Cache-Control": "no-cache"}
214
+ )
215
+
216
+ except Exception as e:
217
+ return {
218
+ "jsonrpc": "2.0",
219
+ "error": {
220
+ "code": -32700,
221
+ "message": f"Parse error: {str(e)}"
222
+ }
223
+ }
224
+
225
+ @app.get("/mcp/{{ mcp_id }}/health")
226
+ async def health_check():
227
+ """Health check endpoint"""
228
+ return {
229
+ "status": "healthy",
230
+ "mcp_id": "{{ mcp_id }}",
231
+ "protocol": "MCP 2025-03-26",
232
+ "transport": "HTTP Streamable"
233
+ }
234
+
235
+ @app.get("/mcp/{{ mcp_id }}/info")
236
+ async def server_info():
237
+ """Get server information"""
238
+ tools = await list_tools()
239
+ return {
240
+ "serverInfo": {
241
+ "name": "{{ mcp_id }}",
242
+ "version": "1.0.0",
243
+ "description": "Generated MCP server for {{ api_name }}"
244
+ },
245
+ "capabilities": {
246
+ "tools": {}
247
+ },
248
+ "tools": [
249
+ {
250
+ "name": tool.name,
251
+ "description": tool.description,
252
+ "inputSchema": tool.inputSchema
253
+ }
254
+ for tool in tools
255
+ ]
256
+ }
257
+
258
+ if __name__ == "__main__":
259
+ # Run on dynamic port for HuggingFace Spaces
260
+ import os
261
+ port = int(os.getenv("PORT", 8000))
262
+ uvicorn.run(app, host="0.0.0.0", port=port)