brewpage-mcp 1.14.5 → 1.15.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 +10 -6
- package/dist/http.js +65 -13
- package/dist/index.js +3 -10
- package/dist/tools.d.ts +2 -2
- package/dist/tools.js +5 -5
- package/package.json +7 -10
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.
|
|
20
|
+
npx -y brewpage-mcp@1.15.0
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Requires Node.js 20 or newer. This starts the stdio server for an MCP client.
|
|
@@ -33,7 +33,7 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
|
|
|
33
33
|
"mcpServers": {
|
|
34
34
|
"brewpage": {
|
|
35
35
|
"command": "npx",
|
|
36
|
-
"args": ["-y", "brewpage-mcp@1.
|
|
36
|
+
"args": ["-y", "brewpage-mcp@1.15.0"]
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -44,7 +44,7 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
|
|
|
44
44
|
Register a project-scoped stdio server:
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
|
-
claude mcp add --transport stdio --scope project brewpage -- npx -y brewpage-mcp@1.
|
|
47
|
+
claude mcp add --transport stdio --scope project brewpage -- npx -y brewpage-mcp@1.15.0
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
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).
|
|
@@ -58,7 +58,7 @@ Open **Settings > MCP** and add:
|
|
|
58
58
|
"mcpServers": {
|
|
59
59
|
"brewpage": {
|
|
60
60
|
"command": "npx",
|
|
61
|
-
"args": ["-y", "brewpage-mcp@1.
|
|
61
|
+
"args": ["-y", "brewpage-mcp@1.15.0"]
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -72,7 +72,7 @@ Open the Cline MCP settings panel and add:
|
|
|
72
72
|
{
|
|
73
73
|
"brewpage": {
|
|
74
74
|
"command": "npx",
|
|
75
|
-
"args": ["-y", "brewpage-mcp@1.
|
|
75
|
+
"args": ["-y", "brewpage-mcp@1.15.0"]
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
```
|
|
@@ -80,7 +80,7 @@ Open the Cline MCP settings panel and add:
|
|
|
80
80
|
### Global Install
|
|
81
81
|
|
|
82
82
|
```bash
|
|
83
|
-
npm install -g brewpage-mcp@1.
|
|
83
|
+
npm install -g brewpage-mcp@1.15.0
|
|
84
84
|
brewpage-mcp
|
|
85
85
|
```
|
|
86
86
|
|
|
@@ -513,6 +513,10 @@ Every publish response includes an **owner token** -- the only credential that a
|
|
|
513
513
|
|
|
514
514
|
## Changelog
|
|
515
515
|
|
|
516
|
+
## 1.15.0 -- 2026-10-27
|
|
517
|
+
|
|
518
|
+
- Supports MCP protocol 2026-07-28 (SDK v2).
|
|
519
|
+
|
|
516
520
|
## 1.14.5 -- 2026-10-27
|
|
517
521
|
|
|
518
522
|
- Site publish hints add 400 for an unreadable, password-protected or too complex PDF/Office file and 503 when busy; a failed publish on 503 prints `Retry-After`.
|
package/dist/http.js
CHANGED
|
@@ -5,9 +5,8 @@ import { lookup as dnsLookup } from "node:dns";
|
|
|
5
5
|
import { BlockList, isIP } from "node:net";
|
|
6
6
|
import { realpathSync } from "node:fs";
|
|
7
7
|
import { pathToFileURL } from "node:url";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
8
|
+
import { createMcpHandler, InMemoryTransport, isLegacyRequest, LATEST_PROTOCOL_VERSION } from "@modelcontextprotocol/server";
|
|
9
|
+
import { NodeStreamableHTTPServerTransport, toWebRequest } from "@modelcontextprotocol/node";
|
|
11
10
|
import { createServer, OWNER_TOKEN_ENV, USER_AGENT } from "./tools.js";
|
|
12
11
|
const MIB = 1024 * 1024;
|
|
13
12
|
/** Fits the largest site (25 MiB backend request: 20 MiB of inline text plus JSON escaping).
|
|
@@ -54,6 +53,7 @@ const VERSION = USER_AGENT.split("/")[1];
|
|
|
54
53
|
const REMOTE_USER_AGENT = `${USER_AGENT} (remote)`;
|
|
55
54
|
const PARSE_ERROR = -32700;
|
|
56
55
|
const INVALID_REQUEST = -32600;
|
|
56
|
+
const METHOD_NOT_FOUND = -32601;
|
|
57
57
|
const SERVER_ERROR = -32000;
|
|
58
58
|
/** One JSON line per event on stdout; callers never pass arguments, tokens or IPs. */
|
|
59
59
|
function log(fields) {
|
|
@@ -342,18 +342,40 @@ function readBody(req, limit, onLarge) {
|
|
|
342
342
|
finish("aborted");
|
|
343
343
|
});
|
|
344
344
|
}
|
|
345
|
-
/** Tool names from an in-memory handshake; logs name only these. */
|
|
345
|
+
/** Tool names from an in-memory 2025 handshake (raw JSON-RPC, no client package); logs name only these. */
|
|
346
346
|
async function registeredToolNames() {
|
|
347
347
|
const [clientSide, serverSide] = InMemoryTransport.createLinkedPair();
|
|
348
348
|
const server = createServer({ baseUrl: "http://127.0.0.1:9" });
|
|
349
|
-
const
|
|
349
|
+
const answers = new Map();
|
|
350
|
+
clientSide.onmessage = (message) => {
|
|
351
|
+
if ("id" in message && typeof message.id === "number")
|
|
352
|
+
answers.get(message.id)?.(message);
|
|
353
|
+
};
|
|
354
|
+
const call = (id, method, params) => new Promise((resolve) => {
|
|
355
|
+
answers.set(id, resolve);
|
|
356
|
+
void clientSide.send({ jsonrpc: "2.0", id, method, params });
|
|
357
|
+
});
|
|
350
358
|
await server.connect(serverSide);
|
|
351
|
-
await
|
|
352
|
-
|
|
353
|
-
await
|
|
359
|
+
await clientSide.start();
|
|
360
|
+
await call(1, "initialize", { protocolVersion: LATEST_PROTOCOL_VERSION, capabilities: {}, clientInfo: { name: "startup", version: VERSION } });
|
|
361
|
+
await clientSide.send({ jsonrpc: "2.0", method: "notifications/initialized" });
|
|
362
|
+
const listed = await call(2, "tools/list", {});
|
|
363
|
+
await clientSide.close();
|
|
354
364
|
await server.close();
|
|
365
|
+
const tools = ("result" in listed ? listed.result.tools : undefined) ?? [];
|
|
366
|
+
if (tools.length === 0)
|
|
367
|
+
throw new Error("tools/list returned no tools");
|
|
355
368
|
return new Set(tools.map(({ name }) => name));
|
|
356
369
|
}
|
|
370
|
+
/** Tool-level outcome of one JSON-RPC answer; undefined = success. */
|
|
371
|
+
function rpcOutcome(outgoing) {
|
|
372
|
+
if (outgoing === null || typeof outgoing !== "object")
|
|
373
|
+
return undefined;
|
|
374
|
+
if ("error" in outgoing)
|
|
375
|
+
return "rpc_error";
|
|
376
|
+
const result = outgoing.result;
|
|
377
|
+
return result?.isError === true ? "tool_error" : undefined;
|
|
378
|
+
}
|
|
357
379
|
/** Slot pool: global limit with a FIFO wait queue, plus a per-client cap (client undefined = shared egress, no cap). */
|
|
358
380
|
class Pool {
|
|
359
381
|
limit;
|
|
@@ -540,6 +562,15 @@ async function main() {
|
|
|
540
562
|
const toolName = rpc.method === "tools/call" ? rpc.params?.name : undefined;
|
|
541
563
|
entry.method = typeof rpc.method === "string" ? rpc.method.slice(0, MAX_LOGGED_METHOD) : null;
|
|
542
564
|
entry.tool = rpc.method === "tools/call" ? (typeof toolName === "string" && toolNames.has(toolName) ? toolName : "unknown") : null;
|
|
565
|
+
// Stateless: no server-push streams, as the 2025 GET stream (405); a refused auto-open never fails a connect.
|
|
566
|
+
if (rpc.method === "subscriptions/listen") {
|
|
567
|
+
const id = message.id;
|
|
568
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
569
|
+
res.end(JSON.stringify({ jsonrpc: "2.0", id: typeof id === "string" || typeof id === "number" ? id : null,
|
|
570
|
+
error: { code: METHOD_NOT_FOUND, message: "subscriptions/listen is not supported by this stateless endpoint." } }));
|
|
571
|
+
entry.outcome = "rpc_error";
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
543
574
|
const isLight = rpc.method !== "tools/call" || (typeof toolName === "string" && LIGHT_TOOLS.has(toolName));
|
|
544
575
|
if (toolName === "publish_file" && slot !== heavy && !(await acquire(heavy)))
|
|
545
576
|
return busy();
|
|
@@ -559,13 +590,34 @@ async function main() {
|
|
|
559
590
|
: Promise.reject(new Error("Download limit reached for this address; try again in a few minutes.")),
|
|
560
591
|
fetch: callBackend,
|
|
561
592
|
});
|
|
562
|
-
const
|
|
593
|
+
const request = await toWebRequest(req, message, { signal: abort.signal });
|
|
594
|
+
if (!(await isLegacyRequest(request, message))) {
|
|
595
|
+
// 2026-07-28 envelope: one exchange through the SDK entry; any SSE body is read to its end.
|
|
596
|
+
const modern = createMcpHandler(() => server, { legacy: "reject" });
|
|
597
|
+
try {
|
|
598
|
+
const response = await modern.fetch(request, { parsedBody: message });
|
|
599
|
+
const text = await response.text();
|
|
600
|
+
if ((response.headers.get("content-type") ?? "").startsWith("application/json")) {
|
|
601
|
+
try {
|
|
602
|
+
entry.outcome = rpcOutcome(JSON.parse(text)) ?? null;
|
|
603
|
+
}
|
|
604
|
+
catch {
|
|
605
|
+
entry.outcome = null;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
res.writeHead(response.status, Object.fromEntries(response.headers.entries()));
|
|
609
|
+
res.end(text);
|
|
610
|
+
}
|
|
611
|
+
finally {
|
|
612
|
+
await modern.close();
|
|
613
|
+
await server.close();
|
|
614
|
+
}
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true });
|
|
563
618
|
const send = transport.send.bind(transport);
|
|
564
619
|
transport.send = (outgoing, options) => {
|
|
565
|
-
|
|
566
|
-
entry.outcome = "rpc_error";
|
|
567
|
-
else if ("result" in outgoing && outgoing.result.isError === true)
|
|
568
|
-
entry.outcome = "tool_error";
|
|
620
|
+
entry.outcome = rpcOutcome(outgoing) ?? entry.outcome;
|
|
569
621
|
return send(outgoing, options);
|
|
570
622
|
};
|
|
571
623
|
try {
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
3
3
|
import { createServer, DEFAULT_BASE_URL, OWNER_TOKEN_ENV } from "./tools.js";
|
|
4
4
|
const OWNER_TOKEN_SHAPE = /^[A-Za-z0-9]{32}$/;
|
|
5
5
|
/** Optional default owner token for every call; a malformed value is ignored with one stderr line. */
|
|
@@ -12,12 +12,5 @@ const ENV_OWNER_TOKEN = (() => {
|
|
|
12
12
|
console.error(`${OWNER_TOKEN_ENV} ignored: expected 32 letters/digits.`);
|
|
13
13
|
return undefined;
|
|
14
14
|
})();
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const transport = new StdioServerTransport();
|
|
18
|
-
await server.connect(transport);
|
|
19
|
-
}
|
|
20
|
-
main().catch((error) => {
|
|
21
|
-
console.error("Fatal error:", error);
|
|
22
|
-
process.exit(1);
|
|
23
|
-
});
|
|
15
|
+
/** Serves 2026-07-28 (server/discover) and 2025-era initialize openings from one factory. */
|
|
16
|
+
serveStdio(() => createServer({ baseUrl: process.env.BREWPAGE_URL || DEFAULT_BASE_URL, ownerToken: ENV_OWNER_TOKEN }));
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/
|
|
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.
|
|
3
|
+
export declare const USER_AGENT = "brewpage-mcp/1.15.0";
|
|
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
|
-
import { McpServer } from "@modelcontextprotocol/
|
|
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.
|
|
4
|
+
export const USER_AGENT = "brewpage-mcp/1.15.0";
|
|
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 };
|
|
@@ -188,7 +188,7 @@ export function createServer(options = {}) {
|
|
|
188
188
|
const apiFetch = options.fetch ?? fetch;
|
|
189
189
|
const server = new McpServer({
|
|
190
190
|
name: "brewpage-mcp",
|
|
191
|
-
version: "1.
|
|
191
|
+
version: "1.15.0",
|
|
192
192
|
});
|
|
193
193
|
/** Caller argument first, then the configured default; `fromEnv` marks the configured one. */
|
|
194
194
|
function ownerTokenFor(argument) {
|
|
@@ -750,7 +750,7 @@ export function createServer(options = {}) {
|
|
|
750
750
|
inputSchema: {
|
|
751
751
|
deliveryMode: DELIVERY_MODE_SCHEMA,
|
|
752
752
|
json: z
|
|
753
|
-
.union([z.string(), z.record(z.unknown()), z.array(z.unknown())])
|
|
753
|
+
.union([z.string(), z.record(z.string(), z.unknown()), z.array(z.unknown())])
|
|
754
754
|
.describe("JSON document to publish (object, array, or JSON-encoded string)"),
|
|
755
755
|
namespace: z
|
|
756
756
|
.string()
|
|
@@ -849,7 +849,7 @@ export function createServer(options = {}) {
|
|
|
849
849
|
namespace: z.string().describe("Document namespace"),
|
|
850
850
|
id: z.string().describe("Document ID"),
|
|
851
851
|
json: z
|
|
852
|
-
.union([z.string(), z.record(z.unknown()), z.array(z.unknown())])
|
|
852
|
+
.union([z.string(), z.record(z.string(), z.unknown()), z.array(z.unknown())])
|
|
853
853
|
.describe("New JSON document (object, array, or JSON-encoded string)"),
|
|
854
854
|
ownerToken: z.string().optional().describe(OWNER_TOKEN_DESCRIPTION),
|
|
855
855
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brewpage-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.0",
|
|
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",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"start": "node dist/index.js",
|
|
23
23
|
"start:http": "node dist/http.js",
|
|
24
24
|
"dev": "tsx src/index.ts",
|
|
25
|
-
"test": "npm run build && node --import tsx --test test/smoke.test.ts test/http.test.ts",
|
|
25
|
+
"test": "npm run build && node --import tsx --test test/smoke.test.ts test/http.test.ts test/protocol.test.ts",
|
|
26
26
|
"prepublishOnly": "npm run build"
|
|
27
27
|
},
|
|
28
28
|
"keywords": [
|
|
@@ -54,21 +54,18 @@
|
|
|
54
54
|
"url": "https://github.com/kochetkov-ma/brewpage-openapi/issues"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@modelcontextprotocol/
|
|
58
|
-
"
|
|
57
|
+
"@modelcontextprotocol/node": "2.1.2",
|
|
58
|
+
"@modelcontextprotocol/server": "2.4.0",
|
|
59
|
+
"zod": "4.6.7"
|
|
59
60
|
},
|
|
60
61
|
"devDependencies": {
|
|
62
|
+
"@modelcontextprotocol/client": "2.4.0",
|
|
61
63
|
"@types/node": "22.19.15",
|
|
62
64
|
"tsx": "4.22.0",
|
|
63
65
|
"typescript": "5.9.3"
|
|
64
66
|
},
|
|
65
67
|
"overrides": {
|
|
66
68
|
"@hono/node-server": "1.19.15",
|
|
67
|
-
"
|
|
68
|
-
"fast-uri": "3.1.8",
|
|
69
|
-
"hono": "4.13.7",
|
|
70
|
-
"ip-address": "10.7.1",
|
|
71
|
-
"proxy-addr": "2.0.8",
|
|
72
|
-
"qs": "6.16.0"
|
|
69
|
+
"hono": "4.13.14"
|
|
73
70
|
}
|
|
74
71
|
}
|