brewpage-mcp 1.16.4 → 1.16.5

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.16.4
20
+ npx -y brewpage-mcp@1.16.5
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.16.4"]
53
+ "args": ["-y", "brewpage-mcp@1.16.5"]
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.16.4
64
+ claude mcp add --transport stdio --scope project brewpage -- npx -y brewpage-mcp@1.16.5
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.16.4"]
78
+ "args": ["-y", "brewpage-mcp@1.16.5"]
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.16.4"]
92
+ "args": ["-y", "brewpage-mcp@1.16.5"]
93
93
  }
94
94
  }
95
95
  ```
@@ -97,7 +97,7 @@ Open the Cline MCP settings panel and add:
97
97
  ### Global Install
98
98
 
99
99
  ```bash
100
- npm install -g brewpage-mcp@1.16.4
100
+ npm install -g brewpage-mcp@1.16.5
101
101
  brewpage-mcp
102
102
  ```
103
103
 
@@ -176,7 +176,7 @@ publish_html(content="<h1>Report</h1>...", format="HTML", ttlDays=15)
176
176
 
177
177
  Update an existing HTML or Markdown page in place, preserving its short URL. Requires the original `ownerToken` returned at creation.
178
178
 
179
- Parameters: `namespace` (string), `id` (string), `content` (string, the new body), `ownerToken` (string), `format` (optional HTML/Markdown alias or supported code language such as `json`, `yaml`, or `typescript`; omitted preserves the stored format).
179
+ Parameters: `namespace` (string), `id` (string), `content` (string, the new body), `ownerToken` (string), `format` (optional HTML/Markdown alias or supported code language such as `json`, `yaml`, or `typescript`; omitted preserves the stored format), `tags` (optional; replace the stored tags, `[]` clears, omitted keeps them).
180
180
 
181
181
  An update never changes the link or delivery mode; an advisory `deliveryModeReason` and `Delivery notice:` line are printed when the server returns them.
182
182
 
@@ -250,7 +250,7 @@ publish_site(files=[{"path": "index.html", "content": "..."}, {"path": "style.cs
250
250
 
251
251
  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.
252
252
 
253
- 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`.
253
+ 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` (optional; replace the stored tags, `[]` clears, omitted keeps them).
254
254
 
255
255
  ```text
256
256
  republish_site(namespace="public", id="aBcDeFgHiJ", ownerToken="tok_...", files=[{"path":"index.html","content":"<h1>Updated site</h1>"}])
@@ -530,6 +530,10 @@ Every publish response includes an **owner token** -- the only credential that a
530
530
 
531
531
  ## Changelog
532
532
 
533
+ ## 1.16.5 -- 2026-10-30
534
+
535
+ - `update_html` takes optional `tags` (sent as `?tags=a,b`); `update_html` and `republish_site` tags replace the stored tags, `[]` clears them, omitted keeps them. A 429 on any write prints the server's `Retry-After` beside the verbatim body (`retryAfterSeconds`).
536
+
533
537
  ## 1.16.4 -- 2026-10-30
534
538
 
