brewpage-mcp 1.10.0 → 1.12.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 (3) hide show
  1. package/README.md +44 -15
  2. package/dist/index.js +52 -38
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -7,12 +7,12 @@ MCP server for [BrewPage](https://brewpage.app) -- publish and manage HTML, KV,
7
7
 
8
8
  ## What is BrewPage
9
9
 
10
- [BrewPage](https://brewpage.app) is a free instant hosting platform designed for AI agents and developers. One `POST` request publishes HTML, Markdown, a multi-file site, a JSON document, or a binary file and returns its primary HTTPS link -- no accounts, no API keys, no infrastructure setup. Every resource carries an **owner token** returned at creation time: use it to update content in place, delete the resource, or authenticate list operations across sessions. `brewpage-mcp` exposes this API as sixteen typed MCP tools so any compatible agent -- Claude, Codex, Gemini, Cursor, Cline -- can publish, update, fetch, and manage BrewPage HTML, JSON, KV, and file content without leaving the conversation.
10
+ [BrewPage](https://brewpage.app) is a free instant hosting platform designed for AI agents and developers. One `POST` request publishes HTML, Markdown, a multi-file site, a JSON document, or a binary file and returns its primary HTTPS link -- no accounts, no API keys, no infrastructure setup. Every resource carries an **owner token** returned at creation time: use it to update content in place, delete the resource, or authenticate list operations across sessions. `brewpage-mcp` exposes this API as seventeen typed MCP tools so any compatible agent -- Claude, Codex, Gemini, Cursor, Cline -- can publish, update, fetch, and manage BrewPage HTML, JSON, KV, and file content without leaving the conversation.
11
11
 
12
12
  ## Quick Start
13
13
 
14
14
  ```bash
15
- npx -y brewpage-mcp@1.10.0
15
+ npx -y brewpage-mcp@1.12.0
16
16
  ```
17
17
 
18
18
  Requires Node.js 20 or newer. This starts the stdio server for an MCP client.
@@ -28,7 +28,7 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
28
28
  "mcpServers": {
29
29
  "brewpage": {
30
30
  "command": "npx",
31
- "args": ["-y", "brewpage-mcp@1.10.0"]
31
+ "args": ["-y", "brewpage-mcp@1.12.0"]
32
32
  }
33
33
  }
34
34
  }
@@ -39,7 +39,7 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
39
39
  Register a project-scoped stdio server:
40
40
 
41
41
  ```bash
42
- claude mcp add --transport stdio --scope project brewpage -- npx -y brewpage-mcp@1.10.0
42
+ claude mcp add --transport stdio --scope project brewpage -- npx -y brewpage-mcp@1.12.0
43
43
  ```
44
44
 
45
45
  This writes `.mcp.json` in the project root. Use `--scope user` for user-scoped registration. See the [official Claude Code MCP guide](https://code.claude.com/docs/en/mcp).
@@ -53,7 +53,7 @@ Open **Settings > MCP** and add:
53
53
  "mcpServers": {
54
54
  "brewpage": {
55
55
  "command": "npx",
56
- "args": ["-y", "brewpage-mcp@1.10.0"]
56
+ "args": ["-y", "brewpage-mcp@1.12.0"]
57
57
  }
58
58
  }
59
59
  }
@@ -67,7 +67,7 @@ Open the Cline MCP settings panel and add:
67
67
  {
68
68
  "brewpage": {
69
69
  "command": "npx",
70
- "args": ["-y", "brewpage-mcp@1.10.0"]
70
+ "args": ["-y", "brewpage-mcp@1.12.0"]
71
71
  }
72
72
  }
73
73
  ```
@@ -75,13 +75,13 @@ Open the Cline MCP settings panel and add:
75
75
  ### Global Install
76
76
 
77
77
  ```bash
78
- npm install -g brewpage-mcp@1.10.0
78
+ npm install -g brewpage-mcp@1.12.0
79
79
  brewpage-mcp
80
80
  ```
81
81
 
82
82
  ## Tools
83
83
 
84
- Sixteen tools are available. Creation returns an owner token; keep it to modify or delete the resource later. Updates (`PUT`/`PATCH`) require the original `ownerToken`. Supported password inputs use `X-Password`.
84
+ Seventeen tools are available. Creation returns an owner token; keep it to modify or delete the resource later. Updates (`PUT`/`PATCH`) require the original `ownerToken`. Supported password inputs use `X-Password`.
85
85
 
86
86
  | Tool | Purpose |
87
87
  |------|---------|
@@ -101,10 +101,11 @@ Sixteen tools are available. Creation returns an owner token; keep it to modify
101
101
  | `delete_resource` | Delete HTML, JSON, KV, or file content |
102
102
  | `search_gallery` | Browse public content or list owned resources |
103
103
  | `get_stats` | Read platform-wide statistics |
104
+ | `get_limits` | Read the current upload limits (`GET /api/limits`) |
104
105
 
105
106
  All five publish tools accept optional `deliveryMode: "path" | "subdomain"`, sent only as `X-Delivery-Mode`; authored JSON and multipart bodies keep their existing format. Visibility and password protection are separate choices. Omitted namespace still generates an unlisted `priv-<random>` namespace; anyone with an unpassworded unlisted link can read it. `public` grants gallery/search eligibility only; password-protected content remains excluded and unlisted, protected or otherwise ineligible native responses carry `noindex`; eligible public HTML, sites and files may permit indexing.
106
107
 
107
- The MCP server wraps a subset of the backend-generated public [REST contract](../openapi/openapi.json). Its inputs retain their published behavior: TTL inputs accept 1–30 days (default 15), while backend owner-specific caps and eligible automatic extensions can affect actual expiry. Use returned `expiresAt`. `get_page` reads stored rendered HTML or format-specific code/text from the API; it does not call the owner-only original-source endpoint or a browser viewer. `publish_file` uploads from a source URL and does not expose password or TTL inputs.
108
+ The MCP server wraps a subset of the backend-generated public [REST contract](../openapi/openapi.json). TTL inputs take days or a string such as `2w`/`36h` (default 15); the server rounds to whole days and clamps to 1–30 (owner-specific caps and eligible automatic extensions can differ). Use returned `expiresAt`. Every tolerated-input adjustment (TTL clamp, format alias, skipped site member, normalised namespace) comes back in the response `notices` array and is printed as `Notice (code): message`. `get_page` reads stored rendered HTML or format-specific code/text from the API; it does not call the owner-only original-source endpoint or a browser viewer. `publish_file` uploads from a source URL and does not expose password or TTL inputs.
108
109
 
109
110
  For a fresh publication, sites and HTML pages of format `HTML` outside `public` (including the generated unlisted namespace) always use Dedicated subdomain (`subdomain`); omit `deliveryMode` there. An explicit `path` there is not rejected: the publication is created on its subdomain and the result reports `deliveryModeMatched: false`, `deliveryModeReason: "policy:subdomain-required"` and a `Delivery notice:` line. Everything else in any namespace (Markdown, code/text, files, JSON, KV, public HTML) defaults to Promotion (`path`) and may choose Dedicated subdomain, including password-protected content. Promotion uses a BrewPage path with stricter active-document restrictions. Each NEW site gets one publication-specific host for its complete bundle, preserving relative/root-relative paths; missing assets return 404, with no SPA fallback. Third-party widgets may still need provider configuration.
110
111
 
@@ -132,7 +133,7 @@ update_hosting(type="html", namespace="public", id="aBcDeFgHiJ", ownerToken="tok
132
133
 
133
134
  Publish HTML or Markdown content to BrewPage. Returns a public URL and owner token. Markdown raw HTML is rendered through an allowlist: scripts, styles, event handlers, forms, frames/embeds and `meta`/`base`/`link` are removed; links allow `http`, `https`, `mailto`, `tel` and relative URLs, images also `data:`; a lone unknown tag such as `<author>` shows as text.
134
135
 
135
- Parameters: `content` (string), `format` (`HTML` | `MARKDOWN`, default `HTML`), `namespace` (optional -- **omit for unlisted content**; pass `public` only for gallery/search eligibility, except when password-protected), `password` (optional), `ttlDays` (1--30, default 15), `filename` (optional, used as title fallback), `showTopBar` (optional boolean -- adds a toolbar with filename, Download button, and theme toggle), `deliveryMode` (optional).
136
+ Parameters: `content` (string), `format` (default `HTML`; `HTML`, `MARKDOWN` or an alias the server maps, e.g. `md`), `namespace` (optional -- **omit for unlisted content**; pass `public` only for gallery/search eligibility, except when password-protected), `password` (optional), `ttlDays` (days or a string such as `2w`; default 15; the server clamps to 1--30), `filename` (optional, used as title fallback), `showTopBar` (optional boolean -- adds a toolbar with filename, Download button, and theme toggle), `deliveryMode` (optional).
136
137
 
137
138
  The result includes `deliveryModeReason` and a `Delivery notice:` line when the server returns them (see above).
138
139
 
@@ -206,7 +207,9 @@ publish_file(url="https://example.com/diagram.png")
206
207
 
207
208
  Publish a single-page or multi-file HTML site. Pass `entryContent` for a single page or `files` (array of `{path, content}`) for a multi-file site. Supports password protection, TTL, and owner token grouping.
208
209
 
209
- Parameters: exactly one of `entryContent` (string) or non-empty `files` (array of `{path, content}`), `entry` (optional entry file path, default `index.html`), `namespace` (optional -- omit for unlisted content; `public` grants gallery eligibility except when password-protected), `password` (optional), `ttlDays` (1--30, default 15), `ownerToken` (optional, groups site under an existing owner), `deliveryMode` (optional; fresh sites require `subdomain`).
210
+ Limits (current values from `get_limits`): a request is at most 25 MB; a site at most 200 files, 5 MB per file and 20 MB in total; each path at most 380 characters. System files such as `.DS_Store` and `__MACOSX/` are skipped and never count; files with an unsupported extension or a too-long path are skipped and reported in notices. Entry: `entry`, else root `index.html`, else the shallowest HTML file. A site limit hit answers 400 with a message naming the limit; 413 means the request is over 25 MB, has too many files and form fields, or a file name/path is too long; 422 means no servable file is left.
211
+
212
+ Parameters: exactly one of `entryContent` (string) or non-empty `files` (array of `{path, content}`), `entry` (optional entry file path, default `index.html`), `namespace` (optional -- omit for unlisted content; `public` grants gallery eligibility except when password-protected), `password` (optional), `ttlDays` (days or a string such as `2w`; default 15; the server clamps to 1--30), `ownerToken` (optional, groups site under an existing owner), `deliveryMode` (optional; fresh sites require `subdomain`).
210
213
 
211
214
  Example prompt:
212
215
 
@@ -220,9 +223,9 @@ publish_site(files=[{"path": "index.html", "content": "..."}, {"path": "style.cs
220
223
 
221
224
  ### `republish_site`
222
225
 
223
- Replace an owned site's complete file set at the same ID and primary link. Files absent from the new bundle are removed; matching files are overwritten and new files added. Hosting stays unchanged.
226
+ Replace an owned site's complete file set at the same ID and primary link. Files absent from the new bundle are removed; matching files are overwritten and new files added. Hosting stays unchanged. The `publish_site` limits and status codes apply.
224
227
 
225
- Parameters: `namespace`, `id`, `ownerToken`, exactly one of `entryContent` or a non-empty `files` array of `{path, content}`, optional `entry` (default `index.html`), `ttlDays` (compatibility input 1--30; existing expiry is preserved), and `tags`.
228
+ Parameters: `namespace`, `id`, `ownerToken`, exactly one of `entryContent` or a non-empty `files` array of `{path, content}`, optional `entry` (default `index.html`), `ttlDays` (ignored; existing expiry is preserved and a `ttl-ignored` notice is returned), and `tags`.
226
229
 
227
230
  ```text
228
231
  republish_site(namespace="public", id="aBcDeFgHiJ", ownerToken="tok_...", files=[{"path":"index.html","content":"<h1>Updated site</h1>"}])
@@ -236,7 +239,7 @@ republish_site(namespace="public", id="aBcDeFgHiJ", ownerToken="tok_...", files=
236
239
 
237
240
  Publish a JSON document to BrewPage. Returns a public URL and owner token. The body may be passed as a JSON string or as a structured object.
238
241
 
239
- Parameters: `json` (string, object, or array), `namespace` (optional -- omit for unlisted content; `public` grants gallery/search eligibility, except when password-protected), `password` (optional), `ttlDays` (1--30, default 15), `tags` (optional `string[]`), `ownerToken` (optional existing owner), `deliveryMode` (optional).
242
+ Parameters: `json` (string, object, or array), `namespace` (optional -- omit for unlisted content; `public` grants gallery/search eligibility, except when password-protected), `password` (optional), `ttlDays` (days or a string such as `2w`; default 15; the server clamps to 1--30), `tags` (optional `string[]`), `ownerToken` (optional existing owner), `deliveryMode` (optional).
240
243
 
241
244
  Example prompt:
242
245
 
@@ -286,7 +289,7 @@ update_json(namespace="public", id="aBcDeFgHiJ", json={"mode": "dark", "version"
286
289
 
287
290
  Create a new KV (key/value) entry. Returns a public URL and owner token. The KV entry is addressed by `(namespace, id, key)`; this tool creates the entry and emits the generated `id` along with the owner token.
288
291
 
289
- Parameters: `key` (string), `value` (string), `namespace` (optional -- omit for unlisted content; `public` grants gallery/search eligibility, except when password-protected), `password` (optional), `ttlDays` (1--30, default 15), `tags` (optional `string[]`), `ownerToken` (optional existing owner), `deliveryMode` (optional).
292
+ Parameters: `key` (string), `value` (string), `namespace` (optional -- omit for unlisted content; `public` grants gallery/search eligibility, except when password-protected), `password` (optional), `ttlDays` (days or a string such as `2w`; default 15; the server clamps to 1--30), `tags` (optional `string[]`), `ownerToken` (optional existing owner), `deliveryMode` (optional).
290
293
 
291
294
  Example prompt:
292
295
 
@@ -382,6 +385,22 @@ Example prompt:
382
385
  get_stats()
383
386
  ```
384
387
 
388
+ ---
389
+
390
+ ### `get_limits`
391
+
392
+ Read the current upload limits from `GET /api/limits` (no auth). Sizes are bytes: `requestMaxBytes` plus `sites.maxFiles`, `maxFileBytes`, `maxTotalBytes` (uncompressed), `maxArchiveExpansionBytes`, `maxPathChars`, `maxPathBytes`, `maxNameBytes`, `defaultTtlDays`, `maxTtlDays`, `allowedExtensions`, `skippedFileNames` and `skippedRootFolders`. Call it before publishing a large or multi-file site.
393
+
394
+ Parameters: none.
395
+
396
+ Example prompt:
397
+
398
+ > "How many files can a BrewPage site have?"
399
+
400
+ ```
401
+ get_limits()
402
+ ```
403
+
385
404
  ## Common LLM Query to BrewPage Tool Mapping
386
405
 
387
406
  | LLM query / intent | Tool | Notes |
@@ -397,6 +416,7 @@ get_stats()
397
416
  | "Remove the page I published" | `delete_resource` | Requires `ownerToken` from creation |
398
417
  | "Read back the page I published" | `get_page` | Returns raw content for editing |
399
418
  | "How many pages are on BrewPage?" | `get_stats` | Returns platform totals |
419
+ | "How big can my site be?" | `get_limits` | Current request and site limits in bytes |
400
420
  | "Store JSON state between turns" | `publish_json` | Returns short URL + owner token |
401
421
  | "Read that JSON document back" | `get_json` | By `namespace` + `id` |
402
422
  | "Update the JSON I stored" | `update_json` | Requires `ownerToken`; preserves URL |
@@ -433,6 +453,15 @@ Every publish response includes an **owner token** -- the only credential that a
433
453
 
434
454
  ## Changelog
435
455
 
456
+ ## 1.12.0 -- 2026-10-22
457
+
458
+ - New `get_limits` tool reads `GET /api/limits`: request size, site file count, per-file and total size, ZIP expansion, path and name lengths, TTL default/maximum, allowed extensions and skipped system files.
459
+ - `publish_site` and `republish_site` describe the backend 1.123.0 site rules: 200 files (was 100), 5 MB per file, 20 MB total, 25 MB per request; 400 for a site limit hit, 413 for an oversized request, too many parts or an overlong file name/path, 422 when no servable file is left; entry resolution and skipped system files. Seventeen tools.
460
+
461
+ ## 1.11.0 -- 2026-10-21
462
+
463
+ - Every publish/update tool prints server `notices` as `Notice (code): message` lines. `ttlDays` takes a positive number or a string such as `2w`; the server rounds and clamps instead of the client rejecting values above 30. `publish_html` `format` accepts any string; the server maps aliases. `republish_site` TTL is ignored with a `ttl-ignored` notice. Same sixteen tools.
464
+
436
465
  ## 1.10.0 -- 2026-10-21
437
466
 
438
467
  - `update_hosting`: `expectedVersion` is optional; a stale version, a locked mode or an OLD link answers with a successful result plus `deliveryModeReason` and a `Delivery notice:` line instead of an error. A publish request for `path` where `subdomain` is required succeeds on the subdomain and prints the notice. Same sixteen tools.
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { z } from "zod";
5
5
  const BASE_URL = process.env.BREWPAGE_URL || "https://brewpage.app";
6
6
  const server = new McpServer({
7
7
  name: "brewpage-mcp",
8
- version: "1.10.0",
8
+ version: "1.12.0",
9
9
  });
10
10
  const PUBLIC_NAMESPACE = "public";
11
11
  /**
@@ -36,10 +36,18 @@ const NAMESPACE_DESCRIPTION = "Optional. Omit to generate an unlisted namespace:
36
36
  "but it is excluded from gallery/search. Pass a custom namespace to group unlisted content, " +
37
37
  "or pass `public` ONLY when the user explicitly wants gallery/search eligibility. " +
38
38
  "Password-protected content stays excluded; unlisted, protected or otherwise ineligible native responses carry noindex; eligible public HTML, sites and files may permit indexing.";
39
+ const TTL_SCHEMA = z.union([z.number().positive(), z.string().min(1).max(32)]).optional().describe("Requested TTL: days as a number, or a string such as '2w', '36h' or '1 month' (default 15 days). " +
40
+ "The server rounds to whole days and clamps to the allowed range (1-30 by default; owner caps differ), reporting each adjustment in notices; use returned expiresAt.");
39
41
  const DELIVERY_MODE_SCHEMA = z.enum(["path", "subdomain"]).optional().describe("Optional delivery choice for a fresh publication, independent of password/namespace. " +
40
42
  "Fresh sites and HTML-format pages outside public (including the default unlisted namespace) always use subdomain; an explicit path there still publishes, on subdomain, with a delivery notice. " +
41
43
  "Everything else in any namespace (Markdown, code/text, files, JSON, KV, public HTML) defaults to path (Promotion); public HTML may automatically choose subdomain for browser features. " +
42
44
  "When creation returns an existing publication, it keeps its actual mode and URL.");
45
+ /** Site upload rules shared by publish_site and republish_site; current values come from get_limits. */
46
+ const SITE_LIMITS_TEXT = "Limits (call get_limits for current values): a request is at most 25 MB; a site at most 200 files, 5 MB per file and 20 MB in total; " +
47
+ "each path at most 380 characters. System files (.DS_Store, __MACOSX/ and similar) are skipped and never count; files with an unsupported extension " +
48
+ "or a too-long path are skipped and listed in notices. Entry: `entry`, else root index.html, else the shallowest HTML file. " +
49
+ "Errors: 400 = a site limit exceeded (the message names it and what to do), invalid path or no HTML file; 413 = request over 25 MB, " +
50
+ "too many files and form fields, or a file name/path too long; 422 = no servable file left.";
43
51
  function hostingResponseLines(data) {
44
52
  const fields = ["routingCohort", "deliveryMode", "requestedDeliveryMode", "effectiveDeliveryMode",
45
53
  "deliveryModeMatched", "modeLocked", "hostingVersion", "access", "managementLink"];
@@ -49,8 +57,17 @@ function hostingResponseLines(data) {
49
57
  if (data.deliveryModeMatched === false)
50
58
  lines.push("Requested hosting differs from the effective mode; use the returned URL.");
51
59
  lines.push(...deliveryReasonLines(data));
60
+ lines.push(...noticeLines(data));
52
61
  return lines;
53
62
  }
63
+ /** Server `notices` ({code, message}) on publish/update responses: each tolerated-input adjustment, verbatim. */
64
+ function noticeLines(data) {
65
+ if (!Array.isArray(data.notices))
66
+ return [];
67
+ return data.notices
68
+ .filter((n) => typeof n === "object" && n !== null && typeof n.code === "string" && typeof n.message === "string")
69
+ .map((n) => `Notice (${n.code}): ${n.message}`);
70
+ }
54
71
  /** Optional delivery fields on HTML and hosting responses: why the mode was chosen, flagged or not applied. */
55
72
  function deliveryReasonLines(data) {
56
73
  const { deliveryModeReason: reason, deliveryNotice: notice } = data;
@@ -115,9 +132,10 @@ server.tool("publish_html", "Publish HTML or Markdown content to BrewPage (repos
115
132
  content: z.string().describe("HTML or Markdown content to publish"),
116
133
  deliveryMode: DELIVERY_MODE_SCHEMA,
117
134
  format: z
118
- .enum(["HTML", "MARKDOWN"])
135
+ .string()
136
+ .min(1)
119
137
  .default("HTML")
120
- .describe("Content format: HTML or MARKDOWN"),
138
+ .describe("Content format, default HTML. HTML or MARKDOWN; the server also maps aliases (md, text/markdown, file extensions, code languages) and detects unknown values from content, reporting the change in notices."),
121
139
  namespace: z
122
140
  .string()
123
141
  .optional()
@@ -126,13 +144,7 @@ server.tool("publish_html", "Publish HTML or Markdown content to BrewPage (repos
126
144
  .string()
127
145
  .optional()
128
146
  .describe("Optional password to protect the page"),
129
- ttlDays: z
130
- .number()
131
- .int()
132
- .min(1)
133
- .max(30)
134
- .optional()
135
- .describe("Requested TTL in days (1-30, default 15). Backend owner caps and eligible automatic extensions can affect expiry; use returned expiresAt."),
147
+ ttlDays: TTL_SCHEMA,
136
148
  filename: z
137
149
  .string()
138
150
  .max(200)
@@ -360,7 +372,7 @@ server.tool("get_stats", "Get BrewPage platform-wide usage statistics including
360
372
  ],
361
373
  };
362
374
  });
363
- 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`. Repository defaults: 20 MiB total, 100 files, 5 MiB per file. Use its returned primary link and retain the owner token.", {
375
+ 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`. Use its returned primary link and retain the owner token. " + SITE_LIMITS_TEXT, {
364
376
  deliveryMode: DELIVERY_MODE_SCHEMA,
365
377
  entryContent: z
366
378
  .string()
@@ -382,12 +394,7 @@ server.tool("publish_site", "Publish an HTML site to BrewPage. Single-page: pass
382
394
  .string()
383
395
  .optional()
384
396
  .describe("Optional password to protect the site"),
385
- ttlDays: z
386
- .number()
387
- .min(1)
388
- .max(30)
389
- .optional()
390
- .describe("Requested TTL in days (1-30, default 15). Backend owner caps and eligible automatic extensions can affect expiry; use returned expiresAt."),
397
+ ttlDays: TTL_SCHEMA,
391
398
  ownerToken: z
392
399
  .string()
393
400
  .optional()
@@ -476,7 +483,7 @@ server.tool("publish_site", "Publish an HTML site to BrewPage. Single-page: pass
476
483
  content: [{ type: "text", text: lines.join("\n") }],
477
484
  };
478
485
  });
479
- server.tool("republish_site", "Republish (full-replace) an existing multi-file BrewPage site in place at the SAME URL/ID. Requires owner token. The uploaded bundle becomes the complete new file set — files absent from it are removed, new files added, matching files overwritten. Single-page: pass `entryContent`. Multi-page: pass `files` (array of `{path, content}`). Optional `entry` overrides the default `index.html`. Site bundles use repository defaults of 20 MiB total, 100 files, 5 MiB per file; republishing preserves existing expiry.", {
486
+ server.tool("republish_site", "Republish (full-replace) an existing multi-file BrewPage site in place at the SAME URL/ID. Requires owner token. The uploaded bundle becomes the complete new file set — files absent from it are removed, new files added, matching files overwritten. Single-page: pass `entryContent`. Multi-page: pass `files` (array of `{path, content}`). Optional `entry` overrides the default `index.html`. Republishing preserves existing expiry. " + SITE_LIMITS_TEXT, {
480
487
  namespace: z.string().describe("Site namespace"),
481
488
  id: z.string().describe("Site ID"),
482
489
  ownerToken: z
@@ -494,12 +501,7 @@ server.tool("republish_site", "Republish (full-replace) an existing multi-file B
494
501
  .string()
495
502
  .optional()
496
503
  .describe("Entry file path, default index.html"),
497
- ttlDays: z
498
- .number()
499
- .min(1)
500
- .max(30)
501
- .optional()
502
- .describe("Compatibility input (1-30); site republishing preserves existing expiry. Use returned expiresAt."),
504
+ ttlDays: TTL_SCHEMA.describe("Ignored: republishing keeps the existing expiry; a supplied value comes back as a ttl-ignored notice."),
503
505
  tags: z
504
506
  .array(z.string())
505
507
  .optional()
@@ -634,13 +636,7 @@ server.tool("publish_json", "Publish a JSON document to BrewPage (repository def
634
636
  .string()
635
637
  .optional()
636
638
  .describe("Optional password to protect the document"),
637
- ttlDays: z
638
- .number()
639
- .int()
640
- .min(1)
641
- .max(30)
642
- .optional()
643
- .describe("Requested TTL in days (1-30, default 15). Backend owner caps and eligible automatic extensions can affect expiry; use returned expiresAt."),
639
+ ttlDays: TTL_SCHEMA,
644
640
  tags: z
645
641
  .array(z.string())
646
642
  .optional()
@@ -780,13 +776,7 @@ server.tool("publish_kv", "Publish a key/value entry to BrewPage. Creates a KV s
780
776
  .string()
781
777
  .optional()
782
778
  .describe("Optional password to protect the KV store"),
783
- ttlDays: z
784
- .number()
785
- .int()
786
- .min(1)
787
- .max(30)
788
- .optional()
789
- .describe("Requested TTL in days (1-30, default 15). Backend owner caps and eligible automatic extensions can affect expiry; use returned expiresAt."),
779
+ ttlDays: TTL_SCHEMA,
790
780
  tags: z
791
781
  .array(z.string())
792
782
  .optional()
@@ -903,6 +893,30 @@ server.tool("get_kv", "Fetch a value from a BrewPage KV store by namespace, ID,
903
893
  ],
904
894
  };
905
895
  });
896
+ server.tool("get_limits", "Get the current BrewPage upload limits (GET /api/limits, no auth): request size, site file count, per-file and total size, " +
897
+ "ZIP expansion, path and name lengths, TTL default/maximum, allowed file extensions and skipped system files. Sizes are bytes. " +
898
+ "Call it before publishing a large or multi-file site.", {}, async () => {
899
+ const { ok, status, data } = await apiRequest("GET", "/api/limits");
900
+ if (!ok) {
901
+ return {
902
+ content: [
903
+ {
904
+ type: "text",
905
+ text: `Failed to fetch limits (${status}): ${JSON.stringify(data)}`,
906
+ },
907
+ ],
908
+ isError: true,
909
+ };
910
+ }
911
+ return {
912
+ content: [
913
+ {
914
+ type: "text",
915
+ text: `BrewPage upload limits (bytes):\n\n${JSON.stringify(data, null, 2)}`,
916
+ },
917
+ ],
918
+ };
919
+ });
906
920
  server.tool("search_gallery", "Search the BrewPage public gallery. Optional filters: text query, pagination, sort, and 'mine' (owner-scoped, requires owner token).", {
907
921
  q: z.string().optional().describe("Free-text search query"),
908
922
  page: z
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brewpage-mcp",
3
- "version": "1.10.0",
3
+ "version": "1.12.0",
4
4
  "mcpName": "io.github.kochetkov-ma/brewpage-mcp",
5
5
  "description": "MCP server for BrewPage — publish and manage HTML, sites, KV, JSON and files from AI assistants",
6
6
  "type": "module",