Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """ | |
| Gradio Web Interface for Token Estimator MCP. | |
| Provides an easy-to-use web UI for testing API integrations. | |
| """ | |
| import gradio as gr | |
| import json | |
| import sys | |
| import os | |
| # Add src to path | |
| sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src')) | |
| from simulators.github import GitHubSimulator | |
| from simulators.slack import SlackSimulator | |
| from simulators.linear import LinearSimulator | |
| from simulators.notion import NotionSimulator | |
| from simulators.stripe import StripeSimulator | |
| from simulators.sentry import SentrySimulator | |
| from permissions.validator import PermissionValidator | |
| from permissions.requirements import get_required_permissions | |
| from pricing.providers import calculate_cost, compare_costs | |
| from utils.session_tracker import SessionTracker | |
| from utils.credential_manager import get_credential_manager | |
| from utils.comparator import MockRealComparator | |
| # Initialize components | |
| simulators = { | |
| 'github': GitHubSimulator(), | |
| 'slack': SlackSimulator(), | |
| 'linear': LinearSimulator(), | |
| 'notion': NotionSimulator(), | |
| 'stripe': StripeSimulator(), | |
| 'sentry': SentrySimulator(), | |
| } | |
| session_tracker = SessionTracker() | |
| permission_validator = PermissionValidator() | |
| credential_mgr = get_credential_manager() | |
| def simulate_api_call(service, action, params_json, use_real_api, credentials_json): | |
| """Simulate an API call.""" | |
| try: | |
| # Parse parameters | |
| params = json.loads(params_json) if params_json.strip() else {} | |
| # Check service | |
| if service not in simulators: | |
| return json.dumps({ | |
| "error": "Invalid service", | |
| "available": list(simulators.keys()) | |
| }, indent=2) | |
| simulator = simulators[service] | |
| # Parse credentials if using real API | |
| credentials = None | |
| if use_real_api: | |
| if credentials_json.strip(): | |
| credentials = json.loads(credentials_json) | |
| else: | |
| return json.dumps({ | |
| "error": "Credentials required for real API mode", | |
| "note": "Provide credentials in JSON format" | |
| }, indent=2) | |
| # Execute | |
| result = simulator.execute(action, params, mock=not use_real_api, credentials=credentials) | |
| # Track in session and add cost info | |
| if result.get('success'): | |
| tokens = simulator.estimate_tokens(result.get('data', {})) | |
| cost = calculate_cost(tokens, 0, 'claude-sonnet-4') | |
| mode = 'real' if use_real_api else 'mock' | |
| session_tracker.record_call(service, action, mode, tokens, cost) | |
| # Add cost information to response | |
| result['π°_cost_info'] = { | |
| 'tokens': tokens, | |
| 'estimated_cost_claude_sonnet': f"${cost:.6f}", | |
| 'mode': mode, | |
| 'π‘_savings': '100% saved (mock mode)' if mode == 'mock' else f'Actual cost: ${cost:.6f}', | |
| 'π_tip': 'This call was FREE in mock mode!' if mode == 'mock' else 'Real API call incurred actual costs' | |
| } | |
| return json.dumps(result, indent=2) | |
| except json.JSONDecodeError as e: | |
| return json.dumps({"error": "Invalid JSON", "details": str(e)}, indent=2) | |
| except Exception as e: | |
| return json.dumps({"error": str(e)}, indent=2) | |
| def validate_permissions(service, action, credentials_json): | |
| """Validate API permissions.""" | |
| try: | |
| credentials = json.loads(credentials_json) if credentials_json.strip() else {} | |
| if not credentials: | |
| return "β οΈ No credentials provided" | |
| # Get required permissions | |
| required = get_required_permissions(service, action) | |
| required_scopes = set(required['scopes']) | |
| # Validate | |
| validation = permission_validator.validate(service, action, credentials, required_scopes) | |
| if validation['valid']: | |
| return f"β Valid!\n\nHas scopes: {validation['has_scopes']}\nRequired: {required_scopes}" | |
| else: | |
| return f"β Invalid\n\nError: {validation.get('error')}\nMissing: {validation.get('missing_scopes')}\n\nDocs: {validation.get('docs_url', 'N/A')}" | |
| except json.JSONDecodeError as e: | |
| return f"β Invalid JSON: {e}" | |
| except Exception as e: | |
| return f"β Error: {e}" | |
| def get_session_report(): | |
| """Get session analytics report.""" | |
| return session_tracker.get_detailed_report() | |
| def list_actions(service): | |
| """List available actions for a service.""" | |
| if service not in simulators: | |
| return "Invalid service selected" | |
| simulator = simulators[service] | |
| actions = simulator.get_action_list() | |
| result = [f"Available actions for {service.upper()}:\n"] | |
| for action in actions: | |
| try: | |
| perms = get_required_permissions(service, action) | |
| result.append(f"\nβ’ {action}") | |
| result.append(f" Description: {perms['description']}") | |
| result.append(f" Required: {perms['scopes']}") | |
| except: | |
| result.append(f"\nβ’ {action}") | |
| return "\n".join(result) | |
| def compare_costs_ui(tokens_input, tokens_output): | |
| """Compare costs across LLM providers.""" | |
| try: | |
| comparison = compare_costs(int(tokens_input), int(tokens_output)) | |
| result = [f"Token Usage: {tokens_input} input + {tokens_output} output\n"] | |
| result.append("Cost Comparison:\n") | |
| for i, (model, info) in enumerate(comparison.items(), 1): | |
| emoji = "π" if i == 1 else "π°" if i == len(comparison) else " " | |
| result.append(f"{emoji} {i}. {info['model_name']}: ${info['cost']:.6f}") | |
| return "\n".join(result) | |
| except Exception as e: | |
| return f"Error: {e}" | |
| def compare_mock_real_ui(service, action, params_json, credentials_json): | |
| """Compare mock vs real API responses.""" | |
| try: | |
| params = json.loads(params_json) if params_json.strip() else {} | |
| credentials = json.loads(credentials_json) if credentials_json.strip() else {} | |
| if service not in simulators: | |
| return "Invalid service" | |
| simulator = simulators[service] | |
| # Execute both | |
| mock_response = simulator.execute(action, params, mock=True) | |
| real_response = simulator.execute(action, params, mock=False, credentials=credentials) | |
| # Compare | |
| comparator = MockRealComparator() | |
| comparison = comparator.compare(mock_response, real_response) | |
| return comparator.generate_report(comparison) | |
| except Exception as e: | |
| return f"Error: {e}" | |
| def get_actions_for_service(service): | |
| """Get available actions for a service.""" | |
| if service not in simulators: | |
| return [] | |
| simulator = simulators[service] | |
| actions = simulator.get_action_list() | |
| return actions | |
| def get_example_params_for_action(service, action): | |
| """Get example parameters for a specific service and action.""" | |
| examples = { | |
| 'github': { | |
| 'get_pull_request': '{\n "owner": "anthropic",\n "repo": "anthropic-sdk-python",\n "number": 123\n}', | |
| 'create_pull_request': '{\n "owner": "anthropic",\n "repo": "sdk",\n "head": "feature-branch",\n "base": "main",\n "title": "Add new feature"\n}', | |
| 'add_comment': '{\n "owner": "anthropic",\n "repo": "sdk",\n "issue_number": 123,\n "body": "Great work!"\n}', | |
| 'create_issue': '{\n "owner": "anthropic",\n "repo": "sdk",\n "title": "Bug report",\n "body": "Description here"\n}', | |
| 'list_pull_requests': '{\n "owner": "anthropic",\n "repo": "sdk",\n "state": "open"\n}' | |
| }, | |
| 'slack': { | |
| 'post_message': '{\n "channel": "general",\n "text": "Hello team!"\n}', | |
| 'create_channel': '{\n "name": "project-updates"\n}', | |
| 'upload_file': '{\n "channels": "general",\n "content": "File content here",\n "filename": "report.txt"\n}', | |
| 'read_messages': '{\n "channel": "general"\n}', | |
| 'update_message': '{\n "channel": "general",\n "ts": "1234567890.123456",\n "text": "Updated message"\n}', | |
| 'add_reaction': '{\n "channel": "general",\n "timestamp": "1234567890.123456",\n "name": "thumbsup"\n}' | |
| }, | |
| 'linear': { | |
| 'create_issue': '{\n "title": "Implement OAuth",\n "teamId": "team-eng",\n "description": "Add OAuth flow"\n}', | |
| 'read_issues': '{}', | |
| 'update_issue': '{\n "id": "issue-123",\n "title": "Updated title",\n "priority": 1\n}', | |
| 'add_comment': '{\n "issueId": "issue-123",\n "body": "Working on this"\n}', | |
| 'get_issue': '{\n "id": "issue-123"\n}', | |
| 'search_issues': '{\n "query": "bug"\n}' | |
| }, | |
| 'notion': { | |
| 'create_page': '{\n "parent": {"database_id": "db-123"}\n}', | |
| 'read_database': '{\n "database_id": "db-123"\n}', | |
| 'update_page': '{\n "page_id": "page-123"\n}', | |
| 'get_page': '{\n "page_id": "page-123"\n}', | |
| 'query_database': '{\n "database_id": "db-123"\n}', | |
| 'create_database': '{\n "parent": {"page_id": "page-123"},\n "title": [{"text": {"content": "Tasks"}}],\n "properties": {}\n}' | |
| }, | |
| 'stripe': { | |
| 'create_payment_intent': '{\n "amount": 5000,\n "currency": "usd"\n}', | |
| 'retrieve_customer': '{\n "customer_id": "cus_123"\n}', | |
| 'create_subscription': '{\n "customer": "cus_123",\n "items": [{"price": "price_123"}]\n}', | |
| 'create_customer': '{\n "email": "customer@example.com",\n "name": "John Doe"\n}', | |
| 'list_charges': '{}', | |
| 'refund_payment': '{\n "payment_intent": "pi_123"\n}' | |
| }, | |
| 'sentry': { | |
| 'get_issues': '{\n "project_id": "123456"\n}', | |
| 'update_issue': '{\n "issue_id": "issue-123"\n}', | |
| 'get_events': '{\n "issue_id": "issue-123"\n}', | |
| 'get_issue_details': '{\n "issue_id": "issue-123"\n}', | |
| 'resolve_issue': '{\n "issue_id": "issue-123"\n}', | |
| 'get_projects': '{}' | |
| } | |
| } | |
| return examples.get(service, {}).get(action, '{}') | |
| # Build Gradio interface | |
| with gr.Blocks(title="MCPilot - MCP Server for API Testing") as app: | |
| gr.Markdown(""" | |
| # π MCPilot | |
| **Your MCP Server for API Integration Testing** | |
| Test GitHub, Slack, Linear, Notion, Stripe, and Sentry through the Model Context Protocol. | |
| Perfect for Claude AI agents, LLM applications, and MCP-based workflows. | |
| β¨ **MCP-Native** β’ π **Mock & Real Modes** β’ π° **Token Cost Tracking** β’ π **Permission Validation** | |
| """) | |
| # Add prominent savings banner | |
| gr.Markdown(""" | |
| <div style="background: linear-gradient(90deg, #10b981 0%, #059669 100%); padding: 20px; border-radius: 10px; margin: 20px 0;"> | |
| <h3 style="color: white; margin: 0 0 10px 0;">π° Save Money on LLM Token Costs</h3> | |
| <p style="color: white; margin: 0; font-size: 16px;"> | |
| <strong>Mock Mode = 100% FREE</strong> β’ Test unlimited API calls without spending a cent<br/> | |
| <strong>Real Mode = Know Before You Spend</strong> β’ Estimate exact token costs across 8 LLM providers<br/> | |
| <strong>Average Savings: $50-200 per project</strong> during development phase | |
| </p> | |
| </div> | |
| """) | |
| with gr.Tabs(): | |
| # Tab 1: Simulate Integration | |
| with gr.Tab("π Simulate Integration"): | |
| gr.Markdown(""" | |
| ### Test API calls with mock or real mode | |
| π‘ **Cost Savings Tip**: Mock mode is FREE! Use it during development to avoid burning tokens. | |
| Only switch to Real mode when you're ready for production validation. | |
| """) | |
| with gr.Row(): | |
| with gr.Column(): | |
| service_input = gr.Dropdown( | |
| choices=list(simulators.keys()), | |
| label="Service", | |
| value="github" | |
| ) | |
| action_input = gr.Dropdown( | |
| choices=get_actions_for_service("github"), | |
| label="Action", | |
| value="get_pull_request" | |
| ) | |
| params_input = gr.Code( | |
| label="Parameters (JSON)", | |
| language="json", | |
| value='{\n "owner": "anthropic",\n "repo": "anthropic-sdk-python",\n "number": 123\n}' | |
| ) | |
| use_real = gr.Checkbox( | |
| label="Use Real API (requires credentials)", | |
| value=False | |
| ) | |
| credentials_input = gr.Code( | |
| label="Credentials (JSON) - Only for Real API", | |
| language="json", | |
| value='{\n "token": "your_token_here"\n}' | |
| ) | |
| simulate_btn = gr.Button("π Simulate", variant="primary") | |
| with gr.Column(): | |
| output = gr.Code( | |
| label="Response", | |
| language="json", | |
| lines=20 | |
| ) | |
| # Update actions when service changes | |
| def update_actions_and_params(service): | |
| actions = get_actions_for_service(service) | |
| if not actions: | |
| return gr.Dropdown(choices=[]), "" | |
| first_action = actions[0] | |
| example_params = get_example_params_for_action(service, first_action) | |
| return gr.Dropdown(choices=actions, value=first_action), example_params | |
| # Update params when action changes | |
| def update_params(service, action): | |
| return get_example_params_for_action(service, action) | |
| service_input.change( | |
| update_actions_and_params, | |
| inputs=[service_input], | |
| outputs=[action_input, params_input] | |
| ) | |
| action_input.change( | |
| update_params, | |
| inputs=[service_input, action_input], | |
| outputs=[params_input] | |
| ) | |
| simulate_btn.click( | |
| simulate_api_call, | |
| inputs=[service_input, action_input, params_input, use_real, credentials_input], | |
| outputs=output | |
| ) | |
| # Tab 2: Validate Permissions | |
| with gr.Tab("π Validate Permissions"): | |
| gr.Markdown("### Check if your credentials have required permissions") | |
| with gr.Row(): | |
| with gr.Column(): | |
| perm_service = gr.Dropdown( | |
| choices=list(simulators.keys()), | |
| label="Service", | |
| value="github" | |
| ) | |
| perm_action = gr.Dropdown( | |
| choices=get_actions_for_service("github"), | |
| label="Action", | |
| value="create_pull_request" | |
| ) | |
| perm_credentials = gr.Code( | |
| label="Credentials (JSON)", | |
| language="json", | |
| value='{\n "token": "ghp_test1234567890abcdef"\n}' | |
| ) | |
| validate_btn = gr.Button("β Validate", variant="primary") | |
| with gr.Column(): | |
| perm_output = gr.Textbox( | |
| label="Validation Result", | |
| lines=15 | |
| ) | |
| # Update actions when service changes for validation tab | |
| def update_perm_actions(service): | |
| actions = get_actions_for_service(service) | |
| if not actions: | |
| return gr.Dropdown(choices=[]) | |
| return gr.Dropdown(choices=actions, value=actions[0]) | |
| perm_service.change( | |
| update_perm_actions, | |
| inputs=[perm_service], | |
| outputs=[perm_action] | |
| ) | |
| validate_btn.click( | |
| validate_permissions, | |
| inputs=[perm_service, perm_action, perm_credentials], | |
| outputs=perm_output | |
| ) | |
| # Tab 3: List Actions | |
| with gr.Tab("π List Actions"): | |
| gr.Markdown("### Discover available actions for each service") | |
| with gr.Row(): | |
| with gr.Column(): | |
| list_service = gr.Dropdown( | |
| choices=list(simulators.keys()), | |
| label="Service", | |
| value="github" | |
| ) | |
| list_btn = gr.Button("π List Actions", variant="primary") | |
| with gr.Column(): | |
| list_output = gr.Textbox( | |
| label="Available Actions", | |
| lines=20 | |
| ) | |
| list_btn.click( | |
| list_actions, | |
| inputs=list_service, | |
| outputs=list_output | |
| ) | |
| # Tab 4: Compare Costs | |
| with gr.Tab("π° Compare Costs"): | |
| gr.Markdown(""" | |
| ### Compare token costs across LLM providers | |
| π― **Why This Matters**: Choosing the right LLM can save you 10-100x on costs! | |
| Example: For 10,000 input + 5,000 output tokens: | |
| - **Gemini Flash**: $0.01 (cheapest) | |
| - **Claude Sonnet 4**: $0.05 (balanced) | |
| - **Claude Opus 4**: $2.63 (most expensive) | |
| π‘ **Your Savings**: Know exactly what you'll pay BEFORE deploying your agent. | |
| """) | |
| with gr.Row(): | |
| with gr.Column(): | |
| cost_input = gr.Number(label="Input Tokens", value=1000) | |
| cost_output = gr.Number(label="Output Tokens", value=500) | |
| cost_btn = gr.Button("π° Compare", variant="primary") | |
| with gr.Column(): | |
| cost_result = gr.Textbox( | |
| label="Cost Comparison", | |
| lines=15 | |
| ) | |
| cost_btn.click( | |
| compare_costs_ui, | |
| inputs=[cost_input, cost_output], | |
| outputs=cost_result | |
| ) | |
| # Tab 5: Mock vs Real | |
| with gr.Tab("π Mock vs Real"): | |
| gr.Markdown("### Compare mock accuracy against real API") | |
| with gr.Row(): | |
| with gr.Column(): | |
| comp_service = gr.Dropdown( | |
| choices=list(simulators.keys()), | |
| label="Service", | |
| value="github" | |
| ) | |
| comp_action = gr.Dropdown( | |
| choices=get_actions_for_service("github"), | |
| label="Action", | |
| value="get_pull_request" | |
| ) | |
| comp_params = gr.Code( | |
| label="Parameters (JSON)", | |
| language="json", | |
| value='{\n "owner": "anthropic",\n "repo": "sdk",\n "number": 1\n}' | |
| ) | |
| comp_creds = gr.Code( | |
| label="Credentials (JSON)", | |
| language="json", | |
| value='{\n "token": "your_token_here"\n}' | |
| ) | |
| comp_btn = gr.Button("π Compare", variant="primary") | |
| with gr.Column(): | |
| comp_output = gr.Textbox( | |
| label="Comparison Report", | |
| lines=20 | |
| ) | |
| # Update actions and params for comparison tab | |
| def update_comp_actions_and_params(service): | |
| actions = get_actions_for_service(service) | |
| if not actions: | |
| return gr.Dropdown(choices=[]), "" | |
| first_action = actions[0] | |
| example_params = get_example_params_for_action(service, first_action) | |
| return gr.Dropdown(choices=actions, value=first_action), example_params | |
| def update_comp_params(service, action): | |
| return get_example_params_for_action(service, action) | |
| comp_service.change( | |
| update_comp_actions_and_params, | |
| inputs=[comp_service], | |
| outputs=[comp_action, comp_params] | |
| ) | |
| comp_action.change( | |
| update_comp_params, | |
| inputs=[comp_service, comp_action], | |
| outputs=[comp_params] | |
| ) | |
| comp_btn.click( | |
| compare_mock_real_ui, | |
| inputs=[comp_service, comp_action, comp_params, comp_creds], | |
| outputs=comp_output | |
| ) | |
| # Tab 6: Session Report | |
| with gr.Tab("π Session Report"): | |
| gr.Markdown(""" | |
| ### View analytics and savings for this session | |
| π° **Track Your Savings**: See exactly how much money you've saved by using mock mode! | |
| The report shows: | |
| - Total API calls made (mock vs real) | |
| - Tokens consumed across all services | |
| - **Money saved** by using mock mode instead of real APIs | |
| - Cost breakdown by service | |
| π― **Typical Savings**: Developers save $50-200 per project during development. | |
| """) | |
| report_btn = gr.Button("π Get Report", variant="primary") | |
| report_output = gr.Textbox( | |
| label="Session Analytics", | |
| lines=30 | |
| ) | |
| report_btn.click( | |
| get_session_report, | |
| outputs=report_output | |
| ) | |
| gr.Markdown(""" | |
| --- | |
| ### π° How MCPilot Saves You Money | |
| **During Development (Mock Mode):** | |
| - β Unlimited API testing for FREE | |
| - β No LLM token costs | |
| - β No API rate limits | |
| - β Typical savings: $50-200 per project | |
| **Before Production (Real Mode):** | |
| - π― Estimate exact token costs | |
| - π― Compare 8 LLM providers | |
| - π― Find 10-100x cheaper options | |
| - π― Catch permission errors early | |
| **Result:** Test fearlessly in mock mode, deploy confidently in real mode! | |
| --- | |
| ### π‘ About MCPilot | |
| **MCPilot** is an MCP (Model Context Protocol) server that enables instant API integration testing. | |
| - **Mock Mode**: Free, instant responses - perfect for development | |
| - **Real Mode**: Actual API calls - for production validation | |
| - **MCP Integration**: Works seamlessly with Claude and other MCP clients | |
| - **Cost Intelligence**: Know your LLM costs before deploying | |
| ### π Resources | |
| - [Hugging Face Repository](https://huggingface.co/spaces/girish-hari/MCPilot) | |
| - [MCP Documentation](https://modelcontextprotocol.io) | |
| - [Quick Start Guide](./README.md) | |
| - [Examples](./EXAMPLES.md) | |
| """) | |
| if __name__ == "__main__": | |
| print("\nπ Starting MCPilot Web Interface...") | |
| print("π Access at: http://localhost:7860") | |
| print("π‘ Your MCP Server for API Integration Testing") | |
| print("Press Ctrl+C to stop\n") | |
| app.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| share=False, # Set to True to create public link | |
| show_error=True | |
| ) | |