User agent, readme

This commit is contained in:
2026-07-28 13:15:12 -07:00
parent 1109a33edb
commit b107aff150
4 changed files with 21 additions and 19 deletions
+7 -1
View File
@@ -5,6 +5,11 @@ A Model Context Protocol (MCP) server designed to provide LLMs with efficient, t
## ⚠️ AI SLOP DISCLAIMER
This entire project was vibe-coded by an AI. It is 100% slop code. Use it at your own risk.
## 🚨 SECURITY WARNING
**This server is designed to be run on `localhost` ONLY.**
It contains tools (such as `read_image` and `list_directory`) that allow the LLM to read arbitrary files from your filesystem. If you expose this server to the network or a public IP, any user or compromised AI could potentially read sensitive system files (e.g., SSH keys, `/etc/passwd`).
**NEVER run this server on a public-facing IP without implementing strict path validation.**
## Tools Overview
### 📁 `list_directory`
@@ -64,9 +69,10 @@ This server requires Python 3.10+ and the following packages:
* `starlette`: Lightweight ASGI framework.
* `Pillow`: Image processing and thumbnail generation.
* `requests`: For communicating with the Stable Diffusion API.
* `httpx`: For asynchronous API requests (e.g., Wikipedia).
```bash
pip install uvicorn starlette Pillow requests
pip install uvicorn starlette Pillow requests httpx
```
### Setup
+1 -1
View File
@@ -12,7 +12,7 @@ PORT = 8000
request_host = ContextVar("request_host", default=f"{HOST}:{PORT}")
LOG_LEVEL = "WARNING" # Options: "DEBUG", "INFO", "WARNING", "ERROR"
LOG_FILE = str(ROOT_DIR / "debug.log")
USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0" # Stealth User-Agent to bypass Wikipedia's bot detection
USER_AGENT = "MooseCP/1.0 Local MCP Server (https://long-cat.net/)"
# --- Stable Diffusion Config ---
SD_URL = "http://127.0.0.1:7860"
+1 -1
View File
@@ -148,7 +148,7 @@ TOOL_REGISTRY = [
"model_name": {"type": "string", "description": "The name of the model to use. Required."},
"prompt": {"type": "string", "description": "The prompt for the image. Required."},
"negative_prompt": {"type": "string", "description": "The negative prompt to exclude unwanted elements."},
"resolution_preset": {"type": "string", "description": "A named resolution preset (e.g., 'square', 'portrait'). Available options depend on the model."},
"resolution_preset": {"type": "string", "description": "A named resolution preset from the model info"},
"cfg_scale": {"type": "number", "description": "CFG scale for prompt adherence. Usually should be left omitted to select the default."},
},
"required": ["model_name", "prompt", "resolution_preset"],
+12 -16
View File
@@ -1,5 +1,4 @@
import urllib.request
import urllib.parse
import httpx
import json
import re
import asyncio
@@ -13,22 +12,19 @@ def strip_html(text: str) -> str:
"""Removes HTML tags from a string using regex to provide clean text to the LLM."""
return re.sub(r'<[^>]*>', '', text)
def _make_request(params: Dict[str, Any]) -> Dict[str, Any]:
async def _make_request(params: Dict[str, Any]) -> Dict[str, Any]:
"""
Synchronous helper to make the API request using urllib.
urllib is used instead of httpx to avoid TLS/HTTP fingerprinting
that triggers 403 Forbidden responses from Wikipedia.
Asynchronous helper to make the API request using httpx.
A custom User-Agent is used to avoid bot detection.
"""
query_string = urllib.parse.urlencode(params)
url = f"{API_URL}?{query_string}"
headers = {
"User-Agent": config.USER_AGENT
}
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req) as response:
return json.loads(response.read().decode('utf-8'))
async with httpx.AsyncClient(headers=headers, timeout=15.0) as client:
response = await client.get(API_URL, params=params)
response.raise_for_status()
return response.json()
async def _search(title: str, limit: int = 5, fallback: bool = False) -> str:
"""
@@ -42,8 +38,8 @@ async def _search(title: str, limit: int = 5, fallback: bool = False) -> str:
"format": "json",
"srlimit": limit
}
# Run synchronous urllib call in a thread to avoid blocking the event loop
data = await asyncio.to_thread(_make_request, params)
# Directly await the async request
data = await _make_request(params)
search_results = data.get("query", {}).get("search", [])
if not search_results:
@@ -80,7 +76,7 @@ async def _fetch_content(title: str, section_index: int) -> Optional[str]:
"redirects": 1,
"format": "json"
}
data = await asyncio.to_thread(_make_request, params)
data = await _make_request(params)
pages = data.get("query", {}).get("pages", {})
if not pages:
@@ -109,7 +105,7 @@ async def _fetch_toc(title: str) -> Optional[str]:
"format": "json",
"redirects": 1
}
data = await asyncio.to_thread(_make_request, params)
data = await _make_request(params)
parse_data = data.get("parse")
if not parse_data: