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 ## ⚠️ AI SLOP DISCLAIMER
This entire project was vibe-coded by an AI. It is 100% slop code. Use it at your own risk. 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 ## Tools Overview
### 📁 `list_directory` ### 📁 `list_directory`
@@ -64,9 +69,10 @@ This server requires Python 3.10+ and the following packages:
* `starlette`: Lightweight ASGI framework. * `starlette`: Lightweight ASGI framework.
* `Pillow`: Image processing and thumbnail generation. * `Pillow`: Image processing and thumbnail generation.
* `requests`: For communicating with the Stable Diffusion API. * `requests`: For communicating with the Stable Diffusion API.
* `httpx`: For asynchronous API requests (e.g., Wikipedia).
```bash ```bash
pip install uvicorn starlette Pillow requests pip install uvicorn starlette Pillow requests httpx
``` ```
### Setup ### Setup
+1 -1
View File
@@ -12,7 +12,7 @@ PORT = 8000
request_host = ContextVar("request_host", default=f"{HOST}:{PORT}") request_host = ContextVar("request_host", default=f"{HOST}:{PORT}")
LOG_LEVEL = "WARNING" # Options: "DEBUG", "INFO", "WARNING", "ERROR" LOG_LEVEL = "WARNING" # Options: "DEBUG", "INFO", "WARNING", "ERROR"
LOG_FILE = str(ROOT_DIR / "debug.log") 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 --- # --- Stable Diffusion Config ---
SD_URL = "http://127.0.0.1:7860" 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."}, "model_name": {"type": "string", "description": "The name of the model to use. Required."},
"prompt": {"type": "string", "description": "The prompt for the image. Required."}, "prompt": {"type": "string", "description": "The prompt for the image. Required."},
"negative_prompt": {"type": "string", "description": "The negative prompt to exclude unwanted elements."}, "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."}, "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"], "required": ["model_name", "prompt", "resolution_preset"],
+12 -16
View File
@@ -1,5 +1,4 @@
import urllib.request import httpx
import urllib.parse
import json import json
import re import re
import asyncio 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.""" """Removes HTML tags from a string using regex to provide clean text to the LLM."""
return re.sub(r'<[^>]*>', '', text) 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. Asynchronous helper to make the API request using httpx.
urllib is used instead of httpx to avoid TLS/HTTP fingerprinting A custom User-Agent is used to avoid bot detection.
that triggers 403 Forbidden responses from Wikipedia.
""" """
query_string = urllib.parse.urlencode(params)
url = f"{API_URL}?{query_string}"
headers = { headers = {
"User-Agent": config.USER_AGENT "User-Agent": config.USER_AGENT
} }
req = urllib.request.Request(url, headers=headers) async with httpx.AsyncClient(headers=headers, timeout=15.0) as client:
with urllib.request.urlopen(req) as response: response = await client.get(API_URL, params=params)
return json.loads(response.read().decode('utf-8')) response.raise_for_status()
return response.json()
async def _search(title: str, limit: int = 5, fallback: bool = False) -> str: 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", "format": "json",
"srlimit": limit "srlimit": limit
} }
# Run synchronous urllib call in a thread to avoid blocking the event loop # Directly await the async request
data = await asyncio.to_thread(_make_request, params) data = await _make_request(params)
search_results = data.get("query", {}).get("search", []) search_results = data.get("query", {}).get("search", [])
if not search_results: if not search_results:
@@ -80,7 +76,7 @@ async def _fetch_content(title: str, section_index: int) -> Optional[str]:
"redirects": 1, "redirects": 1,
"format": "json" "format": "json"
} }
data = await asyncio.to_thread(_make_request, params) data = await _make_request(params)
pages = data.get("query", {}).get("pages", {}) pages = data.get("query", {}).get("pages", {})
if not pages: if not pages:
@@ -109,7 +105,7 @@ async def _fetch_toc(title: str) -> Optional[str]:
"format": "json", "format": "json",
"redirects": 1 "redirects": 1
} }
data = await asyncio.to_thread(_make_request, params) data = await _make_request(params)
parse_data = data.get("parse") parse_data = data.get("parse")
if not parse_data: if not parse_data: