Skip to main content
Glama

MCP Docker Demo

by rakeshsun
mcp_inspector_web.py16.8 kB
""" Web-based MCP Inspector with FastAPI and HTML UI Run with: uvicorn mcp_inspector_web:app --host 0.0.0.0 --port 8000 """ from fastapi import FastAPI, Request from fastapi.responses import HTMLResponse, JSONResponse from fastapi.staticfiles import StaticFiles import asyncio import json from typing import Optional from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client import traceback app = FastAPI(title="MCP Inspector") # Global session holder class ServerConnection: def __init__(self): self.session: Optional[ClientSession] = None self.tools = [] connection = ServerConnection() @app.get("/", response_class=HTMLResponse) async def home(): return """ <!DOCTYPE html> <html> <head> <title>MCP Inspector</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 20px; } .container { max-width: 1400px; margin: 0 auto; background: white; border-radius: 20px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); overflow: hidden; } .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; text-align: center; } .header h1 { font-size: 2.5em; margin-bottom: 10px; text-shadow: 2px 2px 4px rgba(0,0,0,0.2); } .header p { opacity: 0.9; } .content { padding: 30px; } .section { background: #f8f9fa; padding: 25px; margin: 20px 0; border-radius: 12px; border-left: 5px solid #667eea; } .section h2 { color: #667eea; margin-bottom: 20px; font-size: 1.5em; } .button-group { display: flex; gap: 15px; flex-wrap: wrap; margin-bottom: 20px; } button { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 12px 30px; border: none; border-radius: 8px; cursor: pointer; font-size: 1em; font-weight: 600; transition: all 0.3s ease; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); } button:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6); } button:active { transform: translateY(0); } .tool-card { background: white; padding: 20px; margin: 15px 0; border-radius: 10px; border: 2px solid #e0e0e0; transition: all 0.3s ease; } .tool-card:hover { border-color: #667eea; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.2); } .tool-name { color: #667eea; font-size: 1.3em; font-weight: bold; margin-bottom: 10px; } .tool-description { color: #666; margin: 10px 0; } pre { background: #2d2d30; color: #d4d4d4; padding: 20px; border-radius: 8px; overflow-x: auto; font-family: 'Courier New', monospace; font-size: 0.9em; line-height: 1.5; } .result-box { background: white; padding: 20px; margin: 15px 0; border-radius: 10px; border-left: 5px solid #4caf50; } .success { color: #4caf50; font-weight: bold; } .error { color: #f44336; font-weight: bold; } .info { color: #2196F3; font-weight: bold; } .loading { display: inline-block; width: 20px; height: 20px; border: 3px solid #f3f3f3; border-top: 3px solid #667eea; border-radius: 50%; animation: spin 1s linear infinite; margin-left: 10px; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .test-input { width: 100%; padding: 12px; margin: 10px 0; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 1em; } .test-input:focus { outline: none; border-color: #667eea; } .status-indicator { display: inline-block; width: 12px; height: 12px; border-radius: 50%; margin-right: 8px; } .status-connected { background: #4caf50; } .status-disconnected { background: #f44336; } </style> </head> <body> <div class="container"> <div class="header"> <h1>🔍 MCP Inspector</h1> <p>Interactive Model Context Protocol Server Testing Tool</p> </div> <div class="content"> <div class="section"> <h2> <span class="status-indicator" id="statusIndicator"></span> Server Connection </h2> <div class="button-group"> <button onclick="connectServer()">Connect to Server</button> <button onclick="listTools()">List Available Tools</button> <button onclick="clearOutput()">Clear Output</button> </div> <div id="connectionStatus"></div> </div> <div class="section"> <h2>Available Tools</h2> <div id="toolsList"></div> </div> <div class="section"> <h2>Test Tools</h2> <div id="testSection"> <p class="info">Connect to server and list tools first</p> </div> </div> <div class="section"> <h2>Output</h2> <div id="output"></div> </div> </div> </div> <script> let isConnected = false; let availableTools = []; function updateStatus(connected) { isConnected = connected; const indicator = document.getElementById('statusIndicator'); indicator.className = 'status-indicator ' + (connected ? 'status-connected' : 'status-disconnected'); } async function connectServer() { addOutput('info', 'Connecting to MCP server...'); try { const response = await fetch('/api/connect', { method: 'POST' }); const data = await response.json(); if (data.success) { addOutput('success', '✅ Connected to server: ' + data.server_name); updateStatus(true); } else { addOutput('error', '❌ Connection failed: ' + data.error); updateStatus(false); } } catch (error) { addOutput('error', '❌ Connection error: ' + error.message); updateStatus(false); } } async function listTools() { addOutput('info', 'Fetching available tools...'); try { const response = await fetch('/api/tools'); const data = await response.json(); if (data.success) { availableTools = data.tools; displayTools(data.tools); createTestInterface(data.tools); addOutput('success', '✅ Found ' + data.tools.length + ' tool(s)'); } else { addOutput('error', '❌ Failed to list tools: ' + data.error); } } catch (error) { addOutput('error', '❌ Error: ' + error.message); } } function displayTools(tools) { const toolsList = document.getElementById('toolsList'); if (tools.length === 0) { toolsList.innerHTML = '<p class="info">No tools available</p>'; return; } toolsList.innerHTML = tools.map(tool => ` <div class="tool-card"> <div class="tool-name">${tool.name}</div> <div class="tool-description">${tool.description}</div> <details> <summary style="cursor: pointer; color: #667eea; margin-top: 10px;"> View Input Schema </summary> <pre>${JSON.stringify(tool.inputSchema, null, 2)}</pre> </details> </div> `).join(''); } function createTestInterface(tools) { const testSection = document.getElementById('testSection'); if (tools.length === 0) { testSection.innerHTML = '<p class="info">No tools to test</p>'; return; } testSection.innerHTML = tools.map(tool => { const properties = tool.inputSchema?.properties || {}; const required = tool.inputSchema?.required || []; const inputs = Object.entries(properties).map(([key, schema]) => { const isRequired = required.includes(key); return ` <div style="margin: 10px 0;"> <label style="display: block; margin-bottom: 5px; font-weight: bold;"> ${key} ${isRequired ? '<span style="color: red;">*</span>' : ''} </label> <input type="text" class="test-input" id="input_${tool.name}_${key}" placeholder="${schema.description || key}" ${isRequired ? 'required' : ''} /> </div> `; }).join(''); return ` <div class="tool-card"> <div class="tool-name">Test: ${tool.name}</div> ${inputs} <button onclick="testTool('${tool.name}')" style="margin-top: 15px;"> Run Test </button> </div> `; }).join(''); } async function testTool(toolName) { const tool = availableTools.find(t => t.name === toolName); if (!tool) return; const properties = tool.inputSchema?.properties || {}; const arguments = {}; for (const key in properties) { const input = document.getElementById(`input_${toolName}_${key}`); if (input) { arguments[key] = input.value; } } addOutput('info', `Testing tool: ${toolName} with arguments: ${JSON.stringify(arguments)}`); try { const response = await fetch('/api/call-tool', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: toolName, arguments }) }); const data = await response.json(); if (data.success) { addOutput('success', '✅ Tool response:'); addOutput('result', JSON.stringify(data.result, null, 2)); } else { addOutput('error', '❌ Tool call failed: ' + data.error); } } catch (error) { addOutput('error', '❌ Error: ' + error.message); } } function addOutput(type, message) { const output = document.getElementById('output'); const div = document.createElement('div'); div.className = type === 'result' ? 'result-box' : ''; if (type === 'result') { div.innerHTML = '<pre>' + message + '</pre>'; } else { div.innerHTML = `<p class="${type}">${message}</p>`; } output.insertBefore(div, output.firstChild); } function clearOutput() { document.getElementById('output').innerHTML = ''; } // Auto-connect on load window.onload = () => { updateStatus(false); }; </script> </body> </html> """ @app.post("/api/connect") async def connect_to_server(): """Connect to the MCP server""" try: server_params = StdioServerParameters( command="python", args=["-u", "/app/src/mcpdocker/server.py"], env=None ) # This is a simplified connection test # In production, you'd maintain the connection async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: init_result = await session.initialize() return JSONResponse({ "success": True, "server_name": init_result.serverInfo.name, "server_version": init_result.serverInfo.version }) except Exception as e: return JSONResponse({ "success": False, "error": str(e), "traceback": traceback.format_exc() }) @app.get("/api/tools") async def get_tools(): """List all available tools""" try: server_params = StdioServerParameters( command="python", args=["-u", "/app/src/mcpdocker/server.py"], env=None ) async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools_response = await session.list_tools() tools = [ { "name": tool.name, "description": tool.description, "inputSchema": tool.inputSchema } for tool in tools_response.tools ] return JSONResponse({ "success": True, "tools": tools }) except Exception as e: return JSONResponse({ "success": False, "error": str(e), "traceback": traceback.format_exc() }) @app.post("/api/call-tool") async def call_tool(request: Request): """Call a specific tool""" try: data = await request.json() tool_name = data.get("name") arguments = data.get("arguments", {}) server_params = StdioServerParameters( command="python", args=["-u", "/app/src/mcpdocker/server.py"], env=None ) async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() result = await session.call_tool(tool_name, arguments) return JSONResponse({ "success": True, "result": { "content": [ {"text": c.text} if hasattr(c, 'text') else str(c) for c in result.content ] } }) except Exception as e: return JSONResponse({ "success": False, "error": str(e), "traceback": traceback.format_exc() }) if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rakeshsun/mcpdocker'

If you have feedback or need assistance with the MCP directory API, please join our Discord server