seanpoyner commited on
Commit
29c4a42
·
1 Parent(s): 67db9c4

Create functional MCP Playground app with working calculator, text processor, and echo server demos

Browse files
Files changed (2) hide show
  1. app.py +502 -919
  2. requirements.txt +2 -6
app.py CHANGED
@@ -1,998 +1,581 @@
1
- # app.py - Gradio MCP Playground Demo for HF Spaces
2
  import gradio as gr
3
  import json
4
  import os
5
  from datetime import datetime
6
- from typing import Dict, List, Optional, Tuple
7
  import uuid
8
  import asyncio
9
  import time
 
 
 
 
 
10
 
11
- # Demo mode flag
12
- DEMO_MODE = True
13
- HF_SPACE_ID = os.getenv("SPACE_ID", "demo")
14
 
15
- class GradioMCPPlayground:
16
- """Streamlined demo showcasing core MCP agent capabilities"""
17
 
18
  def __init__(self):
19
- self.demo_mode = DEMO_MODE
20
- self.sessions = {} # Session-based storage
21
- self.setup_demo_data()
22
 
23
- def setup_demo_data(self):
24
- """Initialize impressive demo content"""
25
- self.demo_agents = [
26
  {
27
- "id": "weather-assistant",
28
- "name": "Weather Assistant",
29
- "description": "Real-time weather information using MCP tools",
30
- "template": "weather",
31
- "status": "active",
32
- "tools": ["get_weather", "get_forecast"],
33
- "created": "2025-01-08"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  },
35
  {
36
- "id": "code-helper",
37
- "name": "Code Helper",
38
- "description": "AI pair programmer with file system access",
39
- "template": "filesystem",
40
- "status": "active",
41
- "tools": ["read_file", "write_file", "list_directory"],
42
- "created": "2025-01-09"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  },
44
  {
45
- "id": "research-agent",
46
- "name": "Research Agent",
47
- "description": "Web search and summarization",
48
- "template": "search",
49
- "status": "beta",
50
- "tools": ["web_search", "summarize"],
51
- "created": "2025-01-10"
52
- }
53
- ]
54
-
55
- self.mcp_servers = {
56
- "filesystem": {
57
- "name": "File System MCP",
58
- "status": "connected",
59
- "endpoint": "mcp://localhost:3000",
60
- "tools": ["read_file", "write_file", "list_directory", "create_directory", "delete_file"]
61
- },
62
- "brave-search": {
63
- "name": "Brave Search MCP",
64
- "status": "available",
65
- "endpoint": "mcp://localhost:3001",
66
- "tools": ["web_search", "image_search"]
67
- },
68
- "weather": {
69
- "name": "Weather API MCP",
70
- "status": "connected",
71
- "endpoint": "mcp://localhost:3002",
72
- "tools": ["get_weather", "get_forecast"]
73
  }
74
  }
75
-
76
- self.chat_history = []
77
-
78
- def get_session_data(self, session_id: str) -> Dict:
79
- """Get or create session data"""
80
- if session_id not in self.sessions:
81
- self.sessions[session_id] = {
82
- "agents": list(self.demo_agents),
83
- "chat_history": [],
84
- "created_agents": []
85
  }
86
- return self.sessions[session_id]
87
 
88
  def create_interface(self) -> gr.Blocks:
89
- """Create the main Gradio interface"""
90
  with gr.Blocks(
91
  title="🛝 Gradio MCP Playground",
92
  theme=gr.themes.Soft(
93
  primary_hue="blue",
94
- secondary_hue="green",
 
95
  ),
96
- css="""
97
- .gradio-container {max-width: 1200px !important}
98
- .success-message {
99
- background-color: #d4edda !important;
100
- border: 1px solid #c3e6cb;
101
- color: #155724;
102
- padding: 12px;
103
- border-radius: 4px;
104
- margin: 10px 0;
105
- }
106
- .info-box {
107
- background-color: #d1ecf1;
108
- border: 1px solid #bee5eb;
109
- color: #0c5460;
110
- padding: 12px;
111
- border-radius: 4px;
112
- margin: 10px 0;
113
- }
114
- .tool-response {
115
- background-color: #f8f9fa;
116
- border-left: 4px solid #007bff;
117
- padding: 10px;
118
- margin: 10px 0;
119
- font-family: monospace;
120
- }
121
- .template-card {
122
- background: #374151;
123
- border: 2px solid #4b5563;
124
- border-radius: 8px;
125
- padding: 15px;
126
- margin: 10px 0;
127
- cursor: pointer;
128
- transition: all 0.3s ease;
129
- }
130
- .template-card:hover {
131
- border-color: #60a5fa;
132
- transform: translateY(-2px);
133
- }
134
- .template-name {
135
- color: #f3f4f6;
136
- font-weight: bold;
137
- margin-bottom: 5px;
138
- }
139
- .template-description {
140
- color: #d1d5db;
141
- font-size: 0.9em;
142
- }
143
- .template-tools {
144
- color: #9ca3af;
145
- font-size: 0.8em;
146
- margin-top: 8px;
147
- }
148
- """
149
  ) as demo:
150
- # State management
151
- session_id = gr.State(value=str(uuid.uuid4()))
152
-
153
  # Header
