fontforge-mcp

fontforge-mcp

Exposes FontForge as MCP tools for designing, repairing, and building fonts, with PNG previews to visually verify glyphs and compiled fonts.

Category
访问服务器

README

fontforge-mcp

An MCP server that exposes FontForge as tools, so an AI assistant can design, repair and build fonts — and see the result, because every operation can be rendered back as a PNG.

Draw glyphs from SVG paths, import an SVG folder as an icon font, clean up contours, set metrics and kerning, build OpenType features, derive weights, and compile variable fonts. Then look at a proof sheet before claiming it works.


Requirements

FontForge 2023-01-01 or newer tested on 20251009
Python 3.10+ for the server; mcp requires 3.10
mcp, Pillow, fonttools[woff] see requirements.txt installed into a venv
An MCP client Claude Code, or any other stdio transport

That's the whole list. No Node, no compiler, no Rust toolchain.

You do not install the Python packages into FontForge. They go into a normal Python venv that talks to FontForge over a pipe. The next section explains why — it is the single most important thing to understand before you file a bug.


Why two processes

The obvious design is one process: run the MCP server inside FontForge's own Python, since that's where import fontforge works. On Windows that is impossible, for two measured reasons:

$ ffpython -c "import sysconfig; print(sysconfig.get_platform())"
mingw_x86_64_msvcrt_gnu

$ ffpython -m pip install pydantic
ModuleNotFoundError: No module named 'pip._vendor.distlib'

FontForge for Windows bundles a MinGW-built CPython. PyPI's win_amd64 wheels are MSVC-ABI and will not load in it, and the bundled pip is broken out of the box. So mcp and its pydantic dependency can never live there.

Hence the split:

MCP client (Claude Code, …)
      │  MCP over stdio
      ▼
server.py                    your Python 3.10+ venv
  ├─ mcp                     tool definitions and schemas
  ├─ Pillow                  preview rendering via FreeType
  └─ fontTools               binary inspection, WOFF2, variable fonts
      │  newline-delimited JSON over pipes
      ▼
worker.py                    FontForge's interpreter — stdlib only
  └─ holds open fonts in memory between calls

On Linux and macOS the fontforge module is usually importable from a system python3, so the "two interpreters" may resolve to the same binary. The architecture is unchanged; only the discovery result differs.

One more detail worth knowing: libfontforge writes notices straight to fd 1 from C, which would corrupt the JSON stream. The worker's first act is to dup the real stdout to a private descriptor and point fd 1 at stderr.


Install

1. FontForge

Windows — download the installer from github.com/fontforge/fontforge/releases. It is Inno Setup, so it takes silent flags:

.\FontForge-2025-10-09-Windows-x64.exe /VERYSILENT /NORESTART

ffpython.exe lands in <install>\bin\ and is found automatically.

Debian / Ubuntu

sudo apt install fontforge python3-fontforge

Fedora

sudo dnf install fontforge python3-fontforge

macOS

brew install fontforge

On Linux and macOS you can confirm it immediately — if this prints a version, the hard part is done:

python3 -c "import fontforge; print(fontforge.version())"

On Windows there is nothing to check by hand yet: FontForge ships its own interpreter and its location depends on where you installed it. Step 3 finds it for you, on any drive — don't go hunting for the path.

2. The server

Linux / macOS

git clone https://github.com/LHxis/fontforge-mcp
cd fontforge-mcp
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt

Windows — identical in cmd.exe and PowerShell:

git clone https://github.com/LHxis/fontforge-mcp
cd fontforge-mcp
python -m venv .venv
.venv\Scripts\python.exe -m pip install -r requirements.txt

3. Verify, before registering anything

Two scripts, in this order. The first finds FontForge; the second proves the whole chain works.

.venv/bin/python bridge.py             # Linux / macOS
.venv/bin/python selftest.py
.venv\Scripts\python.exe bridge.py     :: Windows — same in cmd.exe and PowerShell
.venv\Scripts\python.exe selftest.py

bridge.py prints every interpreter it tried, marks which ones can actually import fontforge, and names the winner. If it finds nothing, this is where you learn that — with the exact command to point FFPYTHON at your install. Run it first: everything downstream depends on this one answer.

selftest.py runs 13 checks: it builds a small font from SVG paths, validates the contours, generates TTF/OTF/WOFF2, renders a preview, and drives the server over real MCP stdio — the same path your client will use. It also confirms that a tool error surfaces as an error instead of killing the connection.

If both pass, the problem is never the install. That matters later: when something misbehaves, re-running selftest.py tells you in seconds whether to suspect your setup or your font.

