Skip to main content
Glama

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

MCP Hub

Один HF Space размещает несколько MCP-серверов, различаемых по пути, каждый со своим ключом аутентификации. Переработано по плагинной схеме local-mcp-hub: один MCP — один py, main.py автоматически обнаруживает и подключает, для добавления нового MCP не нужно менять главный файл.

Related MCP server: MCP Hub

Структура

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

Мост подпроцессов (duck / academic)

Эти два не реализованы самостоятельно, а запускают оригинальный MCP-сервер как подпроцесс и общаются с ним через stdio; хаб только пересылает протокол (tools/list, tools/call передаются как есть):

  • duck: исходный проект на TS (VM-песочница для решения anti-bot challenge + TLS-отпечаток Chrome134), перенос на Python слишком дорог, весь проект помещён в duck-mcp/, в образе запускается через node 22 dist/index.js.

  • academic: чистый Python, но зависимости (fastmcp) конфликтуют с mcp==1.2.0 хаба, поэтому установлен в отдельный venv /opt/academic-venv для изоляции.

Общая реализация в mcps/_stdio_bridge.pyкаждый вызов создаёт отдельную сессию и закрывает её сразу после использования — это не лень, а вынужденная мера из-за anyio, причина в «Архиве граблей #1», не добавляйте кэш сессий.

Эндпоинты

MCP

SSE-эндпоинт

Инструменты

豆包搜索

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

web_search_custom (версия Custom, web+image, поддержка ограничения по сайтам/отраслям/уровню авторитетности и т.д.) / web_search_global (версия Global, смешанные текст и изображения, поддержка pdf, ограничение ICP, фильтр по короткой стороне/соотношению сторон)

知乎

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 (старая версия, парсит html, легко блокируется анти-ботом DDG)

DuckDuckGo (мост оригинального TS)

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, мост через node-подпроцесс, решение VM-челленджа + TLS-отпечаток Chrome134, устойчивость к анти-боту)

Научные статьи

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

paper_search / paper_download / paper_read (nalkalin/academic-mcp, мост через отдельный venv-подпроцесс, 18 академических источников: arXiv/PubMed/PMC/bioRxiv/medRxiv/Semantic Scholar/CrossRef/IACR/CORE и др., без ключа)

Статус проверки эндпоинтов (2026-08-20)

Эндпоинт

tools/list

Фактический вызов

Примечание

/doubao/sse

Бесплатный лимит Custom+Global общий 500 запросов/месяц, не исчерпайте

/zhihu/sse

/academic/sse

✅ 3 инструмента

✅ возвращает реальные статьи

arXiv работает, источники без ключа (Scopus/WOS/CORE/IEEE…) только предупреждают, не влияют

/duck/sse

✅ 9 инструментов

⚠️ мост работает, но upstream блокируется

DDG возвращает anti-bot challenge для IP дата-центра HF, не проблема кода, нужно сменить IP / использовать прокси

/ddg/sse

⚠️

Старая версия, анти-бот убивает чаще, оставлено для REST, можно удалить

Подводные камни параметров academic

paper_search / paper_download принимают массив объектов query_list, а не строку:

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

Если searcher опущен — поиск по всем источникам (медленно). paper_read принимает {"searcher": ..., "paper_id": ...}.

REST-эндпоинты (для Android-клиента rikkahub, не MCP)

Метод

Путь

body

Ответ

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

Тело ответа полностью изоморфно SearchResult / ScrapedResult из rikkahub, клиент может напрямую десериализовать. Аутентификация та же: Authorization: Bearer <DDG_KEY>, при ошибке возвращается {"detail": "..."}.

Аутентификация

Каждый MCP имеет отдельный Bearer-ключ (Authorization: Bearer <key>):

MCP

key env

Значение по умолчанию

doubao

DOUBAO_KEY

wei123..

zhihu

ZHIHU_KEY

wei123..

ddg

DDG_KEY

wei123..

duck

DUCK_KEY

wei123..

academic

ACADEMIC_KEY

wei123..

Если env задан, используется его значение, иначе — значение по умолчанию. На главной странице GET / видно, настроена ли аутентификация для каждого эндпоинта и на месте ли секреты upstream.

Секреты upstream (хранить в HF Space Settings → Secrets, не коммитить в репозиторий)

env

Назначение

VOLCENGINE_ARK_API_KEY

API-ключ Custom-версии 豆包搜索 от 火山方舟 (обязателен, используется как запасной для Global-версии, если она не настроена)

VOLCENGINE_GLOBAL_API_KEY