154
- with gr.Row():
155
- gr.Markdown("""
156
- # 🛝 Gradio MCP Playground
157
- ### Build AI Agents in Seconds - No Code Required!
158
-
159
- <div class="info-box">
160
- <b>🏆 Hugging Face MCP Hackathon Submission</b><br>
161
- Transform any Python function into an AI agent with visual tools and instant testing.
162
- <a href="https://github.com/seanpoyner/gradio-mcp-playground" target="_blank">GitHub</a> |
163
- <a href="https://huggingface.co/spaces/seanpoyner/gradio-mcp-playground" target="_blank">Documentation</a>
164
- </div>
165
- """)
166
-
167
- # Main tabs
168
- with gr.Tabs() as main_tabs:
169
- # Tab 1: AI Assistant Hub (Main Feature)
170
- with gr.Tab("🛝 AI Assistant", id="assistant"):
171
- self.create_assistant_tab(session_id)
172
-
173
- # Tab 2: Visual Agent Builder
174
- with gr.Tab("🏗️ Server Builder", id="builder"):
175
- self.create_builder_tab(session_id)
176
-
177
- # Tab 3: MCP Connections
178
- with gr.Tab("🔌 MCP Connections", id="tools"):
179
- self.create_tools_tab(session_id)
180
-
181
- # Tab 4: My Agents
182
- with gr.Tab("📦 Server Management", id="agents"):
183
- self.create_agents_tab(session_id)
184
-
185
- # Tab 5: Help
186
- with gr.Tab("📚 Help & Resources", id="help"):
187
- self.create_help_tab()
188
-
189
- # Footer
190
  gr.Markdown("""
191
- ---
192
- <center>
193
- Made with ❤️ using Gradio |
194
- <a href="https://huggingface.co/spaces/seanpoyner/gradio-mcp-playground/discussions" target="_blank">Feedback</a> |
195
- <a href="https://github.com/seanpoyner/gradio-mcp-playground" target="_blank">GitHub</a>
196
- </center>
197
- """)
198
-
199
- return demo
200
-
201
- def create_assistant_tab(self, session_id):
202
- """Multi-mode AI assistant interface"""
203
- with gr.Row():
204
- with gr.Column(scale=1):
205
- assistant_mode = gr.Radio(
206
- choices=[
207
- "Adam (General)",
208
- "Liam (MCP Agent)",
209
- "Arthur (Agent Builder)"
210
- ],
211
- value="Adam (General)",
212
- label="Choose AI Assistant",
213
- info="Each assistant specializes in different tasks"
214
- )
215
-
216
- gr.Markdown("""
217
- ### 🛠️ Available MCP Tools
218
-
219
- **Connected:**
220
- - 📁 **FileSystem**: Read/write files
221
- - 🌤️ **Weather**: Real-time weather
222
- - 🔍 **Search**: Web search
223
-
224
- **Available:**
225
- - 🖼️ Images
226
- - 📊 Data Analysis
227
- - 🗄️ Database
228
- """)
229
-
230
- clear_btn = gr.Button("Clear Chat", variant="secondary")
231
-
232
- with gr.Column(scale=2):
233
- chatbot = gr.Chatbot(
234
- value=[
235
- (None, "👋 Hi! I'm Adam, your AI assistant with MCP superpowers. I can help you build agents, test MCP tools, or answer questions. Try asking me to check the weather or create a calculator agent!")
236
- ],
237
- height=400,
238
- avatar_images=(None, "https://api.dicebear.com/7.x/bottts/svg?seed=adam")
239
- )
240
-
241
- with gr.Row():
242
- msg_input = gr.Textbox(
243
- label="Message",
244
- placeholder="Try: 'What's the weather in Paris?' or 'Help me build a calculator agent'",
245
- scale=4,
246
- lines=1
247
- )
248
- send_btn = gr.Button("Send", variant="primary", scale=1)
249
-
250
- # Tool execution display
251
- with gr.Accordion("🔧 Tool Execution Log", open=False) as tool_log:
252
- tool_display = gr.Markdown("*No tools executed yet*")
253
-
254
- # Example prompts
255
- gr.Examples(
256
- examples=[
257
- "What's the weather in San Francisco?",
258
- "Help me create a calculator agent",
259
- "Show me available MCP tools",
260
- "How do I connect a new MCP server?",
261
- "Build an agent that can search the web"
262
- ],
263
- inputs=msg_input,
264
- label="Quick Start Examples"
265
- )
266
-
267
- # Chat functionality
268
- def respond(message: str, history: List, mode: str) -> Tuple[List, str, str]:
269
- """Process chat messages and simulate MCP tool execution"""
270
- if not message.strip():
271
- return history, "", tool_display.value
272
 
273
- # Simulate different assistant personalities
274
- assistant_name = mode.split(" ")[0]
 
275
 
276
- # Simulate MCP tool execution for certain queries
277
- tool_executed = None
278
 
