Skip to content

Commit 5006d25

Browse files
authored
Merge pull request #38 from Kode-Rex/feat/bearer-auth-v2.3.1
feat: Add optional bearer token authentication (v2.3.1)
2 parents 91124d7 + b645ee6 commit 5006d25

File tree

13 files changed

+507
-150
lines changed

13 files changed

+507
-150
lines changed

README.md

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33
**Web search and content extraction for AI models via Model Context Protocol (MCP)**
44

55
[![Version](https://img.shields.io/badge/version-2.2.0-blue.svg)](https://github.com/Kode-Rex/webcat)
6-
[![Docker](https://img.shields.io/badge/docker-ready-brightgreen.svg)](https://hub.docker.com/r/tmfrisinger/webcat)
76
[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
87

98
## Quick Start
109

1110
```bash
12-
# Run WebCat with Docker (30 seconds to working demo)
13-
docker run -p 8000:8000 tmfrisinger/webcat:latest
11+
cd docker
12+
python -m pip install -e ".[dev]"
13+
14+
# Start demo server with UI
15+
python simple_demo.py
1416

1517
# Open demo client
1618
open http://localhost:8000/demo
@@ -30,40 +32,22 @@ Built with **FastAPI** and **FastMCP** for seamless AI integration.
3032

3133
## Features
3234

33-
-**No Authentication Required** - Simple setup
35+
-**Optional Authentication** - Bearer token auth when needed, or run without
3436
-**Automatic Fallback** - Serper API → DuckDuckGo if needed
3537
-**Smart Content Extraction** - Trafilatura removes navigation/ads/chrome
3638
-**MCP Compliant** - Works with Claude Desktop, LiteLLM, etc.
3739
-**Rate Limited** - Configurable protection
38-
-**Docker Ready** - One command deployment
3940
-**Parallel Processing** - Fast concurrent scraping
4041

4142
## Installation & Usage
4243

43-
### Docker (Recommended)
44-
45-
```bash
46-
# With Serper API (best results)
47-
docker run -p 8000:8000 -e SERPER_API_KEY=your_key tmfrisinger/webcat:2.2.0
48-
49-
# Free tier (DuckDuckGo only)
50-
docker run -p 8000:8000 tmfrisinger/webcat:2.2.0
51-
52-
# Custom configuration
53-
docker run -p 9000:9000 \
54-
-e PORT=9000 \
55-
-e SERPER_API_KEY=your_key \
56-
-e RATE_LIMIT_WINDOW=60 \
57-
-e RATE_LIMIT_MAX_REQUESTS=10 \
58-
tmfrisinger/webcat:2.2.0
59-
```
60-
61-
### Local Development
62-
6344
```bash
6445
cd docker
6546
python -m pip install -e ".[dev]"
6647

48+
# Configure environment (optional)
49+
echo "SERPER_API_KEY=your_key" > .env
50+
6751
# Start MCP server
6852
python mcp_server.py
6953

@@ -88,6 +72,7 @@ python simple_demo.py
8872
| Variable | Default | Description |
8973
|----------|---------|-------------|
9074
| `SERPER_API_KEY` | *(none)* | Serper API key for premium search (optional) |
75+
| `WEBCAT_API_KEY` | *(none)* | Bearer token for authentication (optional, if set all requests must include `Authorization: Bearer <token>`) |
9176
| `PORT` | `8000` | Server port |
9277
| `LOG_LEVEL` | `INFO` | Logging level (DEBUG, INFO, WARNING, ERROR) |
9378
| `LOG_DIR` | `/tmp` | Log file directory |
@@ -99,7 +84,17 @@ python simple_demo.py
9984
1. Visit [serper.dev](https://serper.dev)
10085
2. Sign up for free tier (2,500 searches/month)
10186
3. Copy your API key
102-
4. Pass to Docker: `-e SERPER_API_KEY=your_key`
87+
4. Add to `.env` file: `SERPER_API_KEY=your_key`
88+
89+
### Enable Authentication (Optional)
90+
91+
To require bearer token authentication for all MCP tool calls:
92+
93+
1. Generate a secure random token: `openssl rand -hex 32`
94+
2. Add to `.env` file: `WEBCAT_API_KEY=your_token`
95+
3. Include in all requests: `Authorization: Bearer your_token`
96+
97+
**Note:** If `WEBCAT_API_KEY` is not set, no authentication is required.
10398

10499
## MCP Tools
105100

@@ -216,7 +211,6 @@ MIT License - see [LICENSE](LICENSE) file for details.
216211
## Links
217212

218213
- **GitHub:** [github.com/Kode-Rex/webcat](https://github.com/Kode-Rex/webcat)
219-
- **Docker Hub:** [hub.docker.com/r/tmfrisinger/webcat](https://hub.docker.com/r/tmfrisinger/webcat)
220214
- **MCP Spec:** [modelcontextprotocol.io](https://modelcontextprotocol.io)
221215
- **Serper API:** [serper.dev](https://serper.dev)
222216

docker/.env.example

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Serper API key for premium search (optional)
2+
# If not set, DuckDuckGo fallback will be used
3+
SERPER_API_KEY=
4+
5+
# WebCat API key for bearer token authentication (optional)
6+
# If set, all requests must include: Authorization: Bearer <token>
7+
# If not set, no authentication is required
8+
WEBCAT_API_KEY=
9+
10+
# Server configuration
11+
PORT=8000
12+
LOG_LEVEL=INFO
13+
LOG_DIR=/tmp
14+
15+
# Rate limiting
16+
RATE_LIMIT_WINDOW=60
17+
RATE_LIMIT_MAX_REQUESTS=10

docker/constants.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"""Constants for WebCat application."""
77

88
# Application version
9-
VERSION = "2.3.0"
9+
VERSION = "2.3.1"
1010

1111
# Service information
1212
SERVICE_NAME = "WebCat MCP Server"
@@ -19,8 +19,6 @@
1919
"Content extraction and scraping",
2020
"Markdown conversion",
2121
"FastMCP protocol support",
22-
"SSE streaming",
23-
"Demo UI client",
2422
]
2523

2624
# Content limits

docker/endpoints/health_endpoints.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from fastapi import FastAPI
1111
from fastapi.responses import JSONResponse
1212

13-
from endpoints.demo_client import serve_demo_client
1413
from models.health_responses import (
1514
get_detailed_status,
1615
get_health_status,
@@ -34,11 +33,6 @@ async def health_check():
3433
logger.error(f"Health check failed: {str(e)}")
3534
return JSONResponse(status_code=500, content=get_unhealthy_status(str(e)))
3635

37-
@app.get("/demo")
38-
async def sse_client():
39-
"""Serve the WebCat SSE demo client."""
40-
return serve_demo_client()
41-
4236
@app.get("/status")
4337
async def server_status():
4438
"""Detailed server status endpoint."""

docker/models/responses/health_responses.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,9 @@ def get_server_configuration() -> dict:
4444
def get_server_endpoints() -> dict:
4545
"""Get server endpoints dictionary."""
4646
return {
47-
"main_mcp": "/mcp",
48-
"sse_demo": "/sse",
47+
"mcp": "/mcp",
4948
"health": "/health",
5049
"status": "/status",
51-
"demo_client": "/demo",
5250
}
5351

5452

@@ -81,10 +79,9 @@ def get_root_info() -> dict:
8179
"version": VERSION,
8280
"description": "Web search and content extraction with MCP protocol support",
8381
"endpoints": {
84-
"demo_client": "/demo",
82+
"mcp": "/mcp",
8583
"health": "/health",
8684
"status": "/status",
87-
"mcp_sse": "/mcp",
8885
},
89-
"documentation": "Access /demo for the demo interface",
86+
"documentation": "MCP server - connect via SSE at /mcp/sse endpoint",
9087
}

docker/simple_demo.py

Lines changed: 11 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,19 @@
44
# This source code is licensed under the MIT license found in the
55
# LICENSE file in the root directory of this source tree.
66

7-
"""Simplified demo server that combines health and SSE endpoints in one FastAPI app."""
7+
"""Simplified demo server with FastMCP integration."""
88

9-
import asyncio
109
import logging
1110
import os
1211
import tempfile
13-
import time
1412

1513
import uvicorn
1614
from dotenv import load_dotenv
17-
from fastapi import FastAPI, Query
15+
from fastapi import FastAPI
1816
from fastapi.middleware.cors import CORSMiddleware
19-
from fastapi.responses import StreamingResponse
2017
from fastmcp import FastMCP
2118

2219
from api_tools import create_webcat_functions, setup_webcat_tools
23-
from demo_utils import (
24-
format_sse_message,
25-
get_server_info,
26-
handle_health_operation,
27-
handle_search_operation,
28-
)
2920
from health import setup_health_endpoints
3021

3122
# Load environment variables
@@ -44,73 +35,14 @@
4435
logger.setLevel(getattr(logging, LOG_LEVEL))
4536

4637

47-
async def _generate_webcat_stream(
48-
webcat_functions, operation: str, query: str, max_results: int
49-
):
50-
"""Generate SSE stream for WebCat operations.
51-
52-
Args:
53-
webcat_functions: Dictionary of WebCat functions
54-
operation: Operation to perform
55-
query: Search query
56-
max_results: Maximum results
57-
58-
Yields:
59-
SSE formatted messages
60-
"""
61-
try:
62-
# Send connection message
63-
yield format_sse_message(
64-
"connection",
65-
status="connected",
66-
message="WebCat stream started",
67-
operation=operation,
68-
)
69-
70-
if operation == "search" and query:
71-
search_func = webcat_functions.get("search")
72-
if search_func:
73-
async for msg in handle_search_operation(
74-
search_func, query, max_results
75-
):
76-
yield msg
77-
else:
78-
yield format_sse_message(
79-
"error", message="Search function not available"
80-
)
81-
82-
elif operation == "health":
83-
health_func = webcat_functions.get("health_check")
84-
async for msg in handle_health_operation(health_func):
85-
yield msg
86-
87-
else:
88-
# Just connection - send server info
89-
yield format_sse_message("data", data=get_server_info())
90-
yield format_sse_message("complete", message="Connection established")
91-
92-
# Keep alive with heartbeat
93-
heartbeat_count = 0
94-
while True:
95-
await asyncio.sleep(30)
96-
heartbeat_count += 1
97-
yield format_sse_message(
98-
"heartbeat", timestamp=time.time(), count=heartbeat_count
99-
)
100-
101-
except Exception as e:
102-
logger.error(f"Error in SSE stream: {str(e)}")
103-
yield format_sse_message("error", message=str(e))
104-
105-
10638
def create_demo_app():
10739
"""Create a single FastAPI app with all endpoints."""
10840

10941
# Create FastAPI app with CORS middleware
11042
app = FastAPI(
111-
title="WebCat MCP Demo Server",
112-
description="WebCat server with FastMCP integration and SSE streaming demo",
113-
version="2.2.0",
43+
title="WebCat MCP Server",
44+
description="WebCat server with FastMCP integration",
45+
version="2.3.1",
11446
)
11547

11648
app.add_middleware(
@@ -131,31 +63,10 @@ def create_demo_app():
13163
webcat_functions = create_webcat_functions()
13264
setup_webcat_tools(mcp_server, webcat_functions)
13365

134-
# Add custom SSE endpoint for demo
135-
@app.get("/sse")
136-
async def webcat_stream(
137-
operation: str = Query(
138-
"connect", description="Operation to perform: connect, search, health"
139-
),
140-
query: str = Query("", description="Search query for search operations"),
141-
max_results: int = Query(5, description="Maximum number of search results"),
142-
):
143-
"""Stream WebCat functionality via SSE"""
144-
return StreamingResponse(
145-
_generate_webcat_stream(webcat_functions, operation, query, max_results),
146-
media_type="text/event-stream",
147-
headers={
148-
"Cache-Control": "no-cache",
149-
"Connection": "keep-alive",
150-
"Access-Control-Allow-Origin": "*",
151-
"Access-Control-Allow-Headers": "*",
152-
},
153-
)
154-
155-
# Mount FastMCP server as a sub-application (like Clima project)
66+
# Mount FastMCP server
15667
app.mount("/mcp", mcp_server.sse_app())
15768

158-
logger.info("FastAPI app configured with SSE and FastMCP integration")
69+
logger.info("FastAPI app configured with FastMCP integration")
15970
return app
16071

16172

@@ -166,20 +77,18 @@ def run_simple_demo(host: str = "0.0.0.0", port: int = 8000):
16677
app = create_demo_app()
16778

16879
# Log endpoints
169-
logger.info(f"WebCat Demo Server: http://{host}:{port}")
170-
logger.info(f"SSE Demo Endpoint: http://{host}:{port}/sse")
80+
logger.info(f"WebCat MCP Server: http://{host}:{port}")
17181
logger.info(f"FastMCP Endpoint: http://{host}:{port}/mcp")
17282
logger.info(f"Health Check: http://{host}:{port}/health")
17383
logger.info(f"Demo Client: http://{host}:{port}/demo")
17484
logger.info(f"Server Status: http://{host}:{port}/status")
17585

176-
print("\n🐱 WebCat MCP Demo Server Starting...")
86+
print("\n🐱 WebCat MCP Server Starting...")
17787
print(f"📡 Server: http://{host}:{port}")
178-
print(f"🔗 SSE Demo: http://{host}:{port}/sse")
179-
print(f"🛠️ FastMCP: http://{host}:{port}/mcp")
88+
print(f"🛠️ MCP Endpoint: http://{host}:{port}/mcp")
18089
print(f"💗 Health: http://{host}:{port}/health")
18190
print(f"🎨 Demo UI: http://{host}:{port}/demo")
182-
print(f"📊 Server Status: http://{host}:{port}/status")
91+
print(f"📊 Status: http://{host}:{port}/status")
18392
print("\n✨ Ready for connections!")
18493

18594
# Run the server

0 commit comments

Comments
 (0)