brewpage-mcp 1.1.1 → 1.3.0

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.
Files changed (2) hide show
  1. package/dist/index.js +74 -17
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ function formatPublishResponse(data) {
34
34
  lines.push(``, `===================================`, `OWNER TOKEN: ${data.ownerToken}`, `===================================`, ``, `IMPORTANT: Save your owner token! You need it to update or delete this resource later. It cannot be recovered if lost.`);
35
35
  return lines.join("\n");
36
36
  }
37
- server.tool("publish_html", "Publish HTML or Markdown content to BrewPage. Returns a public URL and owner token. Supports password protection and custom TTL.", {
37
+ server.tool("publish_html", "Publish HTML or Markdown content to BrewPage. Returns a public URL and owner token. Supports password protection, custom TTL, optional filename, and an opt-in top toolbar (showTopBar).", {
38
38
  content: z.string().describe("HTML or Markdown content to publish"),
39
39
  format: z
40
40
  .enum(["HTML", "MARKDOWN"])
@@ -50,11 +50,21 @@ server.tool("publish_html", "Publish HTML or Markdown content to BrewPage. Retur
50
50
  .describe("Optional password to protect the page"),
51
51
  ttlDays: z
52
52
  .number()
53
- .min(30)
54
- .max(365)
53
+ .int()
54
+ .min(1)
55
+ .max(30)
56
+ .optional()
57
+ .describe("Time to live in days (1-30, default: 15)"),
58
+ filename: z
59
+ .string()
60
+ .max(200)
55
61
  .optional()
56
- .describe("Time to live in days (30-365, default: 365)"),
57
- }, async ({ content, format, namespace, password, ttlDays }) => {
62
+ .describe("Optional original filename (≤200 chars). Used as title fallback when no <title>/<h1>; immutable on update."),
63
+ showTopBar: z
64
+ .boolean()
65
+ .optional()
66
+ .describe("Add a thin top toolbar (filename + Download button + theme toggle) on the served page. Default: hidden."),
67
+ }, async ({ content, format, namespace, password, ttlDays, filename, showTopBar }) => {
58
68
  const body = { content, format };
59
69
  if (namespace)
60
70
  body.namespace = namespace;
@@ -62,6 +72,10 @@ server.tool("publish_html", "Publish HTML or Markdown content to BrewPage. Retur
62
72
  body.password = password;
63
73
  if (ttlDays)
64
74
  body.ttlDays = ttlDays;
75
+ if (filename)
76
+ body.filename = filename;
77
+ if (showTopBar !== undefined)
78
+ body.showTopBar = showTopBar;
65
79
  const { ok, data } = await apiRequest("POST", "/api/html", body);
66
80
  if (!ok) {
67
81
  return {
@@ -229,8 +243,19 @@ server.tool("get_stats", "Get BrewPage platform-wide usage statistics including
229
243
  ],
230
244
  };
231
245
  });
232
- server.tool("publish_site", "Publish a single-page HTML site to BrewPage. Accepts HTML content as the entry file (index.html). For multi-file sites with CSS/JS/images, use the REST API directly. Returns a public URL and owner token.", {
233
- entryContent: z.string().describe("HTML content for the site entry file (index.html)"),
246
+ server.tool("publish_site", "Publish an HTML site to BrewPage. Single-page: pass `entryContent`. Multi-page: pass `files` (array of `{path, content}`). Optional `entry` overrides default `index.html`.", {
247
+ entryContent: z
248
+ .string()
249
+ .optional()
250
+ .describe("HTML content for a single-page site (entry file). Mutually exclusive with `files`."),
251
+ files: z
252
+ .array(z.object({ path: z.string(), content: z.string() }))
253
+ .optional()
254
+ .describe("Multi-file site: array of `{path, content}`. Mutually exclusive with `entryContent`."),
255
+ entry: z
256
+ .string()
257
+ .optional()
258
+ .describe("Entry file path, default index.html"),
234
259
  namespace: z
235
260
  .string()
236
261
  .optional()
@@ -244,26 +269,52 @@ server.tool("publish_site", "Publish a single-page HTML site to BrewPage. Accept
244
269
  .min(1)
245
270
  .max(30)
246
271
  .optional()
247
- .describe("Time to live in days (1-30, default: 5)"),
272
+ .describe("Time to live in days (1-30, default: 15)"),
248
273
  ownerToken: z
249
274
  .string()
250
275
  .optional()
251
276
  .describe("Existing owner token to group this site under the same owner as previous content"),
252
- }, async ({ entryContent, namespace, password, ttlDays, ownerToken }) => {
277
+ }, async ({ entryContent, files, entry, namespace, password, ttlDays, ownerToken }) => {
278
+ const hasEntry = typeof entryContent === "string";
279
+ const hasFiles = Array.isArray(files) && files.length > 0;
280
+ if (hasEntry === hasFiles) {
281
+ return {
282
+ content: [
283
+ {
284
+ type: "text",
285
+ text: "Provide exactly one of `entryContent` (single-page) or `files` (multi-page).",
286
+ },
287
+ ],
288
+ isError: true,
289
+ };
290
+ }
253
291
  const formData = new FormData();
254
- const entryBlob = new Blob([entryContent], { type: "text/html" });
255
- formData.append("files", entryBlob, "index.html");
256
- formData.append("paths", "index.html");
257
- if (namespace)
258
- formData.append("ns", namespace);
259
- if (ttlDays)
260
- formData.append("ttl", String(ttlDays));
292
+ if (hasFiles) {
293
+ for (const f of files) {
294
+ formData.append("files", new Blob([f.content], { type: "text/html" }), f.path);
295
+ formData.append("paths", f.path);
296
+ }
297
+ }
298
+ else {
299
+ const entryBlob = new Blob([entryContent], { type: "text/html" });
300
+ const entryName = entry || "index.html";
301
+ formData.append("files", entryBlob, entryName);
302
+ formData.append("paths", entryName);
303
+ }
261
304
  const headers = {};
262
305
  if (password)
263
306
  headers["X-Password"] = password;
264
307
  if (ownerToken)
265
308
  headers["X-Owner-Token"] = ownerToken;
266
- const url = `${BASE_URL}/api/sites`;
309
+ const qsParams = [];
310
+ if (entry)
311
+ qsParams.push(`entry=${encodeURIComponent(entry)}`);
312
+ if (namespace)
313
+ qsParams.push(`ns=${encodeURIComponent(namespace)}`);
314
+ if (ttlDays)
315
+ qsParams.push(`ttl=${encodeURIComponent(String(ttlDays))}`);
316
+ const qs = qsParams.length ? `?${qsParams.join("&")}` : "";
317
+ const url = `${BASE_URL}/api/sites${qs}`;
267
318
  const res = await fetch(url, { method: "POST", headers, body: formData });
268
319
  const data = await res.json().catch(() => null);
269
320
  if (!res.ok) {
@@ -287,6 +338,12 @@ server.tool("publish_site", "Publish a single-page HTML site to BrewPage. Accept
287
338
  `Entry file: ${site.entryFile || "index.html"}`,
288
339
  `Files: ${site.fileCount || 1}`,
289
340
  ];
341
+ if (site.totalSizeBytes !== undefined)
342
+ lines.push(`Size: ${site.totalSizeBytes} bytes`);
343
+ if (site.ownerLink)
344
+ lines.push(`Owner link: ${site.ownerLink}`);
345
+ if (Array.isArray(site.tags) && site.tags.length)
346
+ lines.push(`Tags: ${site.tags.join(", ")}`);
290
347
  if (site.expiresAt)
291
348
  lines.push(`Expires: ${site.expiresAt}`);
292
349
  lines.push(``, `===================================`, `OWNER TOKEN: ${site.ownerToken}`, `===================================`, ``, `IMPORTANT: Save your owner token! You need it to view info or delete this site. It cannot be recovered if lost.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brewpage-mcp",
3
- "version": "1.1.1",
3
+ "version": "1.3.0",
4
4
  "description": "MCP server for BrewPage — publish and manage HTML, KV, JSON, and file content from AI assistants",
5
5
  "type": "module",
6
6
  "bin": {