279
- if "weather" in message.lower():
280
- # Simulate weather tool execution
281
- location = "San Francisco" # Extract from message in real impl
282
- if "paris" in message.lower():
283
- location = "Paris"
284
- tool_executed = f"""
285
- ### 🔧 Tool Execution
286
-
287
- **Tool:** `weather.get_current`
288
- **Parameters:** `{{"location": "{location}"}}`
289
- **Response:**
290
- ```json
291
- {{
292
- "temperature": 72,
293
- "condition": "Sunny",
294
- "humidity": 65,
295
- "wind_speed": 12
296
- }}
297
- ```
298
- **Status:** ✅ Success
299
- """
300
- response = f"I've checked the weather using the MCP weather tool. In {location}, it's currently 72°F and sunny with 65% humidity and winds at 12 mph. Perfect weather for a walk! ☀️"
301
-
302
- elif "create" in message.lower() and ("agent" in message.lower() or "calculator" in message.lower()):
303
- response = """I'll help you create a calculator agent! Here's what I'll do:
304
-
305
- 1. **Choose a template**: I'll use the calculator template that includes basic math operations
306
- 2. **Configure the agent**: Set up addition, subtraction, multiplication, and division tools
307
- 3. **Generate MCP server code**: Create a Python-based MCP server
308
- 4. **Test the tools**: Verify each operation works correctly
309
-
310
- Would you like me to walk you through the visual builder, or should I generate the code directly for you?"""
311
-
312
- elif "mcp tools" in message.lower() or "available tools" in message.lower():
313
- response = """Here are the currently available MCP tools:
314
-
315
- 📁 **FileSystem MCP** (Connected)
316
- - `read_file`: Read file contents
317
- - `write_file`: Write to files
318
- - `list_directory`: List folder contents
319
-
320
- 🌤️ **Weather MCP** (Connected)
321
- - `get_current`: Current weather
322
- - `get_forecast`: 5-day forecast
323
-
324
- 🔍 **Search MCP** (Available)
325
- - `web_search`: Search the web
326
- - `image_search`: Find images
327
-
328
- Would you like to test any of these tools or learn how to create your own?"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
 
330
- else:
331
- # General responses based on assistant mode
332
- if assistant_name == "Adam":
333
- response = f"Great question! As a general assistant with MCP capabilities, I can help you with {message}. Would you like me to use any specific tools or create an agent for this task?"
334
- elif assistant_name == "Liam":
335
- response = f"As an MCP development specialist, I'll help you with {message}. I can show you how to implement this as an MCP tool or integrate it into an existing server. Which approach would you prefer?"
336
- else: # Arthur
337
- response = f"Let's architect a solution for {message}. I can help you design the agent structure, choose the right MCP tools, and create a scalable implementation. What's your main goal with this agent?"
338
 
339
- # Add to history
340
- history.append((message, response))
 
 
 
341
 
342
- # Update tool display
343
- tool_display_text = tool_executed if tool_executed else "*No tools executed*"
 
 
 
344
 
345
- return history, "", tool_display_text
346
-
347
- # Wire up events
348
- msg_input.submit(respond, [msg_input, chatbot, assistant_mode], [chatbot, msg_input, tool_display])
349
- send_btn.click(respond, [msg_input, chatbot, assistant_mode], [chatbot, msg_input, tool_display])
350
- clear_btn.click(lambda: ([(None, "Chat cleared! How can I help you?")], ""), outputs=[chatbot, msg_input])
351
-
352
- def create_builder_tab(self, session_id):
353
- """Visual agent builder interface"""
354
- with gr.Row():
355
- with gr.Column():
356
- gr.Markdown("### 🏗️ Create Your Server")
357
-
358
- agent_name = gr.Textbox(
359
- label="Server Name",
360
- placeholder="my-awesome-server",
361
- info="Lowercase letters, numbers, and hyphens only"
362
- )
363
-
364
- agent_desc = gr.Textbox(
365
- label="Description",
366
- placeholder="What does your server do?",
367
- lines=2
368
- )
369
-
370
- template = gr.Dropdown(
371
- choices=[
372
- ("🧮 Calculator - Math operations", "calculator"),
373
- ("🌤️ Weather - Weather information", "weather"),
374
- ("🔍 Search - Web search", "search"),
375
- ("📁 FileSystem - File operations", "filesystem"),
376
- ("⚡ Custom - Start from scratch", "custom")
377
- ],
378
- label="Template",
379
- value="calculator",
380
- info="Choose a starting template"
381
- )
382
-
383
- with gr.Row():
384
- create_btn = gr.Button("Create Server", variant="primary")
385
- test_btn = gr.Button("Test Server", variant="secondary")
386
-
387
- creation_status = gr.HTML()
388
-
389
- # Templates gallery
390
- gr.Markdown("### 📚 Template Gallery")
391
- templates_html = gr.HTML(self.render_templates_gallery())
392
-
393
- with gr.Column():
394
- gr.Markdown("### 📝 Generated Code")
395
-
396
- code_editor = gr.Code(
397
- label="MCP Server Implementation",
398
- language="python",
399
- value=self.get_template_code("calculator"),
400
- lines=20
401
- )
402
-
403
- with gr.Accordion("🧪 Test Console", open=False):
404
- test_input = gr.Textbox(
405
- label="Test Input",
406
- placeholder='{"operation": "add", "a": 5, "b": 3}'
407
- )
408
- test_output = gr.JSON(label="Test Output")
409
-
410
- # Template change handler
411
- def update_template(template_choice):
412
- return self.get_template_code(template_choice)
413
-
414
- template.change(update_template, template, code_editor)
415
-
416
- # Create agent handler
417
- def create_agent(name, desc, template_choice, session_data):
418
- if not name:
419
- return '<div class="error">Please enter a server name</div>'
420
 
421
- # Add to session agents
422
- new_agent = {
423
- "id": str(uuid.uuid4()),
424
- "name": name,
425
- "description": desc,
426
- "template": template_choice,
427
- "status": "created",
428
- "created": datetime.now().strftime("%Y-%m-%d %H:%M")
429
- }
430
 
431
- # In demo mode, just show success
432
- return f'''
433
- <div class="success-message">
434
- <b>Server "{name}" created successfully!</b><br>
435
- Template: {template_choice}<br>
436
- Status: Ready to deploy<br>
437
- <small>Note: In production, this would be saved to your account.</small>
438
- </div>
439
- '''
440
-
441
- create_btn.click(
442
- create_agent,
443
- [agent_name, agent_desc, template, session_id],
444
- creation_status
445
- )
446
-
447
- # Test handler
448
- def test_agent(test_input_str):
449
- try:
450
- # Simulate test execution
451
- import json
452
- input_data = json.loads(test_input_str)
453
-
454
- # Mock response based on template
455
- if "operation" in input_data:
456
- result = {"result": 8, "operation": "add", "status": "success"}
457
- else:
458
- result = {"status": "success", "message": "Test completed"}
459
-
460
- return result
461
- except Exception as e:
462
- return {"error": str(e), "status": "failed"}
463
-
464
- test_btn.click(test_agent, test_input, test_output)
465
-
466
- def create_tools_tab(self, session_id):
467
- """MCP tools and connections interface"""
468
- with gr.Row():
469
- with gr.Column():
470
- gr.Markdown("### 🔌 Quick Connect")
471
-
472
- # Quick connect buttons
473
- with gr.Row():
474
- fs_btn = gr.Button("📁 Filesystem", variant="secondary")
475
- weather_btn = gr.Button("🌤️ Weather", variant="secondary")
476
- search_btn = gr.Button("🔍 Search", variant="secondary")
477
-
478
- gr.Markdown("### 🔌 Connected MCP Servers")
479
-
480
- # Server status cards
481
- server_html = self.render_server_cards()
482
- server_display = gr.HTML(server_html)
483
-
484
- # Add new server
485
- with gr.Accordion("➕ Add MCP Server", open=False):
486
- server_url = gr.Textbox(
487
- label="Server URL",
488
- placeholder="mcp://localhost:3000"
489
- )
490
- server_name = gr.Textbox(
491
- label="Server Name",
492
- placeholder="My Custom Server"
493
- )
494
- connect_btn = gr.Button("Connect", variant="primary")
495
-
496
- refresh_btn = gr.Button("🔄 Refresh Status", variant="secondary")
497
-
498
- with gr.Column():
499
- gr.Markdown("### 🧪 Test MCP Tools")
500
-
501
- # Tool selector
502
- tool_category = gr.Dropdown(
503
- choices=list(self.mcp_servers.keys()),
504
- label="Select Server",
505
- value="filesystem"
506
- )
507
-
508
- tool_name = gr.Dropdown(
509
- choices=self.mcp_servers["filesystem"]["tools"],
510
- label="Select Tool"
511
- )
512
-
513
- # Tool parameters
514
- tool_params = gr.JSON(
515
- value={"path": "/tmp/test.txt"},
516
- label="Tool Parameters"
517
- )
518
-
519
- execute_btn = gr.Button("▶️ Execute Tool", variant="primary")
520
-
521
- # Results
522
- execution_result = gr.JSON(
523
- label="Execution Result",
524
- value={"status": "ready"}
525
- )
526
-
527
- # Activity log
528
- gr.Markdown("### 📊 Activity Log")
529
- activity_log = gr.Textbox(
530
- value=self.get_activity_log(),
531
- label="Recent Activity",
532
- lines=8,
533
- max_lines=10,
534
- interactive=False
535
- )
536
-
537
- # Update tool list when server changes
538
- def update_tools(server):
539
- return gr.update(choices=self.mcp_servers.get(server, {}).get("tools", []))
540
-
541
- tool_category.change(update_tools, tool_category, tool_name)
542
-
543
- # Execute tool
544
- def execute_tool(server, tool, params):
545
- # Simulate tool execution
546
- result = {
547
- "server": server,
548
- "tool": tool,
549
- "params": params,
550
- "response": {
551
- "status": "success",
552
- "data": f"Executed {tool} successfully",
553
- "timestamp": datetime.now().isoformat()
554
- }
555
- }
556
 
557
- # Update activity log
558
- new_log = f"[{datetime.now().strftime('%H:%M:%S')}] Executed {server}.{tool}\n"
 
 
 
559
 
560
- return result, activity_log.value + new_log
561
-
562
- execute_btn.click(
563
- execute_tool,
564
- [tool_category, tool_name, tool_params],
565
- [execution_result, activity_log]
566
- )
567
-
568
- def create_agents_tab(self, session_id):
569
- """Display user's agents"""
570
- with gr.Row():
571
- with gr.Column():
572
- gr.Markdown("### 📦 Your Servers")
573
-
574
- # Search and filter
575
- with gr.Row():
576
- search_box = gr.Textbox(
577
- label="Search",
578
- placeholder="Search servers...",
579
- scale=3
580
- )
581
- filter_status = gr.Dropdown(
582
- choices=["All", "Active", "Inactive", "Beta"],
583
- value="All",
584
- label="Status",
585
- scale=1
586
- )
587
-
588
- # Agent gallery
589
- agent_gallery = gr.HTML(self.render_agent_gallery())
590
-
591
- # Refresh button
592
- refresh_agents_btn = gr.Button("🔄 Refresh", variant="secondary")
593
-
594
- with gr.Column():
595
- gr.Markdown("### 📊 Server Details")
596
-
597
- selected_agent = gr.JSON(
598
- label="Selected Server",
599
- value=self.demo_agents[0] if self.demo_agents else {}
600
- )
601
-
602
- with gr.Row():
603
- deploy_btn = gr.Button("🚀 Deploy", variant="primary")
604
- edit_btn = gr.Button("✏️ Edit", variant="secondary")
605
- delete_btn = gr.Button("🗑️ Delete", variant="stop")
606
-
607
- deployment_status = gr.HTML()
608
-
609
- # Deploy handler
610
- def deploy_agent(agent_data):
611
- return f'''
612
- <div class="success-message">
613
- 🚀 <b>Deployment Initiated!</b><br>
614
- Server: {agent_data.get('name', 'Unknown')}<br>
615
- Target: Hugging Face Spaces<br>
616
- Status: In Progress...<br>
617
- <small>Note: In production, this would deploy to your HF Space.</small>
618
- </div>
619
- '''
620
-
621
- deploy_btn.click(deploy_agent, selected_agent, deployment_status)
622
 
