mcpField
F
fontpool

fontpool

本地服务
github.com/travisvaleri媒体与设计02 周前更新

关于 fontpool

fontpool 是用于搜索字体目录并将字体安装到项目中的 MCP 服务。它帮助开发者和编码 Agent 根据类别、用途标签及可变轴选择字体,随后下载 WOFF2 文件并生成 CSS,以便自托管。

主要功能

  1. 01整合搜索三个字体提供方
  2. 02按类别、标签和可变轴筛选
  3. 03查看字体详情与字库厂商描述
  4. 04下载字体文件并生成 @font-face CSS
  5. 05在项目中自托管字体

适用场景

  1. 01按类别或设计用途查找字体
  2. 02选择字体前比较可变轴
  3. 03将字体文件和 CSS 加入项目
  4. 04以本地文件替代运行时 CDN 字体请求

MCP 相关

向 MCP 客户端提供字体搜索、属性查询和安装工具。提供工作区根目录时,`install_font` 会将文件和 CSS 写入项目工作区内。

配置

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

mcp.json · 通用配置
{
"mcpServers": {
"travisvaleri-fontpool": {
"command": "npx",
"args": [
"-y",
"fontpool"
]
}
}
}
启动命令npx -y fontpool

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

安装包
npmfontpool1.4.1

工具

3

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

参数类型必填说明
tagstring否用途标签,例如 'editorial'、'branding'、'legible'、'code'。不带参数调用可列出所有标签。
limitinteger否最多返回的字体系列数。默认值为 100。
filterstring否按字体系列名称进行不区分大小写的子字符串匹配。
excludearray否排除由这些 provider 提供的字体系列。['google'] 会保留 Google 的 API 不提供的 218 个字体系列。
hasAxisstring否仅返回带有此可变轴的字体系列,例如 'wdth'(宽度)、'opsz'(光学尺寸)、'slnt'(倾斜)或 'CASL' 等表现性轴。不带参数调用可列出所有轴。
categoryenum否限制为一个类别。sans-serif | serif | slab-serif | display | handwriting | monospace | blackletter | icons | other
providerenum否限定为一个 provider。省略则涵盖全部。fontshare | fontsource | google
variableOnlyboolean否仅返回带有可变轴的字体系列。

详细介绍

Fontpool

一个用于处理字体的 MCP 服务。它将三个字体提供方的资源整合成一个目录进行搜索,告诉你每个字体系列的实际属性——类别、用途标签、字库厂商自己的描述以及每个可变轴——将真正的 woff2 文件下载到你的项目中,并生成匹配的 @font-face 规则。

字体会自行托管——运行时不会请求 CDN,也不会因第三方更改 URL 而出问题。基于 unifont 构建。

重点不在于方便。让 AI 为页面设置样式时,它几乎每次都会选 Inter 或 Roboto,部分原因是这些名称最常见,部分原因是它知道自己可以加载这些字体。Fontpool 的存在,是为了提供另一种选择:2,168 个字体系列,包含类别、用途标签和字库厂商对每个系列的描述,其中有 218 个不在 Google 的目录中。

提供方

提供方提供内容
FontshareITF 自有的字体系列——Satoshi、Switzer、General Sans、Clash Display。另有 65 个其他提供方没有的字体系列
Fontsource按子集打包的开源字体系列,带有 unicode-range。另有 127 个其他提供方没有的字体系列
GoogleGoogle Fonts 目录

查询顺序就是表格中的顺序,最先找到某个字体系列的提供方会胜出,因此 Satoshi 会解析为 ITF 的正版字体,而不是外观相似的字体。

Google 没有独有字体系列——它的全部内容都包含在排在它前面的 Fontsource 中,因此它从未实际提供过安装文件。保留 Google 只是作为后备方案:unifont 会吞掉提供方自身的目录错误,因此如果 Fontsource 及其依赖的 jsDelivr 无法访问,Google 仍可覆盖约 1,950 个字体系列。

