@formfeed/mcp 0.3.17 → 0.3.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/bin/formfeed-mcp.mjs +197 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
Model Context Protocol server for [Formfeed](https://formfeed.dev/?utm_source=npm&utm_content=mcp).
|
|
6
6
|
Gives Claude, Cursor and other agents eight tools: `list_templates`, `get_template_schema`,
|
|
7
7
|
`validate_template` (free: the data against the template's schema, ad-hoc HTML offline), `check_invoice` (free and offline: the
|
|
8
|
-
`_invoice` block of a ZUGFeRD / Factur-X e-invoice against EN 16931), `render` (with `einvoice`, the PDF becomes that e-invoice
|
|
8
|
+
`_invoice` block of a ZUGFeRD / Factur-X e-invoice against EN 16931), `render` (with `einvoice`, the PDF becomes that e-invoice; an
|
|
9
|
+
image comes back as a picture the agent can look at),
|
|
9
10
|
`convert_to_pdf` (Word, Excel, PowerPoint and OpenDocument to PDF), `get_render` and `get_workspace` (which workspace the
|
|
10
11
|
server works in, and the units left).
|
|
11
12
|
|
|
@@ -23,8 +24,10 @@ server works in, and the units left).
|
|
|
23
24
|
}
|
|
24
25
|
```
|
|
25
26
|
|
|
26
|
-
A test key renders free with a
|
|
27
|
-
`
|
|
27
|
+
A test key renders for free (on the Free plan with a test mark); a live key consumes units.
|
|
28
|
+
Without `FORMFEED_API_KEY` the server starts too, with the offline checks only: `validate_template`
|
|
29
|
+
with `html` and `engine`, and `check_invoice`. `FORMFEED_REGION=us` or `FORMFEED_BASE_URL` select
|
|
30
|
+
another API host.
|
|
28
31
|
|
|
29
32
|
## Hosted (Streamable HTTP)
|
|
30
33
|
|
package/bin/formfeed-mcp.mjs
CHANGED
|
@@ -7573,7 +7573,7 @@ var require_base64 = __commonJS({
|
|
|
7573
7573
|
var require_base64_vlq = __commonJS({
|
|
7574
7574
|
"node_modules/.pnpm/source-map@0.6.1/node_modules/source-map/lib/base64-vlq.js"(exports) {
|
|
7575
7575
|
"use strict";
|
|
7576
|
-
var
|
|
7576
|
+
var base642 = require_base64();
|
|
7577
7577
|
var VLQ_BASE_SHIFT = 5;
|
|
7578
7578
|
var VLQ_BASE = 1 << VLQ_BASE_SHIFT;
|
|
7579
7579
|
var VLQ_BASE_MASK = VLQ_BASE - 1;
|
|
@@ -7596,7 +7596,7 @@ var require_base64_vlq = __commonJS({
|
|
|
7596
7596
|
if (vlq > 0) {
|
|
7597
7597
|
digit |= VLQ_CONTINUATION_BIT;
|
|
7598
7598
|
}
|
|
7599
|
-
encoded +=
|
|
7599
|
+
encoded += base642.encode(digit);
|
|
7600
7600
|
} while (vlq > 0);
|
|
7601
7601
|
return encoded;
|
|
7602
7602
|
};
|
|
@@ -7609,7 +7609,7 @@ var require_base64_vlq = __commonJS({
|
|
|
7609
7609
|
if (aIndex >= strLen) {
|
|
7610
7610
|
throw new Error("Expected more digits in base 64 VLQ value.");
|
|
7611
7611
|
}
|
|
7612
|
-
digit =
|
|
7612
|
+
digit = base642.decode(aStr.charCodeAt(aIndex++));
|
|
7613
7613
|
if (digit === -1) {
|
|
7614
7614
|
throw new Error("Invalid base64 digit: " + aStr.charAt(aIndex - 1));
|
|
7615
7615
|
}
|
|
@@ -14656,7 +14656,7 @@ var require_nunjucks = __commonJS({
|
|
|
14656
14656
|
});
|
|
14657
14657
|
return result;
|
|
14658
14658
|
};
|
|
14659
|
-
_proto.fetch = function
|
|
14659
|
+
_proto.fetch = function fetch2(url, cb) {
|
|
14660
14660
|
if (typeof window === "undefined") {
|
|
14661
14661
|
throw new Error("WebLoader can only by used in a browser");
|
|
14662
14662
|
}
|
|
@@ -70857,11 +70857,11 @@ var codeHelpers = [
|
|
|
70857
70857
|
aliases: ["epc_qr", "girocode"],
|
|
70858
70858
|
doc: {
|
|
70859
70859
|
signature: "epcQr({ name, iban, bic?, amount?, reference?, text? }, { size })",
|
|
70860
|
-
description: "SEPA credit transfer QR code (EPC069-12 / GiroCode) that banking apps scan to prefill a payment. Give the image an `alt`, and print the bank details as text as well: the code is a picture.",
|
|
70860
|
+
description: "SEPA credit transfer QR code (EPC069-12 / GiroCode) that banking apps scan to prefill a payment. Put an invoice number into `text` (the remittance line); `reference` is only for a structured creditor reference (RF\u2026). Give the image an `alt`, and print the bank details as text as well: the code is a picture.",
|
|
70861
70861
|
examples: {
|
|
70862
|
-
jinja2: '<img src="{{ epcQr({ name: company.name, iban: company.iban, amount: invoice.total,
|
|
70863
|
-
liquid: '<img src="{{ company.iban | epcQr: name: company.name, amount: invoice.total,
|
|
70864
|
-
handlebars: '<img src="{{epcQr name=company.name iban=company.iban amount=invoice.total
|
|
70862
|
+
jinja2: '<img src="{{ epcQr({ name: company.name, iban: company.iban, amount: invoice.total, text: invoice.number }) }}" alt="QR code to pay invoice {{ invoice.number }}">',
|
|
70863
|
+
liquid: '<img src="{{ company.iban | epcQr: name: company.name, amount: invoice.total, text: invoice.number }}" alt="QR code to pay invoice {{ invoice.number }}">',
|
|
70864
|
+
handlebars: '<img src="{{epcQr name=company.name iban=company.iban amount=invoice.total text=invoice.number}}" alt="QR code to pay invoice {{invoice.number}}">'
|
|
70865
70865
|
},
|
|
70866
70866
|
category: "code"
|
|
70867
70867
|
},
|
|
@@ -83841,7 +83841,44 @@ var encoder2 = new TextEncoder();
|
|
|
83841
83841
|
// packages/mcp/src/lib/server.ts
|
|
83842
83842
|
import { z as z3 } from "zod";
|
|
83843
83843
|
var CONVERT_LIMIT_BYTES = 20 * 1024 * 1024;
|
|
83844
|
-
var
|
|
83844
|
+
var NO_KEY = "This tool needs a Formfeed API key. Set FORMFEED_API_KEY to a key from https://app.formfeed.dev/keys (a test key, ff_test_\u2026, renders for free), or connect to https://mcp.formfeed.dev/mcp and sign in. Without a key, validate_template (with html and engine) and check_invoice work.";
|
|
83845
|
+
var NO_KEY_INSTRUCTION = "No API key is set, so only validate_template with html and engine, and check_invoice, work here; every other tool answers with how to get a key.";
|
|
83846
|
+
var IMAGE_TYPES = {
|
|
83847
|
+
png: "image/png",
|
|
83848
|
+
jpg: "image/jpeg",
|
|
83849
|
+
webp: "image/webp"
|
|
83850
|
+
};
|
|
83851
|
+
var IMAGE_LIMIT_BYTES = 2 * 1024 * 1024;
|
|
83852
|
+
async function readCapped(res, limit) {
|
|
83853
|
+
if (!res.body) return null;
|
|
83854
|
+
const reader = res.body.getReader();
|
|
83855
|
+
const chunks = [];
|
|
83856
|
+
let size = 0;
|
|
83857
|
+
for (; ; ) {
|
|
83858
|
+
const { done, value } = await reader.read();
|
|
83859
|
+
if (done) break;
|
|
83860
|
+
size += value.length;
|
|
83861
|
+
if (size > limit) {
|
|
83862
|
+
await reader.cancel();
|
|
83863
|
+
return null;
|
|
83864
|
+
}
|
|
83865
|
+
chunks.push(value);
|
|
83866
|
+
}
|
|
83867
|
+
const bytes = new Uint8Array(size);
|
|
83868
|
+
let offset = 0;
|
|
83869
|
+
for (const chunk of chunks) {
|
|
83870
|
+
bytes.set(chunk, offset);
|
|
83871
|
+
offset += chunk.length;
|
|
83872
|
+
}
|
|
83873
|
+
return bytes;
|
|
83874
|
+
}
|
|
83875
|
+
function base64(bytes) {
|
|
83876
|
+
let binary = "";
|
|
83877
|
+
for (let i = 0; i < bytes.length; i += 32768)
|
|
83878
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
83879
|
+
return btoa(binary);
|
|
83880
|
+
}
|
|
83881
|
+
var SERVER_INFO = { name: "formfeed", version: "0.3.18" };
|
|
83845
83882
|
var READS = {
|
|
83846
83883
|
readOnlyHint: true,
|
|
83847
83884
|
destructiveHint: false,
|
|
@@ -83856,6 +83893,37 @@ var CREATES = {
|
|
|
83856
83893
|
var engineSchema = z3.enum(["jinja2", "liquid", "handlebars"]);
|
|
83857
83894
|
var outputSchema = z3.enum(outputFormats);
|
|
83858
83895
|
var kindSchema = z3.enum(["pdf", "image", "docx", "pptx"]);
|
|
83896
|
+
var renderOutput = {
|
|
83897
|
+
id: z3.string().describe("rnd_ id of the render"),
|
|
83898
|
+
status: z3.enum(["queued", "rendering", "succeeded", "failed"]),
|
|
83899
|
+
download_url: z3.string().nullable().describe("Signed link to the file; null until the render succeeded"),
|
|
83900
|
+
expires_at: z3.string().nullable().describe("When download_url stops working; get_render issues a fresh one"),
|
|
83901
|
+
page_count: z3.number().nullable(),
|
|
83902
|
+
units: z3.number().describe("Units the render counted"),
|
|
83903
|
+
environment: z3.enum(["live", "test"]),
|
|
83904
|
+
template: z3.object({
|
|
83905
|
+
id: z3.string(),
|
|
83906
|
+
slug: z3.string(),
|
|
83907
|
+
version: z3.number(),
|
|
83908
|
+
channel: z3.string().nullable().optional(),
|
|
83909
|
+
canary: z3.boolean().optional()
|
|
83910
|
+
}).nullable().describe(
|
|
83911
|
+
"The template and version rendered; null for html and conversions"
|
|
83912
|
+
),
|
|
83913
|
+
error: z3.object({ code: z3.string().optional(), message: z3.string().optional() }).nullable().describe("Why a failed render failed"),
|
|
83914
|
+
accessibility: z3.record(z3.string(), z3.unknown()).optional().describe("The PDF/UA-1 verdict, when the template declares it"),
|
|
83915
|
+
einvoice: z3.record(z3.string(), z3.unknown()).optional().describe("The e-invoice report, when the render made one")
|
|
83916
|
+
};
|
|
83917
|
+
var diagnosticOutput = z3.object({
|
|
83918
|
+
severity: z3.enum(["error", "warning", "info"]),
|
|
83919
|
+
code: z3.string(),
|
|
83920
|
+
message: z3.string(),
|
|
83921
|
+
path: z3.string().optional().describe("Where in the data, for data errors"),
|
|
83922
|
+
line: z3.number().optional(),
|
|
83923
|
+
column: z3.number().optional(),
|
|
83924
|
+
part: z3.string().optional().describe("Word and PowerPoint templates: the file part of the tag"),
|
|
83925
|
+
paragraph: z3.number().optional()
|
|
83926
|
+
});
|
|
83859
83927
|
var templateSummary = (t) => ({
|
|
83860
83928
|
id: t.id,
|
|
83861
83929
|
slug: t.slug,
|
|
@@ -83916,7 +83984,7 @@ ${JSON.stringify(details, null, 2)}` : message2
|
|
|
83916
83984
|
};
|
|
83917
83985
|
}
|
|
83918
83986
|
function createFormfeedServer(options) {
|
|
83919
|
-
const client = new Formfeed({
|
|
83987
|
+
const client = options.apiKey ? new Formfeed({
|
|
83920
83988
|
apiKey: options.apiKey,
|
|
83921
83989
|
headers: {
|
|
83922
83990
|
"user-agent": `formfeed-mcp/${SERVER_INFO.version}`,
|
|
@@ -83925,15 +83993,39 @@ function createFormfeedServer(options) {
|
|
|
83925
83993
|
...options.region ? { region: options.region } : {},
|
|
83926
83994
|
...options.baseUrl ? { baseUrl: options.baseUrl } : {},
|
|
83927
83995
|
...options.fetch ? { fetch: options.fetch } : {}
|
|
83928
|
-
});
|
|
83996
|
+
}) : null;
|
|
83997
|
+
const api = () => {
|
|
83998
|
+
if (!client) throw new Error(NO_KEY);
|
|
83999
|
+
return client;
|
|
84000
|
+
};
|
|
84001
|
+
const fetchImpl = options.fetch ?? ((input, init) => fetch(input, init));
|
|
83929
84002
|
const server = new McpServer(SERVER_INFO, {
|
|
83930
|
-
instructions: "Formfeed renders PDFs and images from HTML templates, and fills Word and PowerPoint templates (as DOCX, PPTX or PDF). Start with list_templates, read the data shape with get_template_schema, check data with validate_template, then call render and hand the download_url to the user. For an e-invoice (ZUGFeRD / Factur-X), put the invoice into data._invoice, check it with check_invoice, and call render with einvoice set; the template prints the same block. convert_to_pdf turns office documents into PDFs. get_workspace says which workspace this server works in and what the period has left."
|
|
84003
|
+
instructions: (client ? "" : `${NO_KEY_INSTRUCTION} `) + "Formfeed renders PDFs and images from HTML templates, and fills Word and PowerPoint templates (as DOCX, PPTX or PDF). Start with list_templates, read the data shape with get_template_schema, check data with validate_template, then call render and hand the download_url to the user. An image render also returns the picture itself, so look at it before handing it on. For an e-invoice (ZUGFeRD / Factur-X), put the invoice into data._invoice, check it with check_invoice, and call render with einvoice set; the template prints the same block. convert_to_pdf turns office documents into PDFs. get_workspace says which workspace this server works in and what the period has left."
|
|
83931
84004
|
});
|
|
84005
|
+
const picture = async (render) => {
|
|
84006
|
+
const mimeType = IMAGE_TYPES[render.output];
|
|
84007
|
+
if (render.status !== "succeeded" || !mimeType || !render.download_url)
|
|
84008
|
+
return [];
|
|
84009
|
+
try {
|
|
84010
|
+
const res = await fetchImpl(render.download_url);
|
|
84011
|
+
const bytes = res.ok ? await readCapped(res, IMAGE_LIMIT_BYTES) : null;
|
|
84012
|
+
return bytes ? [{ type: "image", data: base64(bytes), mimeType }] : [];
|
|
84013
|
+
} catch {
|
|
84014
|
+
return [];
|
|
84015
|
+
}
|
|
84016
|
+
};
|
|
84017
|
+
const renderResult = async (render) => {
|
|
84018
|
+
const result = text3(renderSummary(render));
|
|
84019
|
+
return {
|
|
84020
|
+
...result,
|
|
84021
|
+
content: [...result.content, ...await picture(render)]
|
|
84022
|
+
};
|
|
84023
|
+
};
|
|
83932
84024
|
server.registerTool(
|
|
83933
84025
|
"list_templates",
|
|
83934
84026
|
{
|
|
83935
84027
|
title: "List templates",
|
|
83936
|
-
description: "Lists the templates of the workspace the
|
|
84028
|
+
description: "Lists the templates of the workspace this server works in (the one chosen at sign-in, or the API key's), most recently changed first: id, slug, name, kind (pdf, image, docx for Word, pptx for PowerPoint), engine, tags and published_version. Call it first to find the slug that get_template_schema, validate_template and render take; skip it when the user already named the template. Returns every match in one answer, without archived templates, and an empty list when nothing matches. A template whose published_version is null has never been published, so render cannot use it yet. Read-only and free.",
|
|
83937
84029
|
inputSchema: {
|
|
83938
84030
|
kind: kindSchema.optional().describe("Only templates that produce this kind of document"),
|
|
83939
84031
|
engine: engineSchema.optional().describe("Only templates written in this template language"),
|
|
@@ -83944,11 +84036,26 @@ function createFormfeedServer(options) {
|
|
|
83944
84036
|
"Text to find in the name or slug, case-insensitive (e.g. invoice)"
|
|
83945
84037
|
)
|
|
83946
84038
|
},
|
|
84039
|
+
outputSchema: {
|
|
84040
|
+
templates: z3.array(
|
|
84041
|
+
z3.object({
|
|
84042
|
+
id: z3.string().describe("tpl_ id"),
|
|
84043
|
+
slug: z3.string(),
|
|
84044
|
+
name: z3.string(),
|
|
84045
|
+
kind: z3.string().describe("pdf, image, docx (Word) or pptx (PowerPoint)"),
|
|
84046
|
+
engine: z3.string().describe("jinja2, liquid or handlebars"),
|
|
84047
|
+
description: z3.string().nullable(),
|
|
84048
|
+
tags: z3.array(z3.string()),
|
|
84049
|
+
published_version: z3.number().nullable().describe("null: never published, so render cannot use it yet"),
|
|
84050
|
+
updated_at: z3.string()
|
|
84051
|
+
})
|
|
84052
|
+
)
|
|
84053
|
+
},
|
|
83947
84054
|
annotations: READS
|
|
83948
84055
|
},
|
|
83949
84056
|
async (args2) => {
|
|
83950
84057
|
try {
|
|
83951
|
-
const templates = await
|
|
84058
|
+
const templates = await api().templates.all(args2);
|
|
83952
84059
|
return text3({ templates: templates.map(templateSummary) });
|
|
83953
84060
|
} catch (e) {
|
|
83954
84061
|
return failure(e);
|
|
@@ -83965,13 +84072,22 @@ function createFormfeedServer(options) {
|
|
|
83965
84072
|
"Slug (e.g. invoice) or tpl_ id, as list_templates returns them"
|
|
83966
84073
|
)
|
|
83967
84074
|
},
|
|
84075
|
+
outputSchema: {
|
|
84076
|
+
template: z3.string(),
|
|
84077
|
+
version: z3.number().describe("The version the schema comes from"),
|
|
84078
|
+
status: z3.string().describe("published, or draft when nothing is published yet"),
|
|
84079
|
+
schema: z3.record(z3.string(), z3.unknown()).describe(
|
|
84080
|
+
"JSON Schema of data; inferred from sample_data when none is stored"
|
|
84081
|
+
),
|
|
84082
|
+
sample_data: z3.record(z3.string(), z3.unknown())
|
|
84083
|
+
},
|
|
83968
84084
|
annotations: READS
|
|
83969
84085
|
},
|
|
83970
84086
|
async ({ template }) => {
|
|
83971
84087
|
try {
|
|
83972
|
-
const version = await
|
|
84088
|
+
const version = await api().templates.versions.get(template, "published").catch(async (e) => {
|
|
83973
84089
|
if (e.status === 404)
|
|
83974
|
-
return
|
|
84090
|
+
return api().templates.versions.get(template, "latest");
|
|
83975
84091
|
throw e;
|
|
83976
84092
|
});
|
|
83977
84093
|
const schema = version.data_schema ?? inferSchema(version.sample_data ?? {});
|
|
@@ -84002,12 +84118,17 @@ function createFormfeedServer(options) {
|
|
|
84002
84118
|
"The data the document will be rendered with, shaped as get_template_schema describes. Left out, a stored template is checked with its sample data"
|
|
84003
84119
|
)
|
|
84004
84120
|
},
|
|
84121
|
+
outputSchema: {
|
|
84122
|
+
ok: z3.boolean().describe("false when a diagnostic is an error"),
|
|
84123
|
+
template: z3.object({ id: z3.string(), slug: z3.string(), version: z3.number() }).optional().describe("The stored version checked; absent for html"),
|
|
84124
|
+
diagnostics: z3.array(diagnosticOutput)
|
|
84125
|
+
},
|
|
84005
84126
|
annotations: READS
|
|
84006
84127
|
},
|
|
84007
84128
|
async ({ template, html, engine, data }) => {
|
|
84008
84129
|
try {
|
|
84009
84130
|
if (template) {
|
|
84010
|
-
const check = (version) =>
|
|
84131
|
+
const check = (version) => api().templates.validate(template, {
|
|
84011
84132
|
version,
|
|
84012
84133
|
...data ? { data } : {}
|
|
84013
84134
|
});
|
|
@@ -84076,6 +84197,16 @@ function createFormfeedServer(options) {
|
|
|
84076
84197
|
"The profile the render will ask for; en16931 when left out. basic cannot carry a rounding amount and is refused between two French businesses"
|
|
84077
84198
|
)
|
|
84078
84199
|
},
|
|
84200
|
+
outputSchema: {
|
|
84201
|
+
ok: z3.boolean(),
|
|
84202
|
+
problems: z3.array(
|
|
84203
|
+
z3.object({
|
|
84204
|
+
path: z3.string().describe("The field, e.g. _invoice.seller.vat_id"),
|
|
84205
|
+
rule: z3.string().optional().describe("The EN 16931 rule, e.g. BR-03"),
|
|
84206
|
+
message: z3.string()
|
|
84207
|
+
})
|
|
84208
|
+
)
|
|
84209
|
+
},
|
|
84079
84210
|
annotations: READS
|
|
84080
84211
|
},
|
|
84081
84212
|
async ({ invoice, profile }) => {
|
|
@@ -84094,7 +84225,7 @@ function createFormfeedServer(options) {
|
|
|
84094
84225
|
"render",
|
|
84095
84226
|
{
|
|
84096
84227
|
title: "Render a document",
|
|
84097
|
-
description: "Creates a document by filling a stored template, or ad-hoc html, with data: a PDF or image, or DOCX, PPTX or PDF from Word and PowerPoint templates. Returns the render with status and download_url. Use it to make a document from data; to turn an existing office file into a PDF, use convert_to_pdf. Stored templates render their published version, so one whose published_version is null fails with template_not_found. Every call is a new render: a live key consumes units, a test key is free (watermarked on the Free plan). A render that fails returns status failed and an error with code and message; validate_template finds most causes beforehand. With einvoice the PDF becomes a ZUGFeRD / Factur-X e-invoice built from data._invoice (check the block with check_invoice first): the render then carries einvoice, the validator's report, and a refused invoice names the fields and rules to fix. The link expires at expires_at; get_render issues a fresh one.",
|
|
84228
|
+
description: "Creates a document by filling a stored template, or ad-hoc html, with data: a PDF or image, or DOCX, PPTX or PDF from Word and PowerPoint templates. Returns the render with status and download_url; for an image (png, jpg or webp) up to 2 MB the result also carries the picture itself, so look at what you made before handing it on (a PDF comes as its link). Use it to make a document from data; to turn an existing office file into a PDF, use convert_to_pdf. Stored templates render their published version, so one whose published_version is null fails with template_not_found. Every call is a new render: a live key or a signed-in agent consumes units, a test key is free (watermarked on the Free plan). A render that fails returns status failed and an error with code and message; validate_template finds most causes beforehand. With einvoice the PDF becomes a ZUGFeRD / Factur-X e-invoice built from data._invoice (check the block with check_invoice first): the render then carries einvoice, the validator's report, and a refused invoice names the fields and rules to fix. The link expires at expires_at; get_render issues a fresh one.",
|
|
84098
84229
|
inputSchema: {
|
|
84099
84230
|
template: z3.string().optional().describe("Slug or tpl_ id of a stored template; pass this or html"),
|
|
84100
84231
|
html: z3.string().optional().describe(
|
|
@@ -84136,6 +84267,7 @@ function createFormfeedServer(options) {
|
|
|
84136
84267
|
"Makes the PDF a ZUGFeRD / Factur-X e-invoice whose XML is built from data._invoice: true for the defaults, an object to choose, false to switch off what the template declares. PDF only; live renders from the Starter plan, test keys on Free make 20 marked ones a day"
|
|
84137
84268
|
)
|
|
84138
84269
|
},
|
|
84270
|
+
outputSchema: renderOutput,
|
|
84139
84271
|
annotations: CREATES
|
|
84140
84272
|
},
|
|
84141
84273
|
async ({
|
|
@@ -84152,7 +84284,7 @@ function createFormfeedServer(options) {
|
|
|
84152
84284
|
try {
|
|
84153
84285
|
if (!template && !html)
|
|
84154
84286
|
return failure(new Error("Pass template or html"));
|
|
84155
|
-
let render = await
|
|
84287
|
+
let render = await api().renders.create({
|
|
84156
84288
|
...template ? { template } : {},
|
|
84157
84289
|
...html ? { html } : {},
|
|
84158
84290
|
...engine ? { engine } : {},
|
|
@@ -84164,8 +84296,8 @@ function createFormfeedServer(options) {
|
|
|
84164
84296
|
meta: { source: "mcp" }
|
|
84165
84297
|
});
|
|
84166
84298
|
if (wait && render.status !== "succeeded" && render.status !== "failed")
|
|
84167
|
-
render = await
|
|
84168
|
-
return
|
|
84299
|
+
render = await api().renders.waitFor(render.id);
|
|
84300
|
+
return await renderResult(render);
|
|
84169
84301
|
} catch (e) {
|
|
84170
84302
|
return failure(e);
|
|
84171
84303
|
}
|
|
@@ -84200,6 +84332,7 @@ function createFormfeedServer(options) {
|
|
|
84200
84332
|
single_page_sheets: z3.boolean().optional().describe("Each spreadsheet sheet on one page"),
|
|
84201
84333
|
filename: z3.string().optional().describe("Name of the PDF behind download_url, e.g. report.pdf")
|
|
84202
84334
|
},
|
|
84335
|
+
outputSchema: renderOutput,
|
|
84203
84336
|
annotations: CREATES
|
|
84204
84337
|
},
|
|
84205
84338
|
async (args2) => {
|
|
@@ -84232,7 +84365,7 @@ function createFormfeedServer(options) {
|
|
|
84232
84365
|
};
|
|
84233
84366
|
let render;
|
|
84234
84367
|
if (render_id) {
|
|
84235
|
-
render = await
|
|
84368
|
+
render = await api().pdf.convert(render_id, convert);
|
|
84236
84369
|
} else {
|
|
84237
84370
|
let data;
|
|
84238
84371
|
let name;
|
|
@@ -84263,7 +84396,7 @@ function createFormfeedServer(options) {
|
|
|
84263
84396
|
);
|
|
84264
84397
|
name = file_name;
|
|
84265
84398
|
}
|
|
84266
|
-
render = await
|
|
84399
|
+
render = await api().pdf.convert(
|
|
84267
84400
|
{ file: { data, name } },
|
|
84268
84401
|
{
|
|
84269
84402
|
...convert,
|
|
@@ -84272,8 +84405,8 @@ function createFormfeedServer(options) {
|
|
|
84272
84405
|
);
|
|
84273
84406
|
}
|
|
84274
84407
|
if (render.status !== "succeeded" && render.status !== "failed")
|
|
84275
|
-
render = await
|
|
84276
|
-
return
|
|
84408
|
+
render = await api().renders.waitFor(render.id);
|
|
84409
|
+
return await renderResult(render);
|
|
84277
84410
|
} catch (e) {
|
|
84278
84411
|
return failure(e);
|
|
84279
84412
|
}
|
|
@@ -84283,17 +84416,18 @@ function createFormfeedServer(options) {
|
|
|
84283
84416
|
"get_render",
|
|
84284
84417
|
{
|
|
84285
84418
|
title: "Get a render",
|
|
84286
|
-
description: "Returns the current state of a render: status (queued, rendering, succeeded or failed), download_url with expires_at, page_count, units and error. Use it to follow a render started with wait false, or to get a fresh link once the old one has expired; render and convert_to_pdf already return this for the render they just made. Read-only and free; an unknown id returns an error.",
|
|
84419
|
+
description: "Returns the current state of a render: status (queued, rendering, succeeded or failed), download_url with expires_at, page_count, units and error, and the picture of a finished image render up to 2 MB. Use it to follow a render started with wait false, or to get a fresh link once the old one has expired; render and convert_to_pdf already return this for the render they just made. Read-only and free; an unknown id returns an error.",
|
|
84287
84420
|
inputSchema: {
|
|
84288
84421
|
id: z3.string().describe(
|
|
84289
84422
|
"rnd_ id of the render, as render or convert_to_pdf returned it"
|
|
84290
84423
|
)
|
|
84291
84424
|
},
|
|
84425
|
+
outputSchema: renderOutput,
|
|
84292
84426
|
annotations: READS
|
|
84293
84427
|
},
|
|
84294
84428
|
async ({ id }) => {
|
|
84295
84429
|
try {
|
|
84296
|
-
return
|
|
84430
|
+
return await renderResult(await api().renders.get(id));
|
|
84297
84431
|
} catch (e) {
|
|
84298
84432
|
return failure(e);
|
|
84299
84433
|
}
|
|
@@ -84305,11 +84439,36 @@ function createFormfeedServer(options) {
|
|
|
84305
84439
|
title: "Which workspace this server works in",
|
|
84306
84440
|
description: "Returns the workspace and organisation this server acts in, the plan, the environment (live or test) and the units of the current period: included, used and the balance of prepaid units. Call it when the user asks where a document will land or how many renders are left, or when list_templates returns nothing you expected. A signed-in agent works in the workspace chosen when it was connected, which the person changes in Formfeed under Account \u2192 Connected agents; an API key works in the workspace it was made for. Read-only and free.",
|
|
84307
84441
|
inputSchema: {},
|
|
84442
|
+
outputSchema: {
|
|
84443
|
+
workspace: z3.object({
|
|
84444
|
+
id: z3.string(),
|
|
84445
|
+
slug: z3.string(),
|
|
84446
|
+
name: z3.string(),
|
|
84447
|
+
region: z3.string().describe("eu, us or apac")
|
|
84448
|
+
}),
|
|
84449
|
+
organization: z3.object({
|
|
84450
|
+
id: z3.string(),
|
|
84451
|
+
slug: z3.string(),
|
|
84452
|
+
name: z3.string()
|
|
84453
|
+
}),
|
|
84454
|
+
plan: z3.object({
|
|
84455
|
+
id: z3.string(),
|
|
84456
|
+
name: z3.string(),
|
|
84457
|
+
monthly_units: z3.number().nullable().optional().describe("Units included a month; null where the plan sets none")
|
|
84458
|
+
}),
|
|
84459
|
+
environment: z3.enum(["live", "test"]).optional().describe("live renders count units; test renders are free"),
|
|
84460
|
+
period: z3.object({ start: z3.string(), end: z3.string() }).describe("The current billing period"),
|
|
84461
|
+
units: z3.object({
|
|
84462
|
+
included: z3.number().nullable().optional(),
|
|
84463
|
+
used: z3.number(),
|
|
84464
|
+
overage_balance: z3.number().optional().describe("Prepaid units left beyond the included ones")
|
|
84465
|
+
})
|
|
84466
|
+
},
|
|
84308
84467
|
annotations: READS
|
|
84309
84468
|
},
|
|
84310
84469
|
async () => {
|
|
84311
84470
|
try {
|
|
84312
|
-
const { workspace, organization, plan, environment, period: period2, units } = await
|
|
84471
|
+
const { workspace, organization, plan, environment, period: period2, units } = await api().account.get();
|
|
84313
84472
|
return text3({
|
|
84314
84473
|
workspace,
|
|
84315
84474
|
organization,
|
|
@@ -84670,7 +84829,7 @@ var authorizationServer = flag("--auth-server") ?? process.env["FORMFEED_AUTH_SE
|
|
|
84670
84829
|
var publicOrigin = flag("--public-origin") ?? process.env["FORMFEED_PUBLIC_ORIGIN"];
|
|
84671
84830
|
if (args.includes("--help") || args.includes("-h")) {
|
|
84672
84831
|
process.stdout.write(
|
|
84673
|
-
"formfeed-mcp: Model Context Protocol server for Formfeed\n\n formfeed-mcp stdio;
|
|
84832
|
+
"formfeed-mcp: Model Context Protocol server for Formfeed\n\n formfeed-mcp stdio; FORMFEED_API_KEY, without it the offline checks only\n formfeed-mcp --http Streamable HTTP on --port (8790) and --host (127.0.0.1); the key comes from Authorization: Bearer\n --auth-server <issuer> (or FORMFEED_AUTH_SERVER) also takes OAuth access tokens on /mcp and /mcp/<workspace id>;\n --public-origin <url> (or FORMFEED_PUBLIC_ORIGIN) is the origin clients see behind a proxy\n"
|
|
84674
84833
|
);
|
|
84675
84834
|
} else if (args.includes("--http")) {
|
|
84676
84835
|
startHttp({
|
|
@@ -84683,17 +84842,16 @@ if (args.includes("--help") || args.includes("-h")) {
|
|
|
84683
84842
|
}).then((h) => process.stderr.write(`formfeed-mcp listening on ${h.url}
|
|
84684
84843
|
`));
|
|
84685
84844
|
} else {
|
|
84686
|
-
const apiKey = process.env["FORMFEED_API_KEY"];
|
|
84687
|
-
if (!apiKey)
|
|
84688
|
-
process.stderr.write(
|
|
84689
|
-
|
|
84690
|
-
|
|
84691
|
-
|
|
84692
|
-
|
|
84845
|
+
const apiKey = process.env["FORMFEED_API_KEY"] || void 0;
|
|
84846
|
+
if (!apiKey)
|
|
84847
|
+
process.stderr.write(
|
|
84848
|
+
"FORMFEED_API_KEY is not set: only validate_template (with html) and check_invoice work\n"
|
|
84849
|
+
);
|
|
84850
|
+
startStdio({ apiKey, baseUrl, region }).catch((e) => {
|
|
84851
|
+
process.stderr.write(`${e instanceof Error ? e.message : String(e)}
|
|
84693
84852
|
`);
|
|
84694
|
-
|
|
84695
|
-
|
|
84696
|
-
}
|
|
84853
|
+
process.exitCode = 1;
|
|
84854
|
+
});
|
|
84697
84855
|
}
|
|
84698
84856
|
/*! Bundled license information:
|
|
84699
84857
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formfeed/mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.18",
|
|
4
4
|
"mcpName": "io.github.formfeed-dev/formfeed",
|
|
5
5
|
"description": "Model Context Protocol server for Formfeed: list templates, read data schemas, validate and render documents from AI agents.",
|
|
6
6
|
"license": "MIT",
|