mcpField
G
Gopher & Gemini MCP Server

Gopher & Gemini MCP Server

本地服务
github.com/cameronrye浏览器自动化167 周前更新

关于 Gopher & Gemini MCP Server

Gopher & Gemini MCP Server 让 AI 助手以只读方式浏览 Gopher 和 Gemini 资源。它提供结构化输出,并包含 SSRF 防护、TLS、TOFU 证书检查和有界读取等安全措施。

主要功能

  1. 01抓取 Gopher 和 Gemini 内容
  2. 02批量抓取资源
  3. 03解析 Gemtext
  4. 04管理 TOFU 证书
  5. 05SSRF 防护与主机限制
  6. 06结构化 JSON 输出

适用场景

  1. 01浏览 Gopher 菜单和文本
  2. 02阅读 Gemini capsule 与 gemlog
  3. 03查看并更新 Gemini 信任证书
  4. 04以有界并发抓取多个资源

MCP 相关

该 MCP 服务提供 Gopher 和 Gemini 的抓取及批量抓取工具,以及 Gemini 信任和客户端证书管理工具。它还提供策略资源和两个导航提示词。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"cameronrye-gopher": {
"command": "uvx",
"args": [
"gopher-mcp"
]
}
}
}
启动命令uvx gopher-mcp

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypigopher-mcp0.10.2
ocighcr.io/cameronrye/gopher-mcp:0.10.2

工具

8

收录的工具列表 · 更新于 2 周前

参数类型必填说明
urlstring是完整的 gopher:// URL。路径的第一个字符是项目类型(1=菜单,0=文本文件,7=搜索)。通过菜单项中的 `next_url` 导航。查询类型 7 的搜索服务器时,提供搜索选择器本身的 URL——gopher://gopher.floodgap.com/7/v2/vs——并在 `search` 中传入搜索词,切勿将其作为额外路径段。将查询字符串写入 URL(gopher://gopher.floodgap.com/7/v2/vs?python)仍然有效,但 `search` 能正确处理 #、+ 和非 ASCII 字符。示例:gopher://gopher.floodgap.com/1/
offsetinteger否对于返回结果被截断的资源,从此处开始读取。传入上一个结果的 `next_offset`——对于 Gopher 菜单,它计数菜单项;对于页面正文,它计数字符——以获取下一个窗口;保持为 0(默认值)则从头开始读取。`truncated: true` 且带有 `next_offset` 的结果表示仍有更多内容:应从该位置继续,而不是将部分页面作为完整内容呈现;当 `next_offset` 返回 null 时停止。继续读取通常成本很低——资源只下载一次,后续窗口从中呈现——但有两个例外:关闭缓存时,每个窗口都会重新下载;带有查询的 GEMINI URL 总是重新下载,因为 status-10/11 提示的答案会通过该 URL 传递,且不会被保留。因此,仅在需要内容时继续读取,不要机械地读取。
searchany否类型 7(索引搜索)选择器的搜索词,例如 Veronica-2。搜索词会进行百分比编码并作为查询字符串发送,因此请直接传入用户输入的原始词语:如果将含有 #、+、& 或非 ASCII 字符的查询手动写入 URL,就会被截断或篡改,服务器随后会执行用户未请求的搜索。此参数会替换 `url` 中已有的任何查询。对于其他所有项目类型,请留空——RFC 1436 仅为类型 7 定义了查询字段,因此发送到其他类型的搜索会被丢弃。
refreshboolean否绕过此 URL 的缓存副本,重新从服务器获取。当用户想了解当前状态(例如“再检查一次”“他们发了吗?”“看起来过时了”),或先前结果返回了 `cached: true`,且 `cache_age_seconds` 太大,无法诚实地回答问题时,将其设为 true。使用 `offset` 获取的窗口也会报告 `cached: true`,因为它是从获取第一个窗口时下载的内容中呈现的,而非再次获取。`cache_age_seconds` 表示这些字节的缓存时长,与其他缓存结果相同;是否刷新仅取决于这个时长。在遍历过程中途刷新会丢弃该资源的所有窗口,而不仅是所请求的窗口,因此已读取的窗口下次读取时会重新下载——最好先完成遍历,再重新读取。普通浏览和跟随链接时保持 false:Gopher 和 Gemini 大多由小型爱好者主机提供,缓存可避免对它们重复请求流量。无论如何,返回的响应都会存储起来供后续读取。

详细介绍

Gopher 与 Gemini MCP 服务

CI Documentation PyPI version Python 3.11+ License: MIT Code style: ruff Checked with mypy Downloads

一款现代、跨平台的 Model Context Protocol (MCP) 服务,可让 AI 助手安全、高效地浏览和交互 Gopher protocol 与 Gemini protocol 资源。

概述

Gopher & Gemini MCP 服务将复古与现代的另类互联网协议连接到 AI 助手,让 Claude 等大模型探索 Gopherspace 和 Geminispace 上独特的内容与社区。它基于 FastMCP 和现代 Python 实践构建,为这些独特的互联网协议提供安全、高效的访问途径。

主要优势:

  • 探索另类互联网内容 - 访问 Gopher 和 Gemini 协议上的独特资源
  • 安全探索 - 内置安全防护措施、TLS 加密和内容过滤
  • 现代实现 - 使用 FastMCP 框架和 async/await 模式
  • 便于开发者使用 - 提供全面的测试、类型提示和文档
  • 高级安全性 - 为 Gemini 提供 TOFU 证书验证和客户端证书支持

