MCP Hub
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 22dist/index.js.academic: чистый Python, но зависимости (fastmcp) конфликтуют с
mcp==1.2.0хаба, поэтому установлен в отдельный venv/opt/academic-venvдля изоляции.
Общая реализация в mcps/_stdio_bridge.py — каждый вызов создаёт отдельную сессию и закрывает её сразу после использования — это не лень, а вынужденная мера из-за anyio, причина в «Архиве граблей #1», не добавляйте кэш сессий.
Эндпоинты
MCP | SSE-эндпоинт | Инструменты |
豆包搜索 |
|
|
知乎 |
|
|
DuckDuckGo |
|
|
DuckDuckGo (мост оригинального TS) |
|
|
Научные статьи |
|
|
Статус проверки эндпоинтов (2026-08-20)
Эндпоинт | tools/list | Фактический вызов | Примечание |
| ✅ | ✅ | Бесплатный лимит Custom+Global общий 500 запросов/месяц, не исчерпайте |
| ✅ | ✅ | |
| ✅ 3 инструмента | ✅ возвращает реальные статьи | arXiv работает, источники без ключа (Scopus/WOS/CORE/IEEE…) только предупреждают, не влияют |
| ✅ 9 инструментов | ⚠️ мост работает, но upstream блокируется | DDG возвращает anti-bot challenge для IP дата-центра HF, не проблема кода, нужно сменить IP / использовать прокси |
| ✅ | ⚠️ | Старая версия, анти-бот убивает чаще, оставлено для 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 |
|
|
|
POST |
|
|
|
Тело ответа полностью изоморфно SearchResult / ScrapedResult из rikkahub, клиент может напрямую десериализовать.
Аутентификация та же: Authorization: Bearer <DDG_KEY>, при ошибке возвращается {"detail": "..."}.
Аутентификация
Каждый MCP имеет отдельный Bearer-ключ (Authorization: Bearer <key>):
MCP | key env | Значение по умолчанию |
doubao |
|
|
zhihu |
|
|
ddg |
|
|
duck |
|
|
academic |
|
|
Если env задан, используется его значение, иначе — значение по умолчанию. На главной странице GET / видно, настроена ли аутентификация для каждого эндпоинта и на месте ли секреты upstream.
Секреты upstream (хранить в HF Space Settings → Secrets, не коммитить в репозиторий)
env | Назначение |
| API-ключ Custom-версии 豆包搜索 от 火山方舟 (обязателен, используется как запасной для Global-версии, если она не настроена) |
| Отдельный ключ для Global-версии 豆包搜索 (необязательно, создаётся в «API Key管理-按量后付费»; если не настроен, Global-версия использует ARK-ключ и, скорее всего, вернёт ошибку 700901) |
| 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 |
| Действительно собирает образ |
HF |
| Только тянет готовый образ и запускает |
🚨 Железные правила
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 не ограничивает верхние границы зависимостей, и установленная комбинация сломана. Три ошибки подряд:
Ошибка | Причина |
| fastmcp требует его, но academic-mcp не объявляет |
| Подтянулся |
| Двухшаговая установка |
Исправление: установить всё одной командой, явно ограничить верхние границы и добавить самопроверку импорта на этапе сборки:
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 | в | сначала |
bun | Ограничение среды сборки HF | Заменить на официальный tarball Node 22, распаковать через |
|
| Передавать один параметр по старой сигнатуре |
academic ошибка |
| Ограничение только локальной среды, в HF/docker нормально. Для каталога загрузки задать |
Расхождение локального и HF remote | Параллельные push с обеих сторон | После rebase — force push; или чистый clone специально для развёртывания |
Методология отладки (часть, экономящая время)
Не используйте 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 不回 → 问题在桥拉子进程那一步Послойная локализация: мост → может ли подпроцесс работать отдельно → прямой вызов библиотечной функции подпроцесса. В этом примере
ArxivSearcher().search()напрямую работает, значит, сама поисковая часть в порядке, проблема в обёртке.Биться головой о логи на проде — дороже всего. Если можно воспроизвести локально, подняв хаб, никогда не пробуйте на проде; проблемы с зависимостями выносите в самопроверку на этапе сборки, пусть падает в Actions.
serverInfo.version— это не версия кода (это версия библиотеки mcp). Чтобы понять, новый ли код на проде, ищите строку, которая есть только в новой версии, например, текст about, возвращаемыйGET /.
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