Skip to main content
Glama

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

MCP Hub

1つのHF Spaceに複数のMCP Serverをマウントし、パスで区別して、それぞれ独立した認証キーを持ちます。 local-mcp-hub のプラグイン方式に合わせて再構築:1つのMCPに1つのpymain.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/listtools/call をそのまま透過):

  • duck:上流はTSプロジェクト(VMサンドボックスでanti-botチャレンジを解決 + Chrome134 TLSフィンガープリント)。 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チャレンジ解法 + Chrome 3 TLSフィンガープリント、反爬対策)

学術論文

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

paper_search / paper_download / paper_read(nalkalin/academic-mcp、独立venv子プロセスブリッジ、arXiv/PubMed/PMC/bioRxiv/medRxiv/Semantic Scholar/CrossRef/IACR/CORE など18の学術ソース、キー不要)

エンドポイントの実測ステータス(2026-08-20)

エンドポイント

tools/list

実際の呼び出し

備考

/doubao/sse

無料枠はCustom+Globalで合計500回/月、使い切らないように注意

/zhihu/sse

/academic/sse

✅ 3ツール

✅ 実際の論文を返す

arXivは成功、キー不足のソース(Scopus/WOS/CORE/IEEE…)は警告のみで影響なし

/duck/sse

✅ 9ツール

⚠️ ブリッジは接続、上流がブロック

DDGがHFデータセンターのIPにanti-botチャレンジを返す、コードの問題ではない、IP変更/プロキシが必要

/ddg/sse

⚠️

旧版、反スクレイピングで死にやすい、REST用に残してある、削除を検討してもよい

academicのパラメータの落とし穴

paper_search / paper_downloadquery_list オブジェクトの配列を受け取ります。文字列ではありません:

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

searcher を省略 = 全ソースを検索(遅い)。paper_read{"searcher": ..., "paper_id": ...} です。

RESTエンドポイント(rikkahub Android用、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:{...}}]}

戻り値はrikkahubの SearchResult / ScrapedResult と完全に同型なので、クライアントはそのままデシリアライズできます。 認証も 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が設定されていればenvの値を使い、なければデフォルトを使います。GET / のホームページで各エンドポイントの認証設定状況や上流のシークレットが配置されているかを確認できます。

上流のシークレット(HF Space Settings → Secrets に置く、リポジトリには書かない)

env

用途

VOLCENGINE_ARK_API_KEY

火山方舟豆包検索 Custom 版 API キー(必須、Global 版未設定時はこれにフォールバック)

VOLCENGINE_GLOBAL_API_KEY

豆包検索 Global 版専用キー(任意、「APIキー管理-従量課金」で作成、未設定だと Global 版は ARK キーを使い、高確率で 700901 エラー)

ZHIHU_ACCESS_SECRET

知乎オープンプラットフォームの Access Secret

新しいMCPを追加する