Отдельный ключ для Global-версии 豆包搜索 (необязательно, создаётся в «API Key管理-按量后付费»; если не настроен, Global-версия использует ARK-ключ и, скорее всего, вернёт ошибку 700901)

ZHIHU_ACCESS_SECRET

Access Secret открытой платформы 知乎

Добавление нового MCP

Положите py-файл в mcps/, 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]:
    ...
  • Не пишите if __name__ == "__main__": server.run(...) — порт и маршруты управляются хабом.

  • Если один py должен обслуживать несколько эндпоинтов: MOUNTS = {"path1": srv1, "path2": srv2}.

  • Для дополнительных REST-маршрутов (одиночный монтаж): ROUTES = [starlette.Route("/xxx", endpoint=..., methods=["POST"])], они будут смонтированы в пути этого плагина.

  • Для импорта библиотечных файлов из той же директории: import _xxx (файлы, начинающиеся с подчёркивания, не загружаются как плагины).

  • Если импорт одного плагина не удался, ошибка появится только в broken на /, не влияя на другие плагины.

Локальный запуск

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

Цепочка сборки и развёртывания

Среда сборки HF Space имеет много ограничений (bun не устанавливается, curl отсутствует), поэтому сборка не выполняется в HF:

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

Два Dockerfile различаются по содержанию, каждый отвечает за своё:

Расположение

Содержимое

Назначение

GitHub Dockerfile

FROM python:3.12-slim + установка node/venv + COPY

Действительно собирает образ

HF Dockerfile

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

Только тянет готовый образ и запускает

🚨 Железные правила

1. Dockerfile из HF ни в коем случае нельзя синхронизировать обратно в GitHub. Иначе Actions выполнит «матрёшечную сборку»: перетолкнёт предыдущий образ как есть, ни один шаг COPY не выполнится, образ всегда будет со старым кодом, но сборка покажет success. Уже наступали на это дважды (см. Архив граблей #2). Конкретная мина: когда remote локального репозитория указывает на HF, не выполняйте git checkout origin/main -- Dockerfile для Dockerfile — это затянет HF-версию в локаль и затем отправит её в GitHub.

2. На стороне HF фиксируйте digest, не используйте :latest. Сборка HF кэширует старый digest для latest, если тег не меняется, слои не перетягиваются → код изменился, а на проде остаётся старый.

3. Перед развёртыванием проверяйте результат, не верьте слепо «success». Достаньте слой кода из GHCR и проверьте файлы (метод ниже) — это быстрее, чем раз за разом биться головой о логи на проде.

Стандартный процесс замены образа

# 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

Проверка: достать файлы из GHCR

Не нужен docker, чистый curl позволяет разобрать слои образа (крайнее средство проверки, действительно ли сборка применилась):

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

В логах Actions матрёшку видно сразу: нормальная сборка содержит COPY и длится 1–2 минуты; матрёшечная сборка имеет только resolve ghcr.io/... done + exporting layers и завершается за 2 секунды.


Архив граблей

#1 ⭐ anyio cancel scope не может пересекать task (настоящая причина зависания моста подпроцессов)

Симптом: /duck/sse, /academic/sse подключаются, initialize отвечает мгновенно, но tools/list молчит вечно — нет ошибок, нет таймаута, в SSE только ping. Любой MCP-клиент «зависает».

Ошибочные направления (не были причиной): длинное SSE-соединение, тестовый скрипт, node не запускается, баннер загрязняет stdout (баннер идёт в stderr, stdout чист).

Настоящая причина: stdio_client() и ClientSession() — это task-bound контексты anyio. Мост для экономии ресурсов в task запроса A выполнял __aenter__ и кэшировал session в глобальную переменную для повторного использования запросом B. А в хабе каждое SSE-соединение — отдельный task, поэтому:

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

Проявление крайне коварное: initialize отвечает, потому что это отвечает сам мост, не трогая подпроцесс; как только tools/list требует реальной пересылки в подпроцесс, всё умирает на cancel scope, пересекающем task.

Воспроизведение (локальный скрипт на 30 строк, без развёртывания):

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

Исправление: mcps/_stdio_bridge.py — каждый list_tools/call_tool запускает подпроцесс внутри текущего task, закрывает через async with, завершает сразу после использования; кэшируется только описание инструментов (чистые данные, можно пересекать task).

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)

Не «оптимизируйте» до общей session. Если нужно ускорить, правильный способ — поднять отдельный долгоживущий worker task + очередь, и весь IO выполнять внутри этого task, а не передавать объекты контекста между task.

