brewpage-mcp 1.5.0 → 1.6.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.
- package/README.md +12 -6
- package/dist/index.js +57 -21
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -92,7 +92,7 @@ Fourteen tools are available, grouped by resource: **HTML**, **JSON**, **KV**, *
|
|
|
92
92
|
|
|
93
93
|
Publish HTML or Markdown content to BrewPage. Returns a public URL and owner token.
|
|
94
94
|
|
|
95
|
-
Parameters: `content` (string), `format` (`HTML` | `MARKDOWN`, default `HTML`), `namespace` (optional
|
|
95
|
+
Parameters: `content` (string), `format` (`HTML` | `MARKDOWN`, default `HTML`), `namespace` (optional -- **omit to keep the page private/unlisted**: reachable only by its link, not in the gallery, not indexed by search engines; pass `public` only when the user explicitly wants it gallery-listed and search-indexed), `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).
|
|
96
96
|
|
|
97
97
|
Example prompt that invokes this tool:
|
|
98
98
|
|
|
@@ -142,7 +142,7 @@ get_page(namespace="public", id="aBcDeFgHiJ")
|
|
|
142
142
|
|
|
143
143
|
Upload a file to BrewPage by fetching it from a URL. Returns a public URL and owner token. Supports images, PDFs, video, audio, code files, and archives.
|
|
144
144
|
|
|
145
|
-
Parameters: `url` (string, the source URL to fetch), `namespace` (optional), `filename` (optional custom filename).
|
|
145
|
+
Parameters: `url` (string, the source URL to fetch), `namespace` (optional -- omit to keep the file private/unlisted: reachable only by its link, not in the gallery, not search-indexed; pass `public` only to list and index it), `filename` (optional custom filename).
|
|
146
146
|
|
|
147
147
|
Example prompt:
|
|
148
148
|
|
|
@@ -160,7 +160,7 @@ publish_file(url="https://example.com/diagram.png")
|
|
|
160
160
|
|
|
161
161
|
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.
|
|
162
162
|
|
|
163
|
-
Parameters: `entryContent` (string, mutually exclusive with `files`), `files` (array of `{path, content}`, mutually exclusive with `entryContent`), `entry` (optional entry file path, default `index.html`), `namespace` (optional), `password` (optional), `ttlDays` (1--30, default 15), `ownerToken` (optional, groups site under an existing owner).
|
|
163
|
+
Parameters: `entryContent` (string, mutually exclusive with `files`), `files` (array of `{path, content}`, mutually exclusive with `entryContent`), `entry` (optional entry file path, default `index.html`), `namespace` (optional -- omit to keep the site private/unlisted: reachable only by its link, not in the gallery, not search-indexed; pass `public` only to list and index it), `password` (optional), `ttlDays` (1--30, default 15), `ownerToken` (optional, groups site under an existing owner).
|
|
164
164
|
|
|
165
165
|
Example prompt:
|
|
166
166
|
|
|
@@ -178,7 +178,7 @@ publish_site(files=[{"path": "index.html", "content": "..."}, {"path": "style.cs
|
|
|
178
178
|
|
|
179
179
|
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.
|
|
180
180
|
|
|
181
|
-
Parameters: `json` (string or object, the JSON payload), `namespace` (optional
|
|
181
|
+
Parameters: `json` (string or object, the JSON payload), `namespace` (optional -- omit to keep the document private/unlisted: reachable only by its link, not in the gallery, not search-indexed; pass `public` only to list and index it), `password` (optional), `ttlDays` (optional, 1--30, default 15), `tags` (optional `string[]`).
|
|
182
182
|
|
|
183
183
|
Example prompt:
|
|
184
184
|
|
|
@@ -228,7 +228,7 @@ update_json(namespace="public", id="aBcDeFgHiJ", json={"mode": "dark", "version"
|
|
|
228
228
|
|
|
229
229
|
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.
|
|
230
230
|
|
|
231
|
-
Parameters: `key` (string), `value` (string), `namespace` (optional
|
|
231
|
+
Parameters: `key` (string), `value` (string), `namespace` (optional -- omit to keep the KV store private/unlisted: reachable only by its link, not in the gallery, not search-indexed; pass `public` only to list and index it), `password` (optional), `ttlDays` (optional, 1--30, default 15), `tags` (optional `string[]`).
|
|
232
232
|
|
|
233
233
|
Example prompt:
|
|
234
234
|
|
|
@@ -328,7 +328,7 @@ get_stats()
|
|
|
328
328
|
|--------------------|------|-------|
|
|
329
329
|
| "Publish this HTML so I can share it" | `publish_html` | `format=HTML`; save `ownerToken` |
|
|
330
330
|
| "Share these meeting notes as a link" | `publish_html` | `format=MARKDOWN`; readable rendered output |
|
|
331
|
-
| "Host this AI-generated artifact" | `publish_html` | `namespace` optional;
|
|
331
|
+
| "Host this AI-generated artifact" | `publish_html` | `namespace` optional; **private/unlisted by default** -- pass `public` to gallery-list + index |
|
|
332
332
|
| "Upload this image / PDF / video" | `publish_file` | Fetches from URL; inline preview on short URL |
|
|
333
333
|
| "Deploy this static site" | `publish_site` | Pass `files` array or `entryContent` |
|
|
334
334
|
| "Fix a typo in the page I shared -- same link" | `update_html` | Requires `ownerToken`; URL stays the same |
|
|
@@ -371,6 +371,12 @@ Every publish response includes an **owner token** -- the only credential that a
|
|
|
371
371
|
|
|
372
372
|
## Changelog
|
|
373
373
|
|
|
374
|
+
## 1.6.0 -- 2026-06-05
|
|
375
|
+
|
|
376
|
+
- **Private-by-default publishing.** When `namespace` is omitted, all five publish tools (`publish_html`, `publish_file`, `publish_site`, `publish_json`, `publish_kv`) now send an explicit unlisted namespace (`priv-<random>`) instead of letting the backend default to `public`. Omitted namespace = unlisted (reachable only by its link, not gallery-listed, not search-indexed); pass `namespace: "public"` explicitly to list + index. Non-breaking: `namespace` stays optional.
|
|
377
|
+
- Publish responses for non-public namespaces now append an "Unlisted link" notice explaining how to make content public.
|
|
378
|
+
- Tool descriptions and README rewritten to reflect the private-by-default model.
|
|
379
|
+
|
|
374
380
|
## 1.5.0 -- 2026-05-21
|
|
375
381
|
|
|
376
382
|
- Add 8 new MCP tools: `update_html`, `publish_json`, `get_json`, `update_json`, `publish_kv`, `set_kv`, `get_kv`, `search_gallery`.
|
package/dist/index.js
CHANGED
|
@@ -5,8 +5,36 @@ 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.
|
|
8
|
+
version: "1.6.0",
|
|
9
9
|
});
|
|
10
|
+
const PUBLIC_NAMESPACE = "public";
|
|
11
|
+
/**
|
|
12
|
+
* Generate a short, unlisted private namespace slug used when the caller omits
|
|
13
|
+
* `namespace`. Sending an explicit non-public ns prevents the backend from
|
|
14
|
+
* applying its `public` default, which would gallery-list + index the content.
|
|
15
|
+
* Format: `priv-<6 lowercase-alphanumeric chars>`.
|
|
16
|
+
*/
|
|
17
|
+
function defaultPrivateNamespace() {
|
|
18
|
+
const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
19
|
+
let slug = "";
|
|
20
|
+
for (let i = 0; i < 6; i++) {
|
|
21
|
+
slug += alphabet[Math.floor(Math.random() * alphabet.length)];
|
|
22
|
+
}
|
|
23
|
+
return `priv-${slug}`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the effective namespace actually sent to the API: the caller's value
|
|
27
|
+
* verbatim when provided (including `public`), otherwise a generated private slug.
|
|
28
|
+
*/
|
|
29
|
+
function resolveNamespace(namespace) {
|
|
30
|
+
return namespace && namespace.length > 0 ? namespace : defaultPrivateNamespace();
|
|
31
|
+
}
|
|
32
|
+
const UNLISTED_NOTICE = "Unlisted link — anyone who has it can open it, but it's not in the gallery or search. " +
|
|
33
|
+
"Publish with namespace `public` to list it in the gallery and have search engines index it.";
|
|
34
|
+
const NAMESPACE_DESCRIPTION = "Optional. Omit to keep the content PRIVATE/unlisted (reachable only by its link — " +
|
|
35
|
+
"not listed in the gallery and not indexed by search engines). Pass a custom namespace " +
|
|
36
|
+
"to group private content, or pass `public` ONLY when the user explicitly wants the page " +
|
|
37
|
+
"listed in the brewpage.app gallery and indexed by search engines.";
|
|
10
38
|
async function apiRequest(method, path, body, headers) {
|
|
11
39
|
const url = `${BASE_URL}${path}`;
|
|
12
40
|
const res = await fetch(url, {
|
|
@@ -20,7 +48,7 @@ async function apiRequest(method, path, body, headers) {
|
|
|
20
48
|
const data = await res.json().catch(() => null);
|
|
21
49
|
return { ok: res.ok, status: res.status, data };
|
|
22
50
|
}
|
|
23
|
-
function formatPublishResponse(data) {
|
|
51
|
+
function formatPublishResponse(data, effectiveNamespace) {
|
|
24
52
|
const lines = [
|
|
25
53
|
`Published successfully!`,
|
|
26
54
|
``,
|
|
@@ -32,6 +60,10 @@ function formatPublishResponse(data) {
|
|
|
32
60
|
if (data.expiresAt)
|
|
33
61
|
lines.push(`Expires: ${data.expiresAt}`);
|
|
34
62
|
lines.push(``, `===================================`, `OWNER TOKEN: ${data.ownerToken}`, `===================================`, ``, `IMPORTANT: Save your owner token! You need it to update or delete this resource later. It cannot be recovered if lost.`);
|
|
63
|
+
const ns = (effectiveNamespace ?? data.namespace) || "";
|
|
64
|
+
if (ns !== PUBLIC_NAMESPACE) {
|
|
65
|
+
lines.push(``, UNLISTED_NOTICE);
|
|
66
|
+
}
|
|
35
67
|
return lines.join("\n");
|
|
36
68
|
}
|
|
37
69
|
function formatUpdateResponse(data) {
|
|
@@ -58,7 +90,7 @@ server.tool("publish_html", "Publish HTML or Markdown content to BrewPage. Retur
|
|
|
58
90
|
namespace: z
|
|
59
91
|
.string()
|
|
60
92
|
.optional()
|
|
61
|
-
.describe(
|
|
93
|
+
.describe(NAMESPACE_DESCRIPTION),
|
|
62
94
|
password: z
|
|
63
95
|
.string()
|
|
64
96
|
.optional()
|
|
@@ -80,9 +112,9 @@ server.tool("publish_html", "Publish HTML or Markdown content to BrewPage. Retur
|
|
|
80
112
|
.optional()
|
|
81
113
|
.describe("Add a thin top toolbar (filename + Download button + theme toggle) on the served page. Default: hidden."),
|
|
82
114
|
}, async ({ content, format, namespace, password, ttlDays, filename, showTopBar }) => {
|
|
115
|
+
const effectiveNamespace = resolveNamespace(namespace);
|
|
83
116
|
const body = { content, format };
|
|
84
|
-
|
|
85
|
-
body.namespace = namespace;
|
|
117
|
+
body.namespace = effectiveNamespace;
|
|
86
118
|
if (password)
|
|
87
119
|
body.password = password;
|
|
88
120
|
if (ttlDays)
|
|
@@ -107,7 +139,7 @@ server.tool("publish_html", "Publish HTML or Markdown content to BrewPage. Retur
|
|
|
107
139
|
content: [
|
|
108
140
|
{
|
|
109
141
|
type: "text",
|
|
110
|
-
text: formatPublishResponse(data),
|
|
142
|
+
text: formatPublishResponse(data, effectiveNamespace),
|
|
111
143
|
},
|
|
112
144
|
],
|
|
113
145
|
};
|
|
@@ -117,9 +149,10 @@ server.tool("publish_file", "Upload a file to BrewPage via URL. Returns a public
|
|
|
117
149
|
namespace: z
|
|
118
150
|
.string()
|
|
119
151
|
.optional()
|
|
120
|
-
.describe(
|
|
152
|
+
.describe(NAMESPACE_DESCRIPTION),
|
|
121
153
|
filename: z.string().optional().describe("Custom filename (optional)"),
|
|
122
154
|
}, async ({ url: fileUrl, namespace, filename }) => {
|
|
155
|
+
const effectiveNamespace = resolveNamespace(namespace);
|
|
123
156
|
const fileRes = await fetch(fileUrl);
|
|
124
157
|
if (!fileRes.ok) {
|
|
125
158
|
return {
|
|
@@ -135,8 +168,7 @@ server.tool("publish_file", "Upload a file to BrewPage via URL. Returns a public
|
|
|
135
168
|
const blob = await fileRes.blob();
|
|
136
169
|
const formData = new FormData();
|
|
137
170
|
formData.append("file", blob, filename || fileUrl.split("/").pop() || "file");
|
|
138
|
-
|
|
139
|
-
formData.append("namespace", namespace);
|
|
171
|
+
formData.append("namespace", effectiveNamespace);
|
|
140
172
|
const res = await fetch(`${BASE_URL}/api/files`, {
|
|
141
173
|
method: "POST",
|
|
142
174
|
body: formData,
|
|
@@ -157,7 +189,7 @@ server.tool("publish_file", "Upload a file to BrewPage via URL. Returns a public
|
|
|
157
189
|
content: [
|
|
158
190
|
{
|
|
159
191
|
type: "text",
|
|
160
|
-
text: formatPublishResponse(data),
|
|
192
|
+
text: formatPublishResponse(data, effectiveNamespace),
|
|
161
193
|
},
|
|
162
194
|
],
|
|
163
195
|
};
|
|
@@ -280,7 +312,7 @@ server.tool("publish_site", "Publish an HTML site to BrewPage. Single-page: pass
|
|
|
280
312
|
namespace: z
|
|
281
313
|
.string()
|
|
282
314
|
.optional()
|
|
283
|
-
.describe(
|
|
315
|
+
.describe(NAMESPACE_DESCRIPTION),
|
|
284
316
|
password: z
|
|
285
317
|
.string()
|
|
286
318
|
.optional()
|
|
@@ -327,11 +359,11 @@ server.tool("publish_site", "Publish an HTML site to BrewPage. Single-page: pass
|
|
|
327
359
|
headers["X-Password"] = password;
|
|
328
360
|
if (ownerToken)
|
|
329
361
|
headers["X-Owner-Token"] = ownerToken;
|
|
362
|
+
const effectiveNamespace = resolveNamespace(namespace);
|
|
330
363
|
const qsParams = [];
|
|
331
364
|
if (entry)
|
|
332
365
|
qsParams.push(`entry=${encodeURIComponent(entry)}`);
|
|
333
|
-
|
|
334
|
-
qsParams.push(`ns=${encodeURIComponent(namespace)}`);
|
|
366
|
+
qsParams.push(`ns=${encodeURIComponent(effectiveNamespace)}`);
|
|
335
367
|
if (ttlDays)
|
|
336
368
|
qsParams.push(`ttl=${encodeURIComponent(String(ttlDays))}`);
|
|
337
369
|
const qs = qsParams.length ? `?${qsParams.join("&")}` : "";
|
|
@@ -368,6 +400,10 @@ server.tool("publish_site", "Publish an HTML site to BrewPage. Single-page: pass
|
|
|
368
400
|
if (site.expiresAt)
|
|
369
401
|
lines.push(`Expires: ${site.expiresAt}`);
|
|
370
402
|
lines.push(``, `===================================`, `OWNER TOKEN: ${site.ownerToken}`, `===================================`, ``, `IMPORTANT: Save your owner token! You need it to view info or delete this site. It cannot be recovered if lost.`);
|
|
403
|
+
const ns = (site.namespace || effectiveNamespace) || "";
|
|
404
|
+
if (ns !== PUBLIC_NAMESPACE) {
|
|
405
|
+
lines.push(``, UNLISTED_NOTICE);
|
|
406
|
+
}
|
|
371
407
|
return {
|
|
372
408
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
373
409
|
};
|
|
@@ -418,7 +454,7 @@ server.tool("publish_json", "Publish a JSON document to BrewPage. Accepts a JSON
|
|
|
418
454
|
namespace: z
|
|
419
455
|
.string()
|
|
420
456
|
.optional()
|
|
421
|
-
.describe(
|
|
457
|
+
.describe(NAMESPACE_DESCRIPTION),
|
|
422
458
|
password: z
|
|
423
459
|
.string()
|
|
424
460
|
.optional()
|
|
@@ -440,9 +476,9 @@ server.tool("publish_json", "Publish a JSON document to BrewPage. Accepts a JSON
|
|
|
440
476
|
.describe("Existing owner token to group this document under the same owner as previous content"),
|
|
441
477
|
}, async ({ json, namespace, password, ttlDays, tags, ownerToken }) => {
|
|
442
478
|
const jsonString = typeof json === "string" ? json : JSON.stringify(json);
|
|
479
|
+
const effectiveNamespace = resolveNamespace(namespace);
|
|
443
480
|
const qsParams = [];
|
|
444
|
-
|
|
445
|
-
qsParams.push(`ns=${encodeURIComponent(namespace)}`);
|
|
481
|
+
qsParams.push(`ns=${encodeURIComponent(effectiveNamespace)}`);
|
|
446
482
|
if (ttlDays !== undefined)
|
|
447
483
|
qsParams.push(`ttl=${encodeURIComponent(String(ttlDays))}`);
|
|
448
484
|
if (tags && tags.length) {
|
|
@@ -476,7 +512,7 @@ server.tool("publish_json", "Publish a JSON document to BrewPage. Accepts a JSON
|
|
|
476
512
|
content: [
|
|
477
513
|
{
|
|
478
514
|
type: "text",
|
|
479
|
-
text: formatPublishResponse(data),
|
|
515
|
+
text: formatPublishResponse(data, effectiveNamespace),
|
|
480
516
|
},
|
|
481
517
|
],
|
|
482
518
|
};
|
|
@@ -561,7 +597,7 @@ server.tool("publish_kv", "Publish a key/value entry to BrewPage. Creates a KV s
|
|
|
561
597
|
namespace: z
|
|
562
598
|
.string()
|
|
563
599
|
.optional()
|
|
564
|
-
.describe(
|
|
600
|
+
.describe(NAMESPACE_DESCRIPTION),
|
|
565
601
|
password: z
|
|
566
602
|
.string()
|
|
567
603
|
.optional()
|
|
@@ -583,9 +619,9 @@ server.tool("publish_kv", "Publish a key/value entry to BrewPage. Creates a KV s
|
|
|
583
619
|
.describe("Existing owner token to group this KV store under the same owner as previous content"),
|
|
584
620
|
}, async ({ key, value, namespace, password, ttlDays, tags, ownerToken }) => {
|
|
585
621
|
const body = { key, value };
|
|
622
|
+
const effectiveNamespace = resolveNamespace(namespace);
|
|
586
623
|
const qsParams = [];
|
|
587
|
-
|
|
588
|
-
qsParams.push(`ns=${encodeURIComponent(namespace)}`);
|
|
624
|
+
qsParams.push(`ns=${encodeURIComponent(effectiveNamespace)}`);
|
|
589
625
|
if (ttlDays !== undefined)
|
|
590
626
|
qsParams.push(`ttl=${encodeURIComponent(String(ttlDays))}`);
|
|
591
627
|
if (tags && tags.length) {
|
|
@@ -614,7 +650,7 @@ server.tool("publish_kv", "Publish a key/value entry to BrewPage. Creates a KV s
|
|
|
614
650
|
content: [
|
|
615
651
|
{
|
|
616
652
|
type: "text",
|
|
617
|
-
text: formatPublishResponse(data),
|
|
653
|
+
text: formatPublishResponse(data, effectiveNamespace),
|
|
618
654
|
},
|
|
619
655
|
],
|
|
620
656
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brewpage-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"mcpName": "io.github.kochetkov-ma/brewpage-mcp",
|
|
5
5
|
"description": "MCP server for BrewPage — publish and manage HTML, KV, JSON, and file content from AI assistants",
|
|
6
6
|
"type": "module",
|
|
@@ -15,12 +15,13 @@
|
|
|
15
15
|
"LICENSE"
|
|
16
16
|
],
|
|
17
17
|
"engines": {
|
|
18
|
-
"node": ">=
|
|
18
|
+
"node": ">=20"
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
21
|
"build": "tsc",
|
|
22
22
|
"start": "node dist/index.js",
|
|
23
23
|
"dev": "tsx src/index.ts",
|
|
24
|
+
"test": "npm run build && node --import tsx --test test/smoke.test.ts",
|
|
24
25
|
"prepublishOnly": "npm run build"
|
|
25
26
|
},
|
|
26
27
|
"keywords": [
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
"url": "https://github.com/kochetkov-ma/brewpage-openapi/issues"
|
|
53
54
|
},
|
|
54
55
|
"dependencies": {
|
|
55
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
56
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
56
57
|
"zod": "^3.24.0"
|
|
57
58
|
},
|
|
58
59
|
"devDependencies": {
|