Skip to main content
Glama

title: Pioneer emoji: 🔥 colorFrom: purple colorTo: pink sdk: docker app_port: 7860 pinned: false license: mit

MCP Hub

Un solo HF Space que aloja varios servidores MCP, separados por ruta, cada uno con su propia clave de autenticación. Reestructurado siguiendo el patrón de plugins de local-mcp-hub: un MCP por archivo .py, main.py los descubre y ensambla automáticamente; no hace falta tocar el archivo principal para añadir un MCP nuevo.

Related MCP server: MCP Hub

Estructura

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

Puente de subprocesos (duck / academic)

Estos dos no están implementados desde cero, sino que levantan el servidor MCP original de aguas arriba como subproceso, se comunican con él por stdio, y el hub solo hace de proxy de protocolo (reenvío transparente de tools/list y tools/call):

  • duck: el proyecto original es TypeScript (sandbox de VM para resolver anti-bot challenges + huella TLS de Chrome 134). Portarlo a Python sería demasiado costoso; el proyecto entero está en duck-mcp/ y en la imagen se ejecuta dist/index.js con Node 22.

  • academic: es Python puro, pero sus dependencias (fastmcp) chocan con el mcp==1.2.0 del hub, así que está instalado en un venv independiente: /opt/academic-venv.

La implementación común está en mcps/_stdio_bridge.py; cada llamada abre una sesión nueva y la cierra al terminar. No es pereza: es lo que pide anyio. El motivo está en el «Archivo de errores #1»; no añadas caché de sesión.

Endpoints

MCP

Endpoint SSE

Herramientas

Búsqueda Doubao

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

web_search_custom (versión Custom, web+imagen, con filtros por sitio/industria/nivel de autoridad) / web_search_global (versión Global, mixto texto+imagen, con filtros de PDF, dominio ICP, relación de aspecto, etc.)

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 (versión antigua, hace scraping del HTML, fácil que DDG bloquee por anti-bot)

DuckDuckGo (puente TS original)

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 (original de hung319/duck-mcp, puente de subproceso Node, resolución de retos de VM + huella TLS de Chrome 134, resistente a anti-bot)

Artículos académicos

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

paper_search / paper_download / paper_read (nalkalin/academic-mcp, puente de subproceso con venv propio, 18 fuentes académicas: arXiv/PubMed/PMC/bioRxiv/medRxiv/Semantic Scholar/CrossRef/IACR/CORE, sin clave)

Estado real de los endpoints (2026-08-20)

Endpoint

tools/list

Llamada real

Notas

/doubao/sse

La cuota gratuita de Custom+Global es de 500 usos/mes, no la agotes.

/zhihu/sse

/academic/sse

✅ 3 herramientas

✅ devuelve artículos reales

arXiv funciona; las fuentes sin clave (Scopus/WOS/CORE/IEEE…) solo dan warning, no rompen nada.

/duck/sse

✅ 9 herramientas

⚠️ el puente conecta, pero el upstream bloquea

DDG devuelve un anti-bot challenge a las IPs del centro de datos de HF; no es un problema de código, hay que cambiar de IP / usar proxy.

/ddg/sse

⚠️

Versión antigua, el anti-bot la bloquea más fácil; se queda para REST, se puede borrar.

La trampa de los parámetros de academic

paper_search / paper_download reciben un array de objetos query_list, no un string:

{"query_list": [{"query": "quantum computing", "searcher": "arxiv", "max_results": 2}]}

Si omites searcher, busca en todas las fuentes (lento). paper_read usa {"searcher": ..., "paper_id": ...}.

Endpoints REST (para la app Android rikkahub, no MCP)

Método

Ruta

body

Respuesta

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:{...}}]}

La respuesta es totalmente isomórfica con SearchResult / ScrapedResult de rikkahub; el cliente solo tiene que deserializar. La autenticación también es Authorization: Bearer <DDG_KEY>; en caso de error devuelve {"detail": "..."}.

Autenticación

Cada MCP tiene su propia clave Bearer independiente (Authorization: Bearer <key>):

MCP

key env

Valor por defecto

doubao

DOUBAO_KEY

wei123..

zhihu

ZHIHU_KEY

wei123..

ddg

DDG_KEY

wei123..

duck

DUCK_KEY

wei123..

academic

ACADEMIC_KEY

wei123..

Si la variable de entorno está configurada, se usa su valor; si no, el valor por defecto. En la página de inicio GET / se puede ver si la autenticación de cada endpoint está configurada y si los secretos de aguas arriba están listos.

Secretos de aguas arriba (ponlos en HF Space Settings → Secrets, no los subas al repositorio)

env

Propósito

VOLCENGINE_ARK_API_KEY

Clave API de la versión Custom de Búsqueda Doubao de Volcengine Ark (obligatoria; si no configuras la versión Global, se usará esta como respaldo).

VOLCENGINE_GLOBAL_API_KEY