4. Register it

The client stores the command verbatim and does not expand ~ or resolve paths relative to your shell — it needs absolute paths. You do not have to type them: selftest.py printed the exact command for your machine at the end of step 3. Copy that line as printed and you are done.

To build it yourself instead, let the shell fill in the path. Run these from inside the project directory — no username, no drive letter, nothing to edit:

cmd.exe

claude mcp add fontforge --scope user -- "%CD%\.venv\Scripts\python.exe" "%CD%\server.py"

PowerShell

claude mcp add fontforge --scope user -- "$PWD\.venv\Scripts\python.exe" "$PWD\server.py"

Linux / macOS

claude mcp add fontforge --scope user -- "$PWD/.venv/bin/python" "$PWD/server.py"

The shell expands %CD% / $PWD before claude ever sees it, so what gets stored is a plain absolute path.

Keep the Windows command on one line. PowerShell's backtick and bash's backslash continuations both fail in cmd.exe, which is still the default shell for a lot of Windows users — and a broken continuation is a confusing error to hit at the last step.

Any other MCP client — the equivalent stdio entry:

{
  "mcpServers": {
    "fontforge": {
      "command": "/abs/path/fontforge-mcp/.venv/bin/python",
      "args": ["/abs/path/fontforge-mcp/server.py"]
    }
  }
}

Restart the client — MCP servers are loaded at startup. Then ask it to run ff_status; it reports the FontForge version, the interpreter in use and the workspace path.


Configuration

Variable Default Purpose
FONTFORGE_MCP_WORKSPACE ~/fonts where relative paths resolve
FFPYTHON auto-detected pin the FontForge interpreter

How the interpreter is found

Cheapest lookup first, stopping at the first one that can actually import fontforge — existence on disk is never enough:

  1. $FFPYTHON — an explicit override always wins, even when it is wrong, so a bad value fails loudly instead of silently falling through.
  2. Windows registry — FontForge's installer records InstallLocation under the uninstall keys. One lookup finds it on any drive, no scanning.
  3. PATH — ffpython, fontforge-python, and python3 (not on Windows, where python3 resolves to the Microsoft Store stub and pops the Store UI).
  4. Standard roots on every mounted drive — %ProgramFiles%, Program Files, Program Files (x86), Programs, Apps, and each drive root from A: to Z:. This is the fallback for installs the registry does not know about.
  5. The running interpreter — a venv built with --system-site-packages may already see the module.

To see exactly what discovery finds on your machine:

.venv/bin/python bridge.py             # Linux / macOS
.venv\Scripts\python.exe bridge.py     # Windows

It prints every candidate it tried, which ones can import fontforge, and the command to pin the winner.

Pinning it

Skip discovery entirely by setting the variable once:

setx FFPYTHON "D:\wherever\FontForgeBuilds\bin\ffpython.exe"

setx writes it permanently for your user, but only affects new terminals — the one you typed it in keeps the old environment. On Linux/macOS, add export FFPYTHON=/path/to/python3 to your shell rc.

If your MCP client is already running, restart it so it picks up the new environment.


Tools

Font · ff_status ff_new_font ff_open_font ff_list_fonts ff_close_font ff_font_info ff_save ff_generate

Glyphs · ff_list_glyphs ff_glyph_info ff_draw_glyph ff_read_glyph ff_delete_glyph ff_import_svg ff_import_svg_folder ff_transform_glyph ff_clean_glyph ff_stroke_glyph

Metrics · ff_set_metrics ff_auto_metrics ff_kern

Composites & OpenType · ff_add_reference ff_build_accented ff_features

Derivation & variable fonts · ff_derive ff_build_variable_font

Verification · ff_validate ff_inspect_binary ff_preview

Escape hatch · ff_python — runs FontForge Python in the worker, with font pre-bound. FontForge's API is far larger than this server.

ff_preview is the one that matters most. Four modes: outline (one glyph with control points, handles and metric guides), text (real FreeType rasterization of the compiled font), waterfall (many sizes, for small-size legibility), and sheet (a grid of every glyph). Images come back as PNG, so the assistant can judge the result instead of assuming it.

Tool descriptions are currently written in Brazilian Portuguese. They are instructions for the model, not UI text, and models handle them fine — but if you want them in English, they all live in the docstrings in server.py.


Coordinate conventions

Getting these wrong is the number one cause of mangled glyphs.

  • Font units, Y axis UP, baseline at y=0. This is not screen SVG.
  • At em=1000: ascender 800, descender 200, cap height ~700, x-height ~500.
  • Outer contours run CLOCKWISE, counters run counter-clockwise (PostScript/cubic convention). Starting from the bottom-left corner, clockwise means going up first. ff_clean_glyph repairs the direction if you get it backwards.
  • Round letters (O, o, e, s) need overshoot — about 1.5% of the em past the baseline and cap height, or they read smaller than the flat letters beside them.

