@formfeed/mcp 0.3.20 → 0.3.22
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 +4 -3
- package/bin/formfeed-mcp.mjs +309 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
[](https://glama.ai/mcp/servers/formfeed-dev/formfeed)
|
|
4
4
|
|
|
5
5
|
Model Context Protocol server for [Formfeed](https://formfeed.dev/?utm_source=npm&utm_content=mcp).
|
|
6
|
-
Gives Claude, Cursor and other agents
|
|
6
|
+
Gives Claude, Cursor and other agents ten 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
8
|
`_invoice` block of a ZUGFeRD / Factur-X e-invoice against EN 16931), `render` (with `einvoice`, the PDF becomes that e-invoice; an
|
|
9
9
|
image comes back as a picture the agent can look at),
|
|
10
|
-
`convert_to_pdf` (Word, Excel, PowerPoint and OpenDocument to PDF), `get_render
|
|
11
|
-
server works in, and the units left)
|
|
10
|
+
`convert_to_pdf` (Word, Excel, PowerPoint and OpenDocument to PDF), `get_render`, `get_workspace` (which workspace the
|
|
11
|
+
server works in, and the units left), `get_template_source` and `save_template` (a new template or a change as a draft,
|
|
12
|
+
which a person publishes in Formfeed; needs `template:write` on the key, or the person's permission for a signed-in agent).
|
|
12
13
|
|
|
13
14
|
## stdio (Claude Desktop, Cursor, Claude Code)
|
|
14
15
|
|
package/bin/formfeed-mcp.mjs
CHANGED
|
@@ -70732,7 +70732,7 @@ function toNumber(value) {
|
|
|
70732
70732
|
// packages/engine/src/lib/codes/epc.ts
|
|
70733
70733
|
function epcPayload(input) {
|
|
70734
70734
|
const amount = input.amount === void 0 || input.amount === "" ? "" : `EUR${toNumber(input.amount).toFixed(2)}`;
|
|
70735
|
-
|
|
70735
|
+
const elements = [
|
|
70736
70736
|
"BCD",
|
|
70737
70737
|
"002",
|
|
70738
70738
|
"1",
|
|
@@ -70744,8 +70744,12 @@ function epcPayload(input) {
|
|
|
70744
70744
|
(input.purpose ?? "").slice(0, 4),
|
|
70745
70745
|
(input.reference ?? "").slice(0, 35),
|
|
70746
70746
|
input.reference ? "" : (input.text ?? "").slice(0, 140),
|
|
70747
|
+
// the beneficiary to originator information, which a payment here has no field for
|
|
70747
70748
|
""
|
|
70748
|
-
]
|
|
70749
|
+
];
|
|
70750
|
+
while (elements.length > 0 && elements[elements.length - 1] === "")
|
|
70751
|
+
elements.pop();
|
|
70752
|
+
return elements.join("\n");
|
|
70749
70753
|
}
|
|
70750
70754
|
var normaliseIban = (iban2) => iban2.replace(/\s/g, "").toUpperCase();
|
|
70751
70755
|
function ibanChecksum(iban2) {
|
|
@@ -83878,7 +83882,7 @@ function base64(bytes) {
|
|
|
83878
83882
|
binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
83879
83883
|
return btoa(binary);
|
|
83880
83884
|
}
|
|
83881
|
-
var SERVER_INFO = { name: "formfeed", version: "0.3.
|
|
83885
|
+
var SERVER_INFO = { name: "formfeed", version: "0.3.22" };
|
|
83882
83886
|
var READS = {
|
|
83883
83887
|
readOnlyHint: true,
|
|
83884
83888
|
destructiveHint: false,
|
|
@@ -83912,6 +83916,9 @@ var renderOutput = {
|
|
|
83912
83916
|
),
|
|
83913
83917
|
error: z3.object({ code: z3.string().optional(), message: z3.string().optional() }).nullable().describe("Why a failed render failed"),
|
|
83914
83918
|
accessibility: z3.record(z3.string(), z3.unknown()).optional().describe("The PDF/UA-1 verdict, when the template declares it"),
|
|
83919
|
+
pdfa: z3.record(z3.string(), z3.unknown()).optional().describe(
|
|
83920
|
+
"The PDF/A verdict (PDF/A-2b or PDF/A-3b), when the template declares it"
|
|
83921
|
+
),
|
|
83915
83922
|
einvoice: z3.record(z3.string(), z3.unknown()).optional().describe("The e-invoice report, when the render made one")
|
|
83916
83923
|
};
|
|
83917
83924
|
var diagnosticOutput = z3.object({
|
|
@@ -83948,13 +83955,57 @@ var renderSummary = (r) => ({
|
|
|
83948
83955
|
// the validator's verdict of a render whose template declares PDF/UA-1 (plan 21): whether the
|
|
83949
83956
|
// file passed, and for each failed rule what to change and in which elements
|
|
83950
83957
|
...r.accessibility ? { accessibility: r.accessibility } : {},
|
|
83958
|
+
// the verdict of a render whose template declares PDF/A (plan 26): the level and the validator
|
|
83959
|
+
...r.pdfa ? { pdfa: r.pdfa } : {},
|
|
83951
83960
|
// the report of an e-invoice (spec 17): valid, the findings, the XML's link, the hashes it checked
|
|
83952
83961
|
...r.einvoice ? { einvoice: r.einvoice } : {}
|
|
83953
83962
|
});
|
|
83963
|
+
var diagnosticOf = (d) => ({
|
|
83964
|
+
severity: d.severity,
|
|
83965
|
+
code: d.code,
|
|
83966
|
+
message: d.message,
|
|
83967
|
+
...d.path ? { path: d.path } : {},
|
|
83968
|
+
...d.location ? { line: d.location.line, column: d.location.column } : {},
|
|
83969
|
+
...d.part ? { part: d.part, paragraph: d.paragraph } : {}
|
|
83970
|
+
});
|
|
83971
|
+
var slugSchema = z3.string().regex(/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/).describe(
|
|
83972
|
+
"New in the workspace: lowercase letters, digits and dashes, e.g. invoice-de"
|
|
83973
|
+
);
|
|
83974
|
+
var sourceParts = {
|
|
83975
|
+
html: z3.string().max(2 * 1024 * 1024).optional().describe(
|
|
83976
|
+
"The body markup in the template language, without <html>, <head> or <body>; up to 2 MB"
|
|
83977
|
+
),
|
|
83978
|
+
css: z3.string().max(1024 * 1024).optional().describe("The stylesheet; up to 1 MB"),
|
|
83979
|
+
head: z3.string().max(256 * 1024).optional().describe("Extra markup for <head>, such as font links"),
|
|
83980
|
+
settings: z3.record(z3.string(), z3.unknown()).optional().describe(
|
|
83981
|
+
"Paper, margins, header, footer, fonts and the other settings, as get_template_source returns them; replaces the stored settings as a whole"
|
|
83982
|
+
),
|
|
83983
|
+
sample_data: z3.record(z3.string(), z3.unknown()).optional().describe(
|
|
83984
|
+
"The data the editor previews with and the draft is checked with; never used by a render"
|
|
83985
|
+
),
|
|
83986
|
+
data_sets: z3.record(z3.string(), z3.unknown()).optional().describe("Named sample data sets besides sample_data (name \u2192 data)"),
|
|
83987
|
+
data_schema: z3.record(z3.string(), z3.unknown()).nullable().optional().describe("JSON Schema of data; null removes the stored one"),
|
|
83988
|
+
i18n: z3.record(z3.string(), z3.unknown()).nullable().optional().describe("Translations: locale \u2192 key \u2192 text, read by the t helper"),
|
|
83989
|
+
partials: z3.record(z3.string(), z3.string()).optional().describe("Partials the template includes (name \u2192 source)")
|
|
83990
|
+
};
|
|
83991
|
+
function appOrigin(baseUrl2) {
|
|
83992
|
+
if (!baseUrl2) return "https://app.formfeed.dev";
|
|
83993
|
+
try {
|
|
83994
|
+
return /^api(-[a-z]+)?\.formfeed\.dev$/.test(new URL(baseUrl2).hostname) ? "https://app.formfeed.dev" : null;
|
|
83995
|
+
} catch {
|
|
83996
|
+
return null;
|
|
83997
|
+
}
|
|
83998
|
+
}
|
|
83999
|
+
function slugIn(account, part) {
|
|
84000
|
+
const value = account?.[part];
|
|
84001
|
+
const slug = typeof value === "object" && value !== null ? value["slug"] : null;
|
|
84002
|
+
return typeof slug === "string" ? slug : null;
|
|
84003
|
+
}
|
|
83954
84004
|
var EINVOICE_PROBLEMS = /* @__PURE__ */ new Set([
|
|
83955
84005
|
"einvoice_data_invalid",
|
|
83956
84006
|
"einvoice_display_mismatch",
|
|
83957
|
-
"pdfa_conversion_failed"
|
|
84007
|
+
"pdfa_conversion_failed",
|
|
84008
|
+
"einvoice_validator_error"
|
|
83958
84009
|
]);
|
|
83959
84010
|
function text3(data) {
|
|
83960
84011
|
return {
|
|
@@ -83965,11 +84016,13 @@ function text3(data) {
|
|
|
83965
84016
|
function failure(e) {
|
|
83966
84017
|
const message2 = e instanceof FormfeedError ? `${e.code}: ${e.message}` : e instanceof Error ? e.message : String(e);
|
|
83967
84018
|
const verdict = e instanceof FormfeedError && e.code === "pdfua_validation_failed" ? e.problem?.["accessibility"] : void 0;
|
|
84019
|
+
const pdfa = e instanceof FormfeedError && (e.code === "pdfa_conversion_failed" || e.code === "pdfua_validation_failed") ? e.problem?.["pdfa"] : void 0;
|
|
83968
84020
|
const invoice = e instanceof FormfeedError && EINVOICE_PROBLEMS.has(e.code) ? Object.fromEntries(
|
|
83969
84021
|
["errors", "einvoice"].filter((key) => e.problem?.[key] !== void 0).map((key) => [key, e.problem?.[key]])
|
|
83970
84022
|
) : {};
|
|
83971
84023
|
const details = {
|
|
83972
84024
|
...verdict ? { accessibility: verdict } : {},
|
|
84025
|
+
...pdfa ? { pdfa } : {},
|
|
83973
84026
|
...invoice
|
|
83974
84027
|
};
|
|
83975
84028
|
return {
|
|
@@ -84000,7 +84053,7 @@ function createFormfeedServer(options) {
|
|
|
84000
84053
|
};
|
|
84001
84054
|
const fetchImpl = options.fetch ?? ((input, init) => fetch(input, init));
|
|
84002
84055
|
const server = new McpServer(SERVER_INFO, {
|
|
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."
|
|
84056
|
+
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. To change a template or write a new one, read it with get_template_source and save it with save_template: it stays a draft until a person publishes it in Formfeed. get_workspace says which workspace this server works in and what the period has left."
|
|
84004
84057
|
});
|
|
84005
84058
|
const picture = async (render) => {
|
|
84006
84059
|
const mimeType = IMAGE_TYPES[render.output];
|
|
@@ -84103,13 +84156,92 @@ function createFormfeedServer(options) {
|
|
|
84103
84156
|
}
|
|
84104
84157
|
}
|
|
84105
84158
|
);
|
|
84159
|
+
server.registerTool(
|
|
84160
|
+
"get_template_source",
|
|
84161
|
+
{
|
|
84162
|
+
title: "Read the source of a template",
|
|
84163
|
+
description: "Returns what a template version is made of: the body markup (html), css, head, settings (paper, margins, header, footer, fonts), sample data, data sets, data schema, translations and partials, with the version's checksum. Call it before save_template to change a template the user named, and save what you edited with that checksum as base_checksum. Reads the latest version by default, the newest draft a change builds on; published reads what renders use. Word and PowerPoint templates are documents: for them it returns the settings and data, not the document. Read-only and free.",
|
|
84164
|
+
inputSchema: {
|
|
84165
|
+
template: z3.string().describe(
|
|
84166
|
+
"Slug (e.g. invoice) or tpl_ id, as list_templates returns them"
|
|
84167
|
+
),
|
|
84168
|
+
version: z3.union([z3.string().min(1).max(32), z3.number().int().positive()]).optional().describe(
|
|
84169
|
+
"latest (default), published, a version number or a channel name"
|
|
84170
|
+
)
|
|
84171
|
+
},
|
|
84172
|
+
outputSchema: {
|
|
84173
|
+
template: z3.object({
|
|
84174
|
+
id: z3.string().describe("tpl_ id"),
|
|
84175
|
+
slug: z3.string(),
|
|
84176
|
+
name: z3.string(),
|
|
84177
|
+
kind: z3.string().describe("pdf, image, docx (Word) or pptx (PowerPoint)"),
|
|
84178
|
+
engine: z3.string().describe("jinja2, liquid or handlebars"),
|
|
84179
|
+
published_version: z3.number().nullable()
|
|
84180
|
+
}),
|
|
84181
|
+
version: z3.object({
|
|
84182
|
+
number: z3.number(),
|
|
84183
|
+
status: z3.string().describe("draft, published or archived"),
|
|
84184
|
+
checksum: z3.string().describe("Pass it to save_template as base_checksum"),
|
|
84185
|
+
change_note: z3.string().nullable()
|
|
84186
|
+
}),
|
|
84187
|
+
html: z3.string(),
|
|
84188
|
+
css: z3.string(),
|
|
84189
|
+
head: z3.string(),
|
|
84190
|
+
settings: z3.record(z3.string(), z3.unknown()),
|
|
84191
|
+
sample_data: z3.record(z3.string(), z3.unknown()),
|
|
84192
|
+
data_sets: z3.record(z3.string(), z3.unknown()),
|
|
84193
|
+
data_schema: z3.record(z3.string(), z3.unknown()).nullable(),
|
|
84194
|
+
i18n: z3.record(z3.string(), z3.unknown()).nullable(),
|
|
84195
|
+
partials: z3.record(z3.string(), z3.string())
|
|
84196
|
+
},
|
|
84197
|
+
annotations: READS
|
|
84198
|
+
},
|
|
84199
|
+
async ({ template, version }) => {
|
|
84200
|
+
try {
|
|
84201
|
+
const [tpl, row] = await Promise.all([
|
|
84202
|
+
api().templates.get(template),
|
|
84203
|
+
api().templates.versions.get(template, version ?? "latest")
|
|
84204
|
+
]);
|
|
84205
|
+
return text3({
|
|
84206
|
+
template: {
|
|
84207
|
+
id: tpl.id,
|
|
84208
|
+
slug: tpl.slug,
|
|
84209
|
+
name: tpl.name,
|
|
84210
|
+
kind: tpl.kind,
|
|
84211
|
+
engine: tpl.engine,
|
|
84212
|
+
published_version: tpl.published_version
|
|
84213
|
+
},
|
|
84214
|
+
version: {
|
|
84215
|
+
number: row.number,
|
|
84216
|
+
status: row.status,
|
|
84217
|
+
checksum: row.checksum,
|
|
84218
|
+
change_note: row.change_note
|
|
84219
|
+
},
|
|
84220
|
+
html: row.html ?? "",
|
|
84221
|
+
css: row.css ?? "",
|
|
84222
|
+
head: row.head ?? "",
|
|
84223
|
+
settings: row.settings ?? {},
|
|
84224
|
+
sample_data: row.sample_data ?? {},
|
|
84225
|
+
data_sets: row.data_sets ?? {},
|
|
84226
|
+
data_schema: row.data_schema ?? null,
|
|
84227
|
+
i18n: row.i18n ?? null,
|
|
84228
|
+
partials: row.partials ?? {}
|
|
84229
|
+
});
|
|
84230
|
+
} catch (e) {
|
|
84231
|
+
return failure(e);
|
|
84232
|
+
}
|
|
84233
|
+
}
|
|
84234
|
+
);
|
|
84106
84235
|
server.registerTool(
|
|
84107
84236
|
"validate_template",
|
|
84108
84237
|
{
|
|
84109
84238
|
title: "Validate a template with data",
|
|
84110
|
-
description: "Checks a template and its data without rendering: free, no units, nothing stored, no document produced. Call it before render when you assembled the data yourself, or after writing ad-hoc html. With template, the API checks the published version (else the latest) as a render would: syntax, header and footer, errors only this data triggers, and the data against the stored JSON Schema (a wrong type or a missing required field is an error). With html and engine it compiles offline and reports syntax errors, unknown filters and variables missing from data. Returns ok and diagnostics (severity, code, message, plus path, line and column, or part and paragraph for Word and PowerPoint). Warnings leave ok true: settle each before rendering.",
|
|
84239
|
+
description: "Checks a template and its data without rendering: free, no units, nothing stored, no document produced. Call it before render when you assembled the data yourself, or after writing ad-hoc html. With template, the API checks the published version (else the latest, or the version you name) as a render would: syntax, header and footer, errors only this data triggers, and the data against the stored JSON Schema (a wrong type or a missing required field is an error). With html and engine it compiles offline and reports syntax errors, unknown filters and variables missing from data. Returns ok and diagnostics (severity, code, message, plus path, line and column, or part and paragraph for Word and PowerPoint). Warnings leave ok true: settle each before rendering.",
|
|
84111
84240
|
inputSchema: {
|
|
84112
84241
|
template: z3.string().optional().describe("Slug or tpl_ id of a stored template; pass this or html"),
|
|
84242
|
+
version: z3.union([z3.string().min(1).max(32), z3.number().int().positive()]).optional().describe(
|
|
84243
|
+
"With template: the version to check, such as latest for the newest draft or a number; left out, the published one, else the latest"
|
|
84244
|
+
),
|
|
84113
84245
|
html: z3.string().optional().describe(
|
|
84114
84246
|
"Template source to check offline, when there is no stored template; pass this or template"
|
|
84115
84247
|
),
|
|
@@ -84125,28 +84257,21 @@ function createFormfeedServer(options) {
|
|
|
84125
84257
|
},
|
|
84126
84258
|
annotations: READS
|
|
84127
84259
|
},
|
|
84128
|
-
async ({ template, html, engine, data }) => {
|
|
84260
|
+
async ({ template, version, html, engine, data }) => {
|
|
84129
84261
|
try {
|
|
84130
84262
|
if (template) {
|
|
84131
|
-
const check = (
|
|
84132
|
-
version,
|
|
84263
|
+
const check = (which) => api().templates.validate(template, {
|
|
84264
|
+
version: which,
|
|
84133
84265
|
...data ? { data } : {}
|
|
84134
84266
|
});
|
|
84135
|
-
const result = await check("published").catch((e) => {
|
|
84267
|
+
const result = version !== void 0 ? await check(String(version)) : await check("published").catch((e) => {
|
|
84136
84268
|
if (e.status === 404) return check("latest");
|
|
84137
84269
|
throw e;
|
|
84138
84270
|
});
|
|
84139
84271
|
return text3({
|
|
84140
84272
|
ok: result.ok,
|
|
84141
84273
|
template: result.template,
|
|
84142
|
-
diagnostics: result.diagnostics.map(
|
|
84143
|
-
severity: d.severity,
|
|
84144
|
-
code: d.code,
|
|
84145
|
-
message: d.message,
|
|
84146
|
-
...d.path ? { path: d.path } : {},
|
|
84147
|
-
...d.location ? { line: d.location.line, column: d.location.column } : {},
|
|
84148
|
-
...d.part ? { part: d.part, paragraph: d.paragraph } : {}
|
|
84149
|
-
}))
|
|
84274
|
+
diagnostics: result.diagnostics.map(diagnosticOf)
|
|
84150
84275
|
});
|
|
84151
84276
|
}
|
|
84152
84277
|
if (!html || !engine)
|
|
@@ -84184,6 +84309,166 @@ function createFormfeedServer(options) {
|
|
|
84184
84309
|
}
|
|
84185
84310
|
}
|
|
84186
84311
|
);
|
|
84312
|
+
server.registerTool(
|
|
84313
|
+
"save_template",
|
|
84314
|
+
{
|
|
84315
|
+
title: "Save a template as a draft",
|
|
84316
|
+
description: "Saves a template as a draft, never published: renders keep using the published version until a person publishes the draft in Formfeed. With template and base_checksum it saves a new version of that template: read it with get_template_source first, change what the user asked for, and pass only the parts you changed (the others are kept from the version you read). With new_template and html it creates a template whose version 1 is the draft. change_note says what changed; the person reads it before publishing. Returns the saved version, the check of the draft with its sample data (fix every error and save again) and review_url, the page where the person reviews and publishes it: hand that to the user. Needs an API key with template:write, or, for a signed-in agent, the person's permission under Account \u2192 Connected agents in Formfeed. Word and PowerPoint templates cannot be saved here. Free; a new template counts towards the plan's template limit.",
|
|
84317
|
+
inputSchema: {
|
|
84318
|
+
template: z3.string().optional().describe(
|
|
84319
|
+
"Slug or tpl_ id of the template to save a new draft of; pass this or new_template"
|
|
84320
|
+
),
|
|
84321
|
+
base_checksum: z3.string().max(80).optional().describe(
|
|
84322
|
+
"With template: the checksum get_template_source returned. When someone saved a newer version since, nothing is saved: read the source again and redo the change on it"
|
|
84323
|
+
),
|
|
84324
|
+
new_template: z3.object({
|
|
84325
|
+
slug: slugSchema,
|
|
84326
|
+
name: z3.string().min(1).max(200),
|
|
84327
|
+
kind: z3.enum(["pdf", "image"]).describe("pdf for documents, image for pictures"),
|
|
84328
|
+
engine: engineSchema.describe("The template language of html"),
|
|
84329
|
+
description: z3.string().max(2e3).optional(),
|
|
84330
|
+
tags: z3.array(z3.string().max(40)).max(20).optional().describe("Lowercase with dashes, e.g. billing")
|
|
84331
|
+
}).optional().describe(
|
|
84332
|
+
"Creates a template whose version 1 is this draft; pass this and html, or template"
|
|
84333
|
+
),
|
|
84334
|
+
...sourceParts,
|
|
84335
|
+
change_note: z3.string().min(1).max(500).describe(
|
|
84336
|
+
"What the draft changes, in one sentence, shown in the version history"
|
|
84337
|
+
)
|
|
84338
|
+
},
|
|
84339
|
+
outputSchema: {
|
|
84340
|
+
template: z3.object({
|
|
84341
|
+
id: z3.string().describe("tpl_ id"),
|
|
84342
|
+
slug: z3.string(),
|
|
84343
|
+
name: z3.string(),
|
|
84344
|
+
published_version: z3.number().nullable().describe(
|
|
84345
|
+
"The version renders still use; null when nothing is published yet"
|
|
84346
|
+
)
|
|
84347
|
+
}),
|
|
84348
|
+
version: z3.object({
|
|
84349
|
+
number: z3.number(),
|
|
84350
|
+
status: z3.string().describe("draft"),
|
|
84351
|
+
checksum: z3.string().describe("base_checksum for a further change to this draft")
|
|
84352
|
+
}),
|
|
84353
|
+
ok: z3.boolean().nullable().describe(
|
|
84354
|
+
"false when the check of the draft found an error; null when the check could not run"
|
|
84355
|
+
),
|
|
84356
|
+
diagnostics: z3.array(diagnosticOutput),
|
|
84357
|
+
review_url: z3.string().optional().describe(
|
|
84358
|
+
"The page in Formfeed where the person reviews and publishes the draft"
|
|
84359
|
+
),
|
|
84360
|
+
next: z3.string()
|
|
84361
|
+
},
|
|
84362
|
+
annotations: CREATES
|
|
84363
|
+
},
|
|
84364
|
+
async (args2) => {
|
|
84365
|
+
const { template, base_checksum, new_template, change_note } = args2;
|
|
84366
|
+
try {
|
|
84367
|
+
if (!template === !new_template)
|
|
84368
|
+
return failure(
|
|
84369
|
+
new Error(
|
|
84370
|
+
"Pass template with base_checksum to save a new draft of a template, or new_template with html to create one"
|
|
84371
|
+
)
|
|
84372
|
+
);
|
|
84373
|
+
let saved;
|
|
84374
|
+
let slug;
|
|
84375
|
+
if (template) {
|
|
84376
|
+
if (!base_checksum)
|
|
84377
|
+
return failure(
|
|
84378
|
+
new Error(
|
|
84379
|
+
"Pass base_checksum: read the template with get_template_source, change it, and save it with the checksum you read"
|
|
84380
|
+
)
|
|
84381
|
+
);
|
|
84382
|
+
const [tpl2, base] = await Promise.all([
|
|
84383
|
+
api().templates.get(template),
|
|
84384
|
+
api().templates.versions.get(template, "latest")
|
|
84385
|
+
]);
|
|
84386
|
+
if (tpl2.kind === "docx" || tpl2.kind === "pptx")
|
|
84387
|
+
return failure(
|
|
84388
|
+
new Error(
|
|
84389
|
+
`${tpl2.slug} is a ${tpl2.kind === "docx" ? "Word" : "PowerPoint"} template: its document is changed in ${tpl2.kind === "docx" ? "Word" : "PowerPoint"} and uploaded in Formfeed, not saved here`
|
|
84390
|
+
)
|
|
84391
|
+
);
|
|
84392
|
+
if (base.checksum !== base_checksum)
|
|
84393
|
+
return failure(
|
|
84394
|
+
new Error(
|
|
84395
|
+
"conflict: someone saved a newer version of this template since you read it. Read it again with get_template_source and make the change on that version"
|
|
84396
|
+
)
|
|
84397
|
+
);
|
|
84398
|
+
saved = await api().templates.versions.create(template, {
|
|
84399
|
+
html: args2.html ?? base.html ?? "",
|
|
84400
|
+
css: args2.css ?? base.css,
|
|
84401
|
+
head: args2.head ?? base.head,
|
|
84402
|
+
settings: args2.settings ?? base.settings,
|
|
84403
|
+
sample_data: args2.sample_data ?? base.sample_data,
|
|
84404
|
+
data_sets: args2.data_sets ?? base.data_sets,
|
|
84405
|
+
data_schema: args2.data_schema !== void 0 ? args2.data_schema : base.data_schema,
|
|
84406
|
+
i18n: args2.i18n !== void 0 ? args2.i18n : base.i18n,
|
|
84407
|
+
partials: args2.partials ?? base.partials,
|
|
84408
|
+
change_note,
|
|
84409
|
+
base_checksum,
|
|
84410
|
+
publish: false
|
|
84411
|
+
});
|
|
84412
|
+
slug = tpl2.slug;
|
|
84413
|
+
} else {
|
|
84414
|
+
if (!new_template || args2.html === void 0)
|
|
84415
|
+
return failure(new Error("html is required for a new template"));
|
|
84416
|
+
const created = await api().templates.create({
|
|
84417
|
+
...new_template,
|
|
84418
|
+
html: args2.html,
|
|
84419
|
+
css: args2.css,
|
|
84420
|
+
head: args2.head,
|
|
84421
|
+
settings: args2.settings,
|
|
84422
|
+
sample_data: args2.sample_data,
|
|
84423
|
+
data_sets: args2.data_sets,
|
|
84424
|
+
data_schema: args2.data_schema,
|
|
84425
|
+
i18n: args2.i18n,
|
|
84426
|
+
partials: args2.partials,
|
|
84427
|
+
change_note,
|
|
84428
|
+
publish: false
|
|
84429
|
+
});
|
|
84430
|
+
slug = created.slug;
|
|
84431
|
+
saved = await api().templates.versions.get(slug, "latest");
|
|
84432
|
+
}
|
|
84433
|
+
const origin = appOrigin(options.baseUrl);
|
|
84434
|
+
const [tpl, check, account] = await Promise.all([
|
|
84435
|
+
api().templates.get(slug),
|
|
84436
|
+
// the check of the draft itself, with its sample data; a check that cannot run leaves the draft saved
|
|
84437
|
+
api().templates.validate(slug, { version: String(saved.number) }).then(
|
|
84438
|
+
(result) => ({ result, error: null }),
|
|
84439
|
+
(e) => ({
|
|
84440
|
+
result: null,
|
|
84441
|
+
error: e instanceof Error ? e.message : String(e)
|
|
84442
|
+
})
|
|
84443
|
+
),
|
|
84444
|
+
origin ? api().account.get().catch(() => null) : Promise.resolve(null)
|
|
84445
|
+
]);
|
|
84446
|
+
const org = slugIn(account, "organization");
|
|
84447
|
+
const workspace = slugIn(account, "workspace");
|
|
84448
|
+
return text3({
|
|
84449
|
+
template: {
|
|
84450
|
+
id: tpl.id,
|
|
84451
|
+
slug: tpl.slug,
|
|
84452
|
+
name: tpl.name,
|
|
84453
|
+
published_version: tpl.published_version
|
|
84454
|
+
},
|
|
84455
|
+
version: {
|
|
84456
|
+
number: saved.number,
|
|
84457
|
+
status: saved.status,
|
|
84458
|
+
checksum: saved.checksum
|
|
84459
|
+
},
|
|
84460
|
+
ok: check.result ? check.result.ok : null,
|
|
84461
|
+
diagnostics: check.result ? check.result.diagnostics.map(diagnosticOf) : [],
|
|
84462
|
+
...origin && org && workspace ? {
|
|
84463
|
+
review_url: `${origin}/o/${org}/w/${workspace}/templates/${tpl.slug}`
|
|
84464
|
+
} : {},
|
|
84465
|
+
next: check.result ? `Saved as draft version ${saved.number}; nothing is published. Ask the person to review it in Formfeed and publish it there.` : `Saved as draft version ${saved.number}; nothing is published. The check could not run (${check.error}): call validate_template with version ${saved.number} before asking the person to publish it.`
|
|
84466
|
+
});
|
|
84467
|
+
} catch (e) {
|
|
84468
|
+
return failure(e);
|
|
84469
|
+
}
|
|
84470
|
+
}
|
|
84471
|
+
);
|
|
84187
84472
|
server.registerTool(
|
|
84188
84473
|
"check_invoice",
|
|
84189
84474
|
{
|
|
@@ -84734,6 +85019,12 @@ function mcpFetchHandler(options = {}) {
|
|
|
84734
85019
|
challenge(metadata, "the access token expired")
|
|
84735
85020
|
);
|
|
84736
85021
|
}
|
|
85022
|
+
if (request.method !== "POST")
|
|
85023
|
+
return json(
|
|
85024
|
+
{ error: "This endpoint takes POST only; it opens no stream" },
|
|
85025
|
+
405,
|
|
85026
|
+
{ allow: "POST" }
|
|
85027
|
+
);
|
|
84737
85028
|
const mcp = createFormfeedServer({
|
|
84738
85029
|
apiKey: credential,
|
|
84739
85030
|
...workspaceId ? { workspaceId } : {},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formfeed/mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.22",
|
|
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",
|