brewpage-mcp 1.15.5 → 1.16.1

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 CHANGED
@@ -17,7 +17,7 @@ Choosing a host? See how BrewPage compares with GitHub Pages, Netlify Drop, Tiin
17
17
  ## Quick Start
18
18
 
19
19
  ```bash
20
- npx -y brewpage-mcp@1.15.5
20
+ npx -y brewpage-mcp@1.16.1
21
21
  ```
22
22
 
23
23
  Requires Node.js 20 or newer. This starts the stdio server for an MCP client.
@@ -50,7 +50,7 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
50
50
  "mcpServers": {
51
51
  "brewpage": {
52
52
  "command": "npx",
53
- "args": ["-y", "brewpage-mcp@1.15.5"]
53
+ "args": ["-y", "brewpage-mcp@1.16.1"]
54
54
  }
55
55
  }
56
56
  }
@@ -61,7 +61,7 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
61
61
  Register a project-scoped stdio server:
62
62
 
63
63
  ```bash
64
- claude mcp add --transport stdio --scope project brewpage -- npx -y brewpage-mcp@1.15.5
64
+ claude mcp add --transport stdio --scope project brewpage -- npx -y brewpage-mcp@1.16.1
65
65
  ```
66
66
 
67
67
  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).
@@ -75,7 +75,7 @@ Open **Settings > MCP** and add:
75
75
  "mcpServers": {
76
76
  "brewpage": {
77
77
  "command": "npx",
78
- "args": ["-y", "brewpage-mcp@1.15.5"]
78
+ "args": ["-y", "brewpage-mcp@1.16.1"]
79
79
  }
80
80
  }
81
81
  }
@@ -89,7 +89,7 @@ Open the Cline MCP settings panel and add:
89
89
  {
90
90
  "brewpage": {
91
91
  "command": "npx",
92
- "args": ["-y", "brewpage-mcp@1.15.5"]
92
+ "args": ["-y", "brewpage-mcp@1.16.1"]
93
93
  }
94
94
  }
95
95
  ```
@@ -97,13 +97,13 @@ Open the Cline MCP settings panel and add:
97
97
  ### Global Install
98
98
 
99
99
  ```bash
100
- npm install -g brewpage-mcp@1.15.5
100
+ npm install -g brewpage-mcp@1.16.1
101
101
  brewpage-mcp