535
539
  - Owner update tools (`update_html`, `republish_site`, `update_json`, `set_kv`) answer a 404 with the server body plus which publish tool makes a new copy; `delete_resource` relays the 404 unchanged.
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.16.4";
3
+ export declare const USER_AGENT = "brewpage-mcp/1.16.5";
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.16.4";
4
+ export const USER_AGENT = "brewpage-mcp/1.16.5";
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 };
@@ -54,9 +54,9 @@ function maskToken(token) {
54
54
  function textResult(text, isError = false) {
55
55
  return isError ? { content: [{ type: "text", text }], isError: true } : { content: [{ type: "text", text }] };
56
56
  }
57
- /** ` Retry-After: <value>.` for a busy 503 that sends the header, else empty. */
57
+ /** ` Retry-After: <value>.` for a 429 or busy 503 that sends the header, else empty. */
58
58
  function retryAfterText(status, headers) {
59
- const after = status === 503 ? headers?.get("retry-after") : null;
59
+ const after = status === 429 || status === 503 ? headers?.get("retry-after") : null;
60
60
  return after ? ` Retry-After: ${after}.` : "";
61
61
  }
62
62
  /** Create tool for each content update: a 404 update means the row is gone, so the agent publishes anew. */
@@ -216,7 +216,7 @@ export function createServer(options = {}) {
216
216
  const apiFetch = options.fetch ?? fetch;
217
217
  const server = new McpServer({
218
218
  name: "brewpage-mcp",
219
- version: "1.16.4",
219
+ version: "1.16.5",
220
220
  });
221
221
  /** Caller argument first, then the configured default; `fromEnv` marks the configured one. */
222
222
  function ownerTokenFor(argument) {
@@ -427,9 +427,9 @@ export function createServer(options = {}) {
427
427
  if (!auth.token)
428
428
  return textResult(NO_OWNER_TOKEN, true);
429
429
  const path = apiPath(TYPE_SEGMENTS[type], namespace, id, "hosting");
430
- const { ok, status, data } = await apiRequest("PATCH", path, { deliveryMode, expectedVersion }, { "X-Owner-Token": auth.token });
430
+ const { ok, status, data, headers: answerHeaders } = await apiRequest("PATCH", path, { deliveryMode, expectedVersion }, { "X-Owner-Token": auth.token });
431
431
  if (!ok)
432
- return ownerFailure("update hosting", status, data, auth.fromEnv);
432
+ return ownerFailure("update hosting", status, data, auth.fromEnv, answerHeaders);
433
433
  return {
434
434
  content: [{ type: "text", text: formatUpdateResponse(data) }],
435
435
  };
@@ -450,9 +450,9 @@ export function createServer(options = {}) {
450
450
  if (!auth.token)
451
451
  return textResult(NO_OWNER_TOKEN, true);
452
452
  const path = apiPath(TYPE_SEGMENTS[type], namespace, id);
453
- const { ok, status, data } = await apiRequest("DELETE", path, undefined, { "X-Owner-Token": auth.token });
453
+ const { ok, status, data, headers: answerHeaders } = await apiRequest("DELETE", path, undefined, { "X-Owner-Token": auth.token });
454
454
  if (!ok)
455
- return ownerFailure("delete", status, data, auth.fromEnv);
455
+ return ownerFailure("delete", status, data, auth.fromEnv, answerHeaders);
456
456
  return {
457
457
  content: [
458
458
  {
@@ -677,7 +677,7 @@ export function createServer(options = {}) {
677
677
  tags: z
678
678
  .array(z.string())
679
679
  .optional()
680
- .describe("Optional tags (replaces the site's existing tags)"),
680
+ .describe("Optional tags; replace the site's stored tags. Omit to keep them."),
681
681
  },
682
682
  }, async ({ namespace, id, ownerToken, entryContent, files, entry, ttlDays, tags }) => {
683
683
  const hasEntry = typeof entryContent === "string";
@@ -711,7 +711,7 @@ export function createServer(options = {}) {
711
711
  return textResult(NO_OWNER_TOKEN, true);
712
712
  const headers = { "X-Owner-Token": auth.token };
713
713
  const url = baseUrl + withQuery(apiPath("sites", namespace, id), [["entry", entry || undefined],
714
- ["ttl", ttlDays || undefined], ["tags", tags && tags.length ? tags.join(",") : undefined]]);
714
+ ["ttl", ttlDays || undefined], ["tags", tags?.join(",")]]);
715
715
  const res = await bpFetch(url, { method: "PUT", headers, body: formData });
716
716
  const data = await res.json().catch(() => null);
717
717
  if (!res.ok)
@@ -758,20 +758,24 @@ export function createServer(options = {}) {
758
758
  .string()
759
759
  .optional()
760
760
  .describe("Content format. Accepts (case-insensitive on html/markdown): 'html', 'markdown'/'md', or a code language — yaml, json, xml, csv, tsv, log, toml, ini, sql, sh, bat, env, javascript, typescript, tsx, jsx, css, properties, docker, txt. Omit to preserve the stored format."),
761
+ tags: z
762
+ .array(z.string())
763
+ .optional()
764
+ .describe("Optional tags; replace the page's stored tags ([] clears). Omit to keep them."),
761
765
  },
762
- }, async ({ namespace, id, content, ownerToken, format }) => {
766
+ }, async ({ namespace, id, content, ownerToken, format, tags }) => {
763
767
  const body = { content };
764
768
  if (format)
765
769
  body.format = format;
766
770
  const auth = ownerTokenFor(ownerToken);
767
771
  if (!auth.token)
768
772
  return textResult(NO_OWNER_TOKEN, true);
769
- const path = apiPath("html", namespace, id);
770
- const { ok, status, data } = await apiRequest("PUT", path, body, {
773
+ const path = withQuery(apiPath("html", namespace, id), [["tags", tags?.join(",")]]);
774
+ const { ok, status, data, headers: answerHeaders } = await apiRequest("PUT", path, body, {
771
775
  "X-Owner-Token": auth.token,
772
776
  });
773
777
  if (!ok)
774
- return ownerFailure("update HTML", status, data, auth.fromEnv);
778
+ return ownerFailure("update HTML", status, data, auth.fromEnv, answerHeaders);
775
779
  return {
776
780
  content: [
777
781
  {
@@ -828,7 +832,7 @@ export function createServer(options = {}) {
828
832
  content: [
829
833
  {
830
834
  type: "text",
831
- text: `Failed to publish JSON (${res.status}): ${JSON.stringify(data)}`,
835
+ text: `Failed to publish JSON (${res.status}): ${JSON.stringify(data)}${retryAfterText(res.status, res.headers)}`,
832
836
  },
833
837
  ],
834
838
  isError: true,
@@ -907,7 +911,7 @@ export function createServer(options = {}) {
907
911
  });
908
912
  const data = await res.json().catch(() => null);
909
913
  if (!res.ok)
910
- return ownerFailure("update JSON", res.status, data, auth.fromEnv);
914
+ return ownerFailure("update JSON", res.status, data, auth.fromEnv, res.headers);
911
915
  return {
912
916
  content: [
913
917
  {
@@ -952,13 +956,13 @@ export function createServer(options = {}) {
952
956
  headers["X-Owner-Token"] = owner;
953
957
  if (deliveryMode)
954
958
  headers["X-Delivery-Mode"] = deliveryMode;
955
- const { ok, status, data } = await apiRequest("POST", path, body, headers);
959
+ const { ok, status, data, headers: answerHeaders } = await apiRequest("POST", path, body, headers);
956
960
  if (!ok) {
957
961
  return {
958
962
  content: [
959
963
  {
960
964
  type: "text",
961
- text: `Failed to publish KV (${status}): ${JSON.stringify(data)}`,
965
+ text: `Failed to publish KV (${status}): ${JSON.stringify(data)}${retryAfterText(status, answerHeaders)}`,
962
966
  },
963
967
  ],
964
968
  isError: true,
@@ -989,11 +993,11 @@ export function createServer(options = {}) {
989
993
  if (!auth.token)
990
994
  return textResult(NO_OWNER_TOKEN, true);
991
995
  const path = apiPath("kv", namespace, id, key);
992
- const { ok, status, data } = await apiRequest("PUT", path, body, {
996
+ const { ok, status, data, headers: answerHeaders } = await apiRequest("PUT", path, body, {
993
997
  "X-Owner-Token": auth.token,
994
998
  });
995
999
  if (!ok)
996
- return ownerFailure("set KV", status, data, auth.fromEnv);
1000
+ return ownerFailure("set KV", status, data, auth.fromEnv, answerHeaders);
997
1001
  return {
998
1002
  content: [
999
1003
  {
@@ -1150,9 +1154,9 @@ export function createServer(options = {}) {
1150
1154
  if (!auth.token)
1151
1155
  return textResult(NO_OWNER_TOKEN, true);
1152
1156
  const path = apiPath(TYPE_SEGMENTS[type], namespace, id, "visibility");
1153
- const { ok, status, data } = await apiRequest("POST", path, { listed }, { "X-Owner-Token": auth.token });
1157
+ const { ok, status, data, headers: answerHeaders } = await apiRequest("POST", path, { listed }, { "X-Owner-Token": auth.token });
1154
1158
  if (!ok)
1155
- return ownerFailure("set visibility", status, data, auth.fromEnv);
1159
+ return ownerFailure("set visibility", status, data, auth.fromEnv, answerHeaders);
1156
1160
  const result = data;
1157
1161
  return textResult([...credentialAlertLines(result), `LISTED: ${result.listed} · link unchanged`, ...noticeLines(result)].join("\n"));
1158
1162
  });
@@ -1167,9 +1171,9 @@ export function createServer(options = {}) {
1167
1171
  const auth = ownerTokenFor(ownerToken);
1168
1172
  if (!auth.token)
1169
1173
  return textResult(NO_OWNER_TOKEN, true);
1170
- const { ok, status, data } = await apiRequest("POST", "/api/owner-token/bind", { token }, { "X-Owner-Token": auth.token });
1174
+ const { ok, status, data, headers: answerHeaders } = await apiRequest("POST", "/api/owner-token/bind", { token }, { "X-Owner-Token": auth.token });
1171
1175
  if (!ok)
1172
- return textResult(`Failed to bind token (${status}): ${JSON.stringify(data)}`, true);
1176
+ return textResult(`Failed to bind token (${status}): ${JSON.stringify(data)}${retryAfterText(status, answerHeaders)}`, true);
1173
1177
  const result = data;
1174
1178
  return textResult(`BOUND ${maskToken(token)} · ${result.active}/${result.max} active`);
1175
1179
  });
@@ -1186,9 +1190,9 @@ export function createServer(options = {}) {
1186
1190
  const auth = ownerTokenFor(ownerToken);
1187
1191
  if (!auth.token)
1188
1192
  return textResult(NO_OWNER_TOKEN, true);
1189
- const { ok, status, data } = await apiRequest("POST", "/api/owner-token/mint", { label, scope, expiresIn }, { "X-Owner-Token": auth.token });
1193
+ const { ok, status, data, headers: answerHeaders } = await apiRequest("POST", "/api/owner-token/mint", { label, scope, expiresIn }, { "X-Owner-Token": auth.token });
1190
1194
  if (!ok)
1191
- return textResult(`Failed to mint token (${status}): ${JSON.stringify(data)}`, true);
1195
+ return textResult(`Failed to mint token (${status}): ${JSON.stringify(data)}${retryAfterText(status, answerHeaders)}`, true);
1192
1196
  const result = data;
1193
1197
  return textResult([
1194
1198
  `MINTED · ${result.scope} · expires ${result.expiresAt ?? "never"}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brewpage-mcp",
3
- "version": "1.16.4",
3
+ "version": "1.16.5",
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",