šŸš€ FastMCP in the Browser with PyScript

This demo shows FastMCP running directly in your browser, with tools that can access web page content and make AJAX calls.

Web Page Content

Sample Web Content

This is some sample content that our MCP tools can access.

Current time:

User data: Loading...

MCP Server Controls

Implementation Details

This demo showcases:

packages = ["pydantic", "typing-extensions"] 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.")