623
- def create_help_tab(self):
624
- """Help and resources tab"""
625
- gr.Markdown("""
626
- ## 📚 Help & Resources
627
-
628
- ### 🚀 Quick Start Guide
629
-
630
- 1. **Choose an AI Assistant** - Start with Adam for general help, Liam for MCP development, or Arthur for architecture
631
- 2. **Build Your First Agent** - Use the Server Builder tab to create an agent from templates
632
- 3. **Connect MCP Servers** - Use the MCP Connections tab to connect to available servers
633
- 4. **Test and Deploy** - Test your agents and deploy them to production
634
-
635
- ### 🔧 Available Templates
636
-
637
- - **Calculator** - Basic math operations (add, subtract, multiply, divide)
638
- - **Weather** - Get weather information for any location
639
- - **Search** - Web search capabilities
640
- - **FileSystem** - Read and write files (with safety checks)
641
- - **Custom** - Start from scratch with your own implementation
642
 
643
- ### 📖 Documentation
 
 
 
 
 
 
 
 
644
 
645
- - [Getting Started Guide](https://github.com/seanpoyner/gradio-mcp-playground/wiki/Getting-Started)
646
- - [MCP Protocol Specification](https://github.com/anthropics/mcp)
647
- - [Gradio Documentation](https://gradio.app/docs)
 
648
 
649
- ### 💡 Tips
 
 
 
 
650
 
651
- - Use the chat to ask for help building specific agents
652
- - Templates are fully customizable - modify the generated code as needed
653
- - Test your tools before deploying to ensure they work correctly
654
- - Join our Discord for community support and sharing
655
 
656
- ### 🐛 Known Limitations (Demo Mode)
 
 
 
 
 
 
 
 
 
657
 
658
- - MCP servers are simulated in this demo
659
- - File operations are limited to safe paths
660
- - API integrations require your own keys
661
- - Deployments are simulated (not real)
 
 
 
 
 
 
662
 
663
- ### 📞 Support
 
664
 
665
- - [GitHub Issues](https://github.com/seanpoyner/gradio-mcp-playground/issues)
666
- - [Discussions](https://huggingface.co/spaces/seanpoyner/gradio-mcp-playground/discussions)
667
- - [Discord Community](#)
668
- """)
669
-
670
- def get_template_code(self, template: str) -> str:
671
- """Return template code for different agent types"""
672
- templates = {
673
- "calculator": '''"""
674
- Calculator MCP Server
675
- A simple calculator that performs basic math operations
676
- """
677
- import json
678
- from typing import Dict, Any
679
- from mcp.server import Server, Tool
680
-
681
- class CalculatorServer(Server):
682
- def __init__(self):
683
- super().__init__("calculator")
684
- self.register_tools()
685
-
686
- def register_tools(self):
687
- self.add_tool(Tool(
688
- name="calculate",
689
- description="Perform a calculation",
690
- input_schema={
691
- "type": "object",
692
- "properties": {
693
- "operation": {
694
- "type": "string",
695
- "enum": ["add", "subtract", "multiply", "divide"]
696
- },
697
- "a": {"type": "number"},
698
- "b": {"type": "number"}
699
- },
700
- "required": ["operation", "a", "b"]
701
- },
702
- handler=self.calculate
703
- ))
704
-
705
- async def calculate(self, operation: str, a: float, b: float) -> Dict[str, Any]:
706
- """Execute calculation based on operation"""
707
- operations = {
708
- "add": lambda x, y: x + y,
709
- "subtract": lambda x, y: x - y,
710
- "multiply": lambda x, y: x * y,
711
- "divide": lambda x, y: x / y if y != 0 else None
712
  }
713
 
714
- if operation in operations:
715
- result = operations[operation](a, b)
716
- if result is not None:
717
- return {"result": result, "status": "success"}
718
- else:
719
- return {"error": "Division by zero", "status": "error"}
720
 
721
- return {"error": "Unknown operation", "status": "error"}
722
-
723
- if __name__ == "__main__":
724
- server = CalculatorServer()
725
- server.run()
726
- ''',
727
- "weather": '''"""
728
- Weather MCP Server
729
- Provides weather information for any location
730
- """
731
- import aiohttp
732
- from mcp.server import Server, Tool
733
-
734
- class WeatherServer(Server):
735
- def __init__(self):
736
- super().__init__("weather")
737
- self.api_key = "YOUR_API_KEY" # Set via environment variable
738
- self.register_tools()
739
-
740
- def register_tools(self):
741
- self.add_tool(Tool(
742
- name="get_current_weather",
743
- description="Get current weather for a location",
744
- input_schema={
745
- "type": "object",
746
- "properties": {
747
- "location": {"type": "string"}
748
- },
749
- "required": ["location"]
750
- },
751
- handler=self.get_weather
752
- ))
753
-
754
- async def get_weather(self, location: str) -> dict:
755
- """Fetch current weather data"""
756
- # Demo response
757
  return {
758
- "location": location,
759
- "temperature": 72,
760
- "condition": "Sunny",
761
- "humidity": 65,
762
- "wind_speed": 12,
763
- "unit": "fahrenheit"
764
- }
765
-
766
- if __name__ == "__main__":
767
- server = WeatherServer()
768
- server.run()
769
- ''',
770
- "filesystem": '''"""
771
- FileSystem MCP Server
772
- Safe file operations within allowed directories
773
- """
774
- import os
775
- import json
776
- from pathlib import Path
777
- from mcp.server import Server, Tool
778
-
779
- class FileSystemServer(Server):
780
- def __init__(self):
781
- super().__init__("filesystem")
782
- self.allowed_paths = ["/tmp", "/workspace"]
783
- self.register_tools()
784
 
785
- def register_tools(self):
786
- tools = [
787
- Tool(
788
- name="read_file",
789
- description="Read file contents",
790
- input_schema={
791
- "type": "object",
792
- "properties": {
793
- "path": {"type": "string"}
794
- },
795
- "required": ["path"]
796
- },
797
- handler=self.read_file
798
- ),
799
- Tool(
800
- name="write_file",
801
- description="Write content to file",
802
- input_schema={
803
- "type": "object",
804
- "properties": {
805
- "path": {"type": "string"},
806
- "content": {"type": "string"}
807
- },
808
- "required": ["path", "content"]
809
- },
810
- handler=self.write_file
811
- )
812
- ]
813
 
814
- for tool in tools:
815
- self.add_tool(tool)
 
816
 
817
- async def read_file(self, path: str) -> dict:
818
- """Read file with safety checks"""
 
 
 
 
 
 
 
819
  try:
820
- file_path = Path(path).resolve()
821
 
822
- # Safety check
823
- if not any(str(file_path).startswith(allowed)
824
- for allowed in self.allowed_paths):
825
- return {"error": "Path not allowed", "status": "error"}
 
 
826
 
827
- if file_path.exists() and file_path.is_file():
828
- content = file_path.read_text()
829
- return {"content": content, "status": "success"}
830
 
831
- return {"error": "File not found", "status": "error"}
 
 
 
 
832
 
833
  except Exception as e:
834
- return {"error": str(e), "status": "error"}
835
-
836
- if __name__ == "__main__":
837
- server = FileSystemServer()
838
- server.run()
839
- ''',
840
- "custom": '''"""
841
- Custom MCP Server Template
842
- Build your own MCP server with custom tools
843
- """
844
- from mcp.server import Server, Tool
845
-
846
- class CustomServer(Server):
847
- def __init__(self):
848
- super().__init__("custom-server")
849
- self.register_tools()
850
 
851
- def register_tools(self):
852
- # Add your custom tools here
853
- self.add_tool(Tool(
854
- name="example_tool",
855
- description="An example tool",
856
- input_schema={
857
- "type": "object",
858
- "properties": {
859
- "input": {"type": "string"}
860
- },
861
- "required": ["input"]
862
- },
863
- handler=self.example_handler
864
- ))
 
 
 
865
 
866
- async def example_handler(self, input: str) -> dict:
867
- """Your tool implementation"""
868
- return {
869
- "result": f"Processed: {input}",
870
- "status": "success"
871
- }
872
-
873
- if __name__ == "__main__":
874
- server = CustomServer()
875
- server.run()
876
- '''
 
 
 
 
877
  }
