brewpage-mcp 1.4.0 → 1.5.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 +305 -29
- package/dist/index.js +396 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
MCP server for [BrewPage](https://brewpage.app) -- publish and manage HTML, KV, JSON, and file content directly from AI assistants.
|
|
7
7
|
|
|
8
|
+
## What is BrewPage
|
|
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 a stable HTTPS short URL -- 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 (keeping the same URL), delete the resource, or authenticate list operations across sessions. `brewpage-mcp` exposes this API as fourteen 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
|
+
|
|
8
12
|
## Quick Start
|
|
9
13
|
|
|
10
14
|
```bash
|
|
@@ -43,6 +47,34 @@ Add to `~/.claude/settings.json`:
|
|
|
43
47
|
}
|
|
44
48
|
```
|
|
45
49
|
|
|
50
|
+
### Cursor
|
|
51
|
+
|
|
52
|
+
Open **Settings > MCP** and add:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"mcpServers": {
|
|
57
|
+
"brewpage": {
|
|
58
|
+
"command": "npx",
|
|
59
|
+
"args": ["-y", "brewpage-mcp"]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Cline (VS Code extension)
|
|
66
|
+
|
|
67
|
+
Open the Cline MCP settings panel and add:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"brewpage": {
|
|
72
|
+
"command": "npx",
|
|
73
|
+
"args": ["-y", "brewpage-mcp"]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
46
78
|
### Global Install
|
|
47
79
|
|
|
48
80
|
```bash
|
|
@@ -52,27 +84,273 @@ brewpage-mcp
|
|
|
52
84
|
|
|
53
85
|
## Tools
|
|
54
86
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
87
|
+
Fourteen tools are available, grouped by resource: **HTML**, **JSON**, **KV**, **Files**, **Sites**, and **Discovery**. All write operations return an owner token; keep it to modify or delete the resource later. Any update (`PUT`) operation requires the original `ownerToken` -- it is the backend's authorization gate. `password` is optional on reads/creates; if set, it is sent as the `X-Password` header.
|
|
88
|
+
|
|
89
|
+
### HTML
|
|
90
|
+
|
|
91
|
+
### `publish_html`
|
|
92
|
+
|
|
93
|
+
Publish HTML or Markdown content to BrewPage. Returns a public URL and owner token.
|
|
94
|
+
|
|
95
|
+
Parameters: `content` (string), `format` (`HTML` | `MARKDOWN`, default `HTML`), `namespace` (optional, defaults to `public`), `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
|
+
|
|
97
|
+
Example prompt that invokes this tool:
|
|
98
|
+
|
|
99
|
+
> "Save this HTML report so I can share the link with my team."
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
publish_html(content="<h1>Report</h1>...", format="HTML", ttlDays=15)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
### `update_html`
|
|
108
|
+
|
|
109
|
+
Update an existing HTML or Markdown page in place, preserving its short URL. Requires the original `ownerToken` returned at creation.
|
|
110
|
+
|
|
111
|
+
Parameters: `namespace` (string), `id` (string), `content` (string, the new body), `ownerToken` (string), `format` (optional `HTML` | `MARKDOWN` | `code`, defaults to `HTML`).
|
|
112
|
+
|
|
113
|
+
Example prompt:
|
|
114
|
+
|
|
115
|
+
> "Fix the typo in the report I published earlier -- same link."
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
update_html(namespace="public", id="aBcDeFgHiJ", content="<h1>Updated</h1>...", ownerToken="tok_...")
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
### `get_page`
|
|
124
|
+
|
|
125
|
+
Fetch the content of a published BrewPage HTML page by namespace and ID.
|
|
126
|
+
|
|
127
|
+
Parameters: `namespace` (string), `id` (string), `password` (optional, if the page is password-protected).
|
|
128
|
+
|
|
129
|
+
Example prompt:
|
|
130
|
+
|
|
131
|
+
> "Retrieve the content of my published page so I can continue editing it."
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
get_page(namespace="public", id="aBcDeFgHiJ")
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
### Files
|
|
140
|
+
|
|
141
|
+
### `publish_file`
|
|
142
|
+
|
|
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
|
+
|
|
145
|
+
Parameters: `url` (string, the source URL to fetch), `namespace` (optional), `filename` (optional custom filename).
|
|
146
|
+
|
|
147
|
+
Example prompt:
|
|
148
|
+
|
|
149
|
+
> "Upload this PNG and give me a shareable link."
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
publish_file(url="https://example.com/diagram.png")
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
### Sites
|
|
158
|
+
|
|
159
|
+
### `publish_site`
|
|
160
|
+
|
|
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
|
+
|
|
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).
|
|
164
|
+
|
|
165
|
+
Example prompt:
|
|
166
|
+
|
|
167
|
+
> "Deploy this static site with index.html and style.css."
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
publish_site(files=[{"path": "index.html", "content": "..."}, {"path": "style.css", "content": "..."}])
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
### JSON
|
|
176
|
+
|
|
177
|
+
### `publish_json`
|
|
178
|
+
|
|
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
|
+
|
|
181
|
+
Parameters: `json` (string or object, the JSON payload), `namespace` (optional, defaults to `public`), `password` (optional), `ttlDays` (optional, 1--30, default 15), `tags` (optional `string[]`).
|
|
182
|
+
|
|
183
|
+
Example prompt:
|
|
184
|
+
|
|
185
|
+
> "Save this config blob as a JSON document I can fetch later."
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
publish_json(json={"mode": "dark", "version": 3}, ttlDays=15)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
62
192
|
|
|
63
|
-
|
|
193
|
+
### `get_json`
|
|
64
194
|
|
|
65
|
-
|
|
195
|
+
Fetch a published JSON document by namespace and ID.
|
|
66
196
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
197
|
+
Parameters: `namespace` (string), `id` (string), `password` (optional, sent as `X-Password` if set).
|
|
198
|
+
|
|
199
|
+
Example prompt:
|
|
200
|
+
|
|
201
|
+
> "Read back the JSON document I just stored."
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
get_json(namespace="public", id="aBcDeFgHiJ")
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
### `update_json`
|
|
210
|
+
|
|
211
|
+
Update an existing JSON document in place, preserving its short URL. Requires the original `ownerToken`.
|
|
212
|
+
|
|
213
|
+
Parameters: `namespace` (string), `id` (string), `json` (string or object, the new payload), `ownerToken` (string).
|
|
214
|
+
|
|
215
|
+
Example prompt:
|
|
216
|
+
|
|
217
|
+
> "Bump the version field in that JSON doc to 4."
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
update_json(namespace="public", id="aBcDeFgHiJ", json={"mode": "dark", "version": 4}, ownerToken="tok_...")
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
### KV
|
|
226
|
+
|
|
227
|
+
### `publish_kv`
|
|
228
|
+
|
|
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
|
+
|
|
231
|
+
Parameters: `key` (string), `value` (string), `namespace` (optional, defaults to `public`), `password` (optional), `ttlDays` (optional, 1--30, default 15), `tags` (optional `string[]`).
|
|
232
|
+
|
|
233
|
+
Example prompt:
|
|
234
|
+
|
|
235
|
+
> "Store the API key under the label 'staging-token' so I can reuse it next session."
|
|
236
|
+
|
|
237
|
+
```
|
|
238
|
+
publish_kv(key="staging-token", value="abc123")
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
### `set_kv`
|
|
244
|
+
|
|
245
|
+
Set or replace the value at an existing `(namespace, id, key)` slot. Requires the original `ownerToken`.
|
|
246
|
+
|
|
247
|
+
Parameters: `namespace` (string), `id` (string), `key` (string), `value` (string), `ownerToken` (string).
|
|
248
|
+
|
|
249
|
+
Example prompt:
|
|
250
|
+
|
|
251
|
+
> "Update the staging-token value to the new secret."
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
set_kv(namespace="public", id="aBcDeFgHiJ", key="staging-token", value="def456", ownerToken="tok_...")
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
### `get_kv`
|
|
260
|
+
|
|
261
|
+
Fetch the value stored at `(namespace, id, key)`.
|
|
262
|
+
|
|
263
|
+
Parameters: `namespace` (string), `id` (string), `key` (string), `password` (optional, sent as `X-Password` if set).
|
|
264
|
+
|
|
265
|
+
Example prompt:
|
|
266
|
+
|
|
267
|
+
> "What did I store under staging-token?"
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
get_kv(namespace="public", id="aBcDeFgHiJ", key="staging-token")
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
### Discovery
|
|
276
|
+
|
|
277
|
+
### `search_gallery`
|
|
278
|
+
|
|
279
|
+
Browse the public gallery of BrewPage pages, or list resources you own. Returns a paged result with metadata (id, title, view count, created date). All parameters are optional; with no parameters this returns the most recent public pages.
|
|
280
|
+
|
|
281
|
+
Parameters: `q` (optional search query), `page` (optional, 0-indexed), `size` (optional page size), `sort` (optional `date` | `views`), `mine` (optional boolean -- list only resources owned by `ownerToken`), `ownerToken` (required only when `mine=true`).
|
|
282
|
+
|
|
283
|
+
Example prompt:
|
|
284
|
+
|
|
285
|
+
> "What did I publish last week?"
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
search_gallery(mine=true, ownerToken="tok_...", sort="date")
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
### Management
|
|
294
|
+
|
|
295
|
+
### `delete_resource`
|
|
296
|
+
|
|
297
|
+
Delete a BrewPage resource (HTML page, KV store, JSON collection, or file) using the owner token received at creation.
|
|
298
|
+
|
|
299
|
+
Parameters: `type` (`html` | `kv` | `json` | `file`), `namespace` (string), `id` (string), `ownerToken` (string).
|
|
300
|
+
|
|
301
|
+
Example prompt:
|
|
302
|
+
|
|
303
|
+
> "Delete the page I just published -- namespace public, id aBcDeFgHiJ."
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
delete_resource(type="html", namespace="public", id="aBcDeFgHiJ", ownerToken="tok_...")
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
### `get_stats`
|
|
312
|
+
|
|
313
|
+
Get platform-wide BrewPage usage statistics (page count, file count, storage, daily totals). Supports an optional IANA timezone for the "today" boundary.
|
|
314
|
+
|
|
315
|
+
Parameters: `tz` (optional IANA timezone string, e.g. `Europe/Lisbon`, defaults to UTC).
|
|
316
|
+
|
|
317
|
+
Example prompt:
|
|
318
|
+
|
|
319
|
+
> "How many pages are currently hosted on BrewPage?"
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
get_stats()
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Common LLM Query to BrewPage Tool Mapping
|
|
326
|
+
|
|
327
|
+
| LLM query / intent | Tool | Notes |
|
|
328
|
+
|--------------------|------|-------|
|
|
329
|
+
| "Publish this HTML so I can share it" | `publish_html` | `format=HTML`; save `ownerToken` |
|
|
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; public by default |
|
|
332
|
+
| "Upload this image / PDF / video" | `publish_file` | Fetches from URL; inline preview on short URL |
|
|
333
|
+
| "Deploy this static site" | `publish_site` | Pass `files` array or `entryContent` |
|
|
334
|
+
| "Fix a typo in the page I shared -- same link" | `update_html` | Requires `ownerToken`; URL stays the same |
|
|
335
|
+
| "Remove the page I published" | `delete_resource` | Requires `ownerToken` from creation |
|
|
336
|
+
| "Read back the page I published" | `get_page` | Returns raw content for editing |
|
|
337
|
+
| "How many pages are on BrewPage?" | `get_stats` | Returns platform totals |
|
|
338
|
+
| "Store JSON state between turns" | `publish_json` | Returns short URL + owner token |
|
|
339
|
+
| "Read that JSON document back" | `get_json` | By `namespace` + `id` |
|
|
340
|
+
| "Update the JSON I stored" | `update_json` | Requires `ownerToken`; preserves URL |
|
|
341
|
+
| "Save a value under a label / key" | `publish_kv` | Create a new KV slot |
|
|
342
|
+
| "Change the KV value I stored" | `set_kv` | Requires `ownerToken` |
|
|
343
|
+
| "What did I store under that key?" | `get_kv` | By `namespace` + `id` + `key` |
|
|
344
|
+
| "List my published pages" / "What did I publish?" | `search_gallery` | Set `mine=true` + `ownerToken` |
|
|
345
|
+
| "Browse the public BrewPage gallery" | `search_gallery` | Optional `q`, `sort=date\|views` |
|
|
72
346
|
|
|
73
347
|
## Owner Token
|
|
74
348
|
|
|
75
|
-
Every publish response includes an **owner token** -- the only
|
|
349
|
+
Every publish response includes an **owner token** -- the only credential that allows updating or deleting your content. **Save it. It cannot be recovered.**
|
|
350
|
+
|
|
351
|
+
- Reuse it via `X-Owner-Token` on subsequent creates to group resources under one owner.
|
|
352
|
+
- Pass it to `delete_resource` to remove content.
|
|
353
|
+
- Pass it to `get_page` (not needed for reads, but required for listing your own resources via the REST API).
|
|
76
354
|
|
|
77
355
|
## Configuration
|
|
78
356
|
|
|
@@ -80,30 +358,28 @@ Every publish response includes an **owner token** -- the only way to update or
|
|
|
80
358
|
|----------|---------|-------------|
|
|
81
359
|
| `BREWPAGE_URL` | `https://brewpage.app` | API base URL |
|
|
82
360
|
|
|
83
|
-
## What is BrewPage?
|
|
84
|
-
|
|
85
|
-
[BrewPage](https://brewpage.app) is a free content hosting platform. No signup required.
|
|
86
|
-
|
|
87
|
-
- **HTML/Markdown pages** with instant short URLs
|
|
88
|
-
- **Key-value store** (1,000 keys per store)
|
|
89
|
-
- **JSON documents** (10,000 per collection)
|
|
90
|
-
- **File hosting** (5 MB per file)
|
|
91
|
-
|
|
92
|
-
Both [brewpage.app](https://brewpage.app) and [brewdata.app](https://brewdata.app) serve the same API.
|
|
93
|
-
|
|
94
361
|
## Links
|
|
95
362
|
|
|
96
363
|
- [brewpage.app](https://brewpage.app) -- Live platform
|
|
364
|
+
- [brewpage.app/llms.txt](https://brewpage.app/llms.txt) -- LLM context file
|
|
365
|
+
- [brewpage.app/llms-full.txt](https://brewpage.app/llms-full.txt) -- Full LLM reference
|
|
97
366
|
- [API Documentation](https://kochetkov-ma.github.io/brewpage-openapi/) -- Interactive docs
|
|
98
367
|
- [API Reference](https://kochetkov-ma.github.io/brewpage-openapi/api-reference/) -- Scalar API explorer
|
|
99
368
|
- [OpenAPI Spec](https://github.com/kochetkov-ma/brewpage-openapi/blob/main/openapi/openapi.yaml) -- Full specification
|
|
100
|
-
- [Claude Skill](https://github.com/kochetkov-ma/claude-brewcode/tree/main/skills/brewpage-publish) -- `/brewpage` command
|
|
369
|
+
- [Claude Skill](https://github.com/kochetkov-ma/claude-brewcode/tree/main/skills/brewpage-publish) -- `/brewpage` slash command
|
|
101
370
|
- [Brewcode Plugin](https://github.com/kochetkov-ma/claude-brewcode) -- Claude Code plugin suite
|
|
102
371
|
|
|
103
372
|
## Changelog
|
|
104
373
|
|
|
105
|
-
## 1.
|
|
106
|
-
|
|
374
|
+
## 1.5.0 -- 2026-05-21
|
|
375
|
+
|
|
376
|
+
- Add 8 new MCP tools: `update_html`, `publish_json`, `get_json`, `update_json`, `publish_kv`, `set_kv`, `get_kv`, `search_gallery`.
|
|
377
|
+
- Tool count: 6 -> 14. Full coverage of HTML update, JSON CRUD, KV CRUD, and gallery discovery (incl. owner-scoped listing via `mine=true`).
|
|
378
|
+
- Update operations (`update_html`, `update_json`, `set_kv`) require the original `ownerToken`; `password` (when set) is forwarded as `X-Password`.
|
|
379
|
+
|
|
380
|
+
## 1.4.0 -- 2026-05-12
|
|
381
|
+
|
|
382
|
+
- Sync to spec 1.31.0 -- adds raw `text/*` and `application/octet-stream` variants on `POST /api/html`; PUT mirror; 422 with `supportedTypes`.
|
|
107
383
|
|
|
108
384
|
## License
|
|
109
385
|
|
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.
|
|
8
|
+
version: "1.5.0",
|
|
9
9
|
});
|
|
10
10
|
async function apiRequest(method, path, body, headers) {
|
|
11
11
|
const url = `${BASE_URL}${path}`;
|
|
@@ -24,7 +24,7 @@ function formatPublishResponse(data) {
|
|
|
24
24
|
const lines = [
|
|
25
25
|
`Published successfully!`,
|
|
26
26
|
``,
|
|
27
|
-
`URL: ${data.url || "N/A"}`,
|
|
27
|
+
`URL: ${data.url || data.link || "N/A"}`,
|
|
28
28
|
];
|
|
29
29
|
if (data.shortUrl)
|
|
30
30
|
lines.push(`Short URL: ${data.shortUrl}`);
|
|
@@ -34,6 +34,21 @@ function formatPublishResponse(data) {
|
|
|
34
34
|
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.`);
|
|
35
35
|
return lines.join("\n");
|
|
36
36
|
}
|
|
37
|
+
function formatUpdateResponse(data) {
|
|
38
|
+
const lines = [
|
|
39
|
+
`Updated successfully!`,
|
|
40
|
+
``,
|
|
41
|
+
`URL: ${data.url || data.link || "N/A"}`,
|
|
42
|
+
];
|
|
43
|
+
if (data.shortUrl)
|
|
44
|
+
lines.push(`Short URL: ${data.shortUrl}`);
|
|
45
|
+
lines.push(`Namespace: ${data.namespace || "N/A"}`, `ID: ${data.id || "N/A"}`);
|
|
46
|
+
if (data.expiresAt)
|
|
47
|
+
lines.push(`Expires: ${data.expiresAt}`);
|
|
48
|
+
if (data.updatedAt)
|
|
49
|
+
lines.push(`Updated: ${data.updatedAt}`);
|
|
50
|
+
return lines.join("\n");
|
|
51
|
+
}
|
|
37
52
|
server.tool("publish_html", "Publish HTML or Markdown content to BrewPage. Returns a public URL and owner token. Supports password protection, custom TTL, optional filename, and an opt-in top toolbar (showTopBar).", {
|
|
38
53
|
content: z.string().describe("HTML or Markdown content to publish"),
|
|
39
54
|
format: z
|
|
@@ -357,6 +372,385 @@ server.tool("publish_site", "Publish an HTML site to BrewPage. Single-page: pass
|
|
|
357
372
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
358
373
|
};
|
|
359
374
|
});
|
|
375
|
+
server.tool("update_html", "Update an existing BrewPage HTML/Markdown page. Requires owner token. Returns the updated page metadata.", {
|
|
376
|
+
namespace: z.string().describe("Page namespace"),
|
|
377
|
+
id: z.string().describe("Page ID"),
|
|
378
|
+
content: z.string().describe("New HTML or Markdown content"),
|
|
379
|
+
ownerToken: z
|
|
380
|
+
.string()
|
|
381
|
+
.describe("Owner token received when the page was created"),
|
|
382
|
+
format: z
|
|
383
|
+
.string()
|
|
384
|
+
.optional()
|
|
385
|
+
.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."),
|
|
386
|
+
}, async ({ namespace, id, content, ownerToken, format }) => {
|
|
387
|
+
const body = { content };
|
|
388
|
+
if (format)
|
|
389
|
+
body.format = format;
|
|
390
|
+
const path = `/api/html/${encodeURIComponent(namespace)}/${encodeURIComponent(id)}`;
|
|
391
|
+
const { ok, status, data } = await apiRequest("PUT", path, body, {
|
|
392
|
+
"X-Owner-Token": ownerToken,
|
|
393
|
+
});
|
|
394
|
+
if (!ok) {
|
|
395
|
+
return {
|
|
396
|
+
content: [
|
|
397
|
+
{
|
|
398
|
+
type: "text",
|
|
399
|
+
text: `Failed to update HTML (${status}): ${JSON.stringify(data)}`,
|
|
400
|
+
},
|
|
401
|
+
],
|
|
402
|
+
isError: true,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
return {
|
|
406
|
+
content: [
|
|
407
|
+
{
|
|
408
|
+
type: "text",
|
|
409
|
+
text: formatUpdateResponse(data),
|
|
410
|
+
},
|
|
411
|
+
],
|
|
412
|
+
};
|
|
413
|
+
});
|
|
414
|
+
server.tool("publish_json", "Publish a JSON document to BrewPage. Accepts a JSON object, array, or a JSON-encoded string. Returns a public URL and owner token.", {
|
|
415
|
+
json: z
|
|
416
|
+
.union([z.string(), z.record(z.unknown()), z.array(z.unknown())])
|
|
417
|
+
.describe("JSON document to publish (object, array, or JSON-encoded string)"),
|
|
418
|
+
namespace: z
|
|
419
|
+
.string()
|
|
420
|
+
.optional()
|
|
421
|
+
.describe("Namespace for the document (optional, defaults to 'public')"),
|
|
422
|
+
password: z
|
|
423
|
+
.string()
|
|
424
|
+
.optional()
|
|
425
|
+
.describe("Optional password to protect the document"),
|
|
426
|
+
ttlDays: z
|
|
427
|
+
.number()
|
|
428
|
+
.int()
|
|
429
|
+
.min(1)
|
|
430
|
+
.max(30)
|
|
431
|
+
.optional()
|
|
432
|
+
.describe("Time to live in days (1-30, default: 15)"),
|
|
433
|
+
tags: z
|
|
434
|
+
.array(z.string())
|
|
435
|
+
.optional()
|
|
436
|
+
.describe("Optional tags for the document"),
|
|
437
|
+
ownerToken: z
|
|
438
|
+
.string()
|
|
439
|
+
.optional()
|
|
440
|
+
.describe("Existing owner token to group this document under the same owner as previous content"),
|
|
441
|
+
}, async ({ json, namespace, password, ttlDays, tags, ownerToken }) => {
|
|
442
|
+
const jsonString = typeof json === "string" ? json : JSON.stringify(json);
|
|
443
|
+
const qsParams = [];
|
|
444
|
+
if (namespace)
|
|
445
|
+
qsParams.push(`ns=${encodeURIComponent(namespace)}`);
|
|
446
|
+
if (ttlDays !== undefined)
|
|
447
|
+
qsParams.push(`ttl=${encodeURIComponent(String(ttlDays))}`);
|
|
448
|
+
if (tags && tags.length) {
|
|
449
|
+
for (const t of tags)
|
|
450
|
+
qsParams.push(`tags=${encodeURIComponent(t)}`);
|
|
451
|
+
}
|
|
452
|
+
const qs = qsParams.length ? `?${qsParams.join("&")}` : "";
|
|
453
|
+
const headers = { "Content-Type": "application/json" };
|
|
454
|
+
if (password)
|
|
455
|
+
headers["X-Password"] = password;
|
|
456
|
+
if (ownerToken)
|
|
457
|
+
headers["X-Owner-Token"] = ownerToken;
|
|
458
|
+
const res = await fetch(`${BASE_URL}/api/json${qs}`, {
|
|
459
|
+
method: "POST",
|
|
460
|
+
headers,
|
|
461
|
+
body: jsonString,
|
|
462
|
+
});
|
|
463
|
+
const data = await res.json().catch(() => null);
|
|
464
|
+
if (!res.ok) {
|
|
465
|
+
return {
|
|
466
|
+
content: [
|
|
467
|
+
{
|
|
468
|
+
type: "text",
|
|
469
|
+
text: `Failed to publish JSON (${res.status}): ${JSON.stringify(data)}`,
|
|
470
|
+
},
|
|
471
|
+
],
|
|
472
|
+
isError: true,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
return {
|
|
476
|
+
content: [
|
|
477
|
+
{
|
|
478
|
+
type: "text",
|
|
479
|
+
text: formatPublishResponse(data),
|
|
480
|
+
},
|
|
481
|
+
],
|
|
482
|
+
};
|
|
483
|
+
});
|
|
484
|
+
server.tool("get_json", "Fetch a published BrewPage JSON document by namespace and ID.", {
|
|
485
|
+
namespace: z.string().describe("Document namespace"),
|
|
486
|
+
id: z.string().describe("Document ID"),
|
|
487
|
+
password: z
|
|
488
|
+
.string()
|
|
489
|
+
.optional()
|
|
490
|
+
.describe("Password if the document is protected"),
|
|
491
|
+
}, async ({ namespace, id, password }) => {
|
|
492
|
+
const headers = {};
|
|
493
|
+
if (password)
|
|
494
|
+
headers["X-Password"] = password;
|
|
495
|
+
const path = `/api/json/${encodeURIComponent(namespace)}/${encodeURIComponent(id)}`;
|
|
496
|
+
const { ok, status, data } = await apiRequest("GET", path, undefined, headers);
|
|
497
|
+
if (!ok) {
|
|
498
|
+
return {
|
|
499
|
+
content: [
|
|
500
|
+
{
|
|
501
|
+
type: "text",
|
|
502
|
+
text: `Failed to fetch JSON (${status}): ${JSON.stringify(data)}`,
|
|
503
|
+
},
|
|
504
|
+
],
|
|
505
|
+
isError: true,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
return {
|
|
509
|
+
content: [
|
|
510
|
+
{
|
|
511
|
+
type: "text",
|
|
512
|
+
text: JSON.stringify(data, null, 2),
|
|
513
|
+
},
|
|
514
|
+
],
|
|
515
|
+
};
|
|
516
|
+
});
|
|
517
|
+
server.tool("update_json", "Update an existing BrewPage JSON document. Requires owner token. Accepts a JSON object, array, or a JSON-encoded string.", {
|
|
518
|
+
namespace: z.string().describe("Document namespace"),
|
|
519
|
+
id: z.string().describe("Document ID"),
|
|
520
|
+
json: z
|
|
521
|
+
.union([z.string(), z.record(z.unknown()), z.array(z.unknown())])
|
|
522
|
+
.describe("New JSON document (object, array, or JSON-encoded string)"),
|
|
523
|
+
ownerToken: z
|
|
524
|
+
.string()
|
|
525
|
+
.describe("Owner token received when the document was created"),
|
|
526
|
+
}, async ({ namespace, id, json, ownerToken }) => {
|
|
527
|
+
const jsonString = typeof json === "string" ? json : JSON.stringify(json);
|
|
528
|
+
const url = `${BASE_URL}/api/json/${encodeURIComponent(namespace)}/${encodeURIComponent(id)}`;
|
|
529
|
+
const res = await fetch(url, {
|
|
530
|
+
method: "PUT",
|
|
531
|
+
headers: {
|
|
532
|
+
"Content-Type": "application/json",
|
|
533
|
+
"X-Owner-Token": ownerToken,
|
|
534
|
+
},
|
|
535
|
+
body: jsonString,
|
|
536
|
+
});
|
|
537
|
+
const data = await res.json().catch(() => null);
|
|
538
|
+
if (!res.ok) {
|
|
539
|
+
return {
|
|
540
|
+
content: [
|
|
541
|
+
{
|
|
542
|
+
type: "text",
|
|
543
|
+
text: `Failed to update JSON (${res.status}): ${JSON.stringify(data)}`,
|
|
544
|
+
},
|
|
545
|
+
],
|
|
546
|
+
isError: true,
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
return {
|
|
550
|
+
content: [
|
|
551
|
+
{
|
|
552
|
+
type: "text",
|
|
553
|
+
text: formatUpdateResponse(data),
|
|
554
|
+
},
|
|
555
|
+
],
|
|
556
|
+
};
|
|
557
|
+
});
|
|
558
|
+
server.tool("publish_kv", "Publish a key/value entry to BrewPage. Creates a KV store; the entry is addressed by (namespace, id, key). Returns a public URL and owner token.", {
|
|
559
|
+
key: z.string().describe("KV key (first part of the (namespace, id, key) addressing tuple)"),
|
|
560
|
+
value: z.string().describe("KV value"),
|
|
561
|
+
namespace: z
|
|
562
|
+
.string()
|
|
563
|
+
.optional()
|
|
564
|
+
.describe("Namespace for the KV store (optional, defaults to 'public')"),
|
|
565
|
+
password: z
|
|
566
|
+
.string()
|
|
567
|
+
.optional()
|
|
568
|
+
.describe("Optional password to protect the KV store"),
|
|
569
|
+
ttlDays: z
|
|
570
|
+
.number()
|
|
571
|
+
.int()
|
|
572
|
+
.min(1)
|
|
573
|
+
.max(30)
|
|
574
|
+
.optional()
|
|
575
|
+
.describe("Time to live in days (1-30, default: 15)"),
|
|
576
|
+
tags: z
|
|
577
|
+
.array(z.string())
|
|
578
|
+
.optional()
|
|
579
|
+
.describe("Optional tags for the KV entry"),
|
|
580
|
+
ownerToken: z
|
|
581
|
+
.string()
|
|
582
|
+
.optional()
|
|
583
|
+
.describe("Existing owner token to group this KV store under the same owner as previous content"),
|
|
584
|
+
}, async ({ key, value, namespace, password, ttlDays, tags, ownerToken }) => {
|
|
585
|
+
const body = { key, value };
|
|
586
|
+
const qsParams = [];
|
|
587
|
+
if (namespace)
|
|
588
|
+
qsParams.push(`ns=${encodeURIComponent(namespace)}`);
|
|
589
|
+
if (ttlDays !== undefined)
|
|
590
|
+
qsParams.push(`ttl=${encodeURIComponent(String(ttlDays))}`);
|
|
591
|
+
if (tags && tags.length) {
|
|
592
|
+
for (const t of tags)
|
|
593
|
+
qsParams.push(`tags=${encodeURIComponent(t)}`);
|
|
594
|
+
}
|
|
595
|
+
const qs = qsParams.length ? `?${qsParams.join("&")}` : "";
|
|
596
|
+
const headers = {};
|
|
597
|
+
if (password)
|
|
598
|
+
headers["X-Password"] = password;
|
|
599
|
+
if (ownerToken)
|
|
600
|
+
headers["X-Owner-Token"] = ownerToken;
|
|
601
|
+
const { ok, status, data } = await apiRequest("POST", `/api/kv${qs}`, body, headers);
|
|
602
|
+
if (!ok) {
|
|
603
|
+
return {
|
|
604
|
+
content: [
|
|
605
|
+
{
|
|
606
|
+
type: "text",
|
|
607
|
+
text: `Failed to publish KV (${status}): ${JSON.stringify(data)}`,
|
|
608
|
+
},
|
|
609
|
+
],
|
|
610
|
+
isError: true,
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
return {
|
|
614
|
+
content: [
|
|
615
|
+
{
|
|
616
|
+
type: "text",
|
|
617
|
+
text: formatPublishResponse(data),
|
|
618
|
+
},
|
|
619
|
+
],
|
|
620
|
+
};
|
|
621
|
+
});
|
|
622
|
+
server.tool("set_kv", "Set or update a key in an existing BrewPage KV store. Requires owner token.", {
|
|
623
|
+
namespace: z.string().describe("KV store namespace"),
|
|
624
|
+
id: z.string().describe("KV store ID"),
|
|
625
|
+
key: z.string().describe("Key to set"),
|
|
626
|
+
value: z.string().describe("Value to assign to the key"),
|
|
627
|
+
ownerToken: z
|
|
628
|
+
.string()
|
|
629
|
+
.describe("Owner token received when the KV store was created"),
|
|
630
|
+
}, async ({ namespace, id, key, value, ownerToken }) => {
|
|
631
|
+
const body = { value };
|
|
632
|
+
const path = `/api/kv/${encodeURIComponent(namespace)}/${encodeURIComponent(id)}/${encodeURIComponent(key)}`;
|
|
633
|
+
const { ok, status, data } = await apiRequest("PUT", path, body, {
|
|
634
|
+
"X-Owner-Token": ownerToken,
|
|
635
|
+
});
|
|
636
|
+
if (!ok) {
|
|
637
|
+
return {
|
|
638
|
+
content: [
|
|
639
|
+
{
|
|
640
|
+
type: "text",
|
|
641
|
+
text: `Failed to set KV (${status}): ${JSON.stringify(data)}`,
|
|
642
|
+
},
|
|
643
|
+
],
|
|
644
|
+
isError: true,
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
return {
|
|
648
|
+
content: [
|
|
649
|
+
{
|
|
650
|
+
type: "text",
|
|
651
|
+
text: formatUpdateResponse(data),
|
|
652
|
+
},
|
|
653
|
+
],
|
|
654
|
+
};
|
|
655
|
+
});
|
|
656
|
+
server.tool("get_kv", "Fetch a value from a BrewPage KV store by namespace, ID, and key.", {
|
|
657
|
+
namespace: z.string().describe("KV store namespace"),
|
|
658
|
+
id: z.string().describe("KV store ID"),
|
|
659
|
+
key: z.string().describe("Key to fetch"),
|
|
660
|
+
password: z
|
|
661
|
+
.string()
|
|
662
|
+
.optional()
|
|
663
|
+
.describe("Password if the KV store is protected"),
|
|
664
|
+
}, async ({ namespace, id, key, password }) => {
|
|
665
|
+
const headers = {};
|
|
666
|
+
if (password)
|
|
667
|
+
headers["X-Password"] = password;
|
|
668
|
+
const path = `/api/kv/${encodeURIComponent(namespace)}/${encodeURIComponent(id)}/${encodeURIComponent(key)}`;
|
|
669
|
+
const { ok, status, data } = await apiRequest("GET", path, undefined, headers);
|
|
670
|
+
if (!ok) {
|
|
671
|
+
return {
|
|
672
|
+
content: [
|
|
673
|
+
{
|
|
674
|
+
type: "text",
|
|
675
|
+
text: `Failed to fetch KV (${status}): ${JSON.stringify(data)}`,
|
|
676
|
+
},
|
|
677
|
+
],
|
|
678
|
+
isError: true,
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
return {
|
|
682
|
+
content: [
|
|
683
|
+
{
|
|
684
|
+
type: "text",
|
|
685
|
+
text: JSON.stringify(data, null, 2),
|
|
686
|
+
},
|
|
687
|
+
],
|
|
688
|
+
};
|
|
689
|
+
});
|
|
690
|
+
server.tool("search_gallery", "Search the BrewPage public gallery. Optional filters: text query, pagination, sort, and 'mine' (owner-scoped, requires owner token).", {
|
|
691
|
+
q: z.string().optional().describe("Free-text search query"),
|
|
692
|
+
page: z
|
|
693
|
+
.number()
|
|
694
|
+
.int()
|
|
695
|
+
.min(1)
|
|
696
|
+
.default(1)
|
|
697
|
+
.describe("Page number (1-based; default 1)"),
|
|
698
|
+
size: z
|
|
699
|
+
.number()
|
|
700
|
+
.int()
|
|
701
|
+
.min(1)
|
|
702
|
+
.max(100)
|
|
703
|
+
.optional()
|
|
704
|
+
.describe("Page size (1-100; backend caps at 100)"),
|
|
705
|
+
sort: z
|
|
706
|
+
.enum(["date", "views"])
|
|
707
|
+
.optional()
|
|
708
|
+
.describe("Sort order: 'date' or 'views'"),
|
|
709
|
+
mine: z
|
|
710
|
+
.boolean()
|
|
711
|
+
.optional()
|
|
712
|
+
.describe("If true, restricts results to entries owned by the supplied owner token"),
|
|
713
|
+
ownerToken: z
|
|
714
|
+
.string()
|
|
715
|
+
.optional()
|
|
716
|
+
.describe("Owner token; only sent as X-Owner-Token when 'mine' is true"),
|
|
717
|
+
}, async ({ q, page, size, sort, mine, ownerToken }) => {
|
|
718
|
+
const qsParams = [];
|
|
719
|
+
if (q !== undefined)
|
|
720
|
+
qsParams.push(`q=${encodeURIComponent(q)}`);
|
|
721
|
+
if (page !== undefined)
|
|
722
|
+
qsParams.push(`page=${encodeURIComponent(String(page))}`);
|
|
723
|
+
if (size !== undefined)
|
|
724
|
+
qsParams.push(`size=${encodeURIComponent(String(size))}`);
|
|
725
|
+
if (sort)
|
|
726
|
+
qsParams.push(`sort=${encodeURIComponent(sort)}`);
|
|
727
|
+
if (mine === true)
|
|
728
|
+
qsParams.push("mine=true");
|
|
729
|
+
const qs = qsParams.length ? `?${qsParams.join("&")}` : "";
|
|
730
|
+
const headers = {};
|
|
731
|
+
if (mine === true && ownerToken)
|
|
732
|
+
headers["X-Owner-Token"] = ownerToken;
|
|
733
|
+
const { ok, status, data } = await apiRequest("GET", `/api/gallery${qs}`, undefined, headers);
|
|
734
|
+
if (!ok) {
|
|
735
|
+
return {
|
|
736
|
+
content: [
|
|
737
|
+
{
|
|
738
|
+
type: "text",
|
|
739
|
+
text: `Failed to search gallery (${status}): ${JSON.stringify(data)}`,
|
|
740
|
+
},
|
|
741
|
+
],
|
|
742
|
+
isError: true,
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
return {
|
|
746
|
+
content: [
|
|
747
|
+
{
|
|
748
|
+
type: "text",
|
|
749
|
+
text: JSON.stringify(data, null, 2),
|
|
750
|
+
},
|
|
751
|
+
],
|
|
752
|
+
};
|
|
753
|
+
});
|
|
360
754
|
async function main() {
|
|
361
755
|
const transport = new StdioServerTransport();
|
|
362
756
|
await server.connect(transport);
|
package/package.json
CHANGED