MCP Hub
title: Pioneer emoji: 🔥 colorFrom: purple colorTo: pink sdk: docker app_port: 7860 pinned: false license: mit
MCP Hub
一个 HF Space 挂多个 MCP Server,路径区分,各自独立鉴权 key。
按 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 server 当子进程拉起来,用 stdio 跟它说话,
hub 只做协议转发(tools/list、tools/call 原样透传):
duck:上游是 TS 项目(VM 沙箱解 anti-bot challenge + Chrome134 TLS 指纹), 移植成 Python 成本太高,整个项目塞进
duck-mcp/,镜像里用 node 22 跑dist/index.js。academic:纯 Python,但依赖(fastmcp)跟 hub 的
mcp==1.2.0打架, 所以装在独立 venv/opt/academic-venv里隔离。
公共实现在 mcps/_stdio_bridge.py,每次调用起一条独立会话、用完即关——
这不是偷懒,是被 anyio 逼的,原因见「踩坑档案 #1」,别去加 session 缓存。
端点
MCP | SSE 端点 | 工具 |
豆包搜索 |
|
|
知乎 |
|
|
DuckDuckGo |
|
|
DuckDuckGo(原版TS桥) |
|
|
学术论文 |
|
|
端点实测状态(2026-08-20)
端点 | tools/list | 实际调用 | 备注 |
| ✅ | ✅ | 免费额度 Custom+Global 共用 500 次/月,别耗尽 |
| ✅ | ✅ | |
| ✅ 3 工具 | ✅ 真论文返回 | arXiv 走通,缺 key 的源(Scopus/WOS/CORE/IEEE…)只是警告不影响 |
| ✅ 9 工具 | ⚠️ 桥通、上游被拦 | DDG 对 HF 数据中心 IP 返回 anti-bot challenge,非代码问题,要换 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 端点(给 rikkahub 安卓端,非 MCP)
方法 | 路径 | body | 返回 |
POST |
|
|
|
POST |
|
|
|
返回体与 rikkahub 的 SearchResult / ScrapedResult 完全同构,客户端直接反序列化即可。
鉴权同为 Authorization: Bearer <DDG_KEY>,出错返回 {"detail": "..."}。
鉴权
每个 MCP 独立 Bearer key(Authorization: Bearer <key>):
MCP | key env | 默认值 |
doubao |
|
|
zhihu |
|
|
ddg |
|
|
duck |
|
|
academic |
|
|
env 配置了就用 env 值,没配用默认。GET / 首页可看各端点鉴权是否配置、上游 secret 有没有就位。
上游 Secrets(放 HF Space Settings → Secrets,勿写进仓库)
env | 用途 |
| 火山方舟豆包搜索 Custom 版 API key(必配,Global 版未配时回落用它) |
| 豆包搜索 Global 版专用 key(可选,去「API Key管理-按量后付费」创建,不配则 Global 版会用 ARK key 并大概率报 700901) |
| 知乎开放平台 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(...)——端口和路由归 hub 管。一个 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 |
| 真正构建镜像 |
HF |
| 只拉现成镜像跑 |
🚨 铁律
1. HF 那份 Dockerfile 绝对不能同步回 GitHub。 否则 Actions 会「套娃构建」:从上一版镜像原地转手重推,
COPY一步都不执行, 镜像永远是旧代码,构建却显示 success。已栽两次(见踩坑档案 #2)。 具体地雷:本地仓库 remote 指向 HF 时,别对 Dockerfile 执行git checkout origin/main -- Dockerfile,会把 HF 版拉进本地再一起推去 GitHub。2. HF 侧钉 digest,不要用
:latest。 HF 构建会缓存 latest 的旧 digest,tag 不变就不重新拉层 → 代码改了线上还是旧的。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 tzActions 日志里也能一眼看出套娃:正常构建有 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 起不来、 banner 污染 stdout(banner 走的是 stderr,stdout 干净)。
真因:stdio_client() 和 ClientSession() 都是 task-bound 的 anyio 上下文。
桥当初为了省开销,在 A 请求的 task 里 __aenter__ 后把 session 缓存到全局给 B 请求复用。
而 hub 里每个 SSE 连接是独立 task,于是:
RuntimeError: Attempted to exit cancel scope in a different task than it was entered in表现极其阴险:initialize 能回是因为那是桥壳自己答的,根本没碰子进程;
一到 tools/list 需要真子进程转发,就死在跨 task 的 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 都在当前 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) 光 new 不 __aenter__ 也会挂 ——
后台的「读 stdout → 派发响应」task 是在 __aenter__ 里才启动的,
不进上下文就等于发出去的请求没人收回复。
#2 ⭐ 套娃构建(镜像永远是旧代码,构建却 success)
现象:改完代码、Actions success、HF 重建 RUNNING,但线上行为一点没变。 反复怀疑 HF 缓存、GHCR 缓存、layer 缓存,全不是。
定位手段:从 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 版拉进工作区,
之后 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 没声明 |
| 拉到了 |
| 分两步 |
修复:一条命令装齐、显式锁上限,并在构建期做 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。
教训:升级依赖别分两步 pip install 再 pip install -U,一次装齐让解析器统一决策;
依赖组合务必本地 venv 实测出来再写进 Dockerfile,并把 import 自检放进构建期 ——
装错就构建失败,而不是等线上日志报错。
#4 其他
现象 | 原因 | 修复 |
bun 下载 exit 127 |
| 先 |
bun | HF 构建环境限制 | 换 Node 22 官方 tarball, |
|
| 按旧签名传单参数 |
academic 报 |
| 纯本地环境限制,HF/docker 正常。下载目录另设 |
本地/HF 远端分叉 | 两边并行推送 | rebase 后强推;或干净 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()直接调是好的,说明搜索本体没问题,锅在包装层。在线上日志里撞墙最贵。能本地起 hub 复现的,绝不推线上试; 依赖问题放进构建期自检,让它在 Actions 里就炸。
serverInfo.version不是代码版本(那是 mcp 库版本)。判断线上是不是新代码, 要找只有新版才有的字符串,比如GET /返回的 about 文案。
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