Попутный урок: ClientSession(read, write) без __aenter__ тоже зависает — фоновый task «чтение stdout → отправка ответов» запускается только в __aenter__, без входа в контекст отправленные запросы остаются без ответа.

#2 ⭐ Матрёшечная сборка (образ всегда со старым кодом, но сборка success)

Симптом: код изменён, Actions success, HF пересобрал RUNNING, но поведение на проде не изменилось. Подозревали кэш HF, кэш GHCR, кэш слоёв — всё не то.

Метод локализации: достали слой COPY mcps/ из GHCR и выполнили tar tzf — обнаружили, что новый _stdio_bridge.py вообще отсутствует в образе, хотя на GitHub он есть. Затем посмотрели логи Actions:

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

Настоящая причина: Dockerfile в репозитории GitHub превратился в HF-версию FROM ghcr.io/fuwei99/hub-mcp@sha256:... — Actions перетолкнул старый образ как есть.

Как это попало: remote локального репозитория указывал на HF, выполнили git checkout origin/main -- Dockerfile, что затянуло HF-версию в рабочую копию, и при push в GitHub она уехала вместе с остальным.

Защита от дурака (добавлено в .github/workflows/build.yml, при повторении сборка сразу упадёт):

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

Кроме того, в Dockerfile добавлена самопроверка на этапе сборки: test -f mcps/_stdio_bridge.py || exit 1.

#3 Ад зависимостей academic-mcp

Верхнеуровневый academic-mcp==0.1.7 не ограничивает верхние границы зависимостей, и установленная комбинация сломана. Три ошибки подряд:

Ошибка

Причина

No module named 'pydantic_settings'

fastmcp требует его, но academic-mcp не объявляет

cannot import name 'McpError' (подсказка Did you mean MCPError?)

Подтянулся mcp 2.0.0, а fastmcp нужен McpError из mcp 1.x (в 2.0 переименован в MCPError)

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

Двухшаговая установка pip install -U fastmcp превратила пакет в пустую оболочку с остатками пространства имён

Исправление: установить всё одной командой, явно ограничить верхние границы и добавить самопроверку импорта на этапе сборки:

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

Проверенная рабочая комбинация: academic-mcp 0.1.7 + fastmcp 3.4.7 (или 2.14.1) + mcp 1.29.0

  • pydantic-settings 2.15.0.

Урок: при обновлении зависимостей не делайте два шага pip install и затем pip install -U, устанавливайте всё сразу, чтобы резолвер принимал единое решение; комбинацию зависимостей обязательно проверяйте в локальном venv перед записью в Dockerfile, и включайте самопроверку импорта в этап сборки — при ошибке сборка падает, а не ждёте ошибок в логах на проде.

#4 Прочее

Симптом

Причина

Исправление

bun download exit 127

в python:3.12-slim нет curl

сначала apt install curl

bun Permission denied

Ограничение среды сборки HF

Заменить на официальный tarball Node 22, распаковать через python tarfile (в tar уже есть бит исполнения, даже xz-utils не нужен)

stdio_client() ошибка env/args

mcp==1.2.0 старый API: принимает только один объект StdioServerParameters

Передавать один параметр по старой сигнатуре

academic ошибка FileNotFoundError: [Errno 2]

xlin.xmap_asyncProcessPoolExecutormultiprocessing.Lock требует /dev/shm; в песочнице proot его нет

Ограничение только локальной среды, в HF/docker нормально. Для каталога загрузки задать ACADEMIC_MCP_DOWNLOAD_PATH=/tmp/papers

Расхождение локального и HF remote

Параллельные push с обеих сторон

После rebase — force push; или чистый clone специально для развёртывания

Методология отладки (часть, экономящая время)

  1. Не используйте Python MCP-клиент для отладки зависаний — он сам зависнет, и не будет видно, где затык. Используйте curl, вручную воспроизводя протокол, и смотрите по кадрам, кто не отвечает:

    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. Послойная локализация: мост → может ли подпроцесс работать отдельно → прямой вызов библиотечной функции подпроцесса. В этом примере ArxivSearcher().search() напрямую работает, значит, сама поисковая часть в порядке, проблема в обёртке.

  3. Биться головой о логи на проде — дороже всего. Если можно воспроизвести локально, подняв хаб, никогда не пробуйте на проде; проблемы с зависимостями выносите в самопроверку на этапе сборки, пусть падает в Actions.

  4. serverInfo.version — это не версия кода (это версия библиотеки mcp). Чтобы понять, новый ли код на проде, ищите строку, которая есть только в новой версии, например, текст about, возвращаемый 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