@formfeed/mcp 0.3.1 → 0.3.2
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 +2 -2
- package/bin/formfeed-mcp.mjs +67 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @formfeed/mcp
|
|
2
2
|
|
|
3
|
-
Model Context Protocol server for Formfeed. Gives Claude, Cursor and other agents
|
|
3
|
+
Model Context Protocol server for Formfeed. Gives Claude, Cursor and other agents six tools:
|
|
4
4
|
`list_templates`, `get_template_schema`, `validate_template` (offline, with the shared engine),
|
|
5
|
-
`render` and `get_render`.
|
|
5
|
+
`render`, `convert_to_pdf` (Word, Excel, PowerPoint and OpenDocument to PDF) and `get_render`.
|
|
6
6
|
|
|
7
7
|
## stdio (Claude Desktop, Cursor, Claude Code)
|
|
8
8
|
|
package/bin/formfeed-mcp.mjs
CHANGED
|
@@ -82779,7 +82779,8 @@ var encoder2 = new TextEncoder();
|
|
|
82779
82779
|
|
|
82780
82780
|
// packages/mcp/src/lib/server.ts
|
|
82781
82781
|
import { z as z2 } from "zod";
|
|
82782
|
-
var
|
|
82782
|
+
var CONVERT_LIMIT_BYTES = 20 * 1024 * 1024;
|
|
82783
|
+
var SERVER_INFO = { name: "formfeed", version: "0.3.2" };
|
|
82783
82784
|
var engineSchema = z2.enum(["jinja2", "liquid", "handlebars"]);
|
|
82784
82785
|
var outputSchema = z2.enum(outputFormats);
|
|
82785
82786
|
var kindSchema = z2.enum(["pdf", "image", "docx", "pptx"]);
|
|
@@ -82820,7 +82821,7 @@ function createFormfeedServer(options) {
|
|
|
82820
82821
|
...options.fetch ? { fetch: options.fetch } : {}
|
|
82821
82822
|
});
|
|
82822
82823
|
const server = new McpServer(SERVER_INFO, {
|
|
82823
|
-
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."
|
|
82824
|
+
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. convert_to_pdf turns office documents into PDFs."
|
|
82824
82825
|
});
|
|
82825
82826
|
server.registerTool(
|
|
82826
82827
|
"list_templates",
|
|
@@ -82961,6 +82962,69 @@ function createFormfeedServer(options) {
|
|
|
82961
82962
|
}
|
|
82962
82963
|
}
|
|
82963
82964
|
);
|
|
82965
|
+
server.registerTool(
|
|
82966
|
+
"convert_to_pdf",
|
|
82967
|
+
{
|
|
82968
|
+
title: "Convert an office document to PDF",
|
|
82969
|
+
description: [
|
|
82970
|
+
"Converts a Word, Excel, PowerPoint, OpenDocument, RTF or HTML document (up to 20 MB) to a PDF and returns a download URL (Starter plan and above; units follow the PDF rule).",
|
|
82971
|
+
"Pass exactly one source: render_id, the render of a Word or PowerPoint template (output docx or pptx);",
|
|
82972
|
+
options.localFiles ? "path, a file on this machine; or file_base64 with file_name." : "or file_base64 with file_name, the document itself.",
|
|
82973
|
+
"The document is converted and not kept."
|
|
82974
|
+
].join(" "),
|
|
82975
|
+
inputSchema: {
|
|
82976
|
+
render_id: z2.string().optional().describe("rnd_ id of a render whose output is docx or pptx"),
|
|
82977
|
+
...options.localFiles ? { path: z2.string().optional().describe("Absolute path of a document on this machine") } : {},
|
|
82978
|
+
file_base64: z2.string().optional().describe("The document, base64 encoded"),
|
|
82979
|
+
file_name: z2.string().optional().describe("Name of the document, e.g. report.xlsx; the type is read from the content"),
|
|
82980
|
+
page_ranges: z2.string().regex(/^\d+(-\d+)?(,\d+(-\d+)?)*$/).optional().describe("Pages to convert, e.g. 1-3,5"),
|
|
82981
|
+
landscape: z2.boolean().optional().describe("Landscape for spreadsheets and documents without their own page setup"),
|
|
82982
|
+
single_page_sheets: z2.boolean().optional().describe("Each spreadsheet sheet on one page"),
|
|
82983
|
+
filename: z2.string().optional().describe("Name of the PDF")
|
|
82984
|
+
},
|
|
82985
|
+
annotations: { readOnlyHint: false, idempotentHint: false }
|
|
82986
|
+
},
|
|
82987
|
+
async (args2) => {
|
|
82988
|
+
try {
|
|
82989
|
+
const { render_id, file_base64, file_name, page_ranges, landscape, single_page_sheets, filename } = args2;
|
|
82990
|
+
const path = args2.path;
|
|
82991
|
+
const sources = [render_id, file_base64, path].filter((s) => s !== void 0 && s !== "");
|
|
82992
|
+
if (sources.length !== 1) return failure(new Error("Pass exactly one of render_id, file_base64" + (options.localFiles ? " or path" : "")));
|
|
82993
|
+
const convert = {
|
|
82994
|
+
meta: { source: "mcp" },
|
|
82995
|
+
...page_ranges ? { page_ranges } : {},
|
|
82996
|
+
...landscape !== void 0 ? { landscape } : {},
|
|
82997
|
+
...single_page_sheets !== void 0 ? { single_page_sheets } : {},
|
|
82998
|
+
...filename ? { filename } : {}
|
|
82999
|
+
};
|
|
83000
|
+
let render;
|
|
83001
|
+
if (render_id) {
|
|
83002
|
+
render = await client.pdf.convert(render_id, convert);
|
|
83003
|
+
} else {
|
|
83004
|
+
let data;
|
|
83005
|
+
let name;
|
|
83006
|
+
if (path) {
|
|
83007
|
+
const { readFile, stat } = await import("node:fs/promises");
|
|
83008
|
+
const { basename } = await import("node:path");
|
|
83009
|
+
if ((await stat(path)).size > CONVERT_LIMIT_BYTES) return failure(new Error(`${path} is larger than 20 MB, the limit for conversions`));
|
|
83010
|
+
data = new Uint8Array(await readFile(path));
|
|
83011
|
+
name = file_name ?? basename(path);
|
|
83012
|
+
} else {
|
|
83013
|
+
if (!file_name) return failure(new Error("file_name is required with file_base64"));
|
|
83014
|
+
data = Uint8Array.from(Buffer.from(file_base64, "base64"));
|
|
83015
|
+
if (data.length === 0) return failure(new Error("file_base64 is empty or not base64"));
|
|
83016
|
+
if (data.length > CONVERT_LIMIT_BYTES) return failure(new Error("The document is larger than 20 MB, the limit for conversions"));
|
|
83017
|
+
name = file_name;
|
|
83018
|
+
}
|
|
83019
|
+
render = await client.pdf.convert({ file: { data, name } }, { ...convert, ...filename ? {} : { filename: `${name.replace(/\.[^.]+$/, "")}.pdf` } });
|
|
83020
|
+
}
|
|
83021
|
+
if (render.status !== "succeeded" && render.status !== "failed") render = await client.renders.waitFor(render.id);
|
|
83022
|
+
return text(renderSummary(render));
|
|
83023
|
+
} catch (e) {
|
|
83024
|
+
return failure(e);
|
|
83025
|
+
}
|
|
83026
|
+
}
|
|
83027
|
+
);
|
|
82964
83028
|
server.registerTool(
|
|
82965
83029
|
"get_render",
|
|
82966
83030
|
{
|
|
@@ -82982,7 +83046,7 @@ function createFormfeedServer(options) {
|
|
|
82982
83046
|
|
|
82983
83047
|
// packages/mcp/src/lib/transports.ts
|
|
82984
83048
|
async function startStdio(options) {
|
|
82985
|
-
const server = createFormfeedServer(options);
|
|
83049
|
+
const server = createFormfeedServer({ localFiles: true, ...options });
|
|
82986
83050
|
await server.connect(new StdioServerTransport());
|
|
82987
83051
|
}
|
|
82988
83052
|
async function startHttp(options = {}) {
|
package/package.json
CHANGED