brewpage-mcp 1.16.3 → 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 +16 -8
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +38 -27
- package/package.json +1 -1
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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,14 @@ 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
|
+
|
|
537
|
+
## 1.16.4 -- 2026-10-30
|
|
538
|
+
|
|
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.
|
|
540
|
+
|
|
533
541
|
## 1.16.3 -- 2026-10-30
|
|
534
542
|
|
|
535
543
|
- Create `ownerToken` description: the token from the first create keeps publications under one owner; the owner-token banner adds that it goes as `ownerToken` on later publishes and updates; `publish_site` says static HTML, CSS and JS (incl. built bundles) run in the browser, PHP and other server code never run. Gemini context: save `ownerToken` privately, never in chat.
|
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.
|
|
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
|
+
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,18 +54,25 @@ 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. */
|
|
63
|
+
const PUBLISH_TOOL_FOR = {
|
|
64
|
+
"update HTML": "publish_html", "republish site": "publish_site", "update JSON": "publish_json", "set KV": "publish_kv",
|
|
65
|
+
};
|
|
66
|
+
/** Owner-tool failure: the server answer verbatim plus a hint for a wrong-token 403, a missing row or a frozen old publication. */
|
|
63
67
|
function ownerFailure(operation, status, data, fromEnv, headers) {
|
|
64
68
|
const lines = [`Failed to ${operation} (${status}): ${JSON.stringify(data)}${retryAfterText(status, headers)}`];
|
|
65
69
|
const code = data?.code;
|
|
66
70
|
if (status === 403 && (code === undefined || code === "OWNER_AUTHORIZATION_FAILED")) {
|
|
67
71
|
lines.push(`Wrong owner token for this publication. Use ownerToken from create.${fromEnv ? ` Sent: ${OWNER_TOKEN_ENV}.` : ""}`);
|
|
68
72
|
}
|
|
73
|
+
if (status === 404 && PUBLISH_TOOL_FOR[operation]) {
|
|
74
|
+
lines.push(`Use the ${PUBLISH_TOOL_FOR[operation]} tool to publish a new copy.`);
|
|
75
|
+
}
|
|
69
76
|
if (status === 409 && code === OLD_READ_ONLY_CODE) {
|
|
70
77
|
const { createUrl, republishUrl } = data;
|
|
71
78
|
lines.push("Read-only old publication: cannot be updated. " +
|
|
@@ -209,7 +216,7 @@ export function createServer(options = {}) {
|
|
|
209
216
|
const apiFetch = options.fetch ?? fetch;
|
|
210
217
|
const server = new McpServer({
|
|
211
218
|
name: "brewpage-mcp",
|
|
212
|
-
version: "1.16.
|
|
219
|
+
version: "1.16.5",
|
|
213
220
|
});
|
|
214
221
|
/** Caller argument first, then the configured default; `fromEnv` marks the configured one. */
|
|
215
222
|
function ownerTokenFor(argument) {
|
|
@@ -420,9 +427,9 @@ export function createServer(options = {}) {
|
|
|
420
427
|
if (!auth.token)
|
|
421
428
|
return textResult(NO_OWNER_TOKEN, true);
|
|
422
429
|
const path = apiPath(TYPE_SEGMENTS[type], namespace, id, "hosting");
|
|
423
|
-
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 });
|
|
424
431
|
if (!ok)
|
|
425
|
-
return ownerFailure("update hosting", status, data, auth.fromEnv);
|
|
432
|
+
return ownerFailure("update hosting", status, data, auth.fromEnv, answerHeaders);
|
|
426
433
|
return {
|
|
427
434
|
content: [{ type: "text", text: formatUpdateResponse(data) }],
|
|
428
435
|
};
|
|
@@ -443,9 +450,9 @@ export function createServer(options = {}) {
|
|
|
443
450
|
if (!auth.token)
|
|
444
451
|
return textResult(NO_OWNER_TOKEN, true);
|
|
445
452
|
const path = apiPath(TYPE_SEGMENTS[type], namespace, id);
|
|
446
|
-
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 });
|
|
447
454
|
if (!ok)
|
|
448
|
-
return ownerFailure("delete", status, data, auth.fromEnv);
|
|
455
|
+
return ownerFailure("delete", status, data, auth.fromEnv, answerHeaders);
|
|
449
456
|
return {
|
|
450
457
|
content: [
|
|
451
458
|
{
|
|
@@ -670,7 +677,7 @@ export function createServer(options = {}) {
|
|
|
670
677
|
tags: z
|
|
671
678
|
.array(z.string())
|
|
672
679
|
.optional()
|
|
673
|
-
.describe("Optional tags
|
|
680
|
+
.describe("Optional tags; replace the site's stored tags. Omit to keep them."),
|
|
674
681
|
},
|
|
675
682
|
}, async ({ namespace, id, ownerToken, entryContent, files, entry, ttlDays, tags }) => {
|
|
676
683
|
const hasEntry = typeof entryContent === "string";
|
|
@@ -704,7 +711,7 @@ export function createServer(options = {}) {
|
|
|
704
711
|
return textResult(NO_OWNER_TOKEN, true);
|
|
705
712
|
const headers = { "X-Owner-Token": auth.token };
|
|
706
713
|
const url = baseUrl + withQuery(apiPath("sites", namespace, id), [["entry", entry || undefined],
|
|
707
|
-
["ttl", ttlDays || undefined], ["tags", tags
|
|
714
|
+
["ttl", ttlDays || undefined], ["tags", tags?.join(",")]]);
|
|
708
715
|
const res = await bpFetch(url, { method: "PUT", headers, body: formData });
|
|
709
716
|
const data = await res.json().catch(() => null);
|
|
710
717
|
if (!res.ok)
|
|
@@ -751,20 +758,24 @@ export function createServer(options = {}) {
|
|
|
751
758
|
.string()
|
|
752
759
|
.optional()
|
|
753
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."),
|
|
754
765
|
},
|
|
755
|
-
}, async ({ namespace, id, content, ownerToken, format }) => {
|
|
766
|
+
}, async ({ namespace, id, content, ownerToken, format, tags }) => {
|
|
756
767
|
const body = { content };
|
|
757
768
|
if (format)
|
|
758
769
|
body.format = format;
|
|
759
770
|
const auth = ownerTokenFor(ownerToken);
|
|
760
771
|
if (!auth.token)
|
|
761
772
|
return textResult(NO_OWNER_TOKEN, true);
|
|
762
|
-
const path = apiPath("html", namespace, id);
|
|
763
|
-
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, {
|
|
764
775
|
"X-Owner-Token": auth.token,
|
|
765
776
|
});
|
|
766
777
|
if (!ok)
|
|
767
|
-
return ownerFailure("update HTML", status, data, auth.fromEnv);
|
|
778
|
+
return ownerFailure("update HTML", status, data, auth.fromEnv, answerHeaders);
|
|
768
779
|
return {
|
|
769
780
|
content: [
|
|
770
781
|
{
|
|
@@ -821,7 +832,7 @@ export function createServer(options = {}) {
|
|
|
821
832
|
content: [
|
|
822
833
|
{
|
|
823
834
|
type: "text",
|
|
824
|
-
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)}`,
|
|
825
836
|
},
|
|
826
837
|
],
|
|
827
838
|
isError: true,
|
|
@@ -900,7 +911,7 @@ export function createServer(options = {}) {
|
|
|
900
911
|
});
|
|
901
912
|
const data = await res.json().catch(() => null);
|
|
902
913
|
if (!res.ok)
|
|
903
|
-
return ownerFailure("update JSON", res.status, data, auth.fromEnv);
|
|
914
|
+
return ownerFailure("update JSON", res.status, data, auth.fromEnv, res.headers);
|
|
904
915
|
return {
|
|
905
916
|
content: [
|
|
906
917
|
{
|
|
@@ -945,13 +956,13 @@ export function createServer(options = {}) {
|
|
|
945
956
|
headers["X-Owner-Token"] = owner;
|
|
946
957
|
if (deliveryMode)
|
|
947
958
|
headers["X-Delivery-Mode"] = deliveryMode;
|
|
948
|
-
const { ok, status, data } = await apiRequest("POST", path, body, headers);
|
|
959
|
+
const { ok, status, data, headers: answerHeaders } = await apiRequest("POST", path, body, headers);
|
|
949
960
|
if (!ok) {
|
|
950
961
|
return {
|
|
951
962
|
content: [
|
|
952
963
|
{
|
|
953
964
|
type: "text",
|
|
954
|
-
text: `Failed to publish KV (${status}): ${JSON.stringify(data)}`,
|
|
965
|
+
text: `Failed to publish KV (${status}): ${JSON.stringify(data)}${retryAfterText(status, answerHeaders)}`,
|
|
955
966
|
},
|
|
956
967
|
],
|
|
957
968
|
isError: true,
|
|
@@ -982,11 +993,11 @@ export function createServer(options = {}) {
|
|
|
982
993
|
if (!auth.token)
|
|
983
994
|
return textResult(NO_OWNER_TOKEN, true);
|
|
984
995
|
const path = apiPath("kv", namespace, id, key);
|
|
985
|
-
const { ok, status, data } = await apiRequest("PUT", path, body, {
|
|
996
|
+
const { ok, status, data, headers: answerHeaders } = await apiRequest("PUT", path, body, {
|
|
986
997
|
"X-Owner-Token": auth.token,
|
|
987
998
|
});
|
|
988
999
|
if (!ok)
|
|
989
|
-
return ownerFailure("set KV", status, data, auth.fromEnv);
|
|
1000
|
+
return ownerFailure("set KV", status, data, auth.fromEnv, answerHeaders);
|
|
990
1001
|
return {
|
|
991
1002
|
content: [
|
|
992
1003
|
{
|
|
@@ -1143,9 +1154,9 @@ export function createServer(options = {}) {
|
|
|
1143
1154
|
if (!auth.token)
|
|
1144
1155
|
return textResult(NO_OWNER_TOKEN, true);
|
|
1145
1156
|
const path = apiPath(TYPE_SEGMENTS[type], namespace, id, "visibility");
|
|
1146
|
-
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 });
|
|
1147
1158
|
if (!ok)
|
|
1148
|
-
return ownerFailure("set visibility", status, data, auth.fromEnv);
|
|
1159
|
+
return ownerFailure("set visibility", status, data, auth.fromEnv, answerHeaders);
|
|
1149
1160
|
const result = data;
|
|
1150
1161
|
return textResult([...credentialAlertLines(result), `LISTED: ${result.listed} · link unchanged`, ...noticeLines(result)].join("\n"));
|
|
1151
1162
|
});
|
|
@@ -1160,9 +1171,9 @@ export function createServer(options = {}) {
|
|
|
1160
1171
|
const auth = ownerTokenFor(ownerToken);
|
|
1161
1172
|
if (!auth.token)
|
|
1162
1173
|
return textResult(NO_OWNER_TOKEN, true);
|
|
1163
|
-
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 });
|
|
1164
1175
|
if (!ok)
|
|
1165
|
-
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);
|
|
1166
1177
|
const result = data;
|
|
1167
1178
|
return textResult(`BOUND ${maskToken(token)} · ${result.active}/${result.max} active`);
|
|
1168
1179
|
});
|
|
@@ -1179,9 +1190,9 @@ export function createServer(options = {}) {
|
|
|
1179
1190
|
const auth = ownerTokenFor(ownerToken);
|
|
1180
1191
|
if (!auth.token)
|
|
1181
1192
|
return textResult(NO_OWNER_TOKEN, true);
|
|
1182
|
-
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 });
|
|
1183
1194
|
if (!ok)
|
|
1184
|
-
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);
|
|
1185
1196
|
const result = data;
|
|
1186
1197
|
return textResult([
|
|
1187
1198
|
`MINTED · ${result.scope} · expires ${result.expiresAt ?? "never"}`,
|
package/package.json
CHANGED