**Bunny 已在 1.3.0 中移除。**它只是 Google Fonts 的镜像,移除它没有任何损失:无论是否包含它,目录中都有 2,168 个字体系列。它曾保留的 26 个已停用的 Google 字体名称——Source Sans Pro、Material Symbols、Big Shoulders 系列——在 Fontsource 中也都有,因此没有字体变得无法访问。它所谓的“隐私优先”也与此无关:fontpool 会下载文件并自行托管,因此运行时不会向任何 CDN 发出请求。

关于 218 这个数字,有一点需要说明:它统计的是 Google API 当前不包含的字体系列,其中约 26 个是 Google 曾经提供、后来停用的,而非从未提供过。Google 从未提供过的字体系列约有 192 个。

**我们有意不支持 Adobe Fonts。**Adobe 的条款要求通过 kit 嵌入从 Adobe 自己的 CDN 加载其字体,因此无法将它们下载到项目中。承诺提供自行托管文件的工具不能如实提供这些字体,因此我们选择完全不支持 Adobe,而不是只提供部分支持。

安装

需要 Node 20 或更高版本。无需 API 密钥或账户。

npx -y fontpool

这也是应填入客户端配置的命令。

Claude Code
claude mcp add fontpool -- npx -y fontpool
Claude Desktop

macOS 上的 ~/Library/Application Support/Claude/claude_desktop_config.json,Windows 上的 %APPDATA%\Claude\claude_desktop_config.json:

{
  "mcpServers": {
    "fontpool": {
      "command": "npx",
      "args": ["-y", "fontpool"]
    }
  }
}
Cursor / Windsurf / Zed 及其他 stdio 客户端

在相应客户端的 MCP 配置中使用相同格式:

{
  "mcpServers": {
    "fontpool": {
      "command": "npx",
      "args": ["-y", "fontpool"]
    }
  }
}
技能

服务器随附 SKILL.md。如果模型从未想到要调用这些工具,它们就毫无用处;而编写 CSS 本身也不会自然地促使模型停下来查找字体——因此,这项技能会在“这里需要一种无衬线字体”和输入字体名称之间加入一个步骤。将它复制到客户端读取技能的任意位置,或直接让你的 Agent 使用它。

工具

list_fonts

搜索目录。不带参数调用时,它会说明目录的结构,而不是列出 2,168 个名称;这也是了解标签词汇的方法:

{}
2168 families:

By category:
  sans-serif: 824
  display: 500
  serif: 379
  handwriting: 369
  monospace: 72
  other: 17
  icons: 8
  slab-serif: 4
  blackletter: 1

Not carried by Google: 218 families (exclude: ["google"] to see only these).

Tags: poster (53), magazine (50), book (37), logo (31), website (29),
banners (28), editorial (23), branding (20), headline (18), app (11),
legible (11), packaging (9), screen (8), code (5), fun (5), …

Variable axes: wght [Weight] (614), ital [Italic] (185), wdth [Width] (97),
opsz [Optical Size] (32), slnt [Slant] (23), ELSH [Element Shape] (15),
CRSV [Cursive] (14), GRAD [Grade] (8), MONO [Monospace] (3), …30 rarer axes

Narrow with category, tag, filter or exclude — listing all 2168 names at
once is rarely the useful answer.

使用筛选条件,可以获得一份值得挑选的候选列表:

{ "category": "serif", "tag": "editorial" }
6 families (category: serif, tag: editorial):
Bespoke Serif — serif · fontshare · Indian Type Foundry · magazine, poster, book, branding, editorial
Crimson Pro — serif · fontshare+fontsource+google · magazine, publishing, editorial
Gambarino — serif · fontshare · Indian Type Foundry · magazine, poster, book, editorial
Karma — serif · fontshare+fontsource+google · Indian Type Foundry · banners, editorial
Rowan — serif · fontshare · Indian Type Foundry · book, text, legible, editorial
Zodiak — serif · fontshare · Indian Type Foundry · editorial, magazine, book
参数作用
provider选择 fontshare、fontsource、google 之一
filter按字体系列名称进行不区分大小写的子字符串匹配
categorysans-serif、serif、slab-serif、display、handwriting、monospace、blackletter、icons、other
tag用途标签 — editorial、branding、legible、code、…
exclude排除这些提供方收录的字体系列。["google"] 会留下 Google API 未收录的 218 个系列
variableOnly仅返回带有可变轴的字体系列
hasAxis仅返回带有此轴的字体系列 — wdth、opsz、slnt,或 CASL 这类表现性轴。不区分大小写
limit返回的字体系列数量上限。默认值为 100,最大值为 500

