fastmcp/examples/pyscript_browser_demo.html
marvin-context-protocol[bot] 474fe542ca feat: Add PyScript browser integration demo for FastMCP
- Simple browser demo with working PyScript integration
- Comprehensive demo with advanced FastMCP tools
- Browser-compatible server example
- Complete integration guide and documentation
- Test suite for browser demo functionality

Enables FastMCP to run in browser via PyScript, allowing:
- Web page content access for LLM context
- Authenticated API calls using user cookies
- Dynamic page content updates
- Form data extraction and analysis

Addresses issue #1508 - Run In Browser

Co-authored-by: Jeremiah Lowin <jlowin@users.noreply.github.com>
2025-08-15 15:47:53 +00:00

286 lines
No EOL
10 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FastMCP PyScript Browser Demo</title>
<link rel="stylesheet" href="https://pyscript.net/releases/2024.12.1/core.css">
<script type="module" src="https://pyscript.net/releases/2024.12.1/core.js"></script>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }
.container { max-width: 1200px; margin: 0 auto; }
.header { background: #2196F3; color: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; }
.demo-section { background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.output { background: #f8f9fa; border: 1px solid #e9ecef; padding: 15px; border-radius: 4px; font-family: monospace; white-space: pre-wrap; }
.button { background: #4CAF50; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; margin: 5px; }
.button:hover { background: #45a049; }
.web-content { background: #e3f2fd; padding: 15px; border-radius: 4px; margin: 10px 0; }
.error { color: #d32f2f; background: #ffebee; padding: 10px; border-radius: 4px; margin: 10px 0; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚀 FastMCP in the Browser with PyScript</h1>
<p>This demo shows FastMCP running directly in your browser, with tools that can access web page content and make AJAX calls.</p>
</div>
<div class="demo-section">
<h2>Web Page Content</h2>
<div class="web-content" id="sample-content">
<h3>Sample Web Content</h3>
<p>This is some sample content that our MCP tools can access.</p>
<p>Current time: <span id="current-time"></span></p>
<p>User data: <span id="user-data">Loading...</span></p>
<button onclick="updateContent()">Update Content</button>
</div>
</div>
<div class="demo-section">
<h2>MCP Server Controls</h2>
<button class="button" py-click="run_mcp_demo">Start MCP Server Demo</button>
<button class="button" py-click="test_dom_access">Test DOM Access Tool</button>
<button class="button" py-click="test_ajax_tool">Test AJAX Tool</button>
<button class="button" py-click="test_cookie_tool">Test Cookie Tool</button>
<div id="mcp-output" class="output"></div>
</div>
<div class="demo-section">
<h2>Implementation Details</h2>
<p>This demo showcases:</p>
<ul>
<li><strong>DOM Access</strong>: Tools that can read and interact with page elements</li>
<li><strong>AJAX Calls</strong>: Tools that can make HTTP requests with user cookies</li>
<li><strong>Client-Side MCP</strong>: Full MCP server running in the browser</li>
<li><strong>Real-time Updates</strong>: Dynamic content that tools can observe</li>
</ul>
</div>
</div>
<py-config>
packages = ["pydantic", "typing-extensions"]
</py-config>
<py-script>
import asyncio
from js import document, fetch, console, JSON
from pyodide.ffi import create_proxy, to_js
import json
import time
from typing import Any, Dict
# Mock FastMCP classes for browser environment
class FastMCP:
def __init__(self, name: str):
self.name = name
self.tools = {}
self.resources = {}
def tool(self, func):
"""Register a tool function"""
self.tools[func.__name__] = func
return func
def resource(self, uri_template):
"""Register a resource function"""
def decorator(func):
self.resources[uri_template] = func
return func
return decorator
async def call_tool(self, name: str, args: Dict[str, Any]) -> Any:
"""Call a registered tool"""
if name in self.tools:
return await self.tools[name](**args)
else:
raise ValueError(f"Tool {name} not found")
# Create our FastMCP server instance
mcp = FastMCP("Browser MCP Server")
@mcp.tool
async def get_page_content(selector: str = "body") -> str:
"""Get content from the current web page using CSS selector"""
try:
element = document.querySelector(selector)
if element:
return element.textContent or element.innerHTML
else:
return f"No element found with selector: {selector}"
except Exception as e:
return f"Error accessing DOM: {str(e)}"
@mcp.tool
async def get_page_title() -> str:
"""Get the current page title"""
return document.title
@mcp.tool
async def update_page_element(selector: str, content: str) -> str:
"""Update content of a page element"""
try:
element = document.querySelector(selector)
if element:
element.innerHTML = content
return f"Updated element {selector} with new content"
else:
return f"Element not found: {selector}"
except Exception as e:
return f"Error updating element: {str(e)}"
@mcp.tool
async def make_ajax_request(url: str, method: str = "GET") -> str:
"""Make an AJAX request (cookies will be included automatically)"""
try:
# Use JavaScript fetch API from Python
response = await fetch(url, to_js({
"method": method,
"credentials": "include", # Include cookies
"headers": {
"Content-Type": "application/json"
}
}))
if response.ok:
text_data = await response.text()
return f"Request to {url} successful: {text_data[:200]}..."
else:
return f"Request failed with status: {response.status}"
except Exception as e:
return f"Error making request: {str(e)}"
@mcp.tool
async def get_cookies() -> str:
"""Get current page cookies"""
try:
cookies = document.cookie
if cookies:
return f"Current cookies: {cookies}"
else:
return "No cookies found"
except Exception as e:
return f"Error accessing cookies: {str(e)}"
@mcp.tool
async def get_current_url() -> str:
"""Get the current page URL"""
return document.location.href
@mcp.resource("page://content")
async def page_content_resource():
"""Resource that provides current page content"""
return document.body.textContent
def log_output(message: str):
"""Helper to log output to the page"""
output_element = document.getElementById("mcp-output")
current_content = output_element.textContent
output_element.textContent = current_content + "\n" + str(message)
async def run_mcp_demo(*args):
"""Main demo function"""
try:
log_output("🚀 Starting FastMCP Browser Demo...")
log_output(f"Server name: {mcp.name}")
log_output(f"Registered tools: {list(mcp.tools.keys())}")
log_output(f"Registered resources: {list(mcp.resources.keys())}")
# Test basic tool calls
title = await mcp.call_tool("get_page_title", {})
log_output(f"Page title: {title}")
url = await mcp.call_tool("get_current_url", {})
log_output(f"Current URL: {url}")
log_output("✅ MCP Server demo completed successfully!")
except Exception as e:
log_output(f"❌ Error: {str(e)}")
async def test_dom_access(*args):
"""Test DOM access tools"""
try:
log_output("\n🔍 Testing DOM Access...")
# Get sample content
content = await mcp.call_tool("get_page_content", {"selector": "#sample-content"})
log_output(f"Sample content: {content[:100]}...")
# Update an element
current_time = time.strftime("%H:%M:%S")
result = await mcp.call_tool("update_page_element", {
"selector": "#current-time",
"content": current_time
})
log_output(result)
except Exception as e:
log_output(f"❌ DOM Access Error: {str(e)}")
async def test_ajax_tool(*args):
"""Test AJAX functionality"""
try:
log_output("\n🌐 Testing AJAX Tool...")
# Make a simple request to JSONPlaceholder API
result = await mcp.call_tool("make_ajax_request", {
"url": "https://jsonplaceholder.typicode.com/posts/1"
})
log_output(result)
except Exception as e:
log_output(f"❌ AJAX Error: {str(e)}")
async def test_cookie_tool(*args):
"""Test cookie access"""
try:
log_output("\n🍪 Testing Cookie Access...")
# Set a test cookie first
document.cookie = "fastmcp_demo=test_value; path=/"
result = await mcp.call_tool("get_cookies", {})
log_output(result)
except Exception as e:
log_output(f"❌ Cookie Error: {str(e)}")
# Register event handlers
run_mcp_demo_proxy = create_proxy(run_mcp_demo)
test_dom_access_proxy = create_proxy(test_dom_access)
test_ajax_tool_proxy = create_proxy(test_ajax_tool)
test_cookie_tool_proxy = create_proxy(test_cookie_tool)
# Make functions available globally
globals()['run_mcp_demo'] = run_mcp_demo_proxy
globals()['test_dom_access'] = test_dom_access_proxy
globals()['test_ajax_tool'] = test_ajax_tool_proxy
globals()['test_cookie_tool'] = test_cookie_tool_proxy
# Initialize demo
log_output("FastMCP PyScript Demo Loaded! Click buttons above to test.")
</py-script>
<script>
// JavaScript helper functions
function updateContent() {
const userData = document.getElementById('user-data');
userData.textContent = `User Agent: ${navigator.userAgent.substring(0, 50)}...`;
const currentTime = document.getElementById('current-time');
currentTime.textContent = new Date().toLocaleTimeString();
}
// Initialize content on load
window.addEventListener('load', updateContent);
// Update time every second
setInterval(() => {
const currentTime = document.getElementById('current-time');
if (currentTime) {
currentTime.textContent = new Date().toLocaleTimeString();
}
}, 1000);
</script>
</body>
</html>