102
102
  ```
103
103
 
104
104
  ## Tools
105
105
 
106
- Twenty tools are available. Creation returns an owner token; keep it to modify or delete the resource later. Owner tools take `ownerToken`, else `BREWPAGE_OWNER_TOKEN`. Supported password inputs use `X-Password`. Secrets allowed; likely ones are flagged in notices. For a secret: private namespace + password.
106
+ Twenty tools are available. Creation returns an owner token; keep it to modify or delete the resource later. Owner tools take `ownerToken`, else `BREWPAGE_OWNER_TOKEN` (local server only). Supported password inputs use `X-Password`. Secrets allowed; likely ones are flagged in notices. For a secret: private namespace + password.
107
107
 
108
108
  | Tool | Purpose |
109
109
  |------|---------|
@@ -130,7 +130,7 @@ Twenty tools are available. Creation returns an owner token; keep it to modify o
130
130
 
131
131
  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 is private (a generated `priv-<random>` namespace): unlisted and not indexed; anyone with the link can still open it. `public` lists HTML, sites and files in the gallery and sitemap, so Google, Bing and AI search can find and index them; public pages are served on the brewpage.app domain for the strongest reach (a site always gets its own subdomain). Never put secrets in a public publication. Password-protected publications, JSON and KV are never listed.
132
132
 
133
- 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`; `secret-detected` and `manage-link-disabled` print as `WARNING (code): message`: relay them to the user. A minted `manageUrl` prints as `MANAGE LINK (private): …`: give it to the user privately, never publish it. A listed public result prints `VISIBILITY: public, listed. Hide: set_visibility listed=false`. `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.
133
+ 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`; `secret-detected` prints as `WARNING (code): message`: relay it to the user. A live BrewPage token or manage link in public content (`credential-published`) prints first as `SECURITY ALERT (credential-published): …`: tell the user to remove and reset it; that result is hidden from the gallery, so it prints no `VISIBILITY` line. A minted `manageUrl` prints as `MANAGE LINK (private): …`: give it to the user privately, never publish it. A listed public result prints `VISIBILITY: public, listed. Hide: set_visibility listed=false`. `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.
134
134
 
135
135
  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.
136
136
 
@@ -232,7 +232,7 @@ publish_file(url="https://example.com/diagram.png")
232
232
 
233
233
  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. The result prints `Title:` (entry page title) after `ID:` when the server derives one.
234
234
 
235
- 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, as does a PDF/Office file that is unreadable or password-protected; 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; 503 means busy: retry after `Retry-After` seconds (failed publish tools print it).
235
+ Limits (current values from `get_limits`): a request is at most 25 MiB; a site at most 200 files, 5 MiB per file and 20 MiB 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, as does a PDF/Office file that is unreadable or password-protected; 413 means the request is over 25 MiB, has too many files and form fields, or a file name/path is too long; 422 means no servable file is left; 503 means busy: retry after `Retry-After` seconds (failed publish tools print it).
236
236
 
237
237
  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 private, unlisted content; `public` lists the site in the gallery and sitemap for Google, Bing and AI search; a site always gets its own subdomain), `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`).
238
238
 
@@ -382,7 +382,7 @@ search_gallery(mine=true, ownerToken="tok_...", sort="date")
382
382
 
383
383
  Delete a BrewPage resource (HTML page, site, KV store, JSON document, or file) using the owner token received at creation.
384
384
 
385
- Parameters: `type` (`html` | `site` | `kv` | `json` | `file`), `namespace` (string), `id` (string), `ownerToken` (optional; default `BREWPAGE_OWNER_TOKEN`). Path segments are URL-encoded.
385
+ Parameters: `type` (`html` | `site` | `kv` | `json` | `file`), `namespace` (string), `id` (string), `ownerToken` (optional; default `BREWPAGE_OWNER_TOKEN`, local server only). Path segments are URL-encoded.
386
386
 
387
387
  Example prompt:
388
388
 
@@ -412,7 +412,7 @@ Output: `LISTED: false · link unchanged`.
412
412
 
413
413
  Add an owner token to your BrewPage profile (as `publish`) with an `admin` token of that profile (`POST /api/owner-token/bind`). Output: `BOUND …ab12 · 3/20 active`; tokens are never printed in full.
414
414
 
415
- Parameters: `token`, `ownerToken` (admin token; optional, default `BREWPAGE_OWNER_TOKEN`).
415
+ Parameters: `token`, `ownerToken` (admin token; optional, default `BREWPAGE_OWNER_TOKEN`, local server only).
416
416
 
417
417
  ---
418
418
 
@@ -442,9 +442,9 @@ get_stats()
442
442
 
443
443
  ### `get_limits`
444
444
 
445
- Read the current limits from `GET /api/limits` (no auth; with `BREWPAGE_OWNER_TOKEN` also `reach` and `ownerMaxTtlDays`). Sizes are bytes, TTLs days: `requestMaxBytes`; `html`, `json`, `kv` and `files` max size, counts and TTL default/maximum; `profileMaxTtlDays` (TTL cap for a token in a profile); `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.
445
+ Read the current limits from `GET /api/limits` (no auth needed; an owner token goes as `X-Owner-Token`). Always returns `reach`: `profile` only when the owner token is active in a profile, else `token` (also with no token); with a usable owner token also `ownerMaxTtlDays`. Sizes are bytes, TTLs days: `requestMaxBytes`; `html`, `json`, `kv` and `files` max size, counts and TTL default/maximum; `profileMaxTtlDays` (TTL cap for a token in a profile); `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.
446
446
 
447
- Parameters: none.
447
+ Parameters: `ownerToken` (optional; default `BREWPAGE_OWNER_TOKEN`, local server only).
448
448
 
449
449
  Example prompt:
450
450
 
@@ -530,6 +530,14 @@ Every publish response includes an **owner token** -- the only credential that a
530
530
 
531
531
  ## Changelog
532
532
 
533
+ ## 1.16.1 -- 2026-10-30
534
+
535
+ - Update tools (`update_html`, `update_json`, `set_kv`, `republish_site`) state that tokens and manage links never go in public content.
536
+
537
+ ## 1.16.0 -- 2026-10-29
538
+
539
+ - `get_limits` accepts `ownerToken` (remote `/mcp` gets `reach`/`ownerMaxTtlDays`). Results open with `SECURITY ALERT (credential-published)` when public content holds a live BrewPage token or manage link. Wording: limits reach, local-only env token, MiB.
540
+
533
541
  ## 1.15.5 -- 2026-10-29
534
542
 
535
543
  - `publish_html` prints the page title (`Title:`) when the server reports one; tool texts state plain size limits; `get_limits` names the per-type limits and the profile TTL cap.
package/dist/tools.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { McpServer } from "@modelcontextprotocol/server";
2
2
  export declare const DEFAULT_BASE_URL = "https://brewpage.app";
3
- export declare const USER_AGENT = "brewpage-mcp/1.15.5";
3
+ export declare const USER_AGENT = "brewpage-mcp/1.16.1";
4
4
  export declare const OWNER_TOKEN_ENV = "BREWPAGE_OWNER_TOKEN";
5
5
  export interface ServerOptions {
6
6
  /** BrewPage origin for API calls; default https://brewpage.app. */
package/dist/tools.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { McpServer } from "@modelcontextprotocol/server";
2
2
  import { z } from "zod";
3
3
  export const DEFAULT_BASE_URL = "https://brewpage.app";
4
- export const USER_AGENT = "brewpage-mcp/1.15.5";
4
+ export const USER_AGENT = "brewpage-mcp/1.16.1";
5
5
  export const OWNER_TOKEN_ENV = "BREWPAGE_OWNER_TOKEN";
6
6
  const READ_ONLY = { readOnlyHint: true };
7
7
  const DESTRUCTIVE = { readOnlyHint: false, destructiveHint: true };
@@ -10,15 +10,23 @@ const ADDITIVE = { readOnlyHint: false, destructiveHint: false };
10
10
  function toolMeta(title, hints) {
11
11
  return { title, annotations: { title, ...hints } };
12
12
  }
13
- const NO_OWNER_TOKEN = `No owner token: pass ownerToken or set ${OWNER_TOKEN_ENV}.`;
14
- const CREATE_OWNER_TOKEN_DESCRIPTION = `Optional. Default: ${OWNER_TOKEN_ENV}, else a new token per create.`;
15
- const OWNER_TOKEN_DESCRIPTION = `Owner token from create. Default: ${OWNER_TOKEN_ENV}.`;
16
- const SECRETS = "Secrets allowed; likely ones are flagged in notices. Private namespace + password suit secrets.";
13
+ /** Env default exists only in the local (stdio) server; remote /mcp takes ownerToken per call. */
14
+ const ENV_DEFAULT = `env ${OWNER_TOKEN_ENV} (local server only)`;
15
+ const NO_OWNER_TOKEN = `No owner token: pass ownerToken (local server: or set ${OWNER_TOKEN_ENV}).`;
16
+ const CREATE_OWNER_TOKEN_DESCRIPTION = `Optional. Default: ${ENV_DEFAULT}, else a new token per create.`;
17
+ const OWNER_TOKEN_DESCRIPTION = `Owner token from create. Default: ${ENV_DEFAULT}.`;
18
+ /** Backend guard checks create and update content alike. */
19
+ const NO_PUBLIC_CREDENTIALS = "BrewPage tokens and manage links must never be in public content.";
20
+ const SECRETS = "Secrets allowed; likely ones are flagged in notices. Private namespace + password suit secrets. " +
21
+ NO_PUBLIC_CREDENTIALS;
17
22
  const MANAGE_URL_TEXT = "Returns manageUrl, a private management link (grants control; not for public sharing).";
18
23
  const OLD_READ_ONLY_CODE = "OLD_PUBLICATION_READ_ONLY";
19
24
  const OLD_READ_ONLY_TEXT = ` Old publications (made before 5 October 2026) are read-only: 409 ${OLD_READ_ONLY_CODE} with createUrl to publish anew.`;
20
25
  /** Notice codes the agent must relay to the user. */
21
- const WARNING_CODES = new Set(["secret-detected", "manage-link-disabled"]);
26
+ const WARNING_CODES = new Set(["secret-detected"]);
27
+ /** Live BrewPage token or manage link in public content: printed first as a security alert, never listed. */
28
+ const CREDENTIAL_CODE = "credential-published";
29
+ const CREDENTIAL_ALERT_TAIL = "Tell the user now: anyone with this link can use that token; they must remove it and reset it.";
22
30
  /** MCP type name -> REST path segment. */
23
31
  const TYPE_SEGMENTS = { html: "html", site: "sites", file: "files", json: "json", kv: "kv" };
24
32
  /** `/api/<segments>` with each segment encoded; empty, `.` and `..` refused (URL parsing would resolve them away). */
@@ -108,11 +116,11 @@ const DELIVERY_MODE_SCHEMA = z.enum(["path", "subdomain"]).optional().describe("
108
116
  "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. " +
109
117
  "When creation returns an existing publication, it keeps its actual mode and URL.");
110
118
  /** Site upload rules shared by publish_site and republish_site; current values come from get_limits. */
111
- const SITE_LIMITS_TEXT = "Default limits (current values: GET /api/limits): a request is at most 25 MB; a site at most 200 files, 5 MB per file and 20 MB in total; " +
119
+ const SITE_LIMITS_TEXT = "Default limits (current values: GET /api/limits): a request is at most 25 MiB; a site at most 200 files, 5 MiB per file and 20 MiB in total; " +
112
120
  "each path at most 380 characters. System files (.DS_Store, __MACOSX/ and similar) are skipped and never count; files with an unsupported extension " +
113
121
  "or a too-long path are skipped and listed in notices. Entry: `entry`, else root index.html, else the shallowest HTML file. " +
114
122
  "Errors: 400 = a site limit exceeded (the message names it), invalid path, no HTML file, or a PDF/Office file unreadable " +
115
- "or password-protected; 413 = request over 25 MB, too many files and form fields, or a file name/path too long; " +
123
+ "or password-protected; 413 = request over 25 MiB, too many files and form fields, or a file name/path too long; " +
116
124
  "422 = no servable file left; 503 = busy, retry after Retry-After seconds.";
117
125
  function hostingResponseLines(data) {
118
126
  const fields = ["routingCohort", "deliveryMode", "requestedDeliveryMode", "effectiveDeliveryMode",
@@ -126,14 +134,26 @@ function hostingResponseLines(data) {
126
134
  lines.push(...noticeLines(data));
127
135
  return lines;
128
136
  }
129
- /** Server `notices` ({code, message}) on publish/update responses: each tolerated-input adjustment, verbatim. */
130
- function noticeLines(data) {
137
+ /** Server `notices` ({code, message}) on publish/update responses, well-formed only. */
138
+ function notices(data) {
131
139
  if (!Array.isArray(data.notices))
132
140
  return [];
133
- return data.notices
134
- .filter((n) => typeof n === "object" && n !== null && typeof n.code === "string" && typeof n.message === "string")
141
+ return data.notices.filter((n) => typeof n === "object" && n !== null && typeof n.code === "string" && typeof n.message === "string");
142
+ }
143
+ /** Each tolerated-input adjustment, verbatim; the credential alert prints separately, first. */
144
+ function noticeLines(data) {
145
+ return notices(data).filter((n) => n.code !== CREDENTIAL_CODE)
135
146
  .map((n) => `${WARNING_CODES.has(n.code) ? "WARNING" : "Notice"} (${n.code}): ${n.message}`);
136
147
  }
148
+ function hasCredentialNotice(data) {
149
+ return notices(data).some((n) => n.code === CREDENTIAL_CODE);
150
+ }
151
+ /** Opening lines of a result whose public content holds a live BrewPage credential, else none. */
152
+ function credentialAlertLines(data) {
153
+ const alerts = notices(data).filter((n) => n.code === CREDENTIAL_CODE)
154
+ .map((n) => `SECURITY ALERT (${CREDENTIAL_CODE}): ${n.message} ${CREDENTIAL_ALERT_TAIL}`);
155
+ return alerts.length ? [...alerts, ``] : [];
156
+ }
137
157
  const SIGN_IN_HINT = "Optional: sign in free (Google or any email) at https://account.brewpage.app to never lose your pages or tokens and to mint tokens that never expire. Not needed to publish.";
138
158
  /** Private management link (present only when minted) and the public listing state. */
139
159
  function ownerLinkLines(data) {
@@ -141,7 +161,7 @@ function ownerLinkLines(data) {
141
161
  if (typeof data.manageUrl === "string" && data.manageUrl.length > 0) {
142
162
  lines.push(``, `MANAGE LINK (private): ${data.manageUrl}`, `Give it to the user privately; never publish it.`);
143
163
  }
144
- if (data.listed === true)
164
+ if (data.listed === true && !hasCredentialNotice(data))
145
165
  lines.push(``, `VISIBILITY: public, listed. Hide: set_visibility listed=false`);
146
166
  return lines;
147
167
  }
@@ -157,6 +177,7 @@ function deliveryReasonLines(data) {
157
177
  }
158
178
  function formatUpdateResponse(data) {
159
179
  const lines = [
180
+ ...credentialAlertLines(data),
160
181
  `Updated successfully!`,
161
182
  ``,
162
183
  `URL: ${data.link || data.url || "N/A"}`,
@@ -188,7 +209,7 @@ export function createServer(options = {}) {
188
209
  const apiFetch = options.fetch ?? fetch;
189
210
  const server = new McpServer({
190
211
  name: "brewpage-mcp",
191
- version: "1.15.5",
212
+ version: "1.16.1",
192
213
  });
193
214
  /** Caller argument first, then the configured default; `fromEnv` marks the configured one. */
194
215
  function ownerTokenFor(argument) {
@@ -242,6 +263,7 @@ export function createServer(options = {}) {
242
263
  }
243
264
  function formatPublishResponse(data, effectiveNamespace, sentOwnerToken, listable = true) {
244
265
  const lines = [
266
+ ...credentialAlertLines(data),
245
267
  `Published successfully!`,
246
268
  ``,
247
269
  `URL: ${data.link || data.url || "N/A"}`,
@@ -595,6 +617,7 @@ export function createServer(options = {}) {
595
617
  }
596
618
  const site = data;
597
619
  const lines = [
620
+ ...credentialAlertLines(site),
598
621
  `Site published successfully!`,
599
622
  ``,
600
623
  `URL: ${site.link || "N/A"}`,
@@ -625,7 +648,7 @@ export function createServer(options = {}) {
625
648
  });
626
649
  server.registerTool("republish_site", {
627
650
  ...toolMeta("Republish site", DESTRUCTIVE),
628
- description: "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: `entryContent`. Multi-page: `files` (array of `{path, content}`). Optional `entry` overrides the default `index.html`. Republishing preserves existing expiry." + OLD_READ_ONLY_TEXT + " " + SITE_LIMITS_TEXT,
651
+ description: "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: `entryContent`. Multi-page: `files` (array of `{path, content}`). Optional `entry` overrides the default `index.html`. Republishing preserves existing expiry." + OLD_READ_ONLY_TEXT + " " + SITE_LIMITS_TEXT + " " + NO_PUBLIC_CREDENTIALS,
629
652
  inputSchema: {
630
653
  namespace: z.string().describe("Site namespace"),
631
654
  id: z.string().describe("Site ID"),
@@ -687,6 +710,7 @@ export function createServer(options = {}) {
687
710
  return ownerFailure("republish site", res.status, data, auth.fromEnv, res.headers);
688
711
  const site = data;
689
712
  const lines = [
713
+ ...credentialAlertLines(site),
690
714
  `Site republished successfully (full replace)!`,
691
715
  ``,
692
716
  `URL: ${site.link || "N/A"}`,
@@ -716,7 +740,7 @@ export function createServer(options = {}) {
716
740
  server.registerTool("update_html", {
717
741
  ...toolMeta("Update page", DESTRUCTIVE),
718
742
  description: "Update an existing BrewPage HTML, Markdown, code or JSON page. Requires owner token. Returns the updated page metadata. " +
719
- "The link and delivery mode never change on update; an advisory deliveryModeReason and delivery notice are included when the new content uses browser features that may not work fully on a BrewPage link." + OLD_READ_ONLY_TEXT,
743
+ "The link and delivery mode never change on update; an advisory deliveryModeReason and delivery notice are included when the new content uses browser features that may not work fully on a BrewPage link." + OLD_READ_ONLY_TEXT + " " + NO_PUBLIC_CREDENTIALS,
720
744
  inputSchema: {
721
745
  namespace: z.string().describe("Page namespace"),
722
746
  id: z.string().describe("Page ID"),
@@ -850,7 +874,7 @@ export function createServer(options = {}) {
850
874
  });
851
875
  server.registerTool("update_json", {
852
876
  ...toolMeta("Update JSON document", DESTRUCTIVE),
853
- description: "Update an existing BrewPage JSON document. Requires owner token. Accepts a JSON object, array, or a JSON-encoded string." + OLD_READ_ONLY_TEXT,
877
+ description: "Update an existing BrewPage JSON document. Requires owner token. Accepts a JSON object, array, or a JSON-encoded string." + OLD_READ_ONLY_TEXT + " " + NO_PUBLIC_CREDENTIALS,
854
878
  inputSchema: {
855
879
  namespace: z.string().describe("Document namespace"),
856
880
  id: z.string().describe("Document ID"),
@@ -943,7 +967,7 @@ export function createServer(options = {}) {
943
967
  });
944
968
  server.registerTool("set_kv", {
945
969
  ...toolMeta("Set KV value", DESTRUCTIVE),
946
- description: "Set or update a key in an existing BrewPage KV store. Requires owner token. The key name `hosting` is reserved." + OLD_READ_ONLY_TEXT,
970
+ description: "Set or update a key in an existing BrewPage KV store. Requires owner token. The key name `hosting` is reserved." + OLD_READ_ONLY_TEXT + " " + NO_PUBLIC_CREDENTIALS,
947
971
  inputSchema: {
948
972
  namespace: z.string().describe("KV store namespace"),
949
973
  id: z.string().describe("KV store ID"),
@@ -1014,10 +1038,13 @@ export function createServer(options = {}) {
1014
1038
  description: "Get the current BrewPage limits (GET /api/limits): request size; per type (html, json, kv, files, sites) max size, " +
1015
1039
  "counts and TTL default/maximum; profileMaxTtlDays (TTL cap for a token in a profile); site ZIP expansion, path and name lengths, " +
1016
1040
  "allowed file extensions and skipped system files. Sizes are bytes, TTLs days. " +
1017
- `With ${OWNER_TOKEN_ENV} set: also reach (token|profile) and ownerMaxTtlDays.`,
1018
- inputSchema: {},
1019
- }, async () => {
1020
- const headers = defaultOwnerToken ? { "X-Owner-Token": defaultOwnerToken } : {};
1041
+ `The answer always has reach: \`profile\` only when the owner token is active in a profile, else \`token\` (also with no token). With a usable owner token also ownerMaxTtlDays.`,
1042
+ inputSchema: {
1043
+ ownerToken: z.string().optional().describe(OWNER_TOKEN_DESCRIPTION),
1044
+ },
1045
+ }, async ({ ownerToken }) => {
1046
+ const { token } = ownerTokenFor(ownerToken);
1047
+ const headers = token ? { "X-Owner-Token": token } : {};
1021
1048
  const { ok, status, data } = await apiRequest("GET", "/api/limits", undefined, headers);
1022
1049
  if (!ok) {
1023
1050
  return {
@@ -1068,7 +1095,7 @@ export function createServer(options = {}) {
1068
1095
  ownerToken: z
1069
1096
  .string()
1070
1097
  .optional()
1071
- .describe(`Owner token; only sent as X-Owner-Token when 'mine' is true. Default: ${OWNER_TOKEN_ENV}.`),
1098
+ .describe(`Owner token; only sent as X-Owner-Token when 'mine' is true. Default: ${ENV_DEFAULT}.`),
1072
1099
  },
1073
1100
  }, async ({ q, page, size, sort, mine, ownerToken }) => {
1074
1101
  const auth = ownerTokenFor(ownerToken);
@@ -1119,14 +1146,14 @@ export function createServer(options = {}) {
1119
1146
  if (!ok)
1120
1147
  return ownerFailure("set visibility", status, data, auth.fromEnv);
1121
1148
  const result = data;
1122
- return textResult([`LISTED: ${result.listed} · link unchanged`, ...noticeLines(result)].join("\n"));
1149
+ return textResult([...credentialAlertLines(result), `LISTED: ${result.listed} · link unchanged`, ...noticeLines(result)].join("\n"));
1123
1150
  });
1124
1151
  server.registerTool("bind_token", {
1125
1152
  ...toolMeta("Add token to profile", ADDITIVE),
1126
1153
  description: "Add an owner token to your BrewPage profile (as publish). Needs an admin token of that profile.",
1127
1154
  inputSchema: {
1128
1155
  token: z.string().describe("Owner token to add"),
1129
- ownerToken: z.string().optional().describe(`Admin token of the profile. Default: ${OWNER_TOKEN_ENV}.`),
1156
+ ownerToken: z.string().optional().describe(`Admin token of the profile. Default: ${ENV_DEFAULT}.`),
1130
1157
  },
1131
1158
  }, async ({ token, ownerToken }) => {
1132
1159
  const auth = ownerTokenFor(ownerToken);
@@ -1145,7 +1172,7 @@ export function createServer(options = {}) {
1145
1172
  label: z.string().optional().describe("Optional label shown in the profile"),
1146
1173
  scope: z.enum(["publish", "admin"]).optional().describe("publish (default) or admin (can manage tokens)"),
1147
1174
  expiresIn: z.enum(["30d", "90d", "1y", "never"]).optional().describe("Default: admin 90d, publish never"),
1148
- ownerToken: z.string().optional().describe(`Admin token of the profile. Default: ${OWNER_TOKEN_ENV}.`),
1175
+ ownerToken: z.string().optional().describe(`Admin token of the profile. Default: ${ENV_DEFAULT}.`),
1149
1176
  },
1150
1177
  }, async ({ label, scope, expiresIn, ownerToken }) => {
1151
1178
  const auth = ownerTokenFor(ownerToken);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brewpage-mcp",
3
- "version": "1.15.5",
3
+ "version": "1.16.1",
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",