ff_draw_glyph accepts full SVG path syntax (M L H V C S Q T A Z plus the relative lowercase forms) but interprets the numbers as font units.


Workflows

A typeface from scratch

ff_new_font → ff_draw_glyph (per glyph) → ff_preview mode=outline
→ ff_clean_glyph → ff_set_metrics / ff_kern → ff_preview mode=text
→ ff_generate

Draw H O n o first. Those four fix cap height, x-height, stem weight and overshoot; the rest of the alphabet inherits all of it.

Derive from an existing font

ff_open_font → ff_font_info (rename the family!) → ff_derive → ff_generate

Icon font

ff_new_font → ff_import_svg_folder (maps from U+E000)
→ ff_clean_glyph → ff_generate .woff2

Keep the name→codepoint map the tool returns — that is what your CSS uses.

Variable font

ff_open_font(copy=true)  — once per master
→ a different ff_derive on each handle → ff_generate one TTF per master
→ ff_build_variable_font

copy=true is not optional here. FontForge returns the same font object for a file that is already open, so without it every handle points at one font: the derives stack on each other and all your masters come out identical — with no error to tell you.

Masters must also be interpolable: identical point counts and contour order in every glyph. Deriving them all from one .sfd preserves that; drawing each master by hand almost never does.


Round-tripping with the GUI

ff_save writes .sfd, FontForge's editable source format. Open it in the GUI, adjust by hand, save, and reopen with ff_open_font. Always keep the .sfd next to the binary — it is the font's source code; the .ttf is the build output.


Troubleshooting

"Nenhum Python capaz de 'import fontforge' foi encontrado" — FontForge isn't installed, or its Python bindings are a separate package (python3-fontforge on Debian/Fedora). Point FFPYTHON at the right interpreter if it lives somewhere unusual.

Deleting .venv fails with "access denied" (Windows) — unregistering the server does not stop it. The process started when your client launched is still alive and holds python.exe and the loaded .pyd files open, and Windows will not delete a binary that is mapped into a running process. Restart the client first, or kill the tree by hand. Note there are three processes per server, not one:

python.exe    (.venv\Scripts)   the venv launcher
 └ python.exe (base install)    what it re-execs — this runs server.py
    └ ffpython.exe              the FontForge worker
Get-CimInstance Win32_Process -Filter "Name='python.exe' OR Name='ffpython.exe'" |
  Where-Object { $_.CommandLine -like "*fontforge-mcp*" } |
  ForEach-Object { Stop-Process -Id $_.ProcessId -Force }

Glyphs render as garbage after copying between fonts — TrueType-sourced fonts are quadratic. Copying into a cubic font through a pen drops the curve data and flattens everything into polygons. Match is_quadratic on both fonts, or use FontForge's own copy/paste, which preserves it.

ff_validate reports "missing extrema" right after cleaning — order matters. round() after addExtrema() nudges points off the true extreme. The order that works for TrueType is removeOverlap → correctDirection → addExtrema → round, with rounding last.

correctDirection won't fix a glyph — run removeOverlap on it first. Direction can't be resolved while contours overlap.

Private Use Area characters come back empty from tools — PUA codepoints don't always survive transport as strings. Pass glyph names, or copy the glyphs into a scratch font mapped to ASCII for rendering.

varLib "succeeds" but a glyph doesn't vary — its masters have different point counts, so varLib skipped it silently. ff_build_variable_font detects this and reports the affected glyphs under glifos_que_NAO_variam.


Platform support

Developed and tested on Windows 10/11 with FontForge 20251009. The Linux and macOS discovery paths are implemented but not yet tested on real machines — reports and fixes welcome.


License

MIT — see LICENSE.

On the FontForge question, since it comes up: FontForge's own LICENSE states that the program as a whole is GPL-3.0-or-later, while almost every individual part carries the 3-clause BSD license (George Williams wrote most of it under BSD; only some post-2012 contributions are GPLv3). That mix is why GitHub reports FontForge as NOASSERTION rather than a single SPDX identifier.

This project does not bundle or distribute FontForge — you install it yourself. worker.py is a standalone script that runs inside whichever FontForge you already have, and it reaches the server only through a pipe: separate processes, no linking, no shared address space. That separation is what makes a permissive license appropriate here.

The license covers this software, not the fonts you build with it. Those are yours.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选