结果会去重。unifont 自己的目录会针对每个收录该字体系列的提供方各返回一次 — 2,168 个系列共 6,124 行,其中约 20k 个 token 大多是重复内容 — 因此这里会合并结果,并改为在每个系列旁列出提供方。

font_properties

查看一个字体系列实际提供什么、是什么样的,再决定是否采用。 匹配字体系列名称时不区分大小写和空格,因此 satoshi 和 jetbrains mono 都能找到对应结果。

{ "family": "Satoshi" }
{
  "family": "Satoshi",
  "formats": ["woff2", "woff", "ttf"],
  "styles": ["normal", "italic"],
  "weights": ["300", "400", "500", "700", "900", "300 900"],
  "provider": "fontshare",
  "availableFrom": ["fontshare"],
  "categories": ["sans-serif"],
  "tags": ["logo", "magazine", "branding"],
  "foundry": "Indian Type Foundry",
  "designers": ["Deni Anggara"],
  "description": "Satoshi is a modernist sans serif typeface. Its design combines typically grotesk-style letterforms, with some characters that are quite geometrically-designed…",
  "descriptionSource": "Indian Type Foundry (marketing copy)",
  "variableAxes": {
    "wght": { "name": "Weight", "min": 300, "max": 900, "default": 780 }
  }
}

可变字体会以范围形式报告字重,例如 "300 900" — 将相同的字符串传给 install_font,即可获取一个可变字体文件,而不是多个静态字体文件。

variableAxes 会列出字体系列包含的所有轴,而不只是字重;这往往正是 选择某款字体而非另一款的原因:

{
  "family": "Recursive",
  "variableAxes": {
    "wght": { "name": "Weight",     "min": 300, "max": 1000, "default": 400 },
    "slnt": { "name": "Slant",      "min": -15, "max": 0,    "default": 0 },
    "CASL": { "name": "Casual",     "min": 0,   "max": 1,    "default": 0,   "step": 0.01 },
    "CRSV": { "name": "Cursive",    "min": 0,   "max": 1,    "default": 0.5, "step": 0.1 },
    "MONO": { "name": "Monospace",  "min": 0,   "max": 1,    "default": 0,   "step": 0.01 }
  }
}

小写标签是 OpenType 注册的五种轴;大写标签由字体厂商定义,含义取决于 相应字体的设计。若 step 的默认值为 1(信息量有限),则不会显示。可用 list_fonts { hasAxis: "wdth" } 按任意轴筛选 — 2,168 个系列中有 97 个带有宽度轴,这一点无法通过阅读名称得知。

description 是字体厂商自己的文案,取自提供方目录,并会标明来源。它对设计特点的描述值得信赖,对优点的评价则比较热情;请酌情参考。如果找不到完全匹配项,会提供相近匹配结果,而不只是报告失败:

No provider knows 'Intar'. Did you mean: Inter, NTR, Anta, Inder, Zina?
install_font

下载字体文件并写入 CSS。

{
  "family": "Inter",
  "targetDir": "./public/fonts",
  "cssPath": "./src/fonts.css",
  "weights": ["400", "700"],
  "styles": ["normal"],
  "subsets": ["latin", "latin-ext"],
  "urlPrefix": "/fonts"
}
Installed Inter from fontsource.

Files in /Users/you/app/public/fonts:
  inter-400-normal-latin-68f645c6.woff2 (23.1 KB)
  inter-700-normal-latin-5197a7ae.woff2 (23.8 KB)
  inter-400-normal-latin-ext-1bf1f837.woff2 (34.2 KB)
  inter-700-normal-latin-ext-c8d55506.woff2 (35.4 KB)

CSS in /Users/you/app/src/fonts.css: 4 rule(s) added, 0 updated, 0 already
present. Import it, then use:
  font-family: 'Inter', sans-serif;

Make sure fonts is served at /fonts.

只有 family、targetDir 和 cssPath 是必填项。其余参数默认使用 字重 400 和 700、样式 normal、子集 latin 和 latin-ext,以及 urlPrefix /fonts。

