MCP Hub
title: Pioneer emoji: 🔥 colorFrom: purple colorTo: pink sdk: docker app_port: 7860 pinned: false license: mit
MCP Hub
Ein HF Space mit mehreren MCP-Servern, getrennt über Pfade, jeweils mit eigenem Auth-Key.
Nach dem Plugin-Prinzip von local-mcp-hub neu aufgebaut: Ein MCP = ein py, main.py erkennt und montiert automatisch, neue MCPs hinzufügen ohne die Hauptdatei zu ändern.
Related MCP server: MCP Hub
Struktur
hub-mcp/
├── main.py ← 插件自动发现 + 鉴权壳 + 路由装配
├── Dockerfile ← ⚠️ GitHub 侧完整构建定义,与 HF 侧那份内容不同,见「构建部署链路」
├── requirements.txt
├── .github/workflows/build.yml ← GHCR 镜像构建(含防套娃闸门)
├── duck-mcp/ ← duck-mcp TS 原版完整项目(npm install + tsc build 出 dist/)
└── mcps/
├── _ddg.py ← 库:DDG 搜索/抓取实现(下划线开头,不加载为插件)
├── _stdio_bridge.py ← 库:stdio 子进程桥公共实现(duck / academic 共用,见「踩坑档案 #1」)
├── doubao-mcp.py → /doubao/sse web_search
├── zhihu-mcp.py → /zhihu/sse zhihu_search / global_search / zhihu_ask / zhihu_trending
├── ddg-mcp.py → /ddg/sse search / scrape(旧版,已被 /duck 取代)
│ + REST: POST /ddg/search、/ddg/scrape(给 rikkahub 安卓端)
├── duck-mcp.py → /duck/sse 桥:bash -c 'cd duck-mcp && node dist/index.js'
└── academic-mcp.py → /academic/sse 桥:/opt/academic-venv/bin/academic-mcpSubprozess-Brücke (duck / academic)
Diese beiden sind nicht selbst implementiert, sondern starten den jeweiligen Original-MCP-Server als Subprozess und kommunizieren per stdio mit ihm.
Der Hub macht nur Protokoll-Forwarding (tools/list, tools/call werden unverändert durchgereicht):
duck: Upstream ist ein TS-Projekt (VM-Sandbox löst Anti-Bot-Challenge + Chrome134 TLS-Fingerprint). Eine Portierung nach Python wäre zu teuer, das ganze Projekt liegt in
duck-mcp/, im Image läuft es mit node 22 alsdist/index.js.academic: Reines Python, aber die Abhängigkeiten (fastmcp) kollidieren mit dem
mcp==1.2.0des Hubs, daher in einem separaten venv/opt/academic-venvisoliert installiert.
Die gemeinsame Implementierung liegt in mcps/_stdio_bridge.py, jeder Aufruf startet eine eigene Session, die danach sofort geschlossen wird –
das ist keine Faulheit, sondern von anyio erzwungen, siehe „Stolperfallen-Archiv #1", füg ja keinen Session-Cache hinzu.
Endpunkte
MCP | SSE-Endpunkt | Tools |
Doubao-Suche |
|
|
Zhihu |
|
|
DuckDuckGo |
|
|
DuckDuckGo (Original-TS-Brücke) |
|
|
Akademische Paper |
|
|
Endpunkt-Status getestet (2026-08-20)
Endpunkt | tools/list | Tatsächlicher Aufruf | Hinweis |
| ✅ | ✅ | Kostenloses Kontingent Custom+Global gemeinsam 500 Aufrufe/Monat, nicht aufbrauchen |
| ✅ | ✅ | |
| ✅ 3 Tools | ✅ echte Paper zurück | arXiv funktioniert, Quellen ohne Key (Scopus/WOS/CORE/IEEE…) nur Warnung, kein Problem |
| ✅ 9 Tools | ⚠️ Brücke ok, Upstream blockiert | DDG liefert Anti-Bot-Challenge für HF-Rechenzentrums-IPs, kein Code-Problem, IP wechseln / Proxy nutzen |
| ✅ | ⚠️ | Alte Version, stirbt leichter am Anti-Scraping, für REST behalten, kann entfernt werden |
Die Parameter-Falle bei academic
paper_search / paper_download erwarten ein query_list-Objekt-Array, keinen String:
{"query_list": [{"query": "quantum computing", "searcher": "arxiv", "max_results": 2}]}searcher weglassen = alle Quellen durchsuchen (langsam). paper_read erwartet dagegen {"searcher": ..., "paper_id": ...}.
REST-Endpunkte (für rikkahub Android-Client, kein MCP)
Methode | Pfad | body | Rückgabe |
POST |
|
|
|
POST |
|
|
|
Die Rückgabe ist strukturell identisch mit SearchResult / ScrapedResult von rikkahub, der Client kann direkt deserialisieren.
Auth ebenfalls Authorization: Bearer <DDG_KEY>, bei Fehlern kommt {"detail": "..."} zurück.
Authentifizierung
Jeder MCP hat einen eigenen Bearer-Key (Authorization: Bearer <key>):
MCP | Key-Env | Standardwert |
doubao |
|
|
zhihu |
|
|
ddg |
|
|
duck |
|
|
academic |
|
|
Wenn die Env-Variable gesetzt ist, wird ihr Wert verwendet, sonst der Standard. Auf der GET /-Startseite sieht man, ob die Auth für jeden Endpunkt konfiguriert ist und ob die Upstream-Secrets vorhanden sind.
Upstream-Secrets (in HF Space Settings → Secrets eintragen, nicht ins Repo schreiben)
env | Zweck |
| Volcano Ark Doubao-Suche Custom-Version API-Key (Pflicht, Global-Version fällt darauf zurück wenn nicht gesetzt) |
| Dedizierter Key für die Doubao-Suche Global-Version (optional, unter „API-Key-Verwaltung – nutzungsabhängig bezahlen" erstellen, ohne diesen nutzt die Global-Version den ARK-Key und meldet wahrscheinlich 700901) |
| Zhihu Open Platform Access Secret |
Neues MCP hinzufügen
Ein py in mcps/ ablegen, main.py muss nicht geändert werden:
"""第一行 docstring 会显示在 / 首页 about 里。"""
import os
from mcp import types
from mcp.server import Server
MOUNT = "myname" # 可选,默认用文件名(去掉 .py)
KEY_ENV = "MY_KEY" # 可选,Bearer 鉴权 env 名
DEFAULT_KEY = "" # 可选,默认 key(env 没配时用)
# ENABLED = False # 可选,临时停用
server = Server("My Server")
@server.list_tools()
async def list_tools() -> list[types.Tool]:
...
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
...Nicht
if __name__ == "__main__": server.run(...)schreiben – Port und Routen verwaltet der Hub.Ein py mit mehreren Endpunkten:
MOUNTS = {"path1": srv1, "path2": srv2}.Zusätzliche REST-Routen (Einzel-Mount):
ROUTES = [starlette.Route("/xxx", endpoint=..., methods=["POST"])], wird unter dem eigenen Plugin-Pfad gemountet.Bibliotheksdateien im selben Verzeichnis referenzieren:
import _xxx(Dateien mit Unterstrich am Anfang werden nicht als Plugin geladen).Wenn ein einzelnes Plugin beim Import scheitert, erscheint nur ein Fehler unter
brokenauf/, andere Plugins sind nicht betroffen.
Lokal ausführen
pip install -r requirements.txt
uvicorn main:app --port 7860Build- und Deployment-Kette
Die Build-Umgebung von HF Space hat viele Einschränkungen (bun lässt sich nicht installieren, nicht mal curl gibt es), daher wird nicht in HF gebaut:
改代码 → push GitHub(fuwei99/hub-mcp) → Actions 构建镜像 → 推 GHCR
↓
HF 的 Dockerfile 只 FROM 拉现成镜像Die beiden Dockerfiles haben unterschiedlichen Inhalt, jedes macht genau eine Sache:
Ort | Inhalt | Zweck |
GitHub |
| Baut das echte Image |
HF |
| Zieht nur das fertige Image und führt es aus |
🚨 Eiserne Regeln
1. Das HF-Dockerfile darf auf keinen Fall zurück nach GitHub synchronisiert werden. Sonst macht Actions „Matroschka-Build": Das Image der letzten Version wird unverändert weitergereicht und neu gepusht, kein einziger
COPY-Schritt wird ausgeführt, das Image ist immer alter Code, aber der Build zeigt success. Schon zweimal reingefallen (siehe Stolperfallen-Archiv #2). Konkrete Mine: Wenn das lokale Repo als Remote auf HF zeigt, auf keinen Fallgit checkout origin/main -- Dockerfileauf dem Dockerfile ausführen, das zieht die HF-Version ins Lokale und pusht sie mit nach GitHub.2. Auf HF-Seite den Digest pinnen, nicht
:latestverwenden. HF-Build cached den alten Digest von latest, wenn das Tag sich nicht ändert, werden die Layer nicht neu gezogen → Code geändert, online ist trotzdem die alte Version.3. Vor dem Deployment prüfen, nicht blind „success" glauben. Die Code-Layer aus GHCR herausholen und prüfen, ob die Dateien stimmen (Methode unten), das ist schneller als sich in den Online-Logs die Stirn zu stoßen.
Standard-Ablauf für Image-Wechsel
# 1. 改代码,只推 GitHub(注意:Dockerfile 必须是完整构建版)
git push --force https://github.com/fuwei99/hub-mcp.git main:main
# 2. 等 Actions(workflow 已带防呆闸门,套娃/缺 COPY 会直接 fail)
curl -H "Authorization: Bearer $GITHUB_TOKEN_FUWEI" \
"https://api.github.com/repos/fuwei99/hub-mcp/actions/runs?per_page=1"
# 3. 取新 digest
tok=$(curl -s "https://ghcr.io/token?scope=repository:fuwei99/hub-mcp:pull" | jq -r .token)
curl -sI -H "Authorization: Bearer $tok" \
-H "Accept: application/vnd.oci.image.index.v1+json" \
"https://ghcr.io/v2/fuwei99/hub-mcp/manifests/latest" | grep -i docker-content-digest
# 4. 改 HF 的 Dockerfile FROM 行为该 digest,推 HF
# 5. 验证线上真的换了代码(找个只有新版才有的字符串)
curl -s https://fluidgender159-hub-mcp.hf.space/ | jq .aboutPrüfen: Dateien aus GHCR holen
Ohne docker, mit reinem curl lassen sich die Image-Layer aufziehen (ultimatives Mittel um zu prüfen, ob der Build wirklich greift):
tok=$(curl -s "https://ghcr.io/token?scope=repository:fuwei99/hub-mcp:pull" | jq -r .token)
A="Accept: application/vnd.oci.image.index.v1+json, application/vnd.oci.image.manifest.v1+json"
# index → amd64 manifest → 找几 KB 的小层(就是 COPY mcps/ 那层)→ 拉 blob 解 tar
curl -s -H "Authorization: Bearer $tok" -H "$A" \
"https://ghcr.io/v2/fuwei99/hub-mcp/manifests/latest" -o idx.json
# ...取 amd64 digest、取 layers 里 size < 20000 的、curl blobs/<digest> | tar tzAuch in den Actions-Logs sieht man die Matroschka sofort: Ein normaler Build hat COPY und dauert 1–2 Minuten;
ein Matroschka-Build hat nur resolve ghcr.io/... done + exporting layers, in 2 Sekunden fertig.
Stolperfallen-Archiv
#1 ⭐ anyio cancel scope kann Tasks nicht überbrücken (wahre Ursache für hängende Subprozess-Brücke)
Symptom: /duck/sse, /academic/sse verbinden sich, initialize antwortet sofort, aber tools/list
bleibt für immer still – kein Fehler, kein Timeout, im SSE nur pings. Jeder MCP-Client hängt „fest".
Falsch verdächtigte Richtungen (alle nicht die Ursache): SSE-Long-Connection, Testskript, node startet nicht, Banner verschmutzt stdout (Banner geht über stderr, stdout ist sauber).
Wahre Ursache: stdio_client() und ClientSession() sind beide task-gebundene anyio-Kontexte.
Die Brücke hatte zur Ersparnis in der Vergangenheit im Task von Anfrage A __aenter__ aufgerufen und die Session global gecacht, um sie für Anfrage B wiederzuverwenden.
Im Hub ist aber jede SSE-Verbindung ein eigener Task, also:
RuntimeError: Attempted to exit cancel scope in a different task than it was entered inDas Verhalten ist heimtückisch: initialize antwortet, weil das die Brückenschale selbst beantwortet, ohne den Subprozess anzufassen;
sobald tools/list echte Subprozess-Weiterleitung braucht, stirbt es am task-übergreifenden cancel scope.
Reproduktion (30-Zeilen-Lokalskript, kein Deployment nötig):
async def task_a():
cm = stdio_client(params); read, write = await cm.__aenter__()
scm = ClientSession(read, write); s = await scm.__aenter__()
await s.initialize(); state["s"] = s # 缓存给别的 task
async def task_b():
await state["s"].list_tools() # 💥 死这儿
await asyncio.create_task(task_a())
await asyncio.create_task(task_b())Fix: mcps/_stdio_bridge.py – bei jedem list_tools/call_tool wird im aktuellen Task
ein Subprozess gestartet, mit async with geschlossen, nach Gebrauch sofort zu; nur die Tool-Beschreibungen werden gecacht (reine Daten, task-übergreifend ok).
async with stdio_client(self._params_factory()) as (read, write):
async with ClientSession(read, write) as session:
await asyncio.wait_for(session.initialize(), timeout=self._timeout)
return await asyncio.wait_for(fn(session), timeout=self._timeout)Nicht „optimieren" zu einer geteilten Session. Für echte Beschleunigung wäre der richtige Weg ein dedizierter langlaufender Worker-Task + Queue, alle IO passieren in diesem Task, statt Kontextobjekte über Tasks hinweg zu reichen.
Zusätzliche Lehre: ClientSession(read, write) nur zu instanziieren ohne __aenter__ hängt ebenfalls –
der Hintergrund-Task „stdout lesen → Antworten verteilen" startet erst in __aenter__,
ohne Kontexteinstieg bekommt keine gesendete Anfrage eine Antwort.
#2 ⭐ Matroschka-Build (Image immer alter Code, aber Build zeigt success)
Symptom: Code geändert, Actions success, HF rebuild RUNNING, aber online ändert sich nichts. Wiederholt HF-Cache, GHCR-Cache, Layer-Cache verdächtigt, alles nicht.
Lokalisierungsmethode: Aus GHCR den COPY mcps/-Layer herausgeholt und tar tzf – die neu hinzugefügte
_stdio_bridge.py ist gar nicht im Image, obwohl sie auf GitHub existiert. Dann die Actions-Logs angesehen:
#1 transferring dockerfile: 647B ← 完整版有 2.7KB
#5 resolve ghcr.io/fuwei99/hub-mcp@sha256:0799864b... done
#7 exporting layers done ← 全程 2 秒,零 COPYWahre Ursache: Das Dockerfile im GitHub-Repo war zur HF-Version geworden –
FROM ghcr.io/fuwei99/hub-mcp@sha256:... – Actions hat das alte Image unverändert weitergereicht und neu gepusht.
Wie es reinkam: Das lokale Repo hatte HF als Remote, und
git checkout origin/main -- Dockerfile hat die HF-Version in den Arbeitsbereich gezogen,
die dann beim Push nach GitHub mitging.
Vorsorge (in .github/workflows/build.yml eingebaut, bei Wiederholung schlägt der Build sofort fehl):
- name: 拒绝套娃构建
run: |
if grep -qE '^FROM +ghcr\.io/fuwei99/hub-mcp' Dockerfile; then
echo "::error::Dockerfile 是 HF 版,会套娃构建"; exit 1
fi
grep -q 'COPY mcps/' Dockerfile || { echo "::error::缺少 COPY mcps/"; exit 1; }Zusätzlich hat das Dockerfile einen Build-Zeit-Selbsttest: test -f mcps/_stdio_bridge.py || exit 1.
#3 Der Abhängigkeits-Albtraum von academic-mcp
Upstream academic-mcp==0.1.7 hat keine Obergrenzen für Abhängigkeiten, die installierte Kombination ist kaputt. Dreifach-Fehler:
Fehler | Ursache |
| fastmcp braucht es, aber academic-mcp deklariert es nicht |
| Es wurde |
| Zweistufiges |
Fix: Alles in einem Befehl installieren, Obergrenzen explizit setzen, und einen Import-Selbsttest in die Build-Phase legen:
RUN python3 -m venv /opt/academic-venv \
&& /opt/academic-venv/bin/pip install --no-cache-dir \
academic-mcp==0.1.7 pydantic-settings "mcp<2.0" \
&& /opt/academic-venv/bin/python -c "from fastmcp import FastMCP; \
from academic_mcp.__main__ import main; print('academic-mcp import OK')"Getestete funktionierende Kombination: academic-mcp 0.1.7 + fastmcp 3.4.7 (oder 2.14.1) + mcp 1.29.0
pydantic-settings 2.15.0.
Lehre: Beim Upgrade von Abhängigkeiten nicht zweistufig pip install und dann pip install -U machen, alles in einem Rutsch installieren, damit der Resolver einheitlich entscheidet;
Abhängigkeitskombinationen unbedingt lokal im venv testen, bevor sie ins Dockerfile kommen, und den Import-Selbsttest in die Build-Phase legen –
falsch installiert = Build schlägt fehl, statt auf Fehler in den Online-Logs zu warten.
#4 Sonstiges
Symptom | Ursache | Fix |
bun-Download exit 127 |
| Erst |
bun | HF-Build-Umgebungsbeschränkung | Node 22 offizielles Tarball verwenden, mit |
|
| Einzelnen Parameter nach alter Signatur übergeben |
academic meldet |
| Reine lokale Umgebungsbeschränkung, HF/docker normal. Download-Verzeichnis separat setzen: |
Lokal/HF-Remote divergieren | Paralleles Pushen auf beiden Seiten | Nach rebase force-pushen; oder eine saubere Clone nur fürs Deployment anlegen |
Fehlersuche-Methodik (der zeitsparende Teil)
Nicht den Python-MCP-Client zum Debuggen von Hängern verwenden – der hängt selbst, man sieht nicht wo. Mit curl das Protokoll von Hand durchspielen, Frame für Frame sehen wer nicht antwortet:
curl -sN -H "Authorization: Bearer wei123.." "$BASE/duck/sse" > sse.log & SID=$(grep -o 'session_id=[a-f0-9]*' sse.log | head -1 | cut -d= -f2) P="$BASE/duck/messages/?session_id=$SID" curl -X POST "$P" -d '{"jsonrpc":"2.0","id":1,"method":"initialize",...}' curl -X POST "$P" -d '{"jsonrpc":"2.0","method":"notifications/initialized"}' curl -X POST "$P" -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' # 盯 sse.log:initialize 回了但 id:2 不回 → 问题在桥拉子进程那一步Schichtweise lokalisieren: Brückenschale → kann der Subprozess allein laufen → Subprozess-Bibliotheksfunktion direkt aufrufen. In diesem Fall funktionierte
ArxivSearcher().search()direkt aufgerufen einwandfrei, die Suche selbst ist also ok, der Fehler liegt in der Verpackungsschicht.In den Online-Logs gegen Wände zu laufen ist am teuersten. Was sich lokal mit dem Hub reproduzieren lässt, niemals online testen; Abhängigkeitsprobleme in die Build-Phase als Selbsttest legen, damit es schon in Actions explodiert.
serverInfo.versionist nicht die Code-Version (das ist die mcp-Bibliotheksversion). Um zu prüfen, ob online neuer Code läuft, nach einem String suchen, den es nur in der neuen Version gibt, z. B. den about-Text vonGET /.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceAn MCP server that provides Hugging Face Hub API and Search endpoints through multiple transport protocols (STDIO, SSE, StreamableHTTP, and StreamableHTTPJson), enabling integration with AI model capabilities.276MIT
- FlicenseNot gradedqualityNot gradedmaintenanceMCP Hub aggregates and proxies multiple Model Context Protocol servers into a unified Streamable HTTP interface. It allows users to combine diverse stdio, SSE, and HTTP-based servers while providing tool namespacing, health monitoring, and secure authentication.781
- AlicenseNot gradedqualityAmaintenanceZero-auth multi-source research MCP server that enables web search, reading URLs, PDFs, GitHub repos, and querying Hacker News, Stack Overflow, Semantic Scholar, and YouTube transcripts without API keys.10Apache 2.0
- FlicenseNot gradedqualityBmaintenanceAn extensible MCP hub that exposes internal services (chat, observability, RAG) as namespaced tools via FastMCP, with OpenAPI auto-generation, auth, and resilient error handling.
Related MCP Connectors
Hosted MCP server for LLM cost estimation, model comparison, and budget-aware routing.
MCP Hub: AI service discovery, per-user OAuth, and multi-service workflow orchestration
MCP server for secureFlows: token-free URL builders and integration-linting tools for AI agents.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/fuwei99/hub-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server