mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-09-02 12:33:20 +02:00
- 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>
286 lines
No EOL
10 KiB
HTML
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> |