mcps/ にpyファイルを置くだけ、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(...) —— ポートとルーティングはハブが管理します。

  • 1つのpyで複数のエンドポイントをマウント:MOUNTS = {"path1": srv1, "path2": srv2}

  • 追加のRESTルートを持たせたい場合(単一マウント):ROUTES = [starlette.Route("/xxx", endpoint=..., methods=["POST"])]、このプラグインのパスの下にマウントされます。

  • 同じディレクトリのライブラリファイルを参照したい場合:import _xxx(アンダースコアで始まるファイルはプラグインとして読み込まれません)。

  • 単一のプラグインのimport失敗は /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. HF の Dockerfile を GitHub に同期してはいけない。 そうしないと Actions が「入れ子ビルド」を起こします:前のバージョンのイメージをその場で転用して再プッシュし、COPY ステップが一切実行されず、 イメージは常に古いコードのまま、ビルドは success と表示される。すでに2回失敗しています(ハマりどころ #2 参照)。 具体的な地雷:ローカルリポジトリの remote が HF を指している場合、Dockerfile に対して git checkout origin/main -- Dockerfile を実行しないでください。HF 版をローカルに取り込んで、一緒に GitHub にプッシュしてしまいます。

2. HF 側は digest を固定し、:latest を使わない。 HF のビルドは latest の古い digest をキャッシュするため、タグが変わらなければレイヤーを再取得しません → コードを変更しても本番は古いまま。

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 はタスクをまたげない(子プロセスブリッジがハングする真の原因)

現象/duck/sse/academic/sse には接続でき、initialize は即座に返るが、tools/list 永久に沈黙 —— エラーも出ず、タイムアウトもせず、SSE には ping だけが流れる。どの MCP クライアントでも「ハング」します。

誤って判断した方向(どれも原因ではない):SSE 長接続、テストスクリプト、node が起動しない、 banner が stdout を汚す(banner は stderr に出力されるので、stdout はクリーン)。

真の原因stdio_client()ClientSession() はどちらも task-bound の anyio コンテキストです。 ブリッジは当初、オーバーヘッドを減らすために、A リクエストのタスク内で __aenter__ した後、セッションをグローバルにキャッシュして B リクエストで再利用していました。 しかし、ハブでは各 SSE 接続が独立したタスクなので、次のようになります:

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

その挙動は非常に陰険です:initialize が返るのは、ブリッジシェルが自分で答えただけで、子プロセスには触れていないからです。 tools/list で実際の子プロセス転送が必要になると、タスクをまたいだ cancel scope で死にます。

再現(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現在のタスク内で 子プロセスを起動し、async with で閉じ、使い終わったら即閉じます。ツールの説明(純データ、タスクをまたげる)だけをキャッシュします。

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)

共有セッションに「最適化」しないでください。本当に高速化したいなら、専用の常駐ワーカータスク + キューを起動し、すべてのIOをそのタスク内で行うのが正しい方法です。コンテキストオブジェクトをタスク間で渡すのはやめてください。

ついでの教訓ClientSession(read, write) を new するだけで __aenter__ しないとハングします —— バックグラウンドの「stdout を読む → レスポンスをディスパッチ」タスクは __aenter__ 内で起動されるので、 コンテキストに入らなければ、送信したリクエストの応答を受け取る人がいません。

#2 ⭐ 入れ子ビルド(イメージは常に古いコード、ビルドは success と表示)

現象:コードを変更し、Actions が success、HF が RUNNING で再構築されたが、本番の挙動はまったく変わらない。 HF キャッシュ、GHCR キャッシュ、レイヤーキャッシュを疑ったが、どれも違った。

特定手段:GHCR から COPY mcps/ のレイヤーを取り出して 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

真の原因:GitHub リポジトリの Dockerfile が HF 版の FROM ghcr.io/fuwei99/hub-mcp@sha256:... になっていた —— Actions が古いイメージをその場で転用して再プッシュしていた。

どうやって混入したか:ローカルリポジトリの remote が HF で、git checkout origin/main -- Dockerfile を実行して HF 版を作業領域に取り込み、その後 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 は依存関係の上限を固定しておらず、インストールされた組み合わせが壊れています。3連続のエラー:

エラー

原因

No module named 'pydantic_settings'

fastmcp が必要だが、academic-mcp が宣言していない

cannot import name 'McpError'Did you mean MCPError? と表示)

mcp 2.0.0 が取得されたが、fastmcp は mcp 1.x の McpError を必要とする(2.0 で MCPError に改名)

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

2段階で pip install -U fastmcp を実行し、パッケージが空の名前空間の残骸になった

修正:1つのコマンドでまとめてインストールし、上限を明示的に固定し、ビルド時に import 自己チェックを実行:

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

教訓:依存関係のアップグレードは2段階の pip install をせず、1回でまとめてインストールしてパーサーに統一判断させる。 依存関係の組み合わせは必ずローカル venv で実測してから Dockerfile に書き込み、import 自己チェックをビルド時に組み込む —— インストールが間違っていればビルドが失敗し、本番ログでエラーを待つ必要がない。

#4 その他

現象

原因

修正

bun ダウンロード exit 127

python:3.12-slim に curl がない

先に apt install curl

bun Permission denied

HF ビルド環境の制限

Node 22 公式 tarball に変更、python tarfile で解凍(tar 内に実行ビットが含まれ、xz-utils も不要)

stdio_client() が env/args エラーを報告

mcp==1.2.0 の旧 API:StdioServerParameters オブジェクトを1つだけ受け取る

旧シグネチャに合わせて単一引数を渡す

academic が FileNotFoundError: [Errno 2] を報告

xlin.xmap_asyncProcessPoolExecutormultiprocessing.Lock/dev/shm を必要とする;proot サンドボックスにはない

純粋にローカル環境の制限、HF/docker では正常。ダウンロードディレクトリは別途 ACADEMIC_MCP_DOWNLOAD_PATH=/tmp/papers を設定

ローカル/HF リモートが分岐

両側で並行プッシュ

rebase 後に強制プッシュ;またはデプロイ専用にクリーンな clone を1つ作成

トラブルシューティングの方法論(時間を節約する部分)

  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 ライブラリのバージョン)。本番が新しいコードかどうかを判断するには、 新しいバージョンにしかない文字列を探す。例えば GET / が返す about のテキスト。

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