@webhouse/cms-mcp-client 0.1.1
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/LICENSE +21 -0
- package/README.md +30 -0
- package/dist/index.cjs +423 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +221 -0
- package/dist/index.d.ts +221 -0
- package/dist/index.js +393 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
// src/tools.ts
|
|
2
|
+
var PUBLIC_TOOLS = [
|
|
3
|
+
{
|
|
4
|
+
name: "get_site_summary",
|
|
5
|
+
description: "Returns an overview of the site: name, description, language, available collections, total document count, and last build time. Always call this first to understand what content is available.",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: "object",
|
|
8
|
+
properties: {},
|
|
9
|
+
required: []
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: "list_collection",
|
|
14
|
+
description: "Lists all published documents in a collection. Returns title, slug, summary, tags, and date for each document.",
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: "object",
|
|
17
|
+
properties: {
|
|
18
|
+
collection: { type: "string", description: "Collection name, e.g. 'posts', 'products'" },
|
|
19
|
+
limit: { type: "number", description: "Max results. Default 20, max 100." },
|
|
20
|
+
offset: { type: "number", description: "Pagination offset. Default 0." },
|
|
21
|
+
sort: { type: "string", enum: ["date_desc", "date_asc", "title_asc"], description: "Sort order. Default: date_desc." }
|
|
22
|
+
},
|
|
23
|
+
required: ["collection"]
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: "search_content",
|
|
28
|
+
description: "Full-text search across all published content. Returns matching documents with excerpt and metadata. Can be scoped to a collection.",
|
|
29
|
+
inputSchema: {
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: {
|
|
32
|
+
query: { type: "string", description: "Search query." },
|
|
33
|
+
collection: { type: "string", description: "Optional: limit search to this collection." },
|
|
34
|
+
limit: { type: "number", description: "Max results. Default 10, max 50." }
|
|
35
|
+
},
|
|
36
|
+
required: ["query"]
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "get_page",
|
|
41
|
+
description: "Retrieves the full content of a single page by slug. Returns complete document as Markdown plus all metadata fields.",
|
|
42
|
+
inputSchema: {
|
|
43
|
+
type: "object",
|
|
44
|
+
properties: {
|
|
45
|
+
slug: { type: "string", description: "Document slug, e.g. 'getting-started'." },
|
|
46
|
+
collection: { type: "string", description: "Optional: collection to scope the lookup." }
|
|
47
|
+
},
|
|
48
|
+
required: ["slug"]
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "get_schema",
|
|
53
|
+
description: "Returns the field schema for a collection, describing all available fields and their types.",
|
|
54
|
+
inputSchema: {
|
|
55
|
+
type: "object",
|
|
56
|
+
properties: {
|
|
57
|
+
collection: { type: "string", description: "Collection name." }
|
|
58
|
+
},
|
|
59
|
+
required: ["collection"]
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: "export_all",
|
|
64
|
+
description: "Exports all published content from all collections as structured JSON. Use when you need comprehensive access to the entire site.",
|
|
65
|
+
inputSchema: {
|
|
66
|
+
type: "object",
|
|
67
|
+
properties: {
|
|
68
|
+
include_body: { type: "boolean", description: "Include full body (true) or metadata only (false). Default: true." }
|
|
69
|
+
},
|
|
70
|
+
required: []
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
// src/reader.ts
|
|
76
|
+
function getExcerpt(data, maxLen = 200) {
|
|
77
|
+
const body = data["content"] ?? data["body"] ?? data["excerpt"] ?? data["description"] ?? "";
|
|
78
|
+
const text = String(body).replace(/<[^>]+>/g, "").trim();
|
|
79
|
+
return text.length > maxLen ? text.slice(0, maxLen) + "\u2026" : text;
|
|
80
|
+
}
|
|
81
|
+
function getTitle(data) {
|
|
82
|
+
return String(data["title"] ?? data["name"] ?? data["heading"] ?? "Untitled");
|
|
83
|
+
}
|
|
84
|
+
function docToSummary(doc) {
|
|
85
|
+
return {
|
|
86
|
+
slug: doc.slug,
|
|
87
|
+
collection: doc.collection,
|
|
88
|
+
title: getTitle(doc.data),
|
|
89
|
+
excerpt: getExcerpt(doc.data),
|
|
90
|
+
tags: doc.data["tags"] ?? [],
|
|
91
|
+
date: doc.data["date"] ?? doc.createdAt,
|
|
92
|
+
updatedAt: doc.updatedAt
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function docToMarkdown(doc) {
|
|
96
|
+
const title = getTitle(doc.data);
|
|
97
|
+
const lines = [`# ${title}`, ""];
|
|
98
|
+
lines.push("---");
|
|
99
|
+
lines.push(`slug: ${doc.slug}`);
|
|
100
|
+
lines.push(`collection: ${doc.collection}`);
|
|
101
|
+
lines.push(`status: ${doc.status}`);
|
|
102
|
+
if (doc.data["date"]) lines.push(`date: ${doc.data["date"]}`);
|
|
103
|
+
if (doc.data["author"]) lines.push(`author: ${doc.data["author"]}`);
|
|
104
|
+
if (doc.data["tags"]) lines.push(`tags: ${doc.data["tags"].join(", ")}`);
|
|
105
|
+
lines.push("---", "");
|
|
106
|
+
const body = doc.data["content"] ?? doc.data["body"] ?? doc.data["description"] ?? "";
|
|
107
|
+
if (body) {
|
|
108
|
+
lines.push(body.replace(/<[^>]+>/g, "").trim());
|
|
109
|
+
}
|
|
110
|
+
return lines.join("\n");
|
|
111
|
+
}
|
|
112
|
+
var ContentReader = class {
|
|
113
|
+
constructor(content, config) {
|
|
114
|
+
this.content = content;
|
|
115
|
+
this.config = config;
|
|
116
|
+
}
|
|
117
|
+
async getSiteSummary() {
|
|
118
|
+
const collectionStats = [];
|
|
119
|
+
let totalDocs = 0;
|
|
120
|
+
for (const col of this.config.collections) {
|
|
121
|
+
const { total } = await this.content.findMany(col.name, { status: "published", limit: 1 });
|
|
122
|
+
collectionStats.push({ name: col.name, label: col.label ?? col.name, count: total });
|
|
123
|
+
totalDocs += total;
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
collections: collectionStats,
|
|
127
|
+
totalDocuments: totalDocs,
|
|
128
|
+
defaultLocale: this.config.defaultLocale ?? "en",
|
|
129
|
+
locales: this.config.locales ?? []
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
async listCollection(args) {
|
|
133
|
+
const limit = Math.min(args.limit ?? 20, 100);
|
|
134
|
+
const offset = args.offset ?? 0;
|
|
135
|
+
let orderBy = "createdAt";
|
|
136
|
+
let order = "desc";
|
|
137
|
+
if (args.sort === "date_asc") {
|
|
138
|
+
orderBy = "createdAt";
|
|
139
|
+
order = "asc";
|
|
140
|
+
} else if (args.sort === "title_asc") {
|
|
141
|
+
orderBy = "title";
|
|
142
|
+
order = "asc";
|
|
143
|
+
}
|
|
144
|
+
const { documents, total } = await this.content.findMany(args.collection, {
|
|
145
|
+
status: "published",
|
|
146
|
+
limit,
|
|
147
|
+
offset,
|
|
148
|
+
orderBy,
|
|
149
|
+
order
|
|
150
|
+
});
|
|
151
|
+
return {
|
|
152
|
+
collection: args.collection,
|
|
153
|
+
total,
|
|
154
|
+
limit,
|
|
155
|
+
offset,
|
|
156
|
+
documents: documents.map(docToSummary)
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
async search(args) {
|
|
160
|
+
const limit = Math.min(args.limit ?? 10, 50);
|
|
161
|
+
const q = args.query.toLowerCase();
|
|
162
|
+
const collections = args.collection ? [args.collection] : this.config.collections.map((c) => c.name);
|
|
163
|
+
const results = [];
|
|
164
|
+
for (const col of collections) {
|
|
165
|
+
const { documents } = await this.content.findMany(col, {
|
|
166
|
+
status: "published",
|
|
167
|
+
limit: 500
|
|
168
|
+
});
|
|
169
|
+
for (const doc of documents) {
|
|
170
|
+
const text = JSON.stringify(doc.data).toLowerCase();
|
|
171
|
+
if (text.includes(q)) {
|
|
172
|
+
const titleMatch = getTitle(doc.data).toLowerCase().includes(q) ? 2 : 0;
|
|
173
|
+
results.push({ ...docToSummary(doc), score: 1 + titleMatch });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
results.sort((a, b) => b.score - a.score);
|
|
178
|
+
return { query: args.query, total: results.length, results: results.slice(0, limit) };
|
|
179
|
+
}
|
|
180
|
+
async getPage(args) {
|
|
181
|
+
const collections = args.collection ? [args.collection] : this.config.collections.map((c) => c.name);
|
|
182
|
+
for (const col of collections) {
|
|
183
|
+
const doc = await this.content.findBySlug(col, args.slug);
|
|
184
|
+
if (doc && doc.status === "published") {
|
|
185
|
+
return docToMarkdown(doc);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return `No published document found with slug "${args.slug}".`;
|
|
189
|
+
}
|
|
190
|
+
async getSchema(collection) {
|
|
191
|
+
const col = this.config.collections.find((c) => c.name === collection);
|
|
192
|
+
if (!col) return { error: `Collection "${collection}" not found` };
|
|
193
|
+
return {
|
|
194
|
+
name: col.name,
|
|
195
|
+
label: col.label ?? col.name,
|
|
196
|
+
fields: col.fields.map((f) => ({
|
|
197
|
+
name: f.name,
|
|
198
|
+
type: f.type,
|
|
199
|
+
label: f.label,
|
|
200
|
+
required: f.required ?? false
|
|
201
|
+
}))
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
async exportAll(args) {
|
|
205
|
+
const includeBody = args.include_body !== false;
|
|
206
|
+
const result = {};
|
|
207
|
+
for (const col of this.config.collections) {
|
|
208
|
+
const { documents } = await this.content.findMany(col.name, {
|
|
209
|
+
status: "published",
|
|
210
|
+
limit: 1e4
|
|
211
|
+
});
|
|
212
|
+
result[col.name] = documents.map((doc) => {
|
|
213
|
+
if (!includeBody) return docToSummary(doc);
|
|
214
|
+
return { ...docToSummary(doc), data: doc.data };
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return result;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// src/transport.ts
|
|
222
|
+
var NextSSETransport = class {
|
|
223
|
+
controller = null;
|
|
224
|
+
encoder = new TextEncoder();
|
|
225
|
+
stream;
|
|
226
|
+
sessionId;
|
|
227
|
+
// MCP Transport interface callbacks
|
|
228
|
+
onclose;
|
|
229
|
+
onerror;
|
|
230
|
+
onmessage;
|
|
231
|
+
constructor(sessionId, endpointUrl) {
|
|
232
|
+
this.sessionId = sessionId;
|
|
233
|
+
const encoder = this.encoder;
|
|
234
|
+
this.stream = new ReadableStream({
|
|
235
|
+
start: (ctrl) => {
|
|
236
|
+
this.controller = ctrl;
|
|
237
|
+
if (endpointUrl) {
|
|
238
|
+
ctrl.enqueue(encoder.encode(`event: endpoint
|
|
239
|
+
data: ${endpointUrl}
|
|
240
|
+
|
|
241
|
+
`));
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
cancel: () => {
|
|
245
|
+
this.onclose?.();
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
/** Called by MCP SDK on startup — nothing to do since stream is already open. */
|
|
250
|
+
async start() {
|
|
251
|
+
}
|
|
252
|
+
/** Called by MCP SDK to send a message to the client via SSE. */
|
|
253
|
+
async send(message) {
|
|
254
|
+
const line = `data: ${JSON.stringify(message)}
|
|
255
|
+
|
|
256
|
+
`;
|
|
257
|
+
this.controller?.enqueue(this.encoder.encode(line));
|
|
258
|
+
}
|
|
259
|
+
/** Called when the server shuts down or the client disconnects. */
|
|
260
|
+
async close() {
|
|
261
|
+
try {
|
|
262
|
+
this.controller?.close();
|
|
263
|
+
} catch {
|
|
264
|
+
}
|
|
265
|
+
this.onclose?.();
|
|
266
|
+
}
|
|
267
|
+
/** Called by the POST route handler to deliver a client message into the MCP server. */
|
|
268
|
+
handleClientMessage(body) {
|
|
269
|
+
this.onmessage?.(body);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
var sessions = /* @__PURE__ */ new Map();
|
|
273
|
+
function getTransportSession(id) {
|
|
274
|
+
return sessions.get(id);
|
|
275
|
+
}
|
|
276
|
+
function registerTransportSession(t) {
|
|
277
|
+
sessions.set(t.sessionId, t);
|
|
278
|
+
t.onclose = () => sessions.delete(t.sessionId);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// src/rate-limit.ts
|
|
282
|
+
var counters = /* @__PURE__ */ new Map();
|
|
283
|
+
var WINDOW_MS = 6e4;
|
|
284
|
+
var MAX_REQUESTS = 60;
|
|
285
|
+
var MAX_EXPORT_ALL = 5;
|
|
286
|
+
function getCounter(ip) {
|
|
287
|
+
const now = Date.now();
|
|
288
|
+
let c = counters.get(ip);
|
|
289
|
+
if (!c || now > c.resetAt) {
|
|
290
|
+
c = { count: 0, exportAllCount: 0, resetAt: now + WINDOW_MS };
|
|
291
|
+
counters.set(ip, c);
|
|
292
|
+
}
|
|
293
|
+
return c;
|
|
294
|
+
}
|
|
295
|
+
function checkRateLimit(ip, tool) {
|
|
296
|
+
const c = getCounter(ip);
|
|
297
|
+
if (tool === "export_all") {
|
|
298
|
+
if (c.exportAllCount >= MAX_EXPORT_ALL) {
|
|
299
|
+
return { allowed: false, reason: "export_all rate limit exceeded (5/minute)" };
|
|
300
|
+
}
|
|
301
|
+
c.exportAllCount++;
|
|
302
|
+
}
|
|
303
|
+
if (c.count >= MAX_REQUESTS) {
|
|
304
|
+
return { allowed: false, reason: "Rate limit exceeded (60 requests/minute)" };
|
|
305
|
+
}
|
|
306
|
+
c.count++;
|
|
307
|
+
return { allowed: true };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/server.ts
|
|
311
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
312
|
+
import {
|
|
313
|
+
CallToolRequestSchema,
|
|
314
|
+
ListToolsRequestSchema
|
|
315
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
316
|
+
function createPublicMcpServer(content, config) {
|
|
317
|
+
const reader = new ContentReader(content, config);
|
|
318
|
+
const server = new Server(
|
|
319
|
+
{ name: "cms-public", version: "1.0.0" },
|
|
320
|
+
{ capabilities: { tools: {} } }
|
|
321
|
+
);
|
|
322
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
323
|
+
tools: PUBLIC_TOOLS
|
|
324
|
+
}));
|
|
325
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
326
|
+
const { name, arguments: args = {} } = req.params;
|
|
327
|
+
const a = args;
|
|
328
|
+
try {
|
|
329
|
+
let result;
|
|
330
|
+
switch (name) {
|
|
331
|
+
case "get_site_summary":
|
|
332
|
+
result = await reader.getSiteSummary();
|
|
333
|
+
break;
|
|
334
|
+
case "list_collection": {
|
|
335
|
+
const listArgs = {
|
|
336
|
+
collection: String(a["collection"] ?? "")
|
|
337
|
+
};
|
|
338
|
+
if (a["limit"] !== void 0) listArgs.limit = a["limit"];
|
|
339
|
+
if (a["offset"] !== void 0) listArgs.offset = a["offset"];
|
|
340
|
+
if (a["sort"] !== void 0) listArgs.sort = a["sort"];
|
|
341
|
+
result = await reader.listCollection(listArgs);
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
case "search_content": {
|
|
345
|
+
const searchArgs = {
|
|
346
|
+
query: String(a["query"] ?? "")
|
|
347
|
+
};
|
|
348
|
+
if (a["collection"] !== void 0) searchArgs.collection = a["collection"];
|
|
349
|
+
if (a["limit"] !== void 0) searchArgs.limit = a["limit"];
|
|
350
|
+
result = await reader.search(searchArgs);
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
case "get_page": {
|
|
354
|
+
const pageArgs = {
|
|
355
|
+
slug: String(a["slug"] ?? "")
|
|
356
|
+
};
|
|
357
|
+
if (a["collection"] !== void 0) pageArgs.collection = a["collection"];
|
|
358
|
+
result = await reader.getPage(pageArgs);
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
case "get_schema":
|
|
362
|
+
result = await reader.getSchema(String(a["collection"] ?? ""));
|
|
363
|
+
break;
|
|
364
|
+
case "export_all": {
|
|
365
|
+
const exportArgs = {};
|
|
366
|
+
if (a["include_body"] !== void 0) exportArgs.include_body = a["include_body"];
|
|
367
|
+
result = await reader.exportAll(exportArgs);
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
default:
|
|
371
|
+
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
372
|
+
}
|
|
373
|
+
const text = typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
374
|
+
return { content: [{ type: "text", text }] };
|
|
375
|
+
} catch (err) {
|
|
376
|
+
return {
|
|
377
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
378
|
+
isError: true
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
return server;
|
|
383
|
+
}
|
|
384
|
+
export {
|
|
385
|
+
ContentReader,
|
|
386
|
+
NextSSETransport,
|
|
387
|
+
PUBLIC_TOOLS,
|
|
388
|
+
checkRateLimit,
|
|
389
|
+
createPublicMcpServer,
|
|
390
|
+
getTransportSession,
|
|
391
|
+
registerTransportSession
|
|
392
|
+
};
|
|
393
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tools.ts","../src/reader.ts","../src/transport.ts","../src/rate-limit.ts","../src/server.ts"],"sourcesContent":["export const PUBLIC_TOOLS = [\n {\n name: \"get_site_summary\",\n description:\n \"Returns an overview of the site: name, description, language, \" +\n \"available collections, total document count, and last build time. \" +\n \"Always call this first to understand what content is available.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {},\n required: [] as string[],\n },\n },\n {\n name: \"list_collection\",\n description:\n \"Lists all published documents in a collection. Returns title, slug, \" +\n \"summary, tags, and date for each document.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n collection: { type: \"string\", description: \"Collection name, e.g. 'posts', 'products'\" },\n limit: { type: \"number\", description: \"Max results. Default 20, max 100.\" },\n offset: { type: \"number\", description: \"Pagination offset. Default 0.\" },\n sort: { type: \"string\", enum: [\"date_desc\", \"date_asc\", \"title_asc\"], description: \"Sort order. Default: date_desc.\" },\n },\n required: [\"collection\"] as string[],\n },\n },\n {\n name: \"search_content\",\n description:\n \"Full-text search across all published content. Returns matching documents \" +\n \"with excerpt and metadata. Can be scoped to a collection.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n query: { type: \"string\", description: \"Search query.\" },\n collection: { type: \"string\", description: \"Optional: limit search to this collection.\" },\n limit: { type: \"number\", description: \"Max results. Default 10, max 50.\" },\n },\n required: [\"query\"] as string[],\n },\n },\n {\n name: \"get_page\",\n description:\n \"Retrieves the full content of a single page by slug. \" +\n \"Returns complete document as Markdown plus all metadata fields.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n slug: { type: \"string\", description: \"Document slug, e.g. 'getting-started'.\" },\n collection: { type: \"string\", description: \"Optional: collection to scope the lookup.\" },\n },\n required: [\"slug\"] as string[],\n },\n },\n {\n name: \"get_schema\",\n description:\n \"Returns the field schema for a collection, describing all available fields and their types.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n collection: { type: \"string\", description: \"Collection name.\" },\n },\n required: [\"collection\"] as string[],\n },\n },\n {\n name: \"export_all\",\n description:\n \"Exports all published content from all collections as structured JSON. \" +\n \"Use when you need comprehensive access to the entire site.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n include_body: { type: \"boolean\", description: \"Include full body (true) or metadata only (false). Default: true.\" },\n },\n required: [] as string[],\n },\n },\n] as const;\n\nexport type ToolName = (typeof PUBLIC_TOOLS)[number][\"name\"];\n","import type { ContentService, CmsConfig, Document } from \"@webhouse/cms\";\n\n// Extract a brief excerpt from document data\nfunction getExcerpt(data: Record<string, unknown>, maxLen = 200): string {\n const body = (data[\"content\"] ?? data[\"body\"] ?? data[\"excerpt\"] ?? data[\"description\"] ?? \"\") as string;\n const text = String(body).replace(/<[^>]+>/g, \"\").trim();\n return text.length > maxLen ? text.slice(0, maxLen) + \"…\" : text;\n}\n\nfunction getTitle(data: Record<string, unknown>): string {\n return String(data[\"title\"] ?? data[\"name\"] ?? data[\"heading\"] ?? \"Untitled\");\n}\n\nfunction docToSummary(doc: Document) {\n return {\n slug: doc.slug,\n collection: doc.collection,\n title: getTitle(doc.data),\n excerpt: getExcerpt(doc.data),\n tags: (doc.data[\"tags\"] as string[] | undefined) ?? [],\n date: doc.data[\"date\"] as string | undefined ?? doc.createdAt,\n updatedAt: doc.updatedAt,\n };\n}\n\nfunction docToMarkdown(doc: Document): string {\n const title = getTitle(doc.data);\n const lines: string[] = [`# ${title}`, \"\"];\n\n // Metadata block\n lines.push(\"---\");\n lines.push(`slug: ${doc.slug}`);\n lines.push(`collection: ${doc.collection}`);\n lines.push(`status: ${doc.status}`);\n if (doc.data[\"date\"]) lines.push(`date: ${doc.data[\"date\"]}`);\n if (doc.data[\"author\"]) lines.push(`author: ${doc.data[\"author\"]}`);\n if (doc.data[\"tags\"]) lines.push(`tags: ${(doc.data[\"tags\"] as string[]).join(\", \")}`);\n lines.push(\"---\", \"\");\n\n // Body\n const body = (doc.data[\"content\"] ?? doc.data[\"body\"] ?? doc.data[\"description\"] ?? \"\") as string;\n if (body) {\n // Strip HTML tags for clean markdown output\n lines.push(body.replace(/<[^>]+>/g, \"\").trim());\n }\n\n return lines.join(\"\\n\");\n}\n\nexport class ContentReader {\n constructor(\n private content: ContentService,\n private config: CmsConfig,\n ) {}\n\n async getSiteSummary() {\n const collectionStats: Array<{ name: string; label: string; count: number }> = [];\n let totalDocs = 0;\n\n for (const col of this.config.collections) {\n const { total } = await this.content.findMany(col.name, { status: \"published\", limit: 1 });\n collectionStats.push({ name: col.name, label: col.label ?? col.name, count: total });\n totalDocs += total;\n }\n\n return {\n collections: collectionStats,\n totalDocuments: totalDocs,\n defaultLocale: this.config.defaultLocale ?? \"en\",\n locales: this.config.locales ?? [],\n };\n }\n\n async listCollection(args: {\n collection: string;\n limit?: number;\n offset?: number;\n sort?: \"date_desc\" | \"date_asc\" | \"title_asc\";\n }) {\n const limit = Math.min(args.limit ?? 20, 100);\n const offset = args.offset ?? 0;\n\n let orderBy = \"createdAt\";\n let order: \"asc\" | \"desc\" = \"desc\";\n if (args.sort === \"date_asc\") { orderBy = \"createdAt\"; order = \"asc\"; }\n else if (args.sort === \"title_asc\") { orderBy = \"title\"; order = \"asc\"; }\n\n const { documents, total } = await this.content.findMany(args.collection, {\n status: \"published\",\n limit,\n offset,\n orderBy,\n order,\n });\n\n return {\n collection: args.collection,\n total,\n limit,\n offset,\n documents: documents.map(docToSummary),\n };\n }\n\n async search(args: { query: string; collection?: string; limit?: number }) {\n const limit = Math.min(args.limit ?? 10, 50);\n const q = args.query.toLowerCase();\n\n const collections = args.collection\n ? [args.collection]\n : this.config.collections.map((c) => c.name);\n\n const results: Array<ReturnType<typeof docToSummary> & { score: number }> = [];\n\n for (const col of collections) {\n const { documents } = await this.content.findMany(col, {\n status: \"published\",\n limit: 500,\n });\n\n for (const doc of documents) {\n const text = JSON.stringify(doc.data).toLowerCase();\n if (text.includes(q)) {\n const titleMatch = getTitle(doc.data).toLowerCase().includes(q) ? 2 : 0;\n results.push({ ...docToSummary(doc), score: 1 + titleMatch });\n }\n }\n }\n\n results.sort((a, b) => b.score - a.score);\n return { query: args.query, total: results.length, results: results.slice(0, limit) };\n }\n\n async getPage(args: { slug: string; collection?: string }) {\n const collections = args.collection\n ? [args.collection]\n : this.config.collections.map((c) => c.name);\n\n for (const col of collections) {\n const doc = await this.content.findBySlug(col, args.slug);\n if (doc && doc.status === \"published\") {\n return docToMarkdown(doc);\n }\n }\n\n return `No published document found with slug \"${args.slug}\".`;\n }\n\n async getSchema(collection: string) {\n const col = this.config.collections.find((c) => c.name === collection);\n if (!col) return { error: `Collection \"${collection}\" not found` };\n\n return {\n name: col.name,\n label: col.label ?? col.name,\n fields: col.fields.map((f) => ({\n name: f.name,\n type: f.type,\n label: f.label,\n required: f.required ?? false,\n })),\n };\n }\n\n async exportAll(args: { include_body?: boolean }) {\n const includeBody = args.include_body !== false;\n const result: Record<string, unknown[]> = {};\n\n for (const col of this.config.collections) {\n const { documents } = await this.content.findMany(col.name, {\n status: \"published\",\n limit: 10000,\n });\n\n result[col.name] = documents.map((doc) => {\n if (!includeBody) return docToSummary(doc);\n return { ...docToSummary(doc), data: doc.data };\n });\n }\n\n return result;\n }\n}\n","import type { JSONRPCMessage } from \"@modelcontextprotocol/sdk/types.js\";\n\n/**\n * MCP Transport that uses Web Streams API (compatible with Next.js App Router).\n * - GET handler creates the transport, returns transport.stream as SSE Response\n * - POST handler calls transport.handleClientMessage(body) to deliver messages\n */\nexport class NextSSETransport {\n private controller: ReadableStreamDefaultController<Uint8Array> | null = null;\n private encoder = new TextEncoder();\n\n readonly stream: ReadableStream<Uint8Array>;\n readonly sessionId: string;\n\n // MCP Transport interface callbacks\n onclose?: () => void;\n onerror?: (error: Error) => void;\n onmessage?: (message: JSONRPCMessage) => void;\n\n constructor(sessionId: string, endpointUrl?: string) {\n this.sessionId = sessionId;\n const encoder = this.encoder;\n this.stream = new ReadableStream<Uint8Array>({\n start: (ctrl) => {\n this.controller = ctrl;\n // MCP SSE protocol requires an initial \"endpoint\" event so the client\n // knows where to POST messages. Send it synchronously before any other event.\n if (endpointUrl) {\n ctrl.enqueue(encoder.encode(`event: endpoint\\ndata: ${endpointUrl}\\n\\n`));\n }\n },\n cancel: () => {\n this.onclose?.();\n },\n });\n }\n\n /** Called by MCP SDK on startup — nothing to do since stream is already open. */\n async start(): Promise<void> {}\n\n /** Called by MCP SDK to send a message to the client via SSE. */\n async send(message: JSONRPCMessage): Promise<void> {\n const line = `data: ${JSON.stringify(message)}\\n\\n`;\n this.controller?.enqueue(this.encoder.encode(line));\n }\n\n /** Called when the server shuts down or the client disconnects. */\n async close(): Promise<void> {\n try {\n this.controller?.close();\n } catch {\n // Already closed\n }\n this.onclose?.();\n }\n\n /** Called by the POST route handler to deliver a client message into the MCP server. */\n handleClientMessage(body: unknown): void {\n this.onmessage?.(body as JSONRPCMessage);\n }\n}\n\n// ── In-memory session store ───────────────────────────────────────\n\nconst sessions = new Map<string, NextSSETransport>();\n\nexport function getTransportSession(id: string): NextSSETransport | undefined {\n return sessions.get(id);\n}\n\nexport function registerTransportSession(t: NextSSETransport): void {\n sessions.set(t.sessionId, t);\n t.onclose = () => sessions.delete(t.sessionId);\n}\n","interface Counter {\n count: number;\n exportAllCount: number;\n resetAt: number;\n}\n\nconst counters = new Map<string, Counter>();\n\nconst WINDOW_MS = 60_000;\nconst MAX_REQUESTS = 60;\nconst MAX_EXPORT_ALL = 5;\n\nfunction getCounter(ip: string): Counter {\n const now = Date.now();\n let c = counters.get(ip);\n if (!c || now > c.resetAt) {\n c = { count: 0, exportAllCount: 0, resetAt: now + WINDOW_MS };\n counters.set(ip, c);\n }\n return c;\n}\n\nexport type RateLimitResult =\n | { allowed: true }\n | { allowed: false; reason: string };\n\nexport function checkRateLimit(ip: string, tool?: string): RateLimitResult {\n const c = getCounter(ip);\n\n if (tool === \"export_all\") {\n if (c.exportAllCount >= MAX_EXPORT_ALL) {\n return { allowed: false, reason: \"export_all rate limit exceeded (5/minute)\" };\n }\n c.exportAllCount++;\n }\n\n if (c.count >= MAX_REQUESTS) {\n return { allowed: false, reason: \"Rate limit exceeded (60 requests/minute)\" };\n }\n\n c.count++;\n return { allowed: true };\n}\n","import { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { PUBLIC_TOOLS } from \"./tools.js\";\nimport { ContentReader } from \"./reader.js\";\nimport type { ContentService, CmsConfig } from \"@webhouse/cms\";\n\nexport function createPublicMcpServer(\n content: ContentService,\n config: CmsConfig,\n): Server {\n const reader = new ContentReader(content, config);\n\n const server = new Server(\n { name: \"cms-public\", version: \"1.0.0\" },\n { capabilities: { tools: {} } },\n );\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (server as any).setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: PUBLIC_TOOLS,\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (req) => {\n const { name, arguments: args = {} } = req.params;\n const a = args as Record<string, unknown>;\n\n try {\n let result: unknown;\n\n switch (name) {\n case \"get_site_summary\":\n result = await reader.getSiteSummary();\n break;\n case \"list_collection\": {\n const listArgs: Parameters<typeof reader.listCollection>[0] = {\n collection: String(a[\"collection\"] ?? \"\"),\n };\n if (a[\"limit\"] !== undefined) listArgs.limit = a[\"limit\"] as number;\n if (a[\"offset\"] !== undefined) listArgs.offset = a[\"offset\"] as number;\n if (a[\"sort\"] !== undefined) listArgs.sort = a[\"sort\"] as \"date_desc\" | \"date_asc\" | \"title_asc\";\n result = await reader.listCollection(listArgs);\n break;\n }\n case \"search_content\": {\n const searchArgs: Parameters<typeof reader.search>[0] = {\n query: String(a[\"query\"] ?? \"\"),\n };\n if (a[\"collection\"] !== undefined) searchArgs.collection = a[\"collection\"] as string;\n if (a[\"limit\"] !== undefined) searchArgs.limit = a[\"limit\"] as number;\n result = await reader.search(searchArgs);\n break;\n }\n case \"get_page\": {\n const pageArgs: Parameters<typeof reader.getPage>[0] = {\n slug: String(a[\"slug\"] ?? \"\"),\n };\n if (a[\"collection\"] !== undefined) pageArgs.collection = a[\"collection\"] as string;\n result = await reader.getPage(pageArgs);\n break;\n }\n case \"get_schema\":\n result = await reader.getSchema(String(a[\"collection\"] ?? \"\"));\n break;\n case \"export_all\": {\n const exportArgs: Parameters<typeof reader.exportAll>[0] = {};\n if (a[\"include_body\"] !== undefined) exportArgs.include_body = a[\"include_body\"] as boolean;\n result = await reader.exportAll(exportArgs);\n break;\n }\n default:\n return { content: [{ type: \"text\" as const, text: `Unknown tool: ${name}` }], isError: true };\n }\n\n const text = typeof result === \"string\" ? result : JSON.stringify(result, null, 2);\n return { content: [{ type: \"text\" as const, text }] };\n } catch (err) {\n return {\n content: [{ type: \"text\" as const, text: `Error: ${(err as Error).message}` }],\n isError: true,\n };\n }\n });\n\n return server;\n}\n"],"mappings":";AAAO,IAAM,eAAe;AAAA,EAC1B;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IAGF,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY,CAAC;AAAA,MACb,UAAU,CAAC;AAAA,IACb;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IAEF,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY;AAAA,QACV,YAAY,EAAE,MAAM,UAAU,aAAa,4CAA4C;AAAA,QACvF,OAAO,EAAE,MAAM,UAAU,aAAa,oCAAoC;AAAA,QAC1E,QAAQ,EAAE,MAAM,UAAU,aAAa,gCAAgC;AAAA,QACvE,MAAM,EAAE,MAAM,UAAU,MAAM,CAAC,aAAa,YAAY,WAAW,GAAG,aAAa,kCAAkC;AAAA,MACvH;AAAA,MACA,UAAU,CAAC,YAAY;AAAA,IACzB;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IAEF,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY;AAAA,QACV,OAAO,EAAE,MAAM,UAAU,aAAa,gBAAgB;AAAA,QACtD,YAAY,EAAE,MAAM,UAAU,aAAa,6CAA6C;AAAA,QACxF,OAAO,EAAE,MAAM,UAAU,aAAa,mCAAmC;AAAA,MAC3E;AAAA,MACA,UAAU,CAAC,OAAO;AAAA,IACpB;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IAEF,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY;AAAA,QACV,MAAM,EAAE,MAAM,UAAU,aAAa,yCAAyC;AAAA,QAC9E,YAAY,EAAE,MAAM,UAAU,aAAa,4CAA4C;AAAA,MACzF;AAAA,MACA,UAAU,CAAC,MAAM;AAAA,IACnB;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IACF,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY;AAAA,QACV,YAAY,EAAE,MAAM,UAAU,aAAa,mBAAmB;AAAA,MAChE;AAAA,MACA,UAAU,CAAC,YAAY;AAAA,IACzB;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aACE;AAAA,IAEF,aAAa;AAAA,MACX,MAAM;AAAA,MACN,YAAY;AAAA,QACV,cAAc,EAAE,MAAM,WAAW,aAAa,oEAAoE;AAAA,MACpH;AAAA,MACA,UAAU,CAAC;AAAA,IACb;AAAA,EACF;AACF;;;AChFA,SAAS,WAAW,MAA+B,SAAS,KAAa;AACvE,QAAM,OAAQ,KAAK,SAAS,KAAK,KAAK,MAAM,KAAK,KAAK,SAAS,KAAK,KAAK,aAAa,KAAK;AAC3F,QAAM,OAAO,OAAO,IAAI,EAAE,QAAQ,YAAY,EAAE,EAAE,KAAK;AACvD,SAAO,KAAK,SAAS,SAAS,KAAK,MAAM,GAAG,MAAM,IAAI,WAAM;AAC9D;AAEA,SAAS,SAAS,MAAuC;AACvD,SAAO,OAAO,KAAK,OAAO,KAAK,KAAK,MAAM,KAAK,KAAK,SAAS,KAAK,UAAU;AAC9E;AAEA,SAAS,aAAa,KAAe;AACnC,SAAO;AAAA,IACL,MAAM,IAAI;AAAA,IACV,YAAY,IAAI;AAAA,IAChB,OAAO,SAAS,IAAI,IAAI;AAAA,IACxB,SAAS,WAAW,IAAI,IAAI;AAAA,IAC5B,MAAO,IAAI,KAAK,MAAM,KAA8B,CAAC;AAAA,IACrD,MAAM,IAAI,KAAK,MAAM,KAA2B,IAAI;AAAA,IACpD,WAAW,IAAI;AAAA,EACjB;AACF;AAEA,SAAS,cAAc,KAAuB;AAC5C,QAAM,QAAQ,SAAS,IAAI,IAAI;AAC/B,QAAM,QAAkB,CAAC,KAAK,KAAK,IAAI,EAAE;AAGzC,QAAM,KAAK,KAAK;AAChB,QAAM,KAAK,SAAS,IAAI,IAAI,EAAE;AAC9B,QAAM,KAAK,eAAe,IAAI,UAAU,EAAE;AAC1C,QAAM,KAAK,WAAW,IAAI,MAAM,EAAE;AAClC,MAAI,IAAI,KAAK,MAAM,EAAG,OAAM,KAAK,SAAS,IAAI,KAAK,MAAM,CAAC,EAAE;AAC5D,MAAI,IAAI,KAAK,QAAQ,EAAG,OAAM,KAAK,WAAW,IAAI,KAAK,QAAQ,CAAC,EAAE;AAClE,MAAI,IAAI,KAAK,MAAM,EAAG,OAAM,KAAK,SAAU,IAAI,KAAK,MAAM,EAAe,KAAK,IAAI,CAAC,EAAE;AACrF,QAAM,KAAK,OAAO,EAAE;AAGpB,QAAM,OAAQ,IAAI,KAAK,SAAS,KAAK,IAAI,KAAK,MAAM,KAAK,IAAI,KAAK,aAAa,KAAK;AACpF,MAAI,MAAM;AAER,UAAM,KAAK,KAAK,QAAQ,YAAY,EAAE,EAAE,KAAK,CAAC;AAAA,EAChD;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEO,IAAM,gBAAN,MAAoB;AAAA,EACzB,YACU,SACA,QACR;AAFQ;AACA;AAAA,EACP;AAAA,EAEH,MAAM,iBAAiB;AACrB,UAAM,kBAAyE,CAAC;AAChF,QAAI,YAAY;AAEhB,eAAW,OAAO,KAAK,OAAO,aAAa;AACzC,YAAM,EAAE,MAAM,IAAI,MAAM,KAAK,QAAQ,SAAS,IAAI,MAAM,EAAE,QAAQ,aAAa,OAAO,EAAE,CAAC;AACzF,sBAAgB,KAAK,EAAE,MAAM,IAAI,MAAM,OAAO,IAAI,SAAS,IAAI,MAAM,OAAO,MAAM,CAAC;AACnF,mBAAa;AAAA,IACf;AAEA,WAAO;AAAA,MACL,aAAa;AAAA,MACb,gBAAgB;AAAA,MAChB,eAAe,KAAK,OAAO,iBAAiB;AAAA,MAC5C,SAAS,KAAK,OAAO,WAAW,CAAC;AAAA,IACnC;AAAA,EACF;AAAA,EAEA,MAAM,eAAe,MAKlB;AACD,UAAM,QAAQ,KAAK,IAAI,KAAK,SAAS,IAAI,GAAG;AAC5C,UAAM,SAAS,KAAK,UAAU;AAE9B,QAAI,UAAU;AACd,QAAI,QAAwB;AAC5B,QAAI,KAAK,SAAS,YAAY;AAAE,gBAAU;AAAa,cAAQ;AAAA,IAAO,WAC7D,KAAK,SAAS,aAAa;AAAE,gBAAU;AAAS,cAAQ;AAAA,IAAO;AAExE,UAAM,EAAE,WAAW,MAAM,IAAI,MAAM,KAAK,QAAQ,SAAS,KAAK,YAAY;AAAA,MACxE,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,WAAO;AAAA,MACL,YAAY,KAAK;AAAA,MACjB;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,UAAU,IAAI,YAAY;AAAA,IACvC;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,MAA8D;AACzE,UAAM,QAAQ,KAAK,IAAI,KAAK,SAAS,IAAI,EAAE;AAC3C,UAAM,IAAI,KAAK,MAAM,YAAY;AAEjC,UAAM,cAAc,KAAK,aACrB,CAAC,KAAK,UAAU,IAChB,KAAK,OAAO,YAAY,IAAI,CAAC,MAAM,EAAE,IAAI;AAE7C,UAAM,UAAsE,CAAC;AAE7E,eAAW,OAAO,aAAa;AAC7B,YAAM,EAAE,UAAU,IAAI,MAAM,KAAK,QAAQ,SAAS,KAAK;AAAA,QACrD,QAAQ;AAAA,QACR,OAAO;AAAA,MACT,CAAC;AAED,iBAAW,OAAO,WAAW;AAC3B,cAAM,OAAO,KAAK,UAAU,IAAI,IAAI,EAAE,YAAY;AAClD,YAAI,KAAK,SAAS,CAAC,GAAG;AACpB,gBAAM,aAAa,SAAS,IAAI,IAAI,EAAE,YAAY,EAAE,SAAS,CAAC,IAAI,IAAI;AACtE,kBAAQ,KAAK,EAAE,GAAG,aAAa,GAAG,GAAG,OAAO,IAAI,WAAW,CAAC;AAAA,QAC9D;AAAA,MACF;AAAA,IACF;AAEA,YAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACxC,WAAO,EAAE,OAAO,KAAK,OAAO,OAAO,QAAQ,QAAQ,SAAS,QAAQ,MAAM,GAAG,KAAK,EAAE;AAAA,EACtF;AAAA,EAEA,MAAM,QAAQ,MAA6C;AACzD,UAAM,cAAc,KAAK,aACrB,CAAC,KAAK,UAAU,IAChB,KAAK,OAAO,YAAY,IAAI,CAAC,MAAM,EAAE,IAAI;AAE7C,eAAW,OAAO,aAAa;AAC7B,YAAM,MAAM,MAAM,KAAK,QAAQ,WAAW,KAAK,KAAK,IAAI;AACxD,UAAI,OAAO,IAAI,WAAW,aAAa;AACrC,eAAO,cAAc,GAAG;AAAA,MAC1B;AAAA,IACF;AAEA,WAAO,0CAA0C,KAAK,IAAI;AAAA,EAC5D;AAAA,EAEA,MAAM,UAAU,YAAoB;AAClC,UAAM,MAAM,KAAK,OAAO,YAAY,KAAK,CAAC,MAAM,EAAE,SAAS,UAAU;AACrE,QAAI,CAAC,IAAK,QAAO,EAAE,OAAO,eAAe,UAAU,cAAc;AAEjE,WAAO;AAAA,MACL,MAAM,IAAI;AAAA,MACV,OAAO,IAAI,SAAS,IAAI;AAAA,MACxB,QAAQ,IAAI,OAAO,IAAI,CAAC,OAAO;AAAA,QAC7B,MAAM,EAAE;AAAA,QACR,MAAM,EAAE;AAAA,QACR,OAAO,EAAE;AAAA,QACT,UAAU,EAAE,YAAY;AAAA,MAC1B,EAAE;AAAA,IACJ;AAAA,EACF;AAAA,EAEA,MAAM,UAAU,MAAkC;AAChD,UAAM,cAAc,KAAK,iBAAiB;AAC1C,UAAM,SAAoC,CAAC;AAE3C,eAAW,OAAO,KAAK,OAAO,aAAa;AACzC,YAAM,EAAE,UAAU,IAAI,MAAM,KAAK,QAAQ,SAAS,IAAI,MAAM;AAAA,QAC1D,QAAQ;AAAA,QACR,OAAO;AAAA,MACT,CAAC;AAED,aAAO,IAAI,IAAI,IAAI,UAAU,IAAI,CAAC,QAAQ;AACxC,YAAI,CAAC,YAAa,QAAO,aAAa,GAAG;AACzC,eAAO,EAAE,GAAG,aAAa,GAAG,GAAG,MAAM,IAAI,KAAK;AAAA,MAChD,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AACF;;;AC/KO,IAAM,mBAAN,MAAuB;AAAA,EACpB,aAAiE;AAAA,EACjE,UAAU,IAAI,YAAY;AAAA,EAEzB;AAAA,EACA;AAAA;AAAA,EAGT;AAAA,EACA;AAAA,EACA;AAAA,EAEA,YAAY,WAAmB,aAAsB;AACnD,SAAK,YAAY;AACjB,UAAM,UAAU,KAAK;AACrB,SAAK,SAAS,IAAI,eAA2B;AAAA,MAC3C,OAAO,CAAC,SAAS;AACf,aAAK,aAAa;AAGlB,YAAI,aAAa;AACf,eAAK,QAAQ,QAAQ,OAAO;AAAA,QAA0B,WAAW;AAAA;AAAA,CAAM,CAAC;AAAA,QAC1E;AAAA,MACF;AAAA,MACA,QAAQ,MAAM;AACZ,aAAK,UAAU;AAAA,MACjB;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,QAAuB;AAAA,EAAC;AAAA;AAAA,EAG9B,MAAM,KAAK,SAAwC;AACjD,UAAM,OAAO,SAAS,KAAK,UAAU,OAAO,CAAC;AAAA;AAAA;AAC7C,SAAK,YAAY,QAAQ,KAAK,QAAQ,OAAO,IAAI,CAAC;AAAA,EACpD;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC3B,QAAI;AACF,WAAK,YAAY,MAAM;AAAA,IACzB,QAAQ;AAAA,IAER;AACA,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA,EAGA,oBAAoB,MAAqB;AACvC,SAAK,YAAY,IAAsB;AAAA,EACzC;AACF;AAIA,IAAM,WAAW,oBAAI,IAA8B;AAE5C,SAAS,oBAAoB,IAA0C;AAC5E,SAAO,SAAS,IAAI,EAAE;AACxB;AAEO,SAAS,yBAAyB,GAA2B;AAClE,WAAS,IAAI,EAAE,WAAW,CAAC;AAC3B,IAAE,UAAU,MAAM,SAAS,OAAO,EAAE,SAAS;AAC/C;;;ACnEA,IAAM,WAAW,oBAAI,IAAqB;AAE1C,IAAM,YAAY;AAClB,IAAM,eAAe;AACrB,IAAM,iBAAiB;AAEvB,SAAS,WAAW,IAAqB;AACvC,QAAM,MAAM,KAAK,IAAI;AACrB,MAAI,IAAI,SAAS,IAAI,EAAE;AACvB,MAAI,CAAC,KAAK,MAAM,EAAE,SAAS;AACzB,QAAI,EAAE,OAAO,GAAG,gBAAgB,GAAG,SAAS,MAAM,UAAU;AAC5D,aAAS,IAAI,IAAI,CAAC;AAAA,EACpB;AACA,SAAO;AACT;AAMO,SAAS,eAAe,IAAY,MAAgC;AACzE,QAAM,IAAI,WAAW,EAAE;AAEvB,MAAI,SAAS,cAAc;AACzB,QAAI,EAAE,kBAAkB,gBAAgB;AACtC,aAAO,EAAE,SAAS,OAAO,QAAQ,4CAA4C;AAAA,IAC/E;AACA,MAAE;AAAA,EACJ;AAEA,MAAI,EAAE,SAAS,cAAc;AAC3B,WAAO,EAAE,SAAS,OAAO,QAAQ,2CAA2C;AAAA,EAC9E;AAEA,IAAE;AACF,SAAO,EAAE,SAAS,KAAK;AACzB;;;AC1CA,SAAS,cAAc;AACvB;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAKA,SAAS,sBACd,SACA,QACQ;AACR,QAAM,SAAS,IAAI,cAAc,SAAS,MAAM;AAEhD,QAAM,SAAS,IAAI;AAAA,IACjB,EAAE,MAAM,cAAc,SAAS,QAAQ;AAAA,IACvC,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,EAAE;AAAA,EAChC;AAGA,EAAC,OAAe,kBAAkB,wBAAwB,aAAa;AAAA,IACrE,OAAO;AAAA,EACT,EAAE;AAEF,SAAO,kBAAkB,uBAAuB,OAAO,QAAQ;AAC7D,UAAM,EAAE,MAAM,WAAW,OAAO,CAAC,EAAE,IAAI,IAAI;AAC3C,UAAM,IAAI;AAEV,QAAI;AACF,UAAI;AAEJ,cAAQ,MAAM;AAAA,QACZ,KAAK;AACH,mBAAS,MAAM,OAAO,eAAe;AACrC;AAAA,QACF,KAAK,mBAAmB;AACtB,gBAAM,WAAwD;AAAA,YAC5D,YAAY,OAAO,EAAE,YAAY,KAAK,EAAE;AAAA,UAC1C;AACA,cAAI,EAAE,OAAO,MAAM,OAAW,UAAS,QAAQ,EAAE,OAAO;AACxD,cAAI,EAAE,QAAQ,MAAM,OAAW,UAAS,SAAS,EAAE,QAAQ;AAC3D,cAAI,EAAE,MAAM,MAAM,OAAW,UAAS,OAAO,EAAE,MAAM;AACrD,mBAAS,MAAM,OAAO,eAAe,QAAQ;AAC7C;AAAA,QACF;AAAA,QACA,KAAK,kBAAkB;AACrB,gBAAM,aAAkD;AAAA,YACtD,OAAO,OAAO,EAAE,OAAO,KAAK,EAAE;AAAA,UAChC;AACA,cAAI,EAAE,YAAY,MAAM,OAAW,YAAW,aAAa,EAAE,YAAY;AACzE,cAAI,EAAE,OAAO,MAAM,OAAW,YAAW,QAAQ,EAAE,OAAO;AAC1D,mBAAS,MAAM,OAAO,OAAO,UAAU;AACvC;AAAA,QACF;AAAA,QACA,KAAK,YAAY;AACf,gBAAM,WAAiD;AAAA,YACrD,MAAM,OAAO,EAAE,MAAM,KAAK,EAAE;AAAA,UAC9B;AACA,cAAI,EAAE,YAAY,MAAM,OAAW,UAAS,aAAa,EAAE,YAAY;AACvE,mBAAS,MAAM,OAAO,QAAQ,QAAQ;AACtC;AAAA,QACF;AAAA,QACA,KAAK;AACH,mBAAS,MAAM,OAAO,UAAU,OAAO,EAAE,YAAY,KAAK,EAAE,CAAC;AAC7D;AAAA,QACF,KAAK,cAAc;AACjB,gBAAM,aAAqD,CAAC;AAC5D,cAAI,EAAE,cAAc,MAAM,OAAW,YAAW,eAAe,EAAE,cAAc;AAC/E,mBAAS,MAAM,OAAO,UAAU,UAAU;AAC1C;AAAA,QACF;AAAA,QACA;AACE,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,iBAAiB,IAAI,GAAG,CAAC,GAAG,SAAS,KAAK;AAAA,MAChG;AAEA,YAAM,OAAO,OAAO,WAAW,WAAW,SAAS,KAAK,UAAU,QAAQ,MAAM,CAAC;AACjF,aAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAiB,KAAK,CAAC,EAAE;AAAA,IACtD,SAAS,KAAK;AACZ,aAAO;AAAA,QACL,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,UAAW,IAAc,OAAO,GAAG,CAAC;AAAA,QAC7E,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO;AACT;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@webhouse/cms-mcp-client",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Public read-only MCP server for @webhouse/cms sites",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"dev": "tsup --watch",
|
|
25
|
+
"typecheck": "tsc --noEmit"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/webhousecode/cms.git",
|
|
30
|
+
"directory": "packages/cms-mcp-client"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/webhousecode/cms#readme",
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/webhousecode/cms/issues"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"cms",
|
|
38
|
+
"mcp",
|
|
39
|
+
"model-context-protocol",
|
|
40
|
+
"ai",
|
|
41
|
+
"read-only",
|
|
42
|
+
"content-api",
|
|
43
|
+
"typescript"
|
|
44
|
+
],
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=22"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
53
|
+
"@webhouse/cms": "^0.1.1"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^22.10.2",
|
|
57
|
+
"tsup": "^8.3.5",
|
|
58
|
+
"typescript": "^5.7.2"
|
|
59
|
+
}
|
|
60
|
+
}
|