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 Server,路径区分,各自独立鉴权 key。 按 local-mcp-hub 的插件玩法重构:一个 MCP 一个 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 server 当子进程拉起来,用 stdio 跟它说话, hub 只做协议转发(tools/listtools/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 端点

工具

豆包搜索

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 挑战解法 + Chrome134 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 学术源,免 key)

端点实测状态(2026-08-20)

端点

tools/list

实际调用

备注

/doubao/sse

免费额度 Custom+Global 共用 500 次/月,别耗尽

/zhihu/sse

/academic/sse

✅ 3 工具

✅ 真论文返回

arXiv 走通,缺 key 的源(Scopus/WOS/CORE/IEEE…)只是警告不影响

/duck/sse

✅ 9 工具

⚠️ 桥通、上游被拦

DDG 对 HF 数据中心 IP 返回 anti-bot challenge,非代码问题,要换 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 端点(给 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:{...}}]}

返回体与 rikkahub 的 SearchResult / ScrapedResult 完全同构,客户端直接反序列化即可。 鉴权同为 Authorization: Bearer <DDG_KEY>,出错返回 {"detail": "..."}

鉴权

每个 MCP 独立 Bearer key(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 / 首页可看各端点鉴权是否配置、上游 secret 有没有就位。

上游 Secrets(放 HF Space Settings → Secrets,勿写进仓库)

env

用途

VOLCENGINE_ARK_API_KEY

火山方舟豆包搜索 Custom 版 API key(必配,Global 版未配时回落用它)

VOLCENGINE_GLOBAL_API_KEY

豆包搜索 Global 版专用 key(可选,去「API Key管理-按量后付费」创建,不配则 Global 版会用 ARK key 并大概率报 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(...)——端口和路由归 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 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)。 具体地雷:本地仓库 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 tz

Actions 日志里也能一眼看出套娃:正常构建有 COPY、跑 1~2 分钟; 套娃构建只有 resolve ghcr.io/... done + exporting layers2 秒完事


踩坑档案

#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 没锁依赖上限,装出来的组合是坏的。三连报错:

报错

原因

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)

分两步 pip install -U fastmcp 把包装成了空壳命名空间残包

修复:一条命令装齐、显式锁上限,并在构建期做 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 installpip install -U,一次装齐让解析器统一决策; 依赖组合务必本地 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 对象

按旧签名传单参数

academic 报 FileNotFoundError: [Errno 2]

xlin.xmap_asyncProcessPoolExecutormultiprocessing.Lock 需要 /dev/shm;proot 沙箱里没有

纯本地环境限制,HF/docker 正常。下载目录另设 ACADEMIC_MCP_DOWNLOAD_PATH=/tmp/papers

本地/HF 远端分叉

两边并行推送

rebase 后强推;或干净 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. 在线上日志里撞墙最贵。能本地起 hub 复现的,绝不推线上试; 依赖问题放进构建期自检,让它在 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