MCPilot / app.py
girish-hari's picture
Update app.py
b38c94d verified
#!/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
)