Skip to main content
Glama

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-mcp

Subprozess-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 als dist/index.js.

  • academic: Reines Python, aber die Abhängigkeiten (fastmcp) kollidieren mit dem mcp==1.2.0 des Hubs, daher in einem separaten venv /opt/academic-venv isoliert 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

https://fluidgender159-hub-mcp.hf.space/doubao/sse

web_search_custom (Custom-Version, web+image, unterstützt Site-Einschränkung/Branche/Autoritätsstufe usw.) / web_search_global (Global-Version, Text+Bild gemischt, unterstützt pdf, ICP-Einschränkung, Kurzseiten-/Seitenverhältnis-Filter)

Zhihu

https://fluidgender159-hub-mcp.hf.space/zhihu/sse

zhihu_search / global_search / zhihu_ask / zhihu_trending

DuckDuckGo

https://fluidgender159-hub-mcp.hf.space/ddg/sse

search / scrape (alte Version, scrapt HTML, wird leicht von DDG-Anti-Scraping blockiert)

DuckDuckGo (Original-TS-Brücke)

https://fluidgender159-hub-mcp.hf.space/duck/sse

ddg_get_answer / ddg_search / ddg_search_news / ddg_search_images / ddg_search_videos / ddg_fetch_content / ddg_get_suggestions / ddg_get_definition / ddg_convert_currency (hung319/duck-mcp Original, node-Subprozess-Brücke, VM-Challenge-Lösung + Chrome134 TLS-Fingerprint, Anti-Scraping)

Akademische Paper

https://fluidgender159-hub-mcp.hf.space/academic/sse

paper_search / paper_download / paper_read (nalkalin/academic-mcp, eigenes venv als Subprozess-Brücke, arXiv/PubMed/PMC/bioRxiv/medRxiv/Semantic Scholar/CrossRef/IACR/CORE usw., 18 akademische Quellen, ohne Key)

Endpunkt-Status getestet (2026-08-20)

Endpunkt

tools/list

Tatsächlicher Aufruf

Hinweis

/doubao/sse

Kostenloses Kontingent Custom+Global gemeinsam 500 Aufrufe/Monat, nicht aufbrauchen

/zhihu/sse

/academic/sse

✅ 3 Tools

✅ echte Paper zurück

arXiv funktioniert, Quellen ohne Key (Scopus/WOS/CORE/IEEE…) nur Warnung, kein Problem

/duck/sse

✅ 9 Tools

⚠️ Brücke ok, Upstream blockiert

DDG liefert Anti-Bot-Challenge für HF-Rechenzentrums-IPs, kein Code-Problem, IP wechseln / Proxy nutzen

/ddg/sse

⚠️

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

/ddg/search

{query, count?, region?, time_range?, safe_search?}

{items:[{title,url,text}], images:[]}

POST

/ddg/scrape

{url, max_length?}

{urls:[{url,content,metadata:{...}}]}

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

DOUBAO_KEY

wei123..

zhihu

ZHIHU_KEY

wei123..

ddg

DDG_KEY

wei123..

duck

DUCK_KEY

wei123..

academic

ACADEMIC_KEY

wei123..

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

VOLCENGINE_ARK_API_KEY

Volcano Ark Doubao-Suche Custom-Version API-Key (Pflicht, Global-Version fällt darauf zurück wenn nicht gesetzt)

VOLCENGINE_GLOBAL_API_KEY

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_ACCESS_SECRET

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 broken auf /, andere Plugins sind nicht betroffen.

Lokal ausführen

pip install -r requirements.txt
uvicorn main:app --port 7860

Build- 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 Dockerfile

FROM python:3.12-slim + node/venv installieren + COPY

Baut das echte Image

HF Dockerfile

FROM ghcr.io/fuwei99/hub-mcp@sha256:...

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 Fall git checkout origin/main -- Dockerfile auf 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 :latest verwenden. 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 .about

Prü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 tz

Auch 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 in

Das 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 秒,零 COPY

Wahre 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

No module named 'pydantic_settings'

fastmcp braucht es, aber academic-mcp deklariert es nicht

cannot import name 'McpError' (Hinweis Did you mean MCPError?)

Es wurde mcp 2.0.0 gezogen, aber fastmcp braucht McpError aus mcp 1.x (in 2.0 umbenannt zu MCPError)

cannot import name 'FastMCP' from 'fastmcp' (unknown location)

Zweistufiges pip install -U fastmcp hat das Paket zu einem leeren Namensraum-Restpaket gemacht

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

python:3.12-slim hat kein curl

Erst apt install curl

bun Permission denied

HF-Build-Umgebungsbeschränkung

Node 22 offizielles Tarball verwenden, mit python tarfile entpacken (das tar hat die Ausführungsbits eingebaut, nicht mal xz-utils nötig)

stdio_client() meldet env/args-Fehler

mcp==1.2.0 alte API: nimmt nur ein StdioServerParameters-Objekt

Einzelnen Parameter nach alter Signatur übergeben

academic meldet FileNotFoundError: [Errno 2]

xlin.xmap_asyncProcessPoolExecutormultiprocessing.Lock braucht /dev/shm; in der proot-Sandbox nicht vorhanden

Reine lokale Umgebungsbeschränkung, HF/docker normal. Download-Verzeichnis separat setzen: ACADEMIC_MCP_DOWNLOAD_PATH=/tmp/papers

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)

  1. 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 不回 → 问题在桥拉子进程那一步
  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.

  3. 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.

  4. serverInfo.version ist 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 von GET /.

F
license - not found
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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

  • A
    license
    Not graded
    quality
    A
    maintenance
    An 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.
    276
    MIT
  • F
    license
    Not graded
    quality
    Not graded
    maintenance
    MCP 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
  • A
    license
    Not graded
    quality
    A
    maintenance
    Zero-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.
    10
    Apache 2.0
  • F
    license
    Not graded
    quality
    B
    maintenance
    An extensible MCP hub that exposes internal services (chat, observability, RAG) as namespaced tools via FastMCP, with OpenAPI auto-generation, auth, and resilient error handling.

View all related MCP servers

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.

View all MCP Connectors

Latest Blog Posts

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