功能

  • 双协议支持:gopher_fetch 和 gemini_fetch 工具,全面覆盖两种协议
  • 全面支持 Gopher:支持 RFC 1436 中的所有标准项目类型 — 菜单 (1)和索引搜索服务器(7)作为结构化菜单,文本(0)、HTML (h)、信息(i)和错误(3)行作为文本,十四种二进制类型仅作为 元数据处理,而三种交互式类型(2、8、T)会在建立连接前被拒绝。唯一 没有独立类别的标准类型是 + (冗余服务器),它为前一项目指定备用主机,而非要呈现的内容;因此会按下文的未知类型路径处理。未知类型会尽力按文本读取,而 hURL URL:<target> 选择器会跳转到服务器实际指定的目标
  • 完整的 Gemini 实现:原生 gemtext 解析、TLS 安全性和状态码处理
  • 高级安全性:TOFU 证书验证,配备专用检查和恢复工具、范围受限的客户端证书以及安全的 TLS 连接
  • 安全优先:内置超时、大小限制、输入清理、SSRF 防护、按主机限流和主机允许列表
  • 针对大模型优化:返回为 AI 使用而设计的结构化 JSON 响应
  • 跨平台:可在 Windows、macOS 和 Linux 上无缝运行
  • 现代化开发:完整的类型检查、代码检查、测试和 CI/CD 流水线
  • 高性能:采用 async/await 模式和智能缓存 — 并会标明缓存结果,同时支持通过单次请求的 refresh 参数绕过缓存
  • 可续读:菜单或页面在达到呈现限制时会报告截断位置,因此可通过 offset 读取剩余内容,而非留下不完整的视图
支持的协议

Gopher(RFC 1436,包括常见的事实标准项目类型)和 Gemini,均为只读。这就是全部支持范围;相邻协议则是有意排除,而非尚未实现:

  • Titan 和 Misfin 是写入协议——用于上传和邮件。LLM 驱动的工具不应能够向他人的 capsule 发布内容,或冒充他人发送邮件;添加其中任何一种协议,都会让这里的所有防护措施(robots、速率限制、允许列表)只能保护工具较小一部分的功能。
  • Spartan 和 Nex 是独立协议,各自有自己的解析器和故障模式,但它们服务的主机只占这两种协议的一小部分。为微不足道的覆盖范围增加它们,会使与安全相关的范围翻倍。
  • Gopher+ 尚未实现。由于 : 项目类型会出现在普通菜单中,因此系统会识别它;但绝不会发送任何 Gopher+ 属性或元数据请求。

如果你需要其中某种协议,选择一个能做好这件事的 MCP 服务,比给这个服务加个标志更合适。

文档

完整文档请访问 cameronrye.github.io/gopher-mcp

快速开始

前置条件
安装
选项 1:使用 uvx 无需安装(推荐)

无需克隆,也无需检出 — uv 会按需获取并运行 已发布的软件包:

uvx gopher-mcp
选项 2:从 PyPI 安装
# Install from PyPI
pip install gopher-mcp

# Or with uv
uv add gopher-mcp
选项 3:开发版安装
# Clone the repository
git clone https://github.com/cameronrye/gopher-mcp.git
cd gopher-mcp

# Set up development environment
./scripts/dev-setup.sh  # Unix/macOS
# or
scripts\dev-setup.bat   # Windows

# Run the server
uv run task serve
选项 4:Docker

带标签的发行版会向 ghcr.io/cameronrye/gopher-mcp 发布一个精简的非 root 镜像,镜像标签为发行版版本号;对于稳定版(非预发布)标签,还会标记为 :latest:

# The default CMD serves streamable-http on 0.0.0.0:8000
docker run --rm -p 8000:8000 \
  -v gopher-mcp-state:/home/app/.local/share/gopher-mcp \
  ghcr.io/cameronrye/gopher-mcp:latest

# Or run over stdio, e.g. for an MCP client
docker run --rm -i --no-healthcheck \
  -v gopher-mcp-state:/home/app/.local/share/gopher-mcp \
  ghcr.io/cameronrye/gopher-mcp:latest --transport stdio

要运行修改后的代码树,仓库提供了构建已发布镜像所用的 Dockerfile:docker build -t gopher-mcp .

请挂载卷,否则 Gemini 的信任机制就毫无意义。 如果不挂载,TOFU 固定信息和客户端证书的私钥会随容器一起消失,因此每次启动都会重新启用盲目的首次使用信任——固定信息是验证 Gemini capsule 身份的唯一依据——并且会销毁你创建的任何身份,其私钥无法恢复。

请挂载到这个确切路径。 /home/app/.local/share/gopher-mcp 是服务写入数据的位置(tofu.json 和 certs/),也是镜像中唯一预先创建、归运行时用户所有且权限为 700 的目录——因此命名卷可以以可写状态挂载,而不是归 root 所有。挂载到其他位置只会持久化一个空目录。~/.gemini 不是目标路径:它只是为在 gopher-mcp 有自己的目录之前固定了证书的安装方式提供的只读升级路径,而且只有当其中的存储文件已存在时才会采用;全新镜像中不会有该文件。

健康检查。 HTTP 传输提供 GET /health,它只会返回 {"status": "ok", "version": "..."},不会返回其他内容——不包含配置、允许列表或存储路径。根据 SDK 的设计,此端点会绕过授权,因此可用作探测端点。镜像的 HEALTHCHECK 会轮询硬编码端口 8000,以匹配默认的 CMD;因此使用 --port 时,也要覆盖健康检查;使用 stdio 运行时,请传入 --no-healthcheck——stdio 容器不提供 HTTP,否则即使运行正常,也会被报告为不健康。