878
- return templates.get(template, templates["custom"])
879
-
880
- def render_templates_gallery(self) -> str:
881
- """Render template gallery as HTML"""
882
- templates = [
883
- {"name": "Calculator", "desc": "Math operations", "tools": 4, "icon": "🧮"},
884
- {"name": "Weather", "desc": "Weather data", "tools": 2, "icon": "🌤️"},
885
- {"name": "Search", "desc": "Web search", "tools": 2, "icon": "🔍"},
886
- {"name": "FileSystem", "desc": "File operations", "tools": 5, "icon": "📁"},
887
- ]
888
 
889
- html = '<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;">'
890
- for tmpl in templates:
891
- html += f'''
892
- <div class="template-card">
893
- <div class="template-name">{tmpl["icon"]} {tmpl["name"]}</div>
894
- <div class="template-description">{tmpl["desc"]}</div>
895
- <div class="template-tools">{tmpl["tools"]} tools included</div>
896
- </div>
897
- '''
898
- html += '</div>'
899
- return html
900
 
901
- def render_server_cards(self) -> str:
902
- """Render MCP server status cards"""
903
- html = '<div style="display: grid; gap: 15px;">'
 
 
904
 
905
- for server_id, server_info in self.mcp_servers.items():
906
- status_color = "#28a745" if server_info["status"] == "connected" else "#ffc107"
907
- status_icon = "🟢" if server_info["status"] == "connected" else "🟡"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
908
 
909
- html += f'''
910
- <div style="border: 1px solid #dee2e6; border-radius: 8px; padding: 15px; background: #f8f9fa;">
911
- <div style="display: flex; justify-content: space-between; align-items: center;">
912
- <h4 style="margin: 0;">{server_info["name"]}</h4>
913
- <span style="color: {status_color};">{status_icon} {server_info["status"].title()}</span>
914
- </div>
915
- <p style="color: #6c757d; font-size: 14px; margin: 5px 0;">
916
- {server_info["endpoint"]}
917
- </p>
918
- <div style="margin-top: 10px;">
919
- <span style="background: #e9ecef; padding: 2px 8px; border-radius: 3px; font-size: 12px;">
920
- {len(server_info["tools"])} tools
921
- </span>
922
- </div>
923
- </div>
924
- '''
925
-
926
- html += '</div>'
927
- return html
928
-
929
- def render_agent_gallery(self) -> str:
930
- """Render agent gallery cards"""
931
- html = '<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px;">'
932
-
933
- for agent in self.demo_agents:
934
- status_color = {
935
- "active": "#28a745",
936
- "inactive": "#dc3545",
937
- "beta": "#ffc107"
938
- }.get(agent["status"], "#6c757d")
939
 
940
- html += f'''
941
- <div style="border: 1px solid #dee2e6; border-radius: 8px; padding: 20px; cursor: pointer; transition: transform 0.2s; hover: transform: scale(1.02);">
942
- <h4 style="margin: 0 0 10px 0;">{agent["name"]}</h4>
943
- <p style="color: #6c757d; font-size: 14px; margin-bottom: 15px;">
944
- {agent["description"]}
945
- </p>
946
- <div style="display: flex; justify-content: space-between; align-items: center;">
947
- <span style="color: {status_color}; font-size: 12px;">
948
- ● {agent["status"].upper()}
949
- </span>
950
- <span style="font-size: 12px; color: #6c757d;">
951
- {len(agent["tools"])} tools
952
- </span>
953
- </div>
954
- <div style="margin-top: 10px;">
955
- <small style="color: #6c757d;">Created: {agent["created"]}</small>
956
- </div>
957
- <button style="width: 100%; margin-top: 15px; padding: 8px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer;">
958
- View Details
959
- </button>
960
- </div>
961
- '''
962
-
963
- html += '</div>'
964
- return html
965
 
966
- def get_activity_log(self) -> str:
967
- """Generate activity log entries"""
968
- return """[10:00:00] System initialized
969
- [10:00:01] Connected to filesystem MCP server
970
- [10:00:02] Connected to weather MCP server
971
- [10:00:03] Tool discovery completed: 10 tools available
972
- [10:00:05] Agent registry loaded: 3 agents
973
- [10:00:10] Ready for connections
974
- [10:01:23] Executed: filesystem.read_file
975
- [10:02:45] Executed: weather.get_current
976
- """
977
 
978
- # Initialize and launch
979
  if __name__ == "__main__":
