fontforge-mcp
Provides tools for designing, repairing, and building fonts using FontForge, including drawing glyphs from SVG paths, importing SVG folders as icon fonts, cleaning contours, setting metrics and kerning, building OpenType features, deriving weights, compiling variable fonts, and rendering previews as PNG.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@fontforge-mcpImport the SVG icons from ./icons into a font and show me a PNG preview"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 |
Python | 3.10+ | for the server; |
| see | 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.
Related MCP server: FontLab MCP Server
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 callsOn 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 /NORESTARTffpython.exe lands in <install>\bin\ and is found automatically.
Debian / Ubuntu
sudo apt install fontforge python3-fontforgeFedora
sudo dnf install fontforge python3-fontforgemacOS
brew install fontforgeOn 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.txtWindows — 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.txt3. 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.pybridge.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 |
|
| where relative paths resolve |
| 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:
$FFPYTHON— an explicit override always wins, even when it is wrong, so a bad value fails loudly instead of silently falling through.Windows registry — FontForge's installer records
InstallLocationunder the uninstall keys. One lookup finds it on any drive, no scanning.PATH—ffpython,fontforge-python, andpython3(not on Windows, wherepython3resolves to the Microsoft Store stub and pops the Store UI).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.The running interpreter — a venv built with
--system-site-packagesmay 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 # WindowsIt 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_glyphrepairs 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_generateDraw 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_generateIcon font
ff_new_font → ff_import_svg_folder (maps from U+E000)
→ ff_clean_glyph → ff_generate .woff2Keep 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_fontcopy=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 workerGet-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.
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
- Flicense-qualityCmaintenanceEnables AI agents to diagnose, modify, and validate OTF/TTF fonts interactively through a set of read-only, write, and validation tools.
- Alicense-qualityDmaintenanceEnables AI assistants to interact with FontLab for font design and manipulation, including querying font metadata, creating/modifying glyphs, applying transformations, and exporting fonts.7MIT
- Alicense-qualityCmaintenanceMCP bridge for AI-assisted type design in GlyphsApp. Lets Claude, Cursor, or any MCP client read and write font data directly in GlyphsApp — bidirectional, real-time, live in the editor.7MIT
- FlicenseAqualityCmaintenanceProvides 8 MCP tools for frontend design: load design system, generate components, scaffold pages, review consistency, extract tokens, check WCAG 2.2 AA, test responsiveness, and export to Figma.8
Related MCP Connectors
JSON tools MCP.
Generate authentic pixel art - sprites, animations, and tilesets - from any MCP client
MCP tools for TON Sites, TON DNS and TON Storage.
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/LHxis/fontforge-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server