**注意:**默认的 CMD 会绑定到 0.0.0.0,因此容器开箱即用即可被访问。非回环的 --host 也会关闭 FastMCP 的 DNS 重绑定 Host/Origin 检查,这与 SDK 在使用此类主机构建时的行为一致——否则,所有不在 localhost 上的客户端都会收到 421 Misdirected Request。如需启用检查,请使用 --allowed-host NAME 指定部署所响应的主机名(可重复使用;不带端口的名称适用于任意端口)。HTTP 传输既未经身份验证,也不提供 TLS——在将容器暴露到本机以外之前,请将其置于可信的反向代理之后,或使用 --transport stdio。

MCP 客户端集成

以下每个客户端都通过 stdio 运行服务——无需端口、TLS 或监听套接字。各处的配置项都由相同的三个字段组成;只有文件和顶层键不同:

客户端配置项所在位置顶层键
Claude Desktop~/Library/Application Support/Claude/claude_desktop_config.json(macOS)、%APPDATA%\Claude\claude_desktop_config.json(Windows)、~/.config/Claude/claude_desktop_config.json(Linux)mcpServers
Claude Codeclaude mcp add --scope user gopher -- uvx gopher-mcp,或在仓库根目录的 .mcp.json 中配置 --scope projectmcpServers
Cursor~/.cursor/mcp.json,或为单个项目配置 .cursor/mcp.jsonmcpServers
VS Code工作区中的 .vscode/mcp.json,或 MCP: Open User Configurationservers
Zedsettings.json(zed: open settings)context_servers
Windsurf~/.codeium/windsurf/mcp_config.jsonmcpServers
{
  "mcpServers": {
    "gopher": {
      "command": "uvx",
      "args": ["gopher-mcp"]
    }
  }
}

配置项就是这些:Configuration 中的每项设置都有可用的默认值,因此只有在确实需要更改时才添加 "env" 块。如果使用 pip 而不是 uvx 安装,请改用 "command": "gopher-mcp" 和 "args": []。

安装指南提供了每个客户端对应的准确 JSON 配置,包括两个不使用 mcpServers 键的客户端。

如果 GUI 客户端报告服务启动失败,几乎总是 PATH 的问题:通过 GUI 启动的应用不会继承 shell 的 PATH,因此可能找不到 uvx。请使用绝对路径(which uvx)作为 "command",并完全退出后重新启动应用,而不是重新加载窗口。

替代方案:从本地检出版本运行
{
  "mcpServers": {
    "gopher": {
      "command": "uv",
      "args": ["--directory", "/path/to/gopher-mcp", "run", "task", "serve"]
    }
  }
}

在 Windows 上,请使用带转义反斜杠的绝对路径 (C:\\path\\to\\gopher-mcp)。

## 用法

该服务器注册了 8 个 MCP 工具:

工具用途
gopher_fetch获取一个 Gopher 资源
gemini_fetch获取一个 Gemini 资源
gopher_batch_fetch一次获取多个 Gopher URL(并发数有限,最多 50 个)
gemini_batch_fetch一次获取多个 Gemini URL(并发数有限,最多 50 个)
gemini_trust_list检查 Gemini TOFU 信任存储(只读)
gemini_trust_update删除或重新固定一个主机的证书(具有破坏性)
gemini_client_cert_list检查存储的 Gemini 客户端身份(只读)
gemini_client_cert_update创建或删除一个客户端身份(具有破坏性)

4 个获取工具都标注为只读且面向开放世界。4 个证书工具不会访问网络,并且每一对工具都分为读和写两种,因此客户端可以自行决定是否允许执行具有破坏性的工具。

此外,服务器还公开了一个资源 gopher-mcp://policy,用于呈现此进程实际采用的获取策略——决定是否拒绝请求所依据的允许列表、上限和 robots 设置;两个存储路径则简化为 <configured> / <default>。我们特意不提供用于编辑该策略的工具:如果获取的页面诱使工具扩大允许列表,那么后续所有获取操作都会受到影响。两个提示词——探索 capsule 或 Gopher 洞和总结 gemlog 或 phlog——将导航和安全规则打包成一键式起点。

gopher_fetch 工具

通过 URL 获取 Gopher 菜单、文本文件或元数据,并提供全面的错误处理和安全防护。

