From b107aff1506c4aef3e0ae8066ee46ba5949818e4 Mon Sep 17 00:00:00 2001 From: moosecrap Date: Tue, 28 Jul 2026 13:15:12 -0700 Subject: [PATCH] User agent, readme --- README.md | 8 +++++++- config.py | 2 +- tools/__init__.py | 2 +- tools/browse_wikipedia.py | 28 ++++++++++++---------------- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index de68f35..5b49236 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/config.py b/config.py index fb20c7c..0f70080 100644 --- a/config.py +++ b/config.py @@ -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" diff --git a/tools/__init__.py b/tools/__init__.py index 1487b08..a68a560 100644 --- a/tools/__init__.py +++ b/tools/__init__.py @@ -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"], diff --git a/tools/browse_wikipedia.py b/tools/browse_wikipedia.py index 0bc79a7..bd530c9 100644 --- a/tools/browse_wikipedia.py +++ b/tools/browse_wikipedia.py @@ -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: