diff --git a/Makefile b/Makefile index 9a8c008..4ecb24f 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ # WebCat MCP Server - Development Makefile -.PHONY: help install install-dev format lint test test-coverage clean build docker-build docker-run demo health check-all pre-commit setup-dev +.PHONY: help install install-dev format lint test test-coverage clean build docker-build docker-run health check-all pre-commit setup-dev # Default target help: ## Show this help message @@ -108,11 +108,7 @@ docker-build: ## Build Docker image docker-run: ## Run Docker container @echo "🐳 Running Docker container..." - docker run -p 8000:8000 -p 8001:8001 -e WEBCAT_MODE=demo webcat:latest - -docker-run-prod: ## Run Docker container in production mode - @echo "🐳 Running Docker container in production mode..." - docker run -p 8000:8000 -e WEBCAT_MODE=mcp webcat:latest + docker run -p 8000:8000 webcat:latest # Development servers dev: ## Start MCP server with auto-reload (development mode) @@ -123,23 +119,6 @@ dev: ## Start MCP server with auto-reload (development mode) @echo "" cd docker && PYTHONPATH=. watchmedo auto-restart --recursive --pattern="*.py" --directory=. -- python mcp_server.py -dev-demo: ## Start demo server with auto-reload (development mode) - @echo "šŸš€ Starting demo server with auto-reload..." - @echo "šŸŽØ Demo client: http://localhost:8000/client" - @echo "šŸ’— Health check: http://localhost:8000/health" - @echo "šŸ“Š Status: http://localhost:8000/status" - @echo "šŸ”„ Auto-reload enabled - edit files to see changes" - @echo "" - cd docker && PYTHONPATH=. watchmedo auto-restart --recursive --pattern="*.py" --directory=. -- python simple_demo.py - -demo: ## Start demo server locally (production mode) - @echo "šŸŽØ Starting demo server..." - cd docker && python cli.py --mode demo --port 8000 - -demo-bg: ## Start demo server in background - @echo "šŸŽØ Starting demo server in background..." - cd docker && nohup python cli.py --mode demo --port 8000 > demo.log 2>&1 & - @echo "Demo server started in background. Check demo.log for logs." mcp: ## Start MCP server locally (production mode) @echo "šŸ› ļø Starting MCP server..." @@ -148,7 +127,6 @@ mcp: ## Start MCP server locally (production mode) stop-bg: ## Stop background servers @echo "šŸ›‘ Stopping background servers..." pkill -f "python cli.py" || true - pkill -f "python simple_demo.py" || true @echo "Background servers stopped." # Health and monitoring @@ -162,7 +140,7 @@ status: ## Check server status logs: ## Show recent logs @echo "šŸ“ Recent logs..." - tail -f docker/demo.log || tail -f /tmp/webcat*.log + tail -f /tmp/webcat*.log # Cleanup clean: ## Clean up temporary files and caches diff --git a/README.md b/README.md index 19ed138..2c574c4 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **Web search and content extraction for AI models via Model Context Protocol (MCP)** -[![Version](https://img.shields.io/badge/version-2.3.1-blue.svg)](https://github.com/Kode-Rex/webcat) +[![Version](https://img.shields.io/badge/version-2.3.2-blue.svg)](https://github.com/Kode-Rex/webcat) [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) [![Docker](https://img.shields.io/badge/docker-multi--platform-blue.svg)](https://hub.docker.com/r/tmfrisinger/webcat) @@ -29,23 +29,19 @@ docker run -p 8000:8000 -e WEBCAT_API_KEY=your_token tmfrisinger/webcat:latest cd docker python -m pip install -e ".[dev]" -# Start demo server with UI -python simple_demo.py +# Start MCP server with auto-reload +make dev -# Or use make commands -make dev # Start with auto-reload -make dev-demo # Start demo with auto-reload +# Or run directly +python mcp_server.py ``` -![WebCat Demo Client](assets/webcat-demo-client.png) - ## What is WebCat? WebCat is an **MCP (Model Context Protocol) server** that provides AI models with: - šŸ” **Web Search** - Serper API (premium) or DuckDuckGo (free fallback) - šŸ“„ **Content Extraction** - Clean markdown conversion with Readability + html2text - 🌐 **SSE Streaming** - Real-time results via Server-Sent Events -- šŸŽØ **Demo UI** - Interactive testing interface - 🐳 **Multi-Platform Docker** - Works on Intel, ARM, and Apple Silicon Built with **FastAPI**, **FastMCP**, and **Readability** for seamless AI integration. @@ -89,18 +85,15 @@ echo "SERPER_API_KEY=your_key" > .env # Development mode with auto-reload make dev # Start MCP server with auto-reload -make dev-demo # Start demo server with auto-reload # Production mode make mcp # Start MCP server -make demo # Start demo server ``` ## Available Endpoints | Endpoint | Description | |----------|-------------| -| `http://localhost:8000/demo` | šŸŽØ Interactive demo UI | | `http://localhost:8000/health` | šŸ’— Health check | | `http://localhost:8000/status` | šŸ“Š Server status | | `http://localhost:8000/mcp` | šŸ› ļø MCP protocol endpoint | @@ -226,7 +219,7 @@ Hooks run automatically on `git commit` to ensure code quality. Install with `ma ``` docker/ ā”œā”€ā”€ mcp_server.py # Main MCP server (FastMCP) -ā”œā”€ā”€ simple_demo.py # Demo server with interactive UI +ā”œā”€ā”€ cli.py # CLI interface for server modes ā”œā”€ā”€ health.py # Health check endpoint ā”œā”€ā”€ api_tools.py # API tooling utilities ā”œā”€ā”€ clients/ # External API clients @@ -270,7 +263,7 @@ cd docker # Manual multi-platform build and push docker buildx build --platform linux/amd64,linux/arm64 \ - -t tmfrisinger/webcat:2.3.1 \ + -t tmfrisinger/webcat:2.3.2 \ -t tmfrisinger/webcat:latest \ -f Dockerfile --push . @@ -281,8 +274,8 @@ docker buildx imagetools inspect tmfrisinger/webcat:latest **Automated Releases:** Push a version tag to trigger automated multi-platform builds via GitHub Actions: ```bash -git tag v2.3.1 -git push origin v2.3.1 +git tag v2.3.2 +git push origin v2.3.2 ``` ## Limitations @@ -317,4 +310,4 @@ MIT License - see [LICENSE](LICENSE) file for details. --- -**Version 2.3.1** | Built with FastMCP, FastAPI, Readability, and html2text +**Version 2.3.2** | Built with FastMCP, FastAPI, Readability, and html2text diff --git a/docker/demo_utils.py b/docker/demo_utils.py deleted file mode 100644 index c79cab4..0000000 --- a/docker/demo_utils.py +++ /dev/null @@ -1,90 +0,0 @@ -# Copyright (c) 2024 Travis Frisinger -# -# This source code is licensed under the MIT license found in the -# LICENSE file in the root directory of this source tree. - -"""Shared utilities for demo servers.""" - -import json -import time - - -def format_sse_message(message_type: str, **kwargs) -> str: - """Format SSE message. - - Args: - message_type: Message type - **kwargs: Additional message fields - - Returns: - Formatted SSE message string - """ - data = {"type": message_type, **kwargs} - return f"data: {json.dumps(data)}\n\n" - - -async def handle_search_operation(search_func, query: str, max_results: int): - """Handle search operation and yield results. - - Args: - search_func: Search function to call - query: Search query - max_results: Maximum results to return - - Yields: - SSE formatted messages - """ - yield format_sse_message("status", message=f"Searching for: {query}") - - result = await search_func(query) - - # Limit results if needed - if result.get("results") and len(result["results"]) > max_results: - result["results"] = result["results"][:max_results] - result["note"] = f"Results limited to {max_results} items" - - yield format_sse_message("data", data=result) - num_results = len(result.get("results", [])) - yield format_sse_message( - "complete", message=f"Search completed. Found {num_results} results." - ) - - -async def handle_health_operation(health_func): - """Handle health check operation. - - Args: - health_func: Health check function to call (or None) - - Yields: - SSE formatted messages - """ - yield format_sse_message("status", message="Checking server health...") - - if health_func: - result = await health_func() - yield format_sse_message("data", data=result) - yield format_sse_message("complete", message="Health check completed") - else: - basic_health = { - "status": "healthy", - "service": "webcat-demo", - "timestamp": time.time(), - } - yield format_sse_message("data", data=basic_health) - yield format_sse_message("complete", message="Basic health check completed") - - -def get_server_info() -> dict: - """Get server information dictionary. - - Returns: - Server info dictionary - """ - return { - "service": "WebCat MCP Demo Server", - "version": "2.2.0", - "status": "connected", - "operations": ["search", "health"], - "timestamp": time.time(), - } diff --git a/docker/endpoints/demo_client.py b/docker/endpoints/demo_client.py deleted file mode 100644 index a6d0365..0000000 --- a/docker/endpoints/demo_client.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright (c) 2024 Travis Frisinger -# -# This source code is licensed under the MIT license found in the -# LICENSE file in the root directory of this source tree. - -"""Demo client endpoint handlers.""" - -import logging -from pathlib import Path - -from fastapi.responses import HTMLResponse, JSONResponse - -logger = logging.getLogger(__name__) - - -def client_not_found_response(client_path: Path) -> JSONResponse: - """Create response for missing client file.""" - return JSONResponse( - status_code=404, - content={ - "error": "WebCat client file not found", - "expected_path": str(client_path), - "exists": False, - }, - ) - - -def client_error_response(error: str) -> JSONResponse: - """Create response for client loading error.""" - return JSONResponse( - status_code=500, - content={"error": "Failed to serve WebCat client", "details": error}, - ) - - -def load_client_file(client_path: Path) -> HTMLResponse: - """Load and return client HTML file.""" - html_content = client_path.read_text(encoding="utf-8") - logger.info("Successfully loaded WebCat demo client") - return HTMLResponse(content=html_content) - - -def serve_demo_client(): - """Serve the WebCat SSE demo client.""" - try: - current_dir = Path(__file__).parent - client_path = current_dir.parent.parent / "examples" / "webcat_client.html" - logger.info(f"Looking for client file at: {client_path}") - - if client_path.exists(): - return load_client_file(client_path) - - logger.error(f"WebCat client file not found at: {client_path}") - return client_not_found_response(client_path) - except Exception as e: - logger.error(f"Failed to serve WebCat client: {str(e)}") - return client_error_response(str(e)) diff --git a/docker/simple_demo.py b/docker/simple_demo.py deleted file mode 100755 index cae6550..0000000 --- a/docker/simple_demo.py +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2024 Travis Frisinger -# -# This source code is licensed under the MIT license found in the -# LICENSE file in the root directory of this source tree. - -"""Simplified demo server with FastMCP integration.""" - -import logging -import os -import tempfile - -import uvicorn -from dotenv import load_dotenv -from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware -from fastmcp import FastMCP - -from api_tools import create_webcat_functions, setup_webcat_tools -from health import setup_health_endpoints - -# Load environment variables -load_dotenv() - -# Set up logging -LOG_LEVEL = os.environ.get("LOG_LEVEL", "INFO") -LOG_DIR = os.environ.get("LOG_DIR", tempfile.gettempdir()) -LOG_FILE = os.path.join(LOG_DIR, "webcat_simple_demo.log") - -# Create log directory if it doesn't exist -os.makedirs(LOG_DIR, exist_ok=True) - -# Configure logger -logger = logging.getLogger(__name__) -logger.setLevel(getattr(logging, LOG_LEVEL)) - - -def create_demo_app(): - """Create a single FastAPI app with all endpoints.""" - - # Create FastAPI app with CORS middleware - app = FastAPI( - title="WebCat MCP Server", - description="WebCat server with FastMCP integration", - version="2.3.1", - ) - - app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, - allow_methods=["GET", "POST", "OPTIONS"], - allow_headers=["*"], - ) - - # Setup health endpoints (this adds /health, /demo, /status, /) - setup_health_endpoints(app) - - # Initialize FastMCP server - mcp_server = FastMCP("WebCat Demo Server") - - # Setup WebCat tools - webcat_functions = create_webcat_functions() - setup_webcat_tools(mcp_server, webcat_functions) - - # Mount FastMCP server - app.mount("/mcp", mcp_server.sse_app()) - - logger.info("FastAPI app configured with FastMCP integration") - return app - - -def run_simple_demo(host: str = "0.0.0.0", port: int = 8000): - """Run the simplified WebCat demo server.""" - - # Initialize the app - app = create_demo_app() - - # Log endpoints - logger.info(f"WebCat MCP Server: http://{host}:{port}") - logger.info(f"FastMCP Endpoint: http://{host}:{port}/mcp") - logger.info(f"Health Check: http://{host}:{port}/health") - logger.info(f"Demo Client: http://{host}:{port}/demo") - logger.info(f"Server Status: http://{host}:{port}/status") - - print("\n🐱 WebCat MCP Server Starting...") - print(f"šŸ“” Server: http://{host}:{port}") - print(f"šŸ› ļø MCP Endpoint: http://{host}:{port}/mcp") - print(f"šŸ’— Health: http://{host}:{port}/health") - print(f"šŸŽØ Demo UI: http://{host}:{port}/demo") - print(f"šŸ“Š Status: http://{host}:{port}/status") - print("\n✨ Ready for connections!") - - # Run the server - uvicorn.run(app, host=host, port=port, log_level="info") - - -if __name__ == "__main__": - port = int(os.environ.get("PORT", 8000)) - run_simple_demo(port=port)