MCP server setup
Install @fenfill/mcp in Claude Code, Claude Desktop, Cursor or any client that launches a stdio server.
Before you start
- Node.js 22 or newer.
npxcomes with it. - A fenfill API key (
ff_live_…): fenfill → Settings → API keys. The API is included on Pro, Team and Business: see pricing.
Add the server to your client
Run this in a terminal.
claude mcp add fenfill --scope user -e FENFILL_API_KEY=ff_live_… -- npx -y @fenfill/mcpAdd this to claude_desktop_config.json, then restart Claude Desktop.
{ "mcpServers": { "fenfill": { "command": "npx", "args": ["-y", "@fenfill/mcp"], "env": { "FENFILL_API_KEY": "ff_live_…" } } }}Add this to ~/.cursor/mcp.json (all projects) or .cursor/mcp.json (one project).
{ "mcpServers": { "fenfill": { "command": "npx", "args": ["-y", "@fenfill/mcp"], "env": { "FENFILL_API_KEY": "ff_live_…" } } }}Any client that launches a stdio server: run this command with FENFILL_API_KEY in its environment.
FENFILL_API_KEY=ff_live_… npx -y @fenfill/mcpClient notes
- Claude Desktop reads
claude_desktop_config.jsonfrom~/Library/Application Support/Claude/on macOS and%APPDATA%\Claude\on Windows. Restart Claude Desktop after editing it. - Native Windows: if the client can't find
npx, use"command": "cmd"with"args": ["/c", "npx", "-y", "@fenfill/mcp"]. - The installed command is
fenfill-mcp.
This page describes version 0.2.6. To update, restart your client. If it still runs an older version, use @fenfill/mcp@latest in the command. See the changelog for what changed.
Configuration
The server reads these environment variables, set in the client's config next to the command.
| Variable | Default | What it does |
|---|---|---|
FENFILL_API_KEY | (none) | Your workspace API key (ff_live_…), from fenfill → Settings → API keys. The tools that call fenfill need it; fill_form works without it once a form has been analyzed. |
FENFILL_API_URL | https://api.fenfill.com | The API origin. https only (http is allowed for localhost). |
FENFILL_CACHE_DIR | the OS cache folder | Where the server keeps form schemas and blank forms. Never answers. |
FENFILL_ANALYZE_WAIT_SECONDS | 50 | How many seconds analyze_form and reanalyze_template wait for a result before they answer "running". Calling again with the same arguments picks up the same run. |
The local cache
The cache (FENFILL_CACHE_DIR) is private to your user. By default it is ~/Library/Caches/fenfill-mcp on macOS, ~/.cache/fenfill-mcp on Linux and %LOCALAPPDATA%\fenfill-mcp\Cache on Windows.
- It holds form schemas, blank template PDFs, the workspace logo, sha256 hashes of the PDFs the server produced and
edit_template's working copies, never the answers you fill in. - At startup, and at most once a day, it deletes blank PDFs and the logo after 1 hour, analyses and working copies unused for 90 days, stale job records after 48 hours, and output hashes after a year.
fonts/holds the Japanese font (Noto Sans JP, about 5.8 MB), downloaded the first time a value needs it and never aged out.- Filled PDFs are written only where you ask, readable by your user only (mode 0600).
- Logs go to stderr and contain tool names, timings and error codes, never field values.
To work offline with Japanese values, copy NotoSansJP-Regular.ttf (from https://fenfill.com/fonts/NotoSansJP-Regular.ttf) into <cache>/fonts/ (FENFILL_CACHE_DIR if you set it). It is used if its sha256 matches the pinned value.
Troubleshooting
Errors come back as a code with a message. The common ones:
| Error | What to do |
|---|---|
missing_api_key / invalid_api_key | Set FENFILL_API_KEY in the client's config and restart the client. |
plan_required | Your plan doesn't include API access: see pricing. |
insufficient_scans | Not enough page scans left. Analyze fewer pages, or check get_account. |
prefilled_form / filled_output | Only blank forms can be analyzed: use the original, empty PDF. If it is the blank form (some ship with default text in their fields), use the fenfill web app. When the refusal names annotations or XFA values that belong to the blank form, pass confirm_blank: true. |
pdf_password_required | Remove the open password first. PDFs with only an owner password work as they are. |
pdf_fill_forbidden | The PDF's author forbids form filling, so fenfill won't fill it. Use a copy that allows filling. |
unsupported_script | A value needs Japanese characters and the font could not be loaded. ids lists the fields and details.reason says why (network, http_status, hash_mismatch, timeout, too_large). Connect once and retry, or pre-seed the font (see the local cache). |
not_analyzed | Run analyze_form on the same file (and pages) before fill_form. |
output_exists | Pick a new output_path, or pass overwrite: true. |
edit_conflict | Another fenfill-mcp process changed the form's local edits during the call, so nothing was applied. Call edit_template again with the same ops. |
rate_limited / queue_full | analyze_form already waited about a minute (waited_s). Try again later. |
too_many_active_jobs | Other analyses in the workspace (native-field ones too) are still running. Try again in a minute, or analyze fewer at once. |
template_conflict | The template changed in fenfill since your edits began. Your local edits are kept: call edit_template with discard: true, re-apply the ops and save again. |
invalid_schema | fenfill (or the local check before it) refused the edited layout. details lists each item and reason; fix them with edit_template. |
check_copy_failed | preview_page could not build its outlined copy; stage and id say where. Fix or remove that field or group with edit_template, then preview again. overlay: "none" still shows the page meanwhile. |
request_too_large | The edited template is too large to save in one request. |
answer_in_template | Edited wording matches an answer from this session, so nothing was sent; matched lists the ids and properties. If it is an answer, put the form's own wording back with edit_template. If it is the form's own wording, call save_template again with confirm_wording: true. |
confirm_wording_needed | The working copy has wording edited in an earlier session. Review the ids and properties in changed, then call save_template with confirm_wording: true. |
local_edits_pending | reanalyze_template won't run over unsaved edits: save them with save_template first, or pass discard: true. |
kind_required | The pages already have fields: pass kind (find, relabel, label_missing, or scratch to replace them). |
job_in_progress | A job is already running on the template. Wait and retry (call reanalyze_template again with the same arguments if you started it). |
upload_outcome_unknown | An earlier upload was interrupted. So you aren't charged twice, the same file isn't uploaded again for about 10 minutes; check get_account meanwhile. |
Verify the package
- Releases are built and published by GitHub Actions from github.com/fenfill/mcp with npm provenance: the npm page links each version to the exact commit and workflow run that built it.
- The bundle (
dist/index.js) is not minified, so what runs on your machine can be read.dist/pdfium.wasmis the unmodified PDFium build from@embedpdf/pdfium(seeTHIRD_PARTY_NOTICES), loaded only bypreview_page.
In a project that installs @fenfill/mcp, this checks the registry signature and the provenance attestation:
npm audit signaturesReport vulnerabilities to security@fenfill.com, or through GitHub private vulnerability reporting on the repository, not in public issues.