User agent, readme
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user