980
- print("🚀 Starting Gradio MCP Playground...")
981
-
982
- # Create app instance
983
- app = GradioMCPPlayground()
984
-
985
- # Create interface
986
- demo = app.create_interface()
987
-
988
- # Configure for HF Spaces
989
- demo.queue(max_size=20)
990
-
991
- # Launch
992
  demo.launch(
993
  server_name="0.0.0.0",
994
  server_port=7860,
995
- share=False,
996
- show_error=True,
997
  favicon_path="🛝"
998
  )
 
1
+ # app.py - Gradio MCP Playground for HF Spaces
2
  import gradio as gr
3
  import json
4
  import os
5
  from datetime import datetime
6
+ from typing import Dict, List, Optional, Tuple, Any
7
  import uuid
8
  import asyncio
9
  import time
10
+ import subprocess
11
+ import sys
12
+ from pathlib import Path
13
+ import tempfile
14
+ import shutil
15
 
16
+ # Configuration
17
+ HF_SPACE_MODE = True
18
+ HF_SPACE_ID = os.getenv("SPACE_ID", "seanpoyner/gradio-mcp-playground")
19
 
20
+ class MCPPlaygroundApp:
21
+ """Gradio MCP Playground - Functional demo for HF Spaces"""
22
 
23
  def __init__(self):
24
+ self.sessions = {}
25
+ self.templates = self.load_templates()
26
+ self.active_servers = {}
27
 
28
+ def load_templates(self) -> List[Dict]:
29
+ """Load available MCP server templates"""
30
+ return [
31
  {
32
+ "name": "Calculator",
33
+ "id": "calculator",
34
+ "description": "Basic arithmetic operations server",
35
+ "code": '''import json
36
+ import sys
37
+
38
+ class CalculatorServer:
39
+ def handle_add(self, a, b):
40
+ return {"result": a + b}
41
+
42
+ def handle_subtract(self, a, b):
43
+ return {"result": a - b}
44
+
45
+ def handle_multiply(self, a, b):
46
+ return {"result": a * b}
47
+
48
+ def handle_divide(self, a, b):
49
+ if b == 0:
50
+ return {"error": "Division by zero"}
51
+ return {"result": a / b}
52
+
53
+ # Simple stdin/stdout handler
54
+ server = CalculatorServer()
55
+ while True:
56
+ try:
57
+ line = sys.stdin.readline()
58
+ if not line:
59
+ break
60
+ request = json.loads(line)
61
+
62
+ method = request.get("method", "")
63
+ params = request.get("params", {})
64
+
65
+ if method == "add":
66
+ result = server.handle_add(params.get("a", 0), params.get("b", 0))
67
+ elif method == "subtract":
68
+ result = server.handle_subtract(params.get("a", 0), params.get("b", 0))
69
+ elif method == "multiply":
70
+ result = server.handle_multiply(params.get("a", 0), params.get("b", 0))
71
+ elif method == "divide":
72
+ result = server.handle_divide(params.get("a", 0), params.get("b", 1))
73
+ else:
74
+ result = {"error": f"Unknown method: {method}"}
75
+
76
+ response = {"id": request.get("id"), "result": result}
77
+ print(json.dumps(response))
78
+ sys.stdout.flush()
79
+ except Exception as e:
80
+ print(json.dumps({"error": str(e)}))
81
+ sys.stdout.flush()
82
+ '''
83
  },
84
  {
85
+ "name": "Text Processor",
86
+ "id": "text_processor",
87
+ "description": "Text manipulation and analysis",
88
+ "code": '''import json
89
+ import sys
90
+
91
+ class TextProcessor:
92
+ def count_words(self, text):
93
+ return {"count": len(text.split())}
94
+
95
+ def reverse_text(self, text):
96
+ return {"reversed": text[::-1]}
97
+
98
+ def to_uppercase(self, text):
99
+ return {"uppercase": text.upper()}
100
+
101
+ def to_lowercase(self, text):
102
+ return {"lowercase": text.lower()}
103
+
104
+ # Simple stdin/stdout handler
105
+ processor = TextProcessor()
106
+ while True:
107
+ try:
108
+ line = sys.stdin.readline()
109
+ if not line:
110
+ break
111
+ request = json.loads(line)
112
+
113
+ method = request.get("method", "")
114
+ params = request.get("params", {})
115
+ text = params.get("text", "")
116
+
117
+ if method == "count_words":
118
+ result = processor.count_words(text)
119
+ elif method == "reverse":
120
+ result = processor.reverse_text(text)
121
+ elif method == "uppercase":
122
+ result = processor.to_uppercase(text)
123
+ elif method == "lowercase":
124
+ result = processor.to_lowercase(text)
125
+ else:
126
+ result = {"error": f"Unknown method: {method}"}
127
+
128
+ response = {"id": request.get("id"), "result": result}
129
+ print(json.dumps(response))
130
+ sys.stdout.flush()
131
+ except Exception as e:
132
+ print(json.dumps({"error": str(e)}))
133
+ sys.stdout.flush()
134
+ '''
135
  },
136
  {
137
+ "name": "Echo Server",
138
+ "id": "echo",
139
+ "description": "Simple echo server for testing",
140
+ "code": '''import json
141
+ import sys
142
+ import datetime
143
+
144
+ # Simple echo server
145
+ while True:
146
+ try:
147
+ line = sys.stdin.readline()
148
+ if not line:
149
+ break
150
+ request = json.loads(line)
151
+
152
+ response = {
153
+ "id": request.get("id"),
154
+ "result": {
155
+ "echo": request,
156
+ "timestamp": datetime.datetime.now().isoformat(),
157
+ "server": "echo-server-v1"
 
 
 
 
 
 
 
158
  }
159
  }
160
+ print(json.dumps(response))
161
+ sys.stdout.flush()
162
+ except Exception as e:
163
+ print(json.dumps({"error": str(e)}))
164
+ sys.stdout.flush()
165
+ '''
 
 
 
 
166
  }
167
+ ]
168
 
169
  def create_interface(self) -> gr.Blocks:
170
+ """Create the Gradio interface"""
171
  with gr.Blocks(
172
  title="🛝 Gradio MCP Playground",
173
  theme=gr.themes.Soft(
174
  primary_hue="blue",
175
+ secondary_hue="gray",
176
+ font=["Inter", "system-ui", "sans-serif"]
177
  ),
178
+ css=self.get_custom_css()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  ) as demo:
 
 
 
180
  # Header
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  gr.Markdown("""
182
+ # 🛝 Gradio MCP Playground
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
 
184
+ Build, test, and deploy Model Context Protocol (MCP) servers directly in your browser.
185
+ This demo showcases core MCP capabilities with functional examples.
186
+ """)
187
 
188
+ # Session state
189
+ session_id = gr.State(value=lambda: str(uuid.uuid4()))
190
 
191
+ with gr.Tabs() as tabs:
192
+ # Dashboard Tab
193
+ with gr.Tab("📊 Dashboard"):
194
+ gr.Markdown("## Active MCP Servers")
195
+
196
+ with gr.Row():
197
+ server_status = gr.JSON(
198
+ value={"message": "No active servers", "count": 0},
199
+ label="Server Status"
200
+ )
201
+ refresh_btn = gr.Button("🔄 Refresh", scale=0)
202
+
203
+ gr.Markdown("## Quick Actions")
204
+ with gr.Row():
205
+ with gr.Column():
206
+ quick_template = gr.Dropdown(
207
+ choices=[t["name"] for t in self.templates],
208
+ label="Select Template",
209
+ value="Calculator"
210
+ )
211
+ deploy_quick = gr.Button("🚀 Quick Deploy", variant="primary")
212
+
213
+ deployment_result = gr.JSON(label="Deployment Result")
214
+
215
+ # Server Builder Tab
216
+ with gr.Tab("🔧 Server Builder"):
217
+ gr.Markdown("## Create MCP Server from Template")
218
+
219
+ with gr.Row():
220
+ with gr.Column(scale=1):
221
+ template_gallery = gr.Radio(
222
+ choices=[t["name"] for t in self.templates],
223
+ label="Available Templates",
224
+ value="Calculator"
225
+ )
226
+
227
+ template_info = gr.JSON(
228
+ value=self.templates[0],
229
+ label="Template Details"
230
+ )
231
+
232
+ with gr.Column(scale=2):
233
+ server_name = gr.Textbox(
234
+ label="Server Name",
235
+ placeholder="my-calculator-server"
236
+ )
237
+
238
+ server_code = gr.Code(
239
+ value=self.templates[0]["code"],
240
+ language="python",
241
+ label="Server Code",
242
+ lines=20
243
+ )
244
+
245
+ with gr.Row():
246
+ create_btn = gr.Button("✨ Create Server", variant="primary")
247
+ test_btn = gr.Button("🧪 Test Server")
248
+
249
+ creation_output = gr.JSON(label="Server Status")
250
+
251
+ # Tools Tab
252
+ with gr.Tab("🛠️ Tools"):
253
+ gr.Markdown("## Test MCP Server Tools")
254
+
255
+ with gr.Row():
256
+ with gr.Column():
257
+ active_server = gr.Dropdown(
258
+ choices=["calculator", "text_processor", "echo"],
259
+ label="Select Server",
260
+ value="calculator"
261
+ )
262
+
263
+ tool_method = gr.Dropdown(
264
+ choices=["add", "subtract", "multiply", "divide"],
265
+ label="Select Method"
266
+ )
267
+
268
+ tool_params = gr.JSON(
269
+ value={"a": 10, "b": 5},
270
+ label="Parameters"
271
+ )
272
+
273
+ execute_btn = gr.Button("▶️ Execute", variant="primary")
274
+
275
+ with gr.Column():
276
+ execution_result = gr.JSON(
277
+ label="Execution Result",
278
+ value={"status": "ready"}
279
+ )
280
+
281
+ execution_log = gr.Textbox(
282
+ label="Execution Log",
283
+ lines=10,
284
+ max_lines=20
285
+ )
286
+
287
+ # Settings Tab
288
+ with gr.Tab("⚙️ Settings"):
289
+ gr.Markdown("## Playground Settings")
290
+
291
+ with gr.Row():
292
+ with gr.Column():
293
+ gr.Markdown("### Display Settings")
294
+ theme_select = gr.Radio(
295
+ choices=["Light", "Dark", "System"],
296
+ label="Theme",
297
+ value="System"
298
+ )
299
+
300
+ gr.Markdown("### About")
301
+ gr.Markdown(f"""
302
+ - **Version**: 1.0.0
303
+ - **Space ID**: {HF_SPACE_ID}
304
+ - **Mode**: {'HF Space' if HF_SPACE_MODE else 'Local'}
305
+ - **Created by**: Gradio MCP Team
306
+ """)
307
 
308
+ # Event handlers
309
+ refresh_btn.click(
310
+ self.refresh_status,
311
+ inputs=[session_id],
312
+ outputs=[server_status]
313
+ )
 
 
314
 
315
+ deploy_quick.click(
316
+ self.quick_deploy,
317
+ inputs=[quick_template, session_id],
318
+ outputs=[deployment_result, server_status]
319
+ )
320
 
321
+ template_gallery.change(
322
+ self.update_template_view,
323
+ inputs=[template_gallery],
324
+ outputs=[template_info, server_code]
325
+ )
326
 
