@formfeed/mcp 0.3.20 → 0.3.21
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 +290 -16
- 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
|
@@ -83878,7 +83878,7 @@ function base64(bytes) {
|
|
|
83878
83878
|
binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
83879
83879
|
return btoa(binary);
|
|
83880
83880
|
}
|
|
83881
|
-
var SERVER_INFO = { name: "formfeed", version: "0.3.
|
|
83881
|
+
var SERVER_INFO = { name: "formfeed", version: "0.3.21" };
|
|
83882
83882
|
var READS = {
|
|
83883
83883
|
readOnlyHint: true,
|
|
83884
83884
|
destructiveHint: false,
|
|
@@ -83951,10 +83951,52 @@ var renderSummary = (r) => ({
|
|
|
83951
83951
|
// the report of an e-invoice (spec 17): valid, the findings, the XML's link, the hashes it checked
|
|
83952
83952
|
...r.einvoice ? { einvoice: r.einvoice } : {}
|
|
83953
83953
|
});
|
|
83954
|
+
var diagnosticOf = (d) => ({
|
|
83955
|
+
severity: d.severity,
|
|
83956
|
+
code: d.code,
|
|
83957
|
+
message: d.message,
|
|
83958
|
+
...d.path ? { path: d.path } : {},
|
|
83959
|
+
...d.location ? { line: d.location.line, column: d.location.column } : {},
|
|
83960
|
+
...d.part ? { part: d.part, paragraph: d.paragraph } : {}
|
|
83961
|
+
});
|
|
83962
|
+
var slugSchema = z3.string().regex(/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/).describe(
|
|
83963
|
+
"New in the workspace: lowercase letters, digits and dashes, e.g. invoice-de"
|
|
83964
|
+
);
|
|
83965
|
+
var sourceParts = {
|
|
83966
|
+
html: z3.string().max(2 * 1024 * 1024).optional().describe(
|
|
83967
|
+
"The body markup in the template language, without <html>, <head> or <body>; up to 2 MB"
|
|
83968
|
+
),
|
|
83969
|
+
css: z3.string().max(1024 * 1024).optional().describe("The stylesheet; up to 1 MB"),
|
|
83970
|
+
head: z3.string().max(256 * 1024).optional().describe("Extra markup for <head>, such as font links"),
|
|
83971
|
+
settings: z3.record(z3.string(), z3.unknown()).optional().describe(
|
|
83972
|
+
"Paper, margins, header, footer, fonts and the other settings, as get_template_source returns them; replaces the stored settings as a whole"
|
|
83973
|
+
),
|
|
83974
|
+
sample_data: z3.record(z3.string(), z3.unknown()).optional().describe(
|
|
83975
|
+
"The data the editor previews with and the draft is checked with; never used by a render"
|
|
83976
|
+
),
|
|
83977
|
+
data_sets: z3.record(z3.string(), z3.unknown()).optional().describe("Named sample data sets besides sample_data (name \u2192 data)"),
|
|
83978
|
+
data_schema: z3.record(z3.string(), z3.unknown()).nullable().optional().describe("JSON Schema of data; null removes the stored one"),
|
|
83979
|
+
i18n: z3.record(z3.string(), z3.unknown()).nullable().optional().describe("Translations: locale \u2192 key \u2192 text, read by the t helper"),
|
|
83980
|
+
partials: z3.record(z3.string(), z3.string()).optional().describe("Partials the template includes (name \u2192 source)")
|
|
83981
|
+
};
|
|
83982
|
+
function appOrigin(baseUrl2) {
|
|
83983
|
+
if (!baseUrl2) return "https://app.formfeed.dev";
|
|
83984
|
+
try {
|
|
83985
|
+
return /^api(-[a-z]+)?\.formfeed\.dev$/.test(new URL(baseUrl2).hostname) ? "https://app.formfeed.dev" : null;
|
|
83986
|
+
} catch {
|
|
83987
|
+
return null;
|
|
83988
|
+
}
|
|
83989
|
+
}
|
|
83990
|
+
function slugIn(account, part) {
|
|
83991
|
+
const value = account?.[part];
|
|
83992
|
+
const slug = typeof value === "object" && value !== null ? value["slug"] : null;
|
|
83993
|
+
return typeof slug === "string" ? slug : null;
|
|
83994
|
+
}
|
|
83954
83995
|
var EINVOICE_PROBLEMS = /* @__PURE__ */ new Set([
|
|
83955
83996
|
"einvoice_data_invalid",
|
|
83956
83997
|
"einvoice_display_mismatch",
|
|
83957
|
-
"pdfa_conversion_failed"
|
|
83998
|
+
"pdfa_conversion_failed",
|
|
83999
|
+
"einvoice_validator_error"
|
|
83958
84000
|
]);
|
|
83959
84001
|
function text3(data) {
|
|
83960
84002
|
return {
|
|
@@ -84000,7 +84042,7 @@ function createFormfeedServer(options) {
|
|
|
84000
84042
|
};
|
|
84001
84043
|
const fetchImpl = options.fetch ?? ((input, init) => fetch(input, init));
|
|
84002
84044
|
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."
|
|
84045
|
+
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
84046
|
});
|
|
84005
84047
|
const picture = async (render) => {
|
|
84006
84048
|
const mimeType = IMAGE_TYPES[render.output];
|
|
@@ -84103,13 +84145,92 @@ function createFormfeedServer(options) {
|
|
|
84103
84145
|
}
|
|
84104
84146
|
}
|
|
84105
84147
|
);
|
|
84148
|
+
server.registerTool(
|
|
84149
|
+
"get_template_source",
|
|
84150
|
+
{
|
|
84151
|
+
title: "Read the source of a template",
|
|
84152
|
+
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.",
|
|
84153
|
+
inputSchema: {
|
|
84154
|
+
template: z3.string().describe(
|
|
84155
|
+
"Slug (e.g. invoice) or tpl_ id, as list_templates returns them"
|
|
84156
|
+
),
|
|
84157
|
+
version: z3.union([z3.string().min(1).max(32), z3.number().int().positive()]).optional().describe(
|
|
84158
|
+
"latest (default), published, a version number or a channel name"
|
|
84159
|
+
)
|
|
84160
|
+
},
|
|
84161
|
+
outputSchema: {
|
|
84162
|
+
template: z3.object({
|
|
84163
|
+
id: z3.string().describe("tpl_ id"),
|
|
84164
|
+
slug: z3.string(),
|
|
84165
|
+
name: z3.string(),
|
|
84166
|
+
kind: z3.string().describe("pdf, image, docx (Word) or pptx (PowerPoint)"),
|
|
84167
|
+
engine: z3.string().describe("jinja2, liquid or handlebars"),
|
|
84168
|
+
published_version: z3.number().nullable()
|
|
84169
|
+
}),
|
|
84170
|
+
version: z3.object({
|
|
84171
|
+
number: z3.number(),
|
|
84172
|
+
status: z3.string().describe("draft, published or archived"),
|
|
84173
|
+
checksum: z3.string().describe("Pass it to save_template as base_checksum"),
|
|
84174
|
+
change_note: z3.string().nullable()
|
|
84175
|
+
}),
|
|
84176
|
+
html: z3.string(),
|
|
84177
|
+
css: z3.string(),
|
|
84178
|
+
head: z3.string(),
|
|
84179
|
+
settings: z3.record(z3.string(), z3.unknown()),
|
|
84180
|
+
sample_data: z3.record(z3.string(), z3.unknown()),
|
|
84181
|
+
data_sets: z3.record(z3.string(), z3.unknown()),
|
|
84182
|
+
data_schema: z3.record(z3.string(), z3.unknown()).nullable(),
|
|
84183
|
+
i18n: z3.record(z3.string(), z3.unknown()).nullable(),
|
|
84184
|
+
partials: z3.record(z3.string(), z3.string())
|
|
84185
|
+
},
|
|
84186
|
+
annotations: READS
|
|
84187
|
+
},
|
|
84188
|
+
async ({ template, version }) => {
|
|
84189
|
+
try {
|
|
84190
|
+
const [tpl, row] = await Promise.all([
|
|
84191
|
+
api().templates.get(template),
|
|
84192
|
+
api().templates.versions.get(template, version ?? "latest")
|
|
84193
|
+
]);
|
|
84194
|
+
return text3({
|
|
84195
|
+
template: {
|
|
84196
|
+
id: tpl.id,
|
|
84197
|
+
slug: tpl.slug,
|
|
84198
|
+
name: tpl.name,
|
|
84199
|
+
kind: tpl.kind,
|
|
84200
|
+
engine: tpl.engine,
|
|
84201
|
+
published_version: tpl.published_version
|
|
84202
|
+
},
|
|
84203
|
+
version: {
|
|
84204
|
+
number: row.number,
|
|
84205
|
+
status: row.status,
|
|
84206
|
+
checksum: row.checksum,
|
|
84207
|
+
change_note: row.change_note
|
|
84208
|
+
},
|
|
84209
|
+
html: row.html ?? "",
|
|
84210
|
+
css: row.css ?? "",
|
|
84211
|
+
head: row.head ?? "",
|
|
84212
|
+
settings: row.settings ?? {},
|
|
84213
|
+
sample_data: row.sample_data ?? {},
|
|
84214
|
+
data_sets: row.data_sets ?? {},
|
|
84215
|
+
data_schema: row.data_schema ?? null,
|
|
84216
|
+
i18n: row.i18n ?? null,
|
|
84217
|
+
partials: row.partials ?? {}
|
|
84218
|
+
});
|
|
84219
|
+
} catch (e) {
|
|
84220
|
+
return failure(e);
|
|
84221
|
+
}
|
|
84222
|
+
}
|
|
84223
|
+
);
|
|
84106
84224
|
server.registerTool(
|
|
84107
84225
|
"validate_template",
|
|
84108
84226
|
{
|
|
84109
84227
|
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.",
|
|
84228
|
+
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
84229
|
inputSchema: {
|
|
84112
84230
|
template: z3.string().optional().describe("Slug or tpl_ id of a stored template; pass this or html"),
|
|
84231
|
+
version: z3.union([z3.string().min(1).max(32), z3.number().int().positive()]).optional().describe(
|
|
84232
|
+
"With template: the version to check, such as latest for the newest draft or a number; left out, the published one, else the latest"
|
|
84233
|
+
),
|
|
84113
84234
|
html: z3.string().optional().describe(
|
|
84114
84235
|
"Template source to check offline, when there is no stored template; pass this or template"
|
|
84115
84236
|
),
|
|
@@ -84125,28 +84246,21 @@ function createFormfeedServer(options) {
|
|
|
84125
84246
|
},
|
|
84126
84247
|
annotations: READS
|
|
84127
84248
|
},
|
|
84128
|
-
async ({ template, html, engine, data }) => {
|
|
84249
|
+
async ({ template, version, html, engine, data }) => {
|
|
84129
84250
|
try {
|
|
84130
84251
|
if (template) {
|
|
84131
|
-
const check = (
|
|
84132
|
-
version,
|
|
84252
|
+
const check = (which) => api().templates.validate(template, {
|
|
84253
|
+
version: which,
|
|
84133
84254
|
...data ? { data } : {}
|
|
84134
84255
|
});
|
|
84135
|
-
const result = await check("published").catch((e) => {
|
|
84256
|
+
const result = version !== void 0 ? await check(String(version)) : await check("published").catch((e) => {
|
|
84136
84257
|
if (e.status === 404) return check("latest");
|
|
84137
84258
|
throw e;
|
|
84138
84259
|
});
|
|
84139
84260
|
return text3({
|
|
84140
84261
|
ok: result.ok,
|
|
84141
84262
|
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
|
-
}))
|
|
84263
|
+
diagnostics: result.diagnostics.map(diagnosticOf)
|
|
84150
84264
|
});
|
|
84151
84265
|
}
|
|
84152
84266
|
if (!html || !engine)
|
|
@@ -84184,6 +84298,166 @@ function createFormfeedServer(options) {
|
|
|
84184
84298
|
}
|
|
84185
84299
|
}
|
|
84186
84300
|
);
|
|
84301
|
+
server.registerTool(
|
|
84302
|
+
"save_template",
|
|
84303
|
+
{
|
|
84304
|
+
title: "Save a template as a draft",
|
|
84305
|
+
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.",
|
|
84306
|
+
inputSchema: {
|
|
84307
|
+
template: z3.string().optional().describe(
|
|
84308
|
+
"Slug or tpl_ id of the template to save a new draft of; pass this or new_template"
|
|
84309
|
+
),
|
|
84310
|
+
base_checksum: z3.string().max(80).optional().describe(
|
|
84311
|
+
"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"
|
|
84312
|
+
),
|
|
84313
|
+
new_template: z3.object({
|
|
84314
|
+
slug: slugSchema,
|
|
84315
|
+
name: z3.string().min(1).max(200),
|
|
84316
|
+
kind: z3.enum(["pdf", "image"]).describe("pdf for documents, image for pictures"),
|
|
84317
|
+
engine: engineSchema.describe("The template language of html"),
|
|
84318
|
+
description: z3.string().max(2e3).optional(),
|
|
84319
|
+
tags: z3.array(z3.string().max(40)).max(20).optional().describe("Lowercase with dashes, e.g. billing")
|
|
84320
|
+
}).optional().describe(
|
|
84321
|
+
"Creates a template whose version 1 is this draft; pass this and html, or template"
|
|
84322
|
+
),
|
|
84323
|
+
...sourceParts,
|
|
84324
|
+
change_note: z3.string().min(1).max(500).describe(
|
|
84325
|
+
"What the draft changes, in one sentence, shown in the version history"
|
|
84326
|
+
)
|
|
84327
|
+
},
|
|
84328
|
+
outputSchema: {
|
|
84329
|
+
template: z3.object({
|
|
84330
|
+
id: z3.string().describe("tpl_ id"),
|
|
84331
|
+
slug: z3.string(),
|
|
84332
|
+
name: z3.string(),
|
|
84333
|
+
published_version: z3.number().nullable().describe(
|
|
84334
|
+
"The version renders still use; null when nothing is published yet"
|
|
84335
|
+
)
|
|
84336
|
+
}),
|
|
84337
|
+
version: z3.object({
|
|
84338
|
+
number: z3.number(),
|
|
84339
|
+
status: z3.string().describe("draft"),
|
|
84340
|
+
checksum: z3.string().describe("base_checksum for a further change to this draft")
|
|
84341
|
+
}),
|
|
84342
|
+
ok: z3.boolean().nullable().describe(
|
|
84343
|
+
"false when the check of the draft found an error; null when the check could not run"
|
|
84344
|
+
),
|
|
84345
|
+
diagnostics: z3.array(diagnosticOutput),
|
|
84346
|
+
review_url: z3.string().optional().describe(
|
|
84347
|
+
"The page in Formfeed where the person reviews and publishes the draft"
|
|
84348
|
+
),
|
|
84349
|
+
next: z3.string()
|
|
84350
|
+
},
|
|
84351
|
+
annotations: CREATES
|
|
84352
|
+
},
|
|
84353
|
+
async (args2) => {
|
|
84354
|
+
const { template, base_checksum, new_template, change_note } = args2;
|
|
84355
|
+
try {
|
|
84356
|
+
if (!template === !new_template)
|
|
84357
|
+
return failure(
|
|
84358
|
+
new Error(
|
|
84359
|
+
"Pass template with base_checksum to save a new draft of a template, or new_template with html to create one"
|
|
84360
|
+
)
|
|
84361
|
+
);
|
|
84362
|
+
let saved;
|
|
84363
|
+
let slug;
|
|
84364
|
+
if (template) {
|
|
84365
|
+
if (!base_checksum)
|
|
84366
|
+
return failure(
|
|
84367
|
+
new Error(
|
|
84368
|
+
"Pass base_checksum: read the template with get_template_source, change it, and save it with the checksum you read"
|
|
84369
|
+
)
|
|
84370
|
+
);
|
|
84371
|
+
const [tpl2, base] = await Promise.all([
|
|
84372
|
+
api().templates.get(template),
|
|
84373
|
+
api().templates.versions.get(template, "latest")
|
|
84374
|
+
]);
|
|
84375
|
+
if (tpl2.kind === "docx" || tpl2.kind === "pptx")
|
|
84376
|
+
return failure(
|
|
84377
|
+
new Error(
|
|
84378
|
+
`${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`
|
|
84379
|
+
)
|
|
84380
|
+
);
|
|
84381
|
+
if (base.checksum !== base_checksum)
|
|
84382
|
+
return failure(
|
|
84383
|
+
new Error(
|
|
84384
|
+
"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"
|
|
84385
|
+
)
|
|
84386
|
+
);
|
|
84387
|
+
saved = await api().templates.versions.create(template, {
|
|
84388
|
+
html: args2.html ?? base.html ?? "",
|
|
84389
|
+
css: args2.css ?? base.css,
|
|
84390
|
+
head: args2.head ?? base.head,
|
|
84391
|
+
settings: args2.settings ?? base.settings,
|
|
84392
|
+
sample_data: args2.sample_data ?? base.sample_data,
|
|
84393
|
+
data_sets: args2.data_sets ?? base.data_sets,
|
|
84394
|
+
data_schema: args2.data_schema !== void 0 ? args2.data_schema : base.data_schema,
|
|
84395
|
+
i18n: args2.i18n !== void 0 ? args2.i18n : base.i18n,
|
|
84396
|
+
partials: args2.partials ?? base.partials,
|
|
84397
|
+
change_note,
|
|
84398
|
+
base_checksum,
|
|
84399
|
+
publish: false
|
|
84400
|
+
});
|
|
84401
|
+
slug = tpl2.slug;
|
|
84402
|
+
} else {
|
|
84403
|
+
if (!new_template || args2.html === void 0)
|
|
84404
|
+
return failure(new Error("html is required for a new template"));
|
|
84405
|
+
const created = await api().templates.create({
|
|
84406
|
+
...new_template,
|
|
84407
|
+
html: args2.html,
|
|
84408
|
+
css: args2.css,
|
|
84409
|
+
head: args2.head,
|
|
84410
|
+
settings: args2.settings,
|
|
84411
|
+
sample_data: args2.sample_data,
|
|
84412
|
+
data_sets: args2.data_sets,
|
|
84413
|
+
data_schema: args2.data_schema,
|
|
84414
|
+
i18n: args2.i18n,
|
|
84415
|
+
partials: args2.partials,
|
|
84416
|
+
change_note,
|
|
84417
|
+
publish: false
|
|
84418
|
+
});
|
|
84419
|
+
slug = created.slug;
|
|
84420
|
+
saved = await api().templates.versions.get(slug, "latest");
|
|
84421
|
+
}
|
|
84422
|
+
const origin = appOrigin(options.baseUrl);
|
|
84423
|
+
const [tpl, check, account] = await Promise.all([
|
|
84424
|
+
api().templates.get(slug),
|
|
84425
|
+
// the check of the draft itself, with its sample data; a check that cannot run leaves the draft saved
|
|
84426
|
+
api().templates.validate(slug, { version: String(saved.number) }).then(
|
|
84427
|
+
(result) => ({ result, error: null }),
|
|
84428
|
+
(e) => ({
|
|
84429
|
+
result: null,
|
|
84430
|
+
error: e instanceof Error ? e.message : String(e)
|
|
84431
|
+
})
|
|
84432
|
+
),
|
|
84433
|
+
origin ? api().account.get().catch(() => null) : Promise.resolve(null)
|
|
84434
|
+
]);
|
|
84435
|
+
const org = slugIn(account, "organization");
|
|
84436
|
+
const workspace = slugIn(account, "workspace");
|
|
84437
|
+
return text3({
|
|
84438
|
+
template: {
|
|
84439
|
+
id: tpl.id,
|
|
84440
|
+
slug: tpl.slug,
|
|
84441
|
+
name: tpl.name,
|
|
84442
|
+
published_version: tpl.published_version
|
|
84443
|
+
},
|
|
84444
|
+
version: {
|
|
84445
|
+
number: saved.number,
|
|
84446
|
+
status: saved.status,
|
|
84447
|
+
checksum: saved.checksum
|
|
84448
|
+
},
|
|
84449
|
+
ok: check.result ? check.result.ok : null,
|
|
84450
|
+
diagnostics: check.result ? check.result.diagnostics.map(diagnosticOf) : [],
|
|
84451
|
+
...origin && org && workspace ? {
|
|
84452
|
+
review_url: `${origin}/o/${org}/w/${workspace}/templates/${tpl.slug}`
|
|
84453
|
+
} : {},
|
|
84454
|
+
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.`
|
|
84455
|
+
});
|
|
84456
|
+
} catch (e) {
|
|
84457
|
+
return failure(e);
|
|
84458
|
+
}
|
|
84459
|
+
}
|
|
84460
|
+
);
|
|
84187
84461
|
server.registerTool(
|
|
84188
84462
|
"check_invoice",
|
|
84189
84463
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formfeed/mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.21",
|
|
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",
|