Add Weather (Open-Meteo) and Wikipedia connectors

- weather/ — Python MCP server (copiato da skald/scripts), no auth, free API
- wikipedia/ — nuovo Python MCP server, Wikipedia API pubblica, no auth
- Entrambi: mcp_local, scope global, auth none, dipendenza httpx
- connectors.json: +2 connector (totale 7), auth:none aggiunto anche a SSH
This commit is contained in:
2026-07-18 21:49:47 +01:00
parent 9cc84fd8ff
commit c3521c4ac0
11 changed files with 1255 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
{
"id": "wikipedia",
"name": "Wikipedia",
"version": "1.0.0",
"type": "mcp_local",
"launch_command": "python3 wikipedia_mcp_server.py",
"transport": "stdio",
"dependencies": [
"httpx>=0.27.0"
],
"docs": [{
"lang": "en",
"description": "Search and read Wikipedia articles — free, no API key required. Search by keyword (returns titles, summaries, and URLs) or read the full text content of any Wikipedia article by title. Automatic redirect resolution and article truncation for long pages.",
"llm_short_description": "Wikipedia MCP: search articles and read full text. Free, no API key. Tools: search, readArticle."
}],
"auth": {
"type": "none"
},
"setup_instructions": [
"Install dependencies: pip install -r requirements.txt",
"No configuration needed — Wikipedia API is free and public."
],
"homepage": "https://www.wikipedia.org",
"icon_small": "icon_sm.svg",
"icon_large": "icon_lg.svg",
"scope": "global",
"tags": ["wikipedia", "mcp", "local", "free", "reference", "research"]
}
+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96">
<rect x="8" y="16" width="80" height="64" rx="6" fill="#f5f5f5" stroke="#aaa" stroke-width="2"/>
<text x="48" y="56" text-anchor="middle" font-family="serif" font-size="36" font-weight="bold" fill="#666">W</text>
</svg>

After

Width:  |  Height:  |  Size: 285 B

+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<rect x="4" y="8" width="40" height="32" rx="4" fill="#f0f0f0" stroke="#ccc" stroke-width="1.5"/>
<text x="24" y="28" text-anchor="middle" font-family="serif" font-size="18" font-weight="bold" fill="#666">W</text>
</svg>

After

Width:  |  Height:  |  Size: 286 B