327
+ create_btn.click(
328
+ self.create_server,
329
+ inputs=[server_name, server_code, session_id],
330
+ outputs=[creation_output]
331
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
 
333
+ test_btn.click(
334
+ self.test_server,
335
+ inputs=[server_code],
336
+ outputs=[creation_output]
337
+ )
 
 
 
 
338
 
339
+ active_server.change(
340
+ self.update_tool_methods,
341
+ inputs=[active_server],
342
+ outputs=[tool_method, tool_params]
343
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
 
345
+ execute_btn.click(
346
+ self.execute_tool,
347
+ inputs=[active_server, tool_method, tool_params, session_id],
348
+ outputs=[execution_result, execution_log]
349
+ )
350
 
351
+ return demo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
 
353
+ def get_custom_css(self) -> str:
354
+ """Custom CSS for the interface"""
355
+ return """
356
+ .gradio-container {
357
+ max-width: 1200px !important;
358
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
359
 
360
+ /* Dark mode friendly styles */
361
+ .template-card {
362
+ background: #374151;
363
+ border: 2px solid #4b5563;
364
+ border-radius: 8px;
365
+ padding: 16px;
366
+ margin: 8px 0;
367
+ transition: all 0.3s ease;
368
+ }
369
 
370
+ .template-card:hover {
371
+ border-color: #60a5fa;
372
+ transform: translateY(-2px);
373
+ }
374
 
375
+ .server-status {
376
+ padding: 12px;
377
+ border-radius: 8px;
378
+ margin: 8px 0;
379
+ }
380
 
381
+ .status-active {
382
+ background: #10b981;
383
+ color: white;
384
+ }
385
 
386
+ .status-inactive {
387
+ background: #ef4444;
388
+ color: white;
389
+ }
390
+ """
391
+
392
+ def refresh_status(self, session_id: str) -> Dict:
393
+ """Refresh server status"""
394
+ session = self.sessions.get(session_id, {})
395
+ servers = session.get("servers", [])
396
 
397
+ return {
398
+ "message": f"Active servers: {len(servers)}",
399
+ "count": len(servers),
400
+ "servers": servers,
401
+ "timestamp": datetime.now().isoformat()
402
+ }
403
+
404
+ def quick_deploy(self, template_name: str, session_id: str) -> Tuple[Dict, Dict]:
405
+ """Quick deploy a template"""
406
+ template = next((t for t in self.templates if t["name"] == template_name), None)
407
 
408
+ if not template:
409
+ return {"error": "Template not found"}, self.refresh_status(session_id)
410
 
411
+ # Initialize session
412
+ if session_id not in self.sessions:
413
+ self.sessions[session_id] = {"servers": []}
414
+
415
+ # Add server to session
416
+ server_id = f"{template['id']}-{int(time.time())}"
417
+ server_info = {
418
+ "id": server_id,
419
+ "name": template_name,
420
+ "status": "active",
421
+ "created": datetime.now().isoformat()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
  }
423
 
424
+ self.sessions[session_id]["servers"].append(server_info)
 
 
 
 
 
425
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
426
  return {
427
+ "success": True,
428
+ "message": f"Deployed {template_name}",
429
+ "server_id": server_id
430
+ }, self.refresh_status(session_id)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
431
 
432
+ def update_template_view(self, template_name: str) -> Tuple[Dict, str]:
433
+ """Update template view when selection changes"""
434
+ template = next((t for t in self.templates if t["name"] == template_name), None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435
 
436
+ if template:
437
+ return template, template["code"]
438
+ return {"error": "Template not found"}, ""
439
 
440
+ def create_server(self, name: str, code: str, session_id: str) -> Dict:
441
+ """Create a new server"""
442
+ if not name:
443
+ return {"error": "Server name is required"}
444
+
445
+ # Save server code temporarily
446
+ temp_dir = tempfile.mkdtemp()
447
+ server_file = Path(temp_dir) / f"{name}.py"
448
+
449
  try:
450
+ server_file.write_text(code)
451
 
452
+ # Test if code is valid Python
453
+ result = subprocess.run(
454
+ [sys.executable, "-m", "py_compile", str(server_file)],
455
+ capture_output=True,
456
+ text=True
457
+ )
458
 
459
+ if result.returncode != 0:
460
+ return {"error": f"Invalid Python code: {result.stderr}"}
 
461
 
462
+ return {
463
+ "success": True,
464
+ "message": f"Server '{name}' created successfully",
465
+ "path": str(server_file)
466
+ }
467
 
468
  except Exception as e:
469
+ return {"error": f"Failed to create server: {str(e)}"}
470
+ finally:
471
+ # Cleanup
472
+ shutil.rmtree(temp_dir, ignore_errors=True)
 
 
 
 
 
 
 
 
 
 
 
 
473
 
474
+ def test_server(self, code: str) -> Dict:
475
+ """Test server code"""
476
+ try:
477
+ # Basic syntax check
478
+ compile(code, '<string>', 'exec')
479
+ return {
480
+ "success": True,
481
+ "message": "Code syntax is valid",
482
+ "timestamp": datetime.now().isoformat()
483
+ }
484
+ except SyntaxError as e:
485
+ return {
486
+ "error": f"Syntax error at line {e.lineno}: {e.msg}",
487
+ "line": e.lineno
488
+ }
489
+ except Exception as e:
490
+ return {"error": f"Validation error: {str(e)}"}
491
 
492
+ def update_tool_methods(self, server: str) -> Tuple[gr.Dropdown, Dict]:
493
+ """Update available methods based on selected server"""
494
+ methods_map = {
495
+ "calculator": {
496
+ "methods": ["add", "subtract", "multiply", "divide"],
497
+ "default_params": {"a": 10, "b": 5}
498
+ },
499
+ "text_processor": {
500
+ "methods": ["count_words", "reverse", "uppercase", "lowercase"],
501
+ "default_params": {"text": "Hello, World!"}
502
+ },
503
+ "echo": {
504
+ "methods": ["echo"],
505
+ "default_params": {"message": "Test message"}
506
+ }
507
  }
 
 
 
 
 
 
 
 
 
 
508
 
509
+ server_info = methods_map.get(server, {"methods": [], "default_params": {}})
510
+
511
+ return gr.Dropdown(
512
+ choices=server_info["methods"],
513
+ value=server_info["methods"][0] if server_info["methods"] else None
514
+ ), server_info["default_params"]
 
 
 
 
 
515
 
516
+ def execute_tool(self, server: str, method: str, params: Dict, session_id: str) -> Tuple[Dict, str]:
517
+ """Execute a tool method"""
518
+ log_lines = []
519
+ log_lines.append(f"[{datetime.now().strftime('%H:%M:%S')}] Executing {server}.{method}")
520
+ log_lines.append(f"Parameters: {json.dumps(params, indent=2)}")
521
 
522
+ try:
523
+ # Simulate execution based on server type
524
+ if server == "calculator":
525
+ if method == "add":
526
+ result = {"result": params.get("a", 0) + params.get("b", 0)}
527
+ elif method == "subtract":
528
+ result = {"result": params.get("a", 0) - params.get("b", 0)}
529
+ elif method == "multiply":
530
+ result = {"result": params.get("a", 0) * params.get("b", 0)}
531
+ elif method == "divide":
532
+ b = params.get("b", 1)
533
+ if b == 0:
534
+ result = {"error": "Division by zero"}
535
+ else:
536
+ result = {"result": params.get("a", 0) / b}
537
+ else:
538
+ result = {"error": f"Unknown method: {method}"}
539
+
540
+ elif server == "text_processor":
541
+ text = params.get("text", "")
542
+ if method == "count_words":
543
+ result = {"count": len(text.split())}
544
+ elif method == "reverse":
545
+ result = {"reversed": text[::-1]}
546
+ elif method == "uppercase":
547
+ result = {"uppercase": text.upper()}
548
+ elif method == "lowercase":
549
+ result = {"lowercase": text.lower()}
550
+ else:
551
+ result = {"error": f"Unknown method: {method}"}
552
+
553
+ elif server == "echo":
554
+ result = {
555
+ "echo": params,
556
+ "timestamp": datetime.now().isoformat(),
557
+ "server": server
558
+ }
559
+ else:
560
+ result = {"error": f"Unknown server: {server}"}
561
 
562
+ log_lines.append(f"Result: {json.dumps(result, indent=2)}")
563
+ log_lines.append(f"[{datetime.now().strftime('%H:%M:%S')}] Execution completed")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564
 
565
+ return result, "\n".join(log_lines)
566
+
567
+ except Exception as e:
568
+ error_result = {"error": str(e), "type": type(e).__name__}
569
+ log_lines.append(f"Error: {str(e)}")
570
+ return error_result, "\n".join(log_lines)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
571
 
572
+ # Create and launch the app
573
+ app = MCPPlaygroundApp()
574
+ demo = app.create_interface()
 
 
 
 
 
 
 
 
575
 
 
576
  if __name__ == "__main__":
 
 
 
 
 
 
 
 
 
 
 
 
577
  demo.launch(
578
  server_name="0.0.0.0",
579
  server_port=7860,
 
 
580
  favicon_path="🛝"
581
  )
requirements.txt CHANGED
@@ -1,9 +1,5 @@
1
  # requirements.txt for HF Space
2
- gradio>=4.44.1
3
  huggingface-hub>=0.23.0
4
  pydantic>=2.0.0
5
- typing-extensions>=4.5.0
6
- aiohttp>=3.9.0
7
- python-multipart>=0.0.6
8
- uvicorn>=0.27.0
9
- fastapi>=0.109.0
 
1
  # requirements.txt for HF Space
2
+ gradio
3
  huggingface-hub>=0.23.0
4
  pydantic>=2.0.0
5
+ typing-extensions>=4.5.0