Clave específica para la versión Global de Búsqueda Doubao (opcional; se crea en «Gestión de claves API - pago por uso». Si no la pones, la versión Global usará la clave ARK y probablemente dé el error 700901).

ZHIHU_ACCESS_SECRET

Access Secret de la plataforma abierta de Zhihu.

Añadir un MCP nuevo

Pon un .py en mcps/; no hace falta tocar main.py:

"""第一行 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]:
    ...
  • No escribas if __name__ == "__main__": server.run(...) — el puerto y las rutas los gestiona el hub.

  • Para montar varios endpoints con un solo .py: MOUNTS = {"path1": srv1, "path2": srv2}.

  • Para añadir rutas REST extra (montaje único): ROUTES = [starlette.Route("/xxx", endpoint=..., methods=["POST"])], se montarán bajo la ruta del plugin.

  • Para importar librerías del mismo directorio: import _xxx (los archivos que empiezan por guion bajo no se cargan como plugins).

  • Si un plugin falla al importar, solo saldrá en broken de /; no afecta a los demás.

Ejecución local

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

Cadena de compilación y despliegue

El entorno de compilación de HF Space tiene muchas limitaciones (no se puede instalar bun, ni siquiera hay curl), así que no se compila en HF:

改代码 → push GitHub(fuwei99/hub-mcp) → Actions 构建镜像 → 推 GHCR
                                                              ↓
                                    HF 的 Dockerfile 只 FROM 拉现成镜像

Los dos Dockerfile tienen contenido distinto y cada uno se encarga de una cosa:

Ubicación

Contenido

Función

Dockerfile de GitHub

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

Compila la imagen real

Dockerfile de HF

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

Solo tira de la imagen ya compilada

🚨 Regla de hierro

1. El Dockerfile de HF no debe sincronizarse jamás a GitHub. Si no, las Actions harán un «build en muñeca rusa»: reutilizan la imagen anterior y la vuelven a subir tal cual, sin ejecutar ni un solo COPY; la imagen siempre será el código viejo, pero el build dirá success. Ya ha pasado dos veces (ver archivo de errores #2). El detonante: si el remoto del repositorio local apunta a HF, no ejecutes git checkout origin/main -- Dockerfile sobre el Dockerfile, porque arrastrará la versión de HF al local y la subirá a GitHub.

2. En HF fija el digest, no uses :latest. El build de HF cachea el digest antiguo de latest; si la etiqueta no cambia, no vuelve a bajar las capas → el código cambia pero en producción sigue la versión vieja.

3. Antes de desplegar, verifica; no te fíes del "success". Saca la capa de código de GHCR y comprueba que los archivos están bien (método abajo); es mucho más rápido que chocarte una y otra vez con los logs de producción.

Flujo estándar para cambiar de imagen

# 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

Verificación: sacar archivos de GHCR

Sin necesidad de docker, con un simple curl se puede desempaquetar la capa de la imagen (la forma definitiva de saber si el build ha ido bien):

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

En los logs de Actions también se ve de un vistazo el «build en muñeca rusa»: un build normal tiene COPY y tarda 1-2 minutos; el de muñeca rusa solo tiene resolve ghcr.io/... done + exporting layers, en 2 segundos está.


Archivo de errores

#1 ⭐ El ámbito de cancelación de anyio no puede cruzar tareas (la causa real del cuelgue del puente de subprocesos)

Síntoma: /duck/sse y /academic/sse conectan, initialize responde al instante, pero tools/list se queda en silencio para siempre — sin error, sin timeout, solo pings en el SSE. Cualquier cliente MCP se queda «colgado».

Pistas que se descartaron (ninguna era la causa): conexión SSE larga, script de prueba, que node no arrancara, contaminación del stdout por el banner (el banner va por stderr, el stdout está limpio).

Causa real: stdio_client() y ClientSession() son contextos anyio ligados a la tarea. El puente, para ahorrar recursos, hacía __aenter__ en la tarea de la petición A y cacheaba la sesión en una variable global para reutilizarla en la petición B. Pero en el hub cada conexión SSE es una tarea independiente, así que:

RuntimeError: Attempted to exit cancel scope in a different task than it was entered in

El comportamiento es muy traicionero: initialize responde porque lo responde la propia carcasa del puente, sin tocar el subproceso; en cuanto llega tools/list, que sí necesita reenviar al subproceso, muere en el ámbito de cancelación entre tareas.

Reproducción (script local de 30 líneas, sin desplegar):

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())

Solución: mcps/_stdio_bridge.py — cada list_tools/call_tool abre el subproceso dentro de la tarea actual, lo cierra con async with y lo cierra al terminar; solo se cachea la descripción de herramientas (datos puros, se pueden compartir entre tareas).

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)

No intentes «optimizar» con una sesión compartida. Si de verdad quieres ir más rápido, la forma correcta es un worker task de larga duración + cola, y que todo el IO se haga dentro de esa tarea, no pasar el objeto de contexto entre tareas.

Lección extra: ClientSession(read, write) sin __aenter__ también se cuelga — la tarea de fondo «leer stdout → despachar respuesta» solo se lanza dentro de __aenter__; si no entras en el contexto, las peticiones enviadas se quedan sin respuesta.

#2 ⭐ Build en muñeca rusa (la imagen siempre es código viejo, pero el build dice success)

Síntoma: cambias el código, Actions da success, HF reconstruye y queda RUNNING, pero el comportamiento en producción no cambia. Se sospechó de la caché de HF, de GHCR, de las capas... y no era nada de eso.

Cómo se localizó: se sacó la capa de COPY mcps/ de GHCR y se hizo tar tzf — el _stdio_bridge.py recién añadido ni siquiera estaba en la imagen, aunque en GitHub sí estaba. Y al mirar los logs de Actions:

#1 transferring dockerfile: 647B          ← 完整版有 2.7KB
#5 resolve ghcr.io/fuwei99/hub-mcp@sha256:0799864b... done
#7 exporting layers done                  ← 全程 2 秒,零 COPY

Causa real: el Dockerfile del repositorio de GitHub se había convertido en el de HF, FROM ghcr.io/fuwei99/hub-mcp@sha256:... — las Actions reutilizaban la imagen anterior y la volvían a subir tal cual.

Cómo se coló: el remoto del repositorio local era HF, se ejecutó git checkout origin/main -- Dockerfile, que trajo la versión de HF al área de trabajo, y al hacer push a GitHub se subió junto.

A prueba de tontos (ya añadido a .github/workflows/build.yml; si vuelve a pasar, el build falla en el acto):

- 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; }

Además, en el Dockerfile se ha añadido una autocomprobación en tiempo de compilación: test -f mcps/_stdio_bridge.py || exit 1.

#3 El infierno de dependencias de academic-mcp

El academic-mcp==0.1.7 de aguas arriba no fija el límite superior de dependencias, y la combinación que instala está rota. Tres errores seguidos:

Error

Causa

No module named 'pydantic_settings'

fastmcp lo necesita, pero academic-mcp no lo declara.

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

Se instaló mcp 2.0.0, pero fastmcp necesita McpError de mcp 1.x (en 2.0 se renombró a MCPError).

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

Al hacer pip install -U fastmcp en dos pasos, el paquete se convirtió en un paquete de espacio de nombres vacío.

Solución: instalar todo de una vez, fijar el límite superior explícitamente y hacer una autocomprobación de import en tiempo de compilación:

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')"

Combinación probada: academic-mcp 0.1.7 + fastmcp 3.4.7 (o 2.14.1) + mcp 1.29.0

  • pydantic-settings 2.15.0.

Lección: al actualizar dependencias, no hagas pip install en dos pasos y luego pip install -U; instala todo de una vez para que el resolvedor decida de forma unificada. La combinación de dependencias hay que probarla en un venv local antes de escribirla en el Dockerfile, y poner la autocomprobación de import en la fase de compilación: si algo falla, que falle el build, no que salte en los logs de producción.

#4 Otros

Síntoma

Causa

Solución

La descarga de bun da exit 127

python:3.12-slim no tiene curl

Primero apt install curl

bun Permission denied

Limitaciones del entorno de compilación de HF

Usar el tarball oficial de Node 22 y descomprimir con python tarfile (el tar ya trae el bit de ejecución, ni siquiera hace falta xz-utils).

stdio_client() da error de env/args

API antigua de mcp==1.2.0: solo acepta un objeto StdioServerParameters

Pasar un solo argumento según la firma antigua.

academic da FileNotFoundError: [Errno 2]

xlin.xmap_asyncProcessPoolExecutormultiprocessing.Lock necesita /dev/shm; no existe en el sandbox de proot.

Es una limitación del entorno local; en HF/docker funciona. En el directorio de descarga, poner ACADEMIC_MCP_DOWNLOAD_PATH=/tmp/papers.

Divergencia local/remoto de HF

Push en paralelo en ambos lados

Hacer rebase y forzar el push; o clonar limpio para desplegar.

Metodología de resolución de problemas (la parte que ahorra tiempo)

  1. No uses un cliente MCP de Python para depurar cuelgues — él mismo se cuelga y no ves nada. Usa curl a mano y mira quién no responde:

    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. Localiza por capas: carcasa del puente → ¿puede ejecutarse el subproceso por separado? → llama directamente a la librería del subproceso. En este caso, ArxivSearcher().search() funciona si se llama directo, así que el buscador está bien; el problema está en la capa de empaquetado.

  3. Chocarse contra los logs de producción es lo más caro. Si puedes reproducirlo en local con el hub, no lo pruebes en producción; los problemas de dependencias se meten en la autocomprobación de la fase de compilación para que revienten en las Actions.

  4. serverInfo.version no es la versión del código (es la versión de la librería mcp). Para saber si en producción hay código nuevo, busca una cadena que solo exista en la versión nueva, como el texto de about que devuelve 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