+1
View File
@@ -0,0 +1 @@
httpx>=0.27.0
@@ -0,0 +1,352 @@
#!/usr/bin/env python3
"""Wikipedia MCP server (JSON-RPC 2.0 over stdio) using the free Wikipedia API.
Capabilities (callable as `mcp__wikipedia__<tool>`):
status — self-check: confirms Wikipedia API is reachable
search — search Wikipedia articles by keyword
readArticle — get the full text content of a Wikipedia article
Data source (free, no API key required):
- Wikipedia API: https://en.wikipedia.org/w/api.php
Run with:
python3 wikipedia_mcp_server.py
"""
from __future__ import annotations
import json
import sys
from typing import Any
import httpx
# ── Logging ─────────────────────────────────────────────────────────────────────
def log(msg: str) -> None:
print(f"[wikipedia_mcp] {msg}", file=sys.stderr, flush=True)
# ── Constants ───────────────────────────────────────────────────────────────────
WIKI_API = "https://en.wikipedia.org/w/api.php"
USER_AGENT = "SkaldWikipediaMCP/1.0"
HTTP_TIMEOUT = 15.0
# ── Tool implementations ────────────────────────────────────────────────────────
def _status(args: dict[str, Any]) -> str:
"""Self-check: confirm Wikipedia API is reachable."""
try:
params = {
"action": "query",
"meta": "siteinfo",
"format": "json",
}
with httpx.Client(timeout=HTTP_TIMEOUT) as client:
resp = client.get(WIKI_API, params=params,
headers={"User-Agent": USER_AGENT})
resp.raise_for_status()
data = resp.json()
if "query" in data:
return ("OK: Wikipedia API is reachable. "
"All tools (search, readArticle) are operational.")
return "Error: Wikipedia API responded but returned unexpected data."
except httpx.TimeoutException:
return "Error: Wikipedia API request timed out."
except httpx.HTTPStatusError as e:
return f"Error: Wikipedia API returned HTTP {e.response.status_code}."
except httpx.HTTPError as e:
return f"Error: Wikipedia API request failed: {e}"
def _search(args: dict[str, Any]) -> str:
"""Search Wikipedia articles by keyword, returning titles and summaries."""
query = args.get("query", "").strip()
if not query:
return "Error: Missing required parameter 'query'."
limit = args.get("limit", 8)
try:
limit = int(limit)
except (TypeError, ValueError):
limit = 8
if limit < 1:
limit = 1
if limit > 50:
limit = 50
try:
params = {
"action": "opensearch",
"search": query,
"limit": limit,
"namespace": 0,
"format": "json",
}
with httpx.Client(timeout=HTTP_TIMEOUT) as client:
resp = client.get(WIKI_API, params=params,
headers={"User-Agent": USER_AGENT})
resp.raise_for_status()
data = resp.json()
except httpx.TimeoutException:
return "Error: Wikipedia search request timed out."
except httpx.HTTPStatusError as e:
return f"Error: Wikipedia API returned HTTP {e.response.status_code}."
except httpx.HTTPError as e:
return f"Error: Wikipedia API request failed: {e}"
# opensearch returns [query, [titles], [descriptions], [urls]]
titles = data[1] if len(data) > 1 else []
descriptions = data[2] if len(data) > 2 else []
urls = data[3] if len(data) > 3 else []
if not titles:
return f"No results found for '{query}'."
lines = [f"📚 Wikipedia search results for '{query}'"]
lines.append("")
for i, title in enumerate(titles):
desc = descriptions[i] if i < len(descriptions) else ""
url = urls[i] if i < len(urls) else ""
lines.append(f" {i+1}. **{title}**")
if desc:
lines.append(f" {desc}")
if url:
lines.append(f" {url}")
lines.append("")
return "\n".join(lines)
def _read_article(args: dict[str, Any]) -> str:
"""Get the full text content of a Wikipedia article by title."""
title = args.get("title", "").strip()
if not title:
return "Error: Missing required parameter 'title'."
try:
# First, get the page ID / exact title
params = {
"action": "query",
"titles": title,
"redirects": 1,
"format": "json",
}
with httpx.Client(timeout=HTTP_TIMEOUT) as client:
resp = client.get(WIKI_API, params=params,
headers={"User-Agent": USER_AGENT})
resp.raise_for_status()
data = resp.json()
pages = data.get("query", {}).get("pages", {})
if not pages:
return f"Error: Article '{title}' not found."
page_id = next(iter(pages))
page_info = pages[page_id]
if "missing" in page_info:
return f"Error: Article '{title}' not found on Wikipedia."
actual_title = page_info.get("title", title)
# Now get the extract (full text)
params = {
"action": "query",
"pageids": page_id,
"prop": "extracts",
"explaintext": 1,
"exlimit": 1,
"format": "json",
}
with httpx.Client(timeout=HTTP_TIMEOUT) as client:
resp = client.get(WIKI_API, params=params,
headers={"User-Agent": USER_AGENT})
resp.raise_for_status()
data = resp.json()
pages = data.get("query", {}).get("pages", {})
extract = pages.get(page_id, {}).get("extract", "")
if not extract:
return f"**{actual_title}**\n\n_(No content available.)_"
# Get the URL
article_url = f"https://en.wikipedia.org/wiki/{actual_title.replace(' ', '_')}"
# Truncate very long articles to avoid blowing up context
max_chars = 15000
if len(extract) > max_chars:
extract = extract[:max_chars] + "\n\n_...(article truncated, see full version on Wikipedia)_"
return (
f"# {actual_title}\n\n"
f"_{article_url}_\n\n"
f"{extract}"
)
except httpx.TimeoutException:
return "Error: Wikipedia API request timed out."
except httpx.HTTPStatusError as e:
return f"Error: Wikipedia API returned HTTP {e.response.status_code}."
except httpx.HTTPError as e:
return f"Error: Wikipedia API request failed: {e}"
# ── Tool manifest ────────────────────────────────────────────────────────────────
TOOLS = [
{
"name": "status",
"description": (
"Self-check that the Wikipedia integration is operational: confirms "
"the Wikipedia API is reachable by issuing a cheap siteinfo query. "
"Call this first if any Wikipedia tool fails."
),
"inputSchema": {"type": "object", "properties": {}},
},
{
"name": "search",
"description": (
"Search Wikipedia articles by keyword. Returns a list of matching "
"article titles with short descriptions and URLs. Use this to find "
"the correct article title before calling readArticle."
),
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query (keywords or phrase).",
},
"limit": {
"type": "integer",
"description": "Maximum number of results to return (1-50). Default: 8.",
},
},
"required": ["query"],
},
},
{
"name": "readArticle",
"description": (
"Get the full text content of a Wikipedia article by title. "
"Returns the article body as Markdown-like text, with a link to the "
"live Wikipedia page. Long articles are truncated at 15,000 characters. "
"First use search() to find the exact article title."
),
"inputSchema": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "Exact Wikipedia article title (e.g. 'Alan Turing', 'Rome').",
},
},
"required": ["title"],
},
},
]
TOOL_DISPATCH = {
"status": _status,
"search": _search,
"readArticle": _read_article,
}
# ── JSON-RPC dispatch ────────────────────────────────────────────────────────────
def _ok(req_id: Any, result: Any) -> str:
return json.dumps({"jsonrpc": "2.0", "id": req_id, "result": result})
def _text_result(req_id: Any, text: str, is_error: bool = False) -> str:
payload: dict = {
"jsonrpc": "2.0",
"id": req_id,
"result": {"content": [{"type": "text", "text": text}]},
}
if is_error:
payload["result"]["isError"] = True
return json.dumps(payload)
def _error(req_id: Any, code: int, message: str) -> str:
return json.dumps({
"jsonrpc": "2.0",
"id": req_id,
"error": {"code": code, "message": message},
})
def handle_request(msg: dict) -> str | None:
method = msg.get("method", "")
req_id = msg.get("id")
if method == "initialize":
return _ok(req_id, {
"protocolVersion": "2024-11-05",
"capabilities": {"tools": {}},
"serverInfo": {
"name": "wikipedia",
"version": "1.0.0",
},
})
if method == "notifications/initialized":
return None
if method == "ping":
return _ok(req_id, {})
if method == "tools/list":
return _ok(req_id, {"tools": TOOLS})
if method == "tools/call":
params = msg.get("params", {})
tool_name = params.get("name", "")
tool_args = params.get("arguments", {})
handler = TOOL_DISPATCH.get(tool_name)
if handler is None:
return _text_result(req_id, f"Error: Unknown tool: {tool_name}", is_error=True)
try:
result = handler(tool_args)
is_err = result.startswith("Error:")
return _text_result(req_id, result, is_error=is_err)
except Exception as e:
log(f"Unhandled exception in tool '{tool_name}': {e}")
return _text_result(req_id, f"Error: Internal error in tool '{tool_name}': {e}", is_error=True)
return _error(req_id, -32601, f"Method not found: {method}")
# ── Main loop ────────────────────────────────────────────────────────────────────
def main() -> None:
log("Starting Wikipedia MCP server")
try:
for line in sys.stdin:
line = line.strip()
if not line:
continue
try:
msg = json.loads(line)
except json.JSONDecodeError as e:
log(f"Invalid JSON input: {e}")
continue
resp = handle_request(msg)
if resp is not None:
sys.stdout.write(resp + "\n")
sys.stdout.flush()
except KeyboardInterrupt:
pass
if __name__ == "__main__":
main()