有三点值得注意:

  • urlPrefix 是公开路径,不是磁盘路径。 文件会写入 targetDir;CSS 会通过 urlPrefix 引用这些文件。如果静态资源根目录是 public/,那么 targetDir: "./public/fonts" 应与 urlPrefix: "/fonts" 配套使用。
  • 如果样式表没有被任何文件导入,会明确告知。 创建一个没有任何 文件引用的 fonts.css,是唯一一种看起来完全成功的失败情况 — 文件已写入, 规则也正确,但页面仍然以 Helvetica 渲染。如果工作区根目录下没有任何文件引用新样式表,结果会说明这一点,并告知需要添加什么。优先使用项目已经导入的 cssPath。
  • 重新安装是安全的。 文件名包含内容哈希;当除 src 之外的所有描述符都匹配时,规则会被识别为同一个字体样式,因此再次安装会原地更新规则,而不会叠加重复规则。

如果字体系列确实存在,但缺少你指定的字重,结果会明确说明,而不会归咎于名称:

'Satoshi' exists, but not in the combination you asked for (weights 350;
styles normal; subsets latin, latin-ext). It has weights 300, 400, 500, 700,
900, 300 900; styles normal, italic.

元数据的来源

类别、标签、字体厂商和描述读取自两个目录 API — Fontshare 的 100 个字体系列,以及 Fontsource 的 2,103 个;两者合起来覆盖整个目录,因为 Google 本身就是 Fontsource 的子集。 Google 的公开 API 只提供类别信息,因此不会查询它。

可变轴来自第三个端点: Fontsource 的 /v1/variable。其中的 573 个字体系列按名称关联到目录,没有遗漏。Fontshare 也会发布自己的轴数据,这是它独有的 65 个字体系列能够报告字重范围的唯一途径——它会为每种可变样式列出一条记录,因此同时有可变罗马体和斜体的字体系列会报告两次 wght;这些记录会合并为一个范围;如果各条记录中的 default 不一致,则会将其舍弃,而不是任意选取一个值。

轴的显示名称取自 Google 的 轴注册表,共 56 个标签,采用硬编码而非实时获取:注册表位于一个 2.7MB 的数据载荷中,其余 2.68MB 是 Google 字体列表,而且注册表大约每年才更新一次。复制后新增的标签会原样保留其范围,但不会显示名称。

unifont 已经会获取这两个目录 API 并保留完整记录,但只将它们用于一件事:选择 serif 或 sans-serif 作为 CSS 后备字体。fontpool 不分叉它,而是再次获取这些 API,因此首次使用时会各多发出一个请求——第一次调用工具时总共并行发出三个请求。

Fontshare 的标签是自由文本,因此在公开之前会先进行规范化处理——Poster 和 Posters 会合并为同一个词条,大小写会统一;如果某个标签本身是目录中某款字体的名称,则会将其舍弃,视为数据录入失误。元数据是增强项,不是前提条件:如果三个端点中任意一个无法访问,搜索仍可正常工作,结果也会说明元数据不完整。

允许写入的位置

install_font 会创建目录并向样式表追加内容,因此其操作范围仅限于项目:

  • 当客户端声明了工作区根目录时(大多数客户端都会声明),相对路径会以该根目录为基准解析,两条路径都必须位于该目录内——符号链接也包括在内。任何越界路径都会被拒绝并报错。
  • 当客户端未声明根目录时,就没有可用的限制范围:相对路径会被直接拒绝;绝对路径则按原样处理,并写入其指向的位置。

开发

npm install
npm test

测试套件会通过真实的 stdio JSON-RPC 将服务作为子进程运行。其中大部分测试会调用实时的提供方 API 并下载真实字体;设置 FONTPOOL_SKIP_NETWORK_TESTS=1 可仅运行离线测试。

./build.sh 会将同一服务打包为 Claude Desktop .mcpb 扩展并放入 ~/Downloads;./build.sh --dev 会将 server.mjs 直接推送到已安装的副本中。

许可证

MIT © Travis Valeri

评论

支持 Markdown

第一次接触 MCP?

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

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