参数:

  • url(字符串,必填):完整的 Gopher URL(例如,gopher://gopher.floodgap.com/1/)
  • search(字符串,可选):用于 type-7(Index-Search)选择器的搜索词。搜索词会进行百分号编码并作为查询字符串发送,因此请直接传入用户输入的原始词语——如果手动将包含 #、+、& 或非 ASCII 字符的查询写入 URL,内容可能会被截断或篡改。RFC 1436 仅为 type 7 定义了查询字段,因此其他所有条目类型都不要设置此参数
  • refresh(布尔值,可选,默认值为 false):跳过缓存副本,重新从服务器获取
  • offset(整数,可选,默认值为 0):继续获取被截断的结果——传入上一个结果的 next_offset;对于菜单,该值按菜单项计数

响应类型:

  • MenuResult(kind: "menu"):用于 Gopher 菜单(type 1)和搜索结果(type 7)
    • 结构化的菜单项,包含类型、标题、选择器、主机和端口;每项都有一个可供后续访问的 next_url。空的 next_url 表示仅供显示的信息行
  • TextResult(kind: "text"):用于文本文件(type 0)
    • 返回带有元数据的文本内容
  • BinaryResult(kind: "binary"):仅返回二进制条目类型的元数据 (4、5、6、9、g、I、d、s、;、p、P、:、M、<)
    • 提供 bytes 和 mime_type,但不下载二进制内容
  • ErrorResult(kind: "error"):用于错误和无法获取的内容
    • 返回 error.code 和 error.message;未获取任何内容。交互式类型(2 CSO、8 telnet、T tn3270)完全没有可获取的正文,并且不会建立连接就会返回 NOT_FETCHABLE
gemini_fetch 工具

使用完整的 TLS 安全机制、TOFU 证书验证和原生 gemtext 解析来获取 Gemini 内容。

参数:

  • url(字符串,必填):完整的 Gemini URL(例如,gemini://geminiprotocol.net/)
  • input(字符串,可选):用于回答 Gemini 输入提示词(状态 10/11)的文本;会进行百分号编码并放入查询字符串
  • refresh(布尔值,可选,默认值 false):跳过缓存副本并从服务端重新获取
  • offset(整数,可选,默认值 0):继续获取被截断的结果 — 传入上一个结果的 next_offset;对于页面正文,该值按字符计数

响应类型: 共七种,每种对应一个 kind。

  • GeminiGemtextResult(kind: "gemtext"):用于 gemtext 内容(text/gemini)
    • 解析后的文档位于 document.lines 和 document.links 中,其中的 url 字段已解析。每行包含自己的 type、content,以及标记无法表达的信息(text、level、alt_text、language);不存在嵌套的逐行对象,载荷中也没有整个文档的 raw_content
  • GeminiSuccessResult(kind: "success"):用于其他文本内容类型
    • 解码后的文本位于 content 中,并附带 MIME 类型信息
  • GeminiBinaryResult(kind: "binary"):用于二进制内容
    • 仅包含元数据 — size 和检测到的 mime_type,绝不包含字节。一段 1 MB 的正文转换成 base64 后约有 350k 个 token,而模型无论如何也无法呈现这些内容
  • GeminiInputResult(kind: "input"):用于输入请求(状态 1x)
    • capsule 的 prompt,状态 11 时带有 sensitive: true。通过再次调用并传入 input= 来回答,切勿手动构造查询字符串
  • GeminiRedirectResult(kind: "redirect"):用于重定向(状态 3x,其中 31 表示永久重定向)
    • new_url 是目标地址。不会替你跟随重定向,因此结果还包含 cross_host(目标属于与你请求的地址不同的一方)和 scheme(除 gemini 以外的任何协议都会离开 Geminispace,无法通过此工具获取)。连续跟随最多五次;遇到已见过的 URL 时停止
  • GeminiErrorResult(kind: "error"):用于错误(状态 40-59),以及线路本端引发的故障 — SSRF 和允许列表拒绝、robots 屏蔽、证书不匹配、超时
    • error.code 和 error.message;其中 message 由此服务编写。capsule 自身不可信的 META 文本单独保存在 error.meta 中,因此恶意的 51 <instruction> 不会被误认为是此服务的指导信息。对于有明确补救方法的状态 — 包括整个临时 4x 状态系列 — 补救方法位于 error.next_step 中
  • GeminiCertificateResult(kind: "certificate"):用于证书状态(60-69)
    • 证书要求信息,以及由此服务编写的 next_step(message 是 capsule 自身的文本)。如果该主机/端口/路径范围已存在证书,系统会自动附加该证书;获取流程本身绝不会创建证书,因此在条件不变时重试仍会再次返回状态 60;gemini_client_cert_update 会为该范围签发证书,但前提是用户已同意在该 capsule 上持有持久身份。

GeminiErrorResult 是 gopher_fetch 返回的同一 ErrorResult 模型的别名,并非单独的类型 — 只有在 capsule 实际返回响应时,其 error 对象才会附带额外的 status、temporary 和 meta 键。

Gemini 结果将内容长度命名为 size,而 Gopher 结果将同一属性命名为 bytes。这是同一个概念,却有两个线路名称;之所以保留区别,只是因为重命名任何一个都会破坏所有现有使用方。

缓存结果和 refresh

成功的响应正文会按协议缓存几分钟。来自缓存的结果会明确标示,因此重放的结果不会被误认为资源的当前状态:

  • cached — 当结果从本地缓存重放时为 true
  • cached_at — 此副本实际获取的时间,以 ISO-8601 UTC 时间戳表示(2026-09-02T12:00:00+00:00)
  • cache_age_seconds — 返回时此副本的缓存时长(秒)

这些字段只会出现在实际会缓存的类型中(Gopher menu、text、binary;Gemini gemtext、success、binary)。错误、重定向以及输入/证书提示永远不会被缓存。

当用户需要当前状态时,传入 refresh: true;这会让此次调用跳过缓存,同时仍保存最新响应。四种 fetch 工具都支持此参数,包括批量工具。

截断的结果和 offset

菜单和页面正文在传给模型之前都会被限制长度(*_MAX_RENDERED_CHARS、GOPHER_MAX_MENU_ITEMS),但达到上限并不意味着无法继续获取。结果被截断时会设置 truncated: true,并指出从哪里继续:

  • next_offset — 被截断部分的起始位置;如果没有更多内容,则为 null
  • total_items(Gopher menu)/ total_chars(text、success、gemtext)— 整个资源的大小。当目录超过渲染上限时,total_items 为 null,因为这种情况下不会统计总数

使用上一次返回的 next_offset 设置 offset,再次调用同一工具,并持续调用,直到 next_offset 返回 null。菜单的单位是项目,正文的单位是字符;bytes 和 size 以字节计,永远不能作为偏移量。对于 gemtext,每个窗口都在最后一条完整行处结束,因此连续窗口首尾相接,半个链接也不会被解析成完整链接。

两种批量工具都不接受 offset:对于一组不同的 URL,一个偏移量没有合理的含义。要继续获取批量结果中被截断的项目,请使用单 URL 工具;只有它才能根据 next_offset 继续。

Gemini 信任存储工具

Gemini 没有证书颁发机构:主机的第一个证书会被固定,之后每次连接都必须出示相同的证书。当主机重新签发证书时——自签名证书通常会在到期时重新签发——获取操作会因 CERTIFICATE_CHANGED 失败。两个工具可以处理这种情况,无需手动编辑磁盘上的信任存储——$XDG_DATA_HOME/gopher-mcp/tofu.json;若该路径不可用,则使用 ~/.local/share/gopher-mcp/、macOS 上的 ~/Library/Application Support/gopher-mcp/,以及 Windows 上的 %LOCALAPPDATA%\gopher-mcp\,也可通过 GEMINI_TOFU_STORAGE_PATH 覆盖。已经有 ~/.gemini/tofu.json 的安装会永久在原位置继续使用它:迁移固定项会导致它们丢失,甚至更糟,让已固定的主机看起来像是未固定状态。完整规则请参阅Gemini 状态的存储位置。

  • gemini_trust_list(只读)报告固定的内容,也可选择指定一个 host:包括指纹、端口、首次/最近发现时间和 ISO-8601 UTC 格式的到期时间,以及预先计算的 expired 值——有效期已结束,很可能就是指纹变化的原因,且属于常规重新签发。
  • gemini_trust_update(具有破坏性)移除(action: "remove")或替换(action: "pin")一个指定 host 的固定项。不支持通配符。

指纹变化也正是主动中间人攻击的表现,而客户端无法区分这两种情况。因此,只有在用户确认新证书符合预期后,才会更改固定项——应向运维人员或通过另一台设备核实,绝不能仅凭获取到的页面所述内容。为确保这一点,action: "remove" 要求提供当前固定的指纹(由 gemini_trust_list 报告);如果不匹配,就会返回 FINGERPRINT_MISMATCH,且不会做任何更改。支持 MCP elicitation 的客户端还会在更改前征求你的意见;如果你拒绝,则会返回 USER_DECLINED,固定项保持不变;不具备该能力的客户端不会收到询问,行为与以往一致。

Gemini 客户端身份工具

客户端证书是 Gemini 证书机制的另一半,也是相反的方向:它是本服务向 capsule 出示的身份,而不是 capsule 向我们出示的身份。带有账户的 capsule 会通过**状态码 60(需要证书)**请求该证书。获取流程会附加已覆盖所请求范围的证书,但绝不会创建证书,因此响应状态码 60 需要显式调用工具:

  • gemini_client_cert_list(只读)报告持有身份的各个范围——每个范围都以可直接使用的范围 URL 列出,并提供其指纹、有效期以及是否已过期。绝不显示私钥或其存放位置。
  • gemini_client_cert_update(具有破坏性)为指定 gemini:// URL 的范围创建身份,或移除覆盖该 URL 的身份。与信任存储工具一样,支持 elicitation 的客户端会先征求你的意见——创建身份意味着决定是否允许他人关联你多次访问的记录,而移除身份则会销毁无法重新生成的私钥。

证书覆盖该 URL 的路径及其下的所有内容,因此 gemini://host/app/page.gmi 覆盖单个页面,gemini://host/app/ 覆盖整个版块,而 gemini://host/ 覆盖整个 capsule。证书存在期间,该范围内的每个请求都会携带它,这正是 capsule 能够关联这些访问记录的原因——因此,这必须由用户决定,绝不能因为页面或 META 字符串要求提供证书而采取行动。创建操作不会替换已覆盖该范围的证书,因为私钥无法找回;而 action: "remove" 要求提供将被销毁的指纹,这与信任工具要求提供已固定指纹完全相同。

可尝试的示例 URL
Gopher 协议
# Classic Gopher menu
gopher://gopher.floodgap.com/1/

# Gopher news and information
gopher://gopher.floodgap.com/1/gopher

# Search example (type 7)
gopher://gopher.floodgap.com/7/v2/vs

# Text file example
gopher://gopher.floodgap.com/0/gopher/welcome
Gemini Protocol
# Gemini protocol homepage
gemini://geminiprotocol.net/

# Gemini software directory
gemini://geminiprotocol.net/software/

# Example personal capsule
gemini://skyjake.fi/

# A large, browsable aggregator capsule
gemini://kennedy.gemi.dev/

Geminispace 没有可供此工具使用的搜索引擎。Kennedy 和 tlgs.one 值得浏览,但它们都在各自的 robots.txt 中设置了 Disallow: /search,因此访问任一站点的搜索 URL 都会返回 BLOCKED_BY_ROBOTS。这意味着应当停止,而不是更改设置:运营方要求自动化客户端不要访问这些路径。

AI 交互示例

配置完成后,你可以向 Claude 提问:

Gopher 探索:

  • “浏览 gopher.floodgap.com 上的 Gopher 主菜单”
  • “在 Veronica-2 搜索服务上搜索 ‘python’”
  • “显示 Floodgap 的 Gopher 服务的欢迎文本”
  • “Gopher 社区目录中有哪些内容?”

Gemini 探索:

  • “获取 Gemini 协议主页”
  • “显示 geminiprotocol.net 上的软件目录”
  • “浏览 gemlog 的最新帖子”
  • “Gopher 和 Gemini 协议有什么区别?”

开发

任务运行器

每条开发命令都是 [tool.taskipy.tasks](pyproject.toml)中的一个任务,它是每条命令的唯一配置来源——无需维护第二张表来保持同步。在所有平台上都以相同的方式运行:

uv run task dev-setup     # install dependencies and pre-commit hooks
uv run task quality       # lint + typecheck + test
uv run task ci            # what CI runs: check + test-cov
uv run task help          # list every task (alias for `task --list`)

在 Unix 和 macOS 上,make <command> 是一个指向同一张表的通用入口,而单独运行 make 会执行 help。uv run task help 是权威的任务列表;有关这些任务,以及不那么直观的任务背后的原因,请参阅 CONTRIBUTING.md。

项目结构
gopher-mcp/
├── src/gopher_mcp/          # Main package
│   ├── __init__.py          # Package initialization
│   ├── __main__.py          # CLI entry point (--transport/--host/--port/--allowed-host)
│   ├── server.py            # FastMCP server + the eight MCP tool definitions
│   ├── client_base.py       # Shared fetch scaffolding for both clients
│   ├── gopher_client.py     # Gopher protocol client
│   ├── gopher_transport.py  # Low-level Gopher transport
│   ├── gopher_parse.py      # Gopher URL and menu parsing
│   ├── gemini_client.py     # Gemini protocol client
│   ├── gemini_tls.py        # Gemini TLS connection handling
│   ├── gemini_parse.py      # Gemini URL and response parsing
│   ├── gemtext.py           # Gemtext document parsing
│   ├── mime.py              # MIME type detection and filtering
│   ├── tofu.py              # Trust-on-First-Use certificate store
│   ├── client_certs.py      # Gemini client certificate storage
│   ├── identity.py          # Trust/identity decision and wording helpers
│   ├── ssrf.py              # SSRF protection / address filtering
│   ├── ratelimit.py         # Per-host rate limiting
│   ├── robots.py            # robots.txt fetching and policy gate
│   ├── cache.py             # Shared TTL + LRU response cache
│   ├── config.py            # Pydantic settings models
│   ├── models.py            # Pydantic data models
│   ├── helpers.py           # Shared URL/IO/sanitization helpers
│   └── utils.py             # Backward-compatible facade re-exporting the above
├── tests/                   # Comprehensive test suite
├── docs/                    # MkDocs documentation
├── scripts/                 # Development scripts
├── config/                  # Example configuration (example.env)
├── .github/workflows/       # CI/CD pipelines
├── Dockerfile               # Slim, non-root container image
├── Makefile                 # Unix/macOS shortcut onto the taskipy tasks
├── server.json              # MCP registry manifest
└── pyproject.toml           # Modern Python project config
开发工作流
  1. 设置:uv run task dev-setup - 安装依赖项和 pre-commit 钩子
  2. 编码:在完整的 IDE 支持(类型提示、代码检查)下进行修改
  3. 质量检查:uv run task quality - 运行所有质量检查(代码检查 + 类型检查 + 测试)
  4. 测试:uv run task test-cov - 运行测试并生成覆盖率报告
  5. 提交:Pre-commit 钩子会自动确保代码质量
测试
# Run all tests
uv run task test

# Run with coverage
uv run task test-cov

# Run specific test types
uv run task test-unit
uv run task test-integration

# Run one file
uv run pytest tests/test_server.py

配置

可通过环境变量配置此服务的两种协议:

Gopher 配置
变量说明默认值示例
GOPHER_MAX_RESPONSE_SIZE最大响应大小,单位为字节1048576 (1MB)2097152
GOPHER_TIMEOUT_SECONDS请求超时时间,单位为秒3060
GOPHER_CACHE_ENABLED启用响应缓存truefalse
GOPHER_CACHE_TTL_SECONDS缓存 TTL,单位为秒;0 = 关闭300600
GOPHER_MAX_CACHE_ENTRIES最大缓存条目数(LRU)10002000
GOPHER_ALLOWED_HOSTS允许的主机(列表)未设置(全部)example.com,test.com
GOPHER_ALLOWED_PORTS允许的端口(列表)未设置(任意)70
GOPHER_ALLOW_LOCAL_HOSTS允许环回/私有主机falsetrue
GOPHER_REQUESTS_PER_MINUTE每台主机每分钟的请求上限(0 = 关闭)6030
GOPHER_MAX_CONCURRENT_REQUESTS同时获取的数量(0 = 关闭)52
GOPHER_RESPECT_ROBOTS_TXT遵循 /robots.txttruefalse
Gemini 配置
Variable描述默认值示例
GEMINI_MAX_RESPONSE_SIZE最大响应大小(字节)1048576 (1MB)2097152
GEMINI_TIMEOUT_SECONDS整个抓取过程的网络耗时预算3060
GEMINI_CACHE_ENABLED启用响应缓存truefalse
GEMINI_CACHE_TTL_SECONDS缓存 TTL(秒);0 = 关闭300600
GEMINI_MAX_CACHE_ENTRIES最大缓存条目数(LRU)10002000
GEMINI_ALLOWED_HOSTS允许的主机(列表)未设置(全部)example.org,test.org
GEMINI_ALLOWED_PORTS允许的端口(列表)未设置(任意)1965
GEMINI_ALLOW_LOCAL_HOSTS允许环回/专用主机falsetrue
GEMINI_TOFU_ENABLED启用 TOFU 证书验证truefalse
GEMINI_CLIENT_CERTS_ENABLED存储并附加客户端证书truefalse
GEMINI_REQUESTS_PER_MINUTE每台主机的请求上限(0 = 关闭)6030
GEMINI_MAX_CONCURRENT_REQUESTS同时抓取数(0 = 关闭)52
GEMINI_RESPECT_ROBOTS_TXT遵循 /robots.txttruefalse

SSRF 防护: 默认情况下,两个工具都会拒绝解析到环回地址、链路本地地址(包括云元数据地址 169.254.169.254)或专用/RFC1918 地址的目标。只有在确实需要访问本地主机时(例如,在 localhost 上测试服务),才将 GOPHER_ALLOW_LOCAL_HOSTS / GEMINI_ALLOW_LOCAL_HOSTS 设置为 true。

超时。 *_TIMEOUT_SECONDS 是每次抓取的整体截止时间,而不是每个阶段的超时时间。对于 Gemini,DNS、连接和 TLS 握手、信任存储写入、发送和读取都会消耗同一预算;启用 robots 检查时,/robots.txt 探测也会占用该预算,因此慢速主机无法在每个步骤中都耗尽完整时长。

列表类型变量。 *_ALLOWED_HOSTS、*_ALLOWED_PORTS 和 GEMINI_DENIED_MIME_TYPES 接受逗号分隔形式(a,b)或 JSON 数组(["a", "b"]);条目周围的空白会被去除。将变量留为未设置(或设为空)表示“不作限制”。若变量已设置但未指定任何条目(" , ",或两个 shell 变量都为空时的 "$A,$B"),则会导致启动错误,因为无法区分空的允许列表和未设置的变量,并且这会悄悄取消你原本要应用的限制。允许列表中的端口若不在 1–65535 范围内,也会导致启动错误,原因相同:这类端口永远无法匹配,因此运行时会拒绝每次抓取。

缓存。 *_CACHE_TTL_SECONDS=0 会禁用缓存,而不是存储写入后立即过期的条目。

上表列出了最常见的设置。其他选项包括 robots 策略缓存和重试(*_ROBOTS_CACHE_TTL_SECONDS、*_ROBOTS_HONOR_AI_TOKENS、*_ROBOTS_FAILURE_BACKOFF_SECONDS)、渲染输出限制(*_MAX_RENDERED_CHARS、GOPHER_MAX_MENU_ITEMS)、Gemini TOFU/证书存储路径和过期策略(GEMINI_TOFU_STORAGE_PATH、GEMINI_TOFU_REJECT_EXPIRED、GEMINI_CLIENT_CERTS_STORAGE_PATH)、MIME 筛选(GEMINI_DENIED_MIME_TYPES),以及 GOPHER_MCP_ 前缀下的服务和日志设置。有关每个变量的类型、范围和默认值,请参阅完整的配置指南,或查看 config/example.env,以获取可直接编辑的起始配置。

示例配置
# Gopher settings
export GOPHER_MAX_RESPONSE_SIZE=2097152
export GOPHER_TIMEOUT_SECONDS=60
export GOPHER_CACHE_ENABLED=true
export GOPHER_ALLOWED_HOSTS="gopher.floodgap.com,gopher.quux.org"

# Gemini settings
export GEMINI_MAX_RESPONSE_SIZE=2097152
export GEMINI_TIMEOUT_SECONDS=60
export GEMINI_TOFU_ENABLED=true
export GEMINI_CLIENT_CERTS_ENABLED=true
export GEMINI_ALLOWED_HOSTS="geminiprotocol.net,skyjake.fi"

# Run with custom config
uv run task serve

网络礼仪

Gopherspace 和 Geminispace 的服务主要由个人通过小型设备提供。本服务在设计时,力求做一位合乎礼仪的访客。

此工具的功能与限制

有人请求助手获取某项资源时,gopher-mcp 会获取该资源,并将内容返回给当前对话。它不会根据获取的内容进行训练,不会存档或重新托管这些内容,也不会让它们变得可搜索。它不会主动跟随链接:它获取的每个 URL 都由调用方指定。

这两种协议都没有 user-agent 字段,因此无法在网络传输中识别此客户端,服务也无法按名称识别或屏蔽它。这是 Gopher 和 Gemini 的特性,并非这里的选择。此服务在下文启用 robots 检查时,会遵守针对 gopher-mcp 标记编写的规则,因此希望单独指定它的运营者可以这样做。

速率限制

两个客户端都会控制对同一主机的请求间隔,并限制同时运行的获取任务数量。与早期版本不同,这些限制默认已启用:每台主机每秒一个请求(*_REQUESTS_PER_MINUTE=60),同时最多进行五次获取(*_MAX_CONCURRENT_REQUESTS=5)。将任一项设为 0 即可禁用。无论这些设置如何,Gemini 服务返回 44 SLOW_DOWN 时,客户端始终会遵守。

机器人排除规则(robots.txt)

此服务会从主机根目录获取 /robots.txt,并在检索任何内容之前遵守其中的规则。此功能默认已启用:这些服务器绝大多数由业余爱好者运营,而对于一个由大模型无人值守驱动的工具来说,默认忽略明示的政策并不合理。每台主机的政策会缓存 24 小时(*_ROBOTS_CACHE_TTL_SECONDS),因此每台主机只需额外往返一次,而不是每次获取都要往返。

GOPHER_RESPECT_ROBOTS_TXT=false / GEMINI_RESPECT_ROBOTS_TXT=false 可以关闭此功能,但此覆盖选项仅适用于由你运营的主机——他人服务器上的 Disallow: / 是一种明确的决定,并非配置错误。错误消息也采用了相同的表达方式,因为读取这些消息的是模型,而不是你:被拦截的获取会返回 BLOCKED_BY_ROBOTS,并要求停止并告知用户,而不是重试或尝试更改路径拼写。若因完全无法读取政策而拒绝获取,则会返回 ROBOTS_UNAVAILABLE——这是单独的错误代码,因为这种情况是暂时性的,并不代表有任何内容被禁止访问。

适用的惯例取决于协议:

  • Gemini 遵循官方的配套规范,该规范定义了虚拟 Agent archiver、indexer、researcher 和 webproxy。此服务匹配 gopher-mcp、webproxy、indexer 和 *。它不声明自己是 archiver 或 researcher:此处不会保留内容,而 researcher 是为不向用户呈现所获取内容的工具定义的。
  • Gopher 遵循 Veronica-2 在 gopher://gopher.floodgap.com/0/v2/help/indexer 中记载的惯例(这里以文本形式写出,而非设为链接,因为 PyPI 的清理器会移除 gopher:// 超链接)。此服务匹配 gopher-mcp 和 *。它不声明自己是 veronica,因为该标记属于 Floodgap 的索引器。

两种协议都采用原始的 1994 年 robots.txt 语法,而非 RFC 9309,因此只识别 #、User-agent: 和 Disallow:,其他所有字段(包括 Allow:)都会被忽略。这一点很重要:RFC 9309 解析器会处理 Allow: 行,而这些网络上的作者原本希望它们被忽略,这会让限制比预期_更宽松_。

默认情况下,此服务还会遵守提及 ClaudeBot、GPTBot 和 CCBot 等 AI 爬虫标记的规则(*_ROBOTS_HONOR_AI_TOKENS)。这些标记不属于任一协议的惯例,但写下这类规则的运营者表达的意思是“不要使用大模型工具”,而这正是此请求的意图。 当完全无法检索到策略时,主机会暂时等待一段时间后再进行探测(默认为 60s,可通过 *_ROBOTS_FAILURE_BACKOFF_SECONDS 设置),而不是每次请求都重新经历一次连接超时。如果要立即重新探测,请将其设为 0;如果经常从不可用的主机获取内容,可以调高此值。Gemini capsule 返回 44 SLOW_DOWN 是个例外:它指定了自己的重试间隔,因此会使用该间隔。

有两项已知限制,均已明确说明,而非刻意掩盖:

  • Gopher 采用故障开放策略。 该协议没有状态码,因此无法通过线路区分缺失的选择器、错误文档和空文件。RFC 9309 §2.3.1.4 会要求在无法访问策略时拒绝所有内容,这会导致 Gopherspace 中大多数内容都无法访问。因此,解析器采用宽松策略:如果内容中没有生成 User-agent: 组,就不施加任何规则。Gemini 有状态码,因此采用故障关闭策略,并将 51 NOT FOUND 视为“无策略”。请注意,故障关闭的适用范围不只包括 4x 状态码:无法访问的 capsule——例如连接被拒绝、TLS 失败、超时或回复格式错误——同样没有可检索的策略,因此也会被拒绝,但会使用单独的 ROBOTS_UNAVAILABLE 代码,因为该 capsule 从未禁止访问。消息会指出底层原因,关闭 robots 检查也无法让此类 capsule 变得可访问。
  • Gopher 路径规则尽力而为。 Gopher URI 会将项目类型作为路径的第一个字符,因此 gopher://host/1/archive 的 URI 路径是 /1/archive,而线路上的选择器是 /archive。规则会针对这两种写法进行测试,但只有 Disallow: / 能保证按其作者预期生效。

不存在按目录或按用户设置的 robots.txt。协议惯例和 RFC 9309 §2.3 均未对此作出规定;在共享主机上,通行做法是在根目录放置一个文件,并使用路径前缀。

参与贡献

欢迎贡献!详情请参阅我们的贡献指南。

快速贡献步骤
  1. 在 GitHub 上派生此仓库
  2. 克隆你的派生仓库:git clone https://github.com/your-username/gopher-mcp.git
  3. 设置开发环境:uv run task dev-setup
  4. 创建功能分支:git checkout -b feature/amazing-feature
  5. 进行更改并添加测试
  6. 质量检查:uv run task quality
  7. 提交更改:git commit -m 'Add amazing feature'
  8. 推送到你的派生仓库:git push origin feature/amazing-feature
  9. 提交拉取请求,并附上清晰的说明
开发标准
  • 所有函数和方法都应提供类型提示
  • 全面的测试(CI 强制要求覆盖率至少达到 95%)
  • 为所有公共 API 编写文档
  • 对所有网络操作考虑安全性
  • 跨平台兼容(Windows、macOS、Linux)

许可证

本项目采用 MIT 许可证授权,详情请参阅 LICENSE 文件。

致谢

  • Anthropic 提供的 Model Context Protocol——让此集成成为可能的基础
  • MCP Python SDK——此服务基于其捆绑的 FastMCP(mcp.server.fastmcp)构建,并固定使用 mcp>=1.28.1,<2
  • Gopher 协议社区——让早期互联网精神薪火相传

相关项目

支持

无论提交哪一项,请附上版本号:gopher-mcp --version 可显示版本号, 并且适用于 uvx 和 Docker 安装;这些安装方式没有可供导入 软件包的检出目录。


由 Cameron Rye 倾心打造 ❤️

如果觉得这个项目有用,请为项目点 Star!

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。