@webhouse/cms-mcp-server 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 +31 -0
- package/dist/index.cjs +478 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +320 -0
- package/dist/index.d.ts +320 -0
- package/dist/index.js +438 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
// src/tools.ts
|
|
2
|
+
import { PUBLIC_TOOLS } from "@webhouse/cms-mcp-client";
|
|
3
|
+
var ADMIN_TOOLS = [
|
|
4
|
+
...PUBLIC_TOOLS,
|
|
5
|
+
// ── Content creation ──────────────────────────────────────────
|
|
6
|
+
{
|
|
7
|
+
name: "create_document",
|
|
8
|
+
description: "Creates a new document in a collection. Provide collection name and fields matching that collection's schema. Use get_schema first to learn required fields. Returns the new document slug.",
|
|
9
|
+
inputSchema: {
|
|
10
|
+
type: "object",
|
|
11
|
+
properties: {
|
|
12
|
+
collection: { type: "string" },
|
|
13
|
+
fields: { type: "object", description: "Document fields matching the collection schema." },
|
|
14
|
+
status: {
|
|
15
|
+
type: "string",
|
|
16
|
+
enum: ["draft", "published"],
|
|
17
|
+
description: "Initial status. Default: draft."
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
required: ["collection", "fields"]
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: "update_document",
|
|
25
|
+
description: "Updates specific fields on an existing document. Only provided fields change \u2014 others are preserved. AI-locked fields are skipped automatically.",
|
|
26
|
+
inputSchema: {
|
|
27
|
+
type: "object",
|
|
28
|
+
properties: {
|
|
29
|
+
collection: { type: "string" },
|
|
30
|
+
slug: { type: "string" },
|
|
31
|
+
fields: { type: "object", description: "Fields to update." }
|
|
32
|
+
},
|
|
33
|
+
required: ["collection", "slug", "fields"]
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "publish_document",
|
|
38
|
+
description: "Sets a document status to 'published'. Optionally triggers an immediate build.",
|
|
39
|
+
inputSchema: {
|
|
40
|
+
type: "object",
|
|
41
|
+
properties: {
|
|
42
|
+
collection: { type: "string" },
|
|
43
|
+
slug: { type: "string" },
|
|
44
|
+
auto_build: { type: "boolean", description: "Trigger site build after publishing. Default: false." }
|
|
45
|
+
},
|
|
46
|
+
required: ["collection", "slug"]
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "unpublish_document",
|
|
51
|
+
description: "Sets a document back to draft status.",
|
|
52
|
+
inputSchema: {
|
|
53
|
+
type: "object",
|
|
54
|
+
properties: {
|
|
55
|
+
collection: { type: "string" },
|
|
56
|
+
slug: { type: "string" }
|
|
57
|
+
},
|
|
58
|
+
required: ["collection", "slug"]
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
// ── AI content generation ─────────────────────────────────────
|
|
62
|
+
{
|
|
63
|
+
name: "generate_with_ai",
|
|
64
|
+
description: "Generates content for a new document using the CMS AI provider. Provide a natural language intent and target collection. Returns a draft document ready for review or direct publishing.",
|
|
65
|
+
inputSchema: {
|
|
66
|
+
type: "object",
|
|
67
|
+
properties: {
|
|
68
|
+
collection: { type: "string" },
|
|
69
|
+
intent: {
|
|
70
|
+
type: "string",
|
|
71
|
+
description: "Natural language description of what to create. E.g. 'A blog post about sustainable packaging trends, around 600 words, friendly tone.'"
|
|
72
|
+
},
|
|
73
|
+
status: {
|
|
74
|
+
type: "string",
|
|
75
|
+
enum: ["draft", "published"],
|
|
76
|
+
description: "Status for the created document. Default: draft."
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
required: ["collection", "intent"]
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "rewrite_field",
|
|
84
|
+
description: "Rewrites a specific field in an existing document. Use for: shorten, expand, change tone, translate, SEO-optimize. Respects AI Lock \u2014 locked fields return an error.",
|
|
85
|
+
inputSchema: {
|
|
86
|
+
type: "object",
|
|
87
|
+
properties: {
|
|
88
|
+
collection: { type: "string" },
|
|
89
|
+
slug: { type: "string" },
|
|
90
|
+
field: { type: "string", description: "Field name to rewrite." },
|
|
91
|
+
instruction: {
|
|
92
|
+
type: "string",
|
|
93
|
+
description: "Rewrite instruction. E.g. 'Translate to Danish', 'Make 30% shorter', 'Rewrite for a technical audience'."
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
required: ["collection", "slug", "field", "instruction"]
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
// ── Builds ────────────────────────────────────────────────────
|
|
100
|
+
{
|
|
101
|
+
name: "trigger_build",
|
|
102
|
+
description: "Triggers a static site build to make published content live. Returns build status information.",
|
|
103
|
+
inputSchema: {
|
|
104
|
+
type: "object",
|
|
105
|
+
properties: {
|
|
106
|
+
mode: {
|
|
107
|
+
type: "string",
|
|
108
|
+
enum: ["full", "incremental"],
|
|
109
|
+
description: "Build mode. Default: incremental."
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
required: []
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
// ── Drafts & review ───────────────────────────────────────────
|
|
116
|
+
{
|
|
117
|
+
name: "list_drafts",
|
|
118
|
+
description: "Lists all unpublished draft documents across all (or one) collection.",
|
|
119
|
+
inputSchema: {
|
|
120
|
+
type: "object",
|
|
121
|
+
properties: {
|
|
122
|
+
collection: { type: "string", description: "Optional: filter by collection." }
|
|
123
|
+
},
|
|
124
|
+
required: []
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "get_version_history",
|
|
129
|
+
description: "Returns revision history for a document.",
|
|
130
|
+
inputSchema: {
|
|
131
|
+
type: "object",
|
|
132
|
+
properties: {
|
|
133
|
+
collection: { type: "string" },
|
|
134
|
+
slug: { type: "string" },
|
|
135
|
+
limit: { type: "number", description: "Number of versions. Default 10." }
|
|
136
|
+
},
|
|
137
|
+
required: ["collection", "slug"]
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
];
|
|
141
|
+
var TOOL_SCOPES = {
|
|
142
|
+
get_site_summary: ["read"],
|
|
143
|
+
list_collection: ["read"],
|
|
144
|
+
search_content: ["read"],
|
|
145
|
+
get_page: ["read"],
|
|
146
|
+
get_schema: ["read"],
|
|
147
|
+
export_all: ["read"],
|
|
148
|
+
create_document: ["write"],
|
|
149
|
+
update_document: ["write"],
|
|
150
|
+
publish_document: ["publish"],
|
|
151
|
+
unpublish_document: ["publish"],
|
|
152
|
+
generate_with_ai: ["write", "ai"],
|
|
153
|
+
rewrite_field: ["write", "ai"],
|
|
154
|
+
trigger_build: ["deploy"],
|
|
155
|
+
list_drafts: ["read"],
|
|
156
|
+
get_version_history: ["read"]
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// src/auth.ts
|
|
160
|
+
import { timingSafeEqual } from "crypto";
|
|
161
|
+
function safeEqual(a, b) {
|
|
162
|
+
try {
|
|
163
|
+
const ab = Buffer.from(a);
|
|
164
|
+
const bb = Buffer.from(b);
|
|
165
|
+
if (ab.length !== bb.length) return false;
|
|
166
|
+
return timingSafeEqual(ab, bb);
|
|
167
|
+
} catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function validateApiKey(authHeader, keys) {
|
|
172
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
173
|
+
return { authenticated: false, error: "Missing Authorization: Bearer <key>" };
|
|
174
|
+
}
|
|
175
|
+
const provided = authHeader.slice(7).trim();
|
|
176
|
+
for (const k of keys) {
|
|
177
|
+
if (safeEqual(provided, k.key)) {
|
|
178
|
+
return { authenticated: true, label: k.label, scopes: k.scopes };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return { authenticated: false, error: "Invalid API key" };
|
|
182
|
+
}
|
|
183
|
+
function hasScope(userScopes, required) {
|
|
184
|
+
return required.every((r) => userScopes.includes(r));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/audit.ts
|
|
188
|
+
import { appendFileSync, mkdirSync } from "fs";
|
|
189
|
+
import { dirname } from "path";
|
|
190
|
+
var auditPath = null;
|
|
191
|
+
function initAuditLog(dataDir) {
|
|
192
|
+
auditPath = `${dataDir}/mcp-audit.jsonl`;
|
|
193
|
+
mkdirSync(dirname(auditPath), { recursive: true });
|
|
194
|
+
}
|
|
195
|
+
function writeAudit(entry) {
|
|
196
|
+
if (!auditPath) return;
|
|
197
|
+
try {
|
|
198
|
+
appendFileSync(auditPath, JSON.stringify(entry) + "\n", "utf-8");
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/server.ts
|
|
204
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
205
|
+
import {
|
|
206
|
+
CallToolRequestSchema,
|
|
207
|
+
ListToolsRequestSchema
|
|
208
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
209
|
+
import { ContentReader } from "@webhouse/cms-mcp-client";
|
|
210
|
+
function createAdminMcpServer(opts) {
|
|
211
|
+
const reader = new ContentReader(opts.content, opts.config);
|
|
212
|
+
const { content, config, scopes, actor, ai, onBuild } = opts;
|
|
213
|
+
const server = new Server(
|
|
214
|
+
{ name: "cms-admin", version: "1.0.0" },
|
|
215
|
+
{ capabilities: { tools: {} } }
|
|
216
|
+
);
|
|
217
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
218
|
+
tools: ADMIN_TOOLS
|
|
219
|
+
}));
|
|
220
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
221
|
+
const { name, arguments: args = {} } = req.params;
|
|
222
|
+
const a = args;
|
|
223
|
+
const toolName = name;
|
|
224
|
+
const required = TOOL_SCOPES[toolName] ?? ["read"];
|
|
225
|
+
if (!hasScope(scopes, required)) {
|
|
226
|
+
writeAudit({ timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool: name, actor, result: "error", error: "Insufficient scope" });
|
|
227
|
+
return {
|
|
228
|
+
content: [{ type: "text", text: `Forbidden: requires scopes [${required.join(", ")}]` }],
|
|
229
|
+
isError: true
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const audit = (auditResult, docRef, auditError) => {
|
|
233
|
+
const entry = { timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool: name, actor, result: auditResult };
|
|
234
|
+
if (docRef !== void 0) entry.documentRef = docRef;
|
|
235
|
+
if (auditError !== void 0) entry.error = auditError;
|
|
236
|
+
writeAudit(entry);
|
|
237
|
+
};
|
|
238
|
+
try {
|
|
239
|
+
let result;
|
|
240
|
+
switch (name) {
|
|
241
|
+
// ── Inherited public read tools ──────────────────────────
|
|
242
|
+
case "get_site_summary":
|
|
243
|
+
result = await reader.getSiteSummary();
|
|
244
|
+
break;
|
|
245
|
+
case "list_collection": {
|
|
246
|
+
const lArgs = { collection: String(a["collection"] ?? "") };
|
|
247
|
+
if (a["limit"] !== void 0) lArgs.limit = a["limit"];
|
|
248
|
+
if (a["offset"] !== void 0) lArgs.offset = a["offset"];
|
|
249
|
+
if (a["sort"] !== void 0) lArgs.sort = a["sort"];
|
|
250
|
+
result = await reader.listCollection(lArgs);
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
case "search_content": {
|
|
254
|
+
const sArgs = { query: String(a["query"] ?? "") };
|
|
255
|
+
if (a["collection"] !== void 0) sArgs.collection = a["collection"];
|
|
256
|
+
if (a["limit"] !== void 0) sArgs.limit = a["limit"];
|
|
257
|
+
result = await reader.search(sArgs);
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
case "get_page": {
|
|
261
|
+
const pArgs = { slug: String(a["slug"] ?? "") };
|
|
262
|
+
if (a["collection"] !== void 0) pArgs.collection = a["collection"];
|
|
263
|
+
result = await reader.getPage(pArgs);
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
case "get_schema":
|
|
267
|
+
result = await reader.getSchema(String(a["collection"] ?? ""));
|
|
268
|
+
break;
|
|
269
|
+
case "export_all": {
|
|
270
|
+
const eArgs = {};
|
|
271
|
+
if (a["include_body"] !== void 0) eArgs.include_body = a["include_body"];
|
|
272
|
+
result = await reader.exportAll(eArgs);
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
// ── Write tools ──────────────────────────────────────────
|
|
276
|
+
case "create_document": {
|
|
277
|
+
const col = String(a["collection"] ?? "");
|
|
278
|
+
const fields = a["fields"] ?? {};
|
|
279
|
+
const status = a["status"] ?? "draft";
|
|
280
|
+
const doc = await content.create(col, { data: fields, status }, { actor: "ai", aiModel: "mcp" });
|
|
281
|
+
result = { slug: doc.slug, id: doc.id, collection: col, status: doc.status };
|
|
282
|
+
audit("success", `${col}/${doc.slug}`);
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
case "update_document": {
|
|
286
|
+
const col = String(a["collection"] ?? "");
|
|
287
|
+
const slug = String(a["slug"] ?? "");
|
|
288
|
+
const fields = a["fields"] ?? {};
|
|
289
|
+
const existing = await content.findBySlug(col, slug);
|
|
290
|
+
if (!existing) {
|
|
291
|
+
result = { error: `Document "${slug}" not found in "${col}"` };
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
const { document: updated, skippedFields } = await content.updateWithContext(
|
|
295
|
+
col,
|
|
296
|
+
existing.id,
|
|
297
|
+
{ data: fields },
|
|
298
|
+
{ actor: "ai", aiModel: "mcp" }
|
|
299
|
+
);
|
|
300
|
+
result = { slug: updated.slug, skippedFields };
|
|
301
|
+
audit("success", `${col}/${slug}`);
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
case "publish_document": {
|
|
305
|
+
const col = String(a["collection"] ?? "");
|
|
306
|
+
const slug = String(a["slug"] ?? "");
|
|
307
|
+
const existing = await content.findBySlug(col, slug);
|
|
308
|
+
if (!existing) {
|
|
309
|
+
result = { error: `Document "${slug}" not found` };
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
await content.update(col, existing.id, { status: "published" }, { actor: "user" });
|
|
313
|
+
result = { slug, status: "published" };
|
|
314
|
+
audit("success", `${col}/${slug}`);
|
|
315
|
+
if (a["auto_build"] && onBuild) {
|
|
316
|
+
const buildResult = await onBuild("incremental");
|
|
317
|
+
result = { ...result, build: buildResult };
|
|
318
|
+
}
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
case "unpublish_document": {
|
|
322
|
+
const col = String(a["collection"] ?? "");
|
|
323
|
+
const slug = String(a["slug"] ?? "");
|
|
324
|
+
const existing = await content.findBySlug(col, slug);
|
|
325
|
+
if (!existing) {
|
|
326
|
+
result = { error: `Document "${slug}" not found` };
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
await content.update(col, existing.id, { status: "draft" }, { actor: "user" });
|
|
330
|
+
result = { slug, status: "draft" };
|
|
331
|
+
audit("success", `${col}/${slug}`);
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
// ── AI tools ─────────────────────────────────────────────
|
|
335
|
+
case "generate_with_ai": {
|
|
336
|
+
if (!ai) {
|
|
337
|
+
result = { error: "AI provider not configured on this server" };
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
const col = String(a["collection"] ?? "");
|
|
341
|
+
const intent = String(a["intent"] ?? "");
|
|
342
|
+
const status = a["status"] ?? "draft";
|
|
343
|
+
const generated = await ai.generate(intent, col);
|
|
344
|
+
const doc = await content.create(col, { data: generated.fields, slug: generated.slug, status }, { actor: "ai", aiModel: "mcp-generate" });
|
|
345
|
+
result = { slug: doc.slug, id: doc.id, collection: col, status: doc.status, fields: doc.data };
|
|
346
|
+
audit("success", `${col}/${doc.slug}`);
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
case "rewrite_field": {
|
|
350
|
+
if (!ai) {
|
|
351
|
+
result = { error: "AI provider not configured on this server" };
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
const col = String(a["collection"] ?? "");
|
|
355
|
+
const slug = String(a["slug"] ?? "");
|
|
356
|
+
const field = String(a["field"] ?? "");
|
|
357
|
+
const instruction = String(a["instruction"] ?? "");
|
|
358
|
+
const existing = await content.findBySlug(col, slug);
|
|
359
|
+
if (!existing) {
|
|
360
|
+
result = { error: `Document "${slug}" not found` };
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
const { isFieldLocked } = await import("@webhouse/cms");
|
|
364
|
+
if (isFieldLocked(existing._fieldMeta ?? {}, field)) {
|
|
365
|
+
result = { error: `FIELD_LOCKED: Field '${field}' is AI-locked and cannot be modified by agents` };
|
|
366
|
+
audit("error", `${col}/${slug}`, "FIELD_LOCKED");
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
const currentValue = String(existing.data[field] ?? "");
|
|
370
|
+
const rewritten = await ai.rewriteField(col, slug, field, instruction, currentValue);
|
|
371
|
+
await content.updateWithContext(col, existing.id, { data: { ...existing.data, [field]: rewritten } }, { actor: "ai", aiModel: "mcp-rewrite" });
|
|
372
|
+
result = { slug, field, rewritten };
|
|
373
|
+
audit("success", `${col}/${slug}`);
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
// ── Build tools ──────────────────────────────────────────
|
|
377
|
+
case "trigger_build": {
|
|
378
|
+
if (!onBuild) {
|
|
379
|
+
result = { error: "Build not configured on this server" };
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
const mode = a["mode"] ?? "incremental";
|
|
383
|
+
result = await onBuild(mode);
|
|
384
|
+
audit("success");
|
|
385
|
+
break;
|
|
386
|
+
}
|
|
387
|
+
// ── Draft tools ──────────────────────────────────────────
|
|
388
|
+
case "list_drafts": {
|
|
389
|
+
const collections = a["collection"] ? [String(a["collection"])] : config.collections.map((c) => c.name);
|
|
390
|
+
const drafts = [];
|
|
391
|
+
for (const col of collections) {
|
|
392
|
+
const { documents } = await content.findMany(col, { status: "draft", limit: 100 });
|
|
393
|
+
for (const doc of documents) {
|
|
394
|
+
drafts.push({
|
|
395
|
+
collection: col,
|
|
396
|
+
slug: doc.slug,
|
|
397
|
+
title: String(doc.data["title"] ?? doc.data["name"] ?? doc.slug),
|
|
398
|
+
updatedAt: doc.updatedAt
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
result = { total: drafts.length, drafts };
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
case "get_version_history": {
|
|
406
|
+
const col = String(a["collection"] ?? "");
|
|
407
|
+
const slug = String(a["slug"] ?? "");
|
|
408
|
+
const doc = await content.findBySlug(col, slug);
|
|
409
|
+
if (!doc) {
|
|
410
|
+
result = { error: `Document "${slug}" not found` };
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
result = { collection: col, slug, id: doc.id, note: "Version history is stored in _data/revisions/ by the admin UI" };
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
default:
|
|
417
|
+
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
418
|
+
}
|
|
419
|
+
const text = typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
420
|
+
return { content: [{ type: "text", text }] };
|
|
421
|
+
} catch (err) {
|
|
422
|
+
const msg = err.message;
|
|
423
|
+
audit("error", void 0, msg ?? "unknown error");
|
|
424
|
+
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
return server;
|
|
428
|
+
}
|
|
429
|
+
export {
|
|
430
|
+
ADMIN_TOOLS,
|
|
431
|
+
TOOL_SCOPES,
|
|
432
|
+
createAdminMcpServer,
|
|
433
|
+
hasScope,
|
|
434
|
+
initAuditLog,
|
|
435
|
+
validateApiKey,
|
|
436
|
+
writeAudit
|
|
437
|
+
};
|
|
438
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tools.ts","../src/auth.ts","../src/audit.ts","../src/server.ts"],"sourcesContent":["import { PUBLIC_TOOLS } from \"@webhouse/cms-mcp-client\";\n\nexport const ADMIN_TOOLS = [\n ...PUBLIC_TOOLS,\n\n // ── Content creation ──────────────────────────────────────────\n {\n name: \"create_document\",\n description:\n \"Creates a new document in a collection. Provide collection name and fields \" +\n \"matching that collection's schema. Use get_schema first to learn required fields. \" +\n \"Returns the new document slug.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n collection: { type: \"string\" },\n fields: { type: \"object\", description: \"Document fields matching the collection schema.\" },\n status: {\n type: \"string\",\n enum: [\"draft\", \"published\"],\n description: \"Initial status. Default: draft.\",\n },\n },\n required: [\"collection\", \"fields\"] as string[],\n },\n },\n {\n name: \"update_document\",\n description:\n \"Updates specific fields on an existing document. Only provided fields change — \" +\n \"others are preserved. AI-locked fields are skipped automatically.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n collection: { type: \"string\" },\n slug: { type: \"string\" },\n fields: { type: \"object\", description: \"Fields to update.\" },\n },\n required: [\"collection\", \"slug\", \"fields\"] as string[],\n },\n },\n {\n name: \"publish_document\",\n description: \"Sets a document status to 'published'. Optionally triggers an immediate build.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n collection: { type: \"string\" },\n slug: { type: \"string\" },\n auto_build: { type: \"boolean\", description: \"Trigger site build after publishing. Default: false.\" },\n },\n required: [\"collection\", \"slug\"] as string[],\n },\n },\n {\n name: \"unpublish_document\",\n description: \"Sets a document back to draft status.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n collection: { type: \"string\" },\n slug: { type: \"string\" },\n },\n required: [\"collection\", \"slug\"] as string[],\n },\n },\n\n // ── AI content generation ─────────────────────────────────────\n {\n name: \"generate_with_ai\",\n description:\n \"Generates content for a new document using the CMS AI provider. \" +\n \"Provide a natural language intent and target collection. \" +\n \"Returns a draft document ready for review or direct publishing.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n collection: { type: \"string\" },\n intent: {\n type: \"string\",\n description:\n \"Natural language description of what to create. E.g. \" +\n \"'A blog post about sustainable packaging trends, around 600 words, friendly tone.'\",\n },\n status: {\n type: \"string\",\n enum: [\"draft\", \"published\"],\n description: \"Status for the created document. Default: draft.\",\n },\n },\n required: [\"collection\", \"intent\"] as string[],\n },\n },\n {\n name: \"rewrite_field\",\n description:\n \"Rewrites a specific field in an existing document. \" +\n \"Use for: shorten, expand, change tone, translate, SEO-optimize. \" +\n \"Respects AI Lock — locked fields return an error.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n collection: { type: \"string\" },\n slug: { type: \"string\" },\n field: { type: \"string\", description: \"Field name to rewrite.\" },\n instruction: {\n type: \"string\",\n description:\n \"Rewrite instruction. E.g. 'Translate to Danish', 'Make 30% shorter', \" +\n \"'Rewrite for a technical audience'.\",\n },\n },\n required: [\"collection\", \"slug\", \"field\", \"instruction\"] as string[],\n },\n },\n\n // ── Builds ────────────────────────────────────────────────────\n {\n name: \"trigger_build\",\n description:\n \"Triggers a static site build to make published content live. \" +\n \"Returns build status information.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n mode: {\n type: \"string\",\n enum: [\"full\", \"incremental\"],\n description: \"Build mode. Default: incremental.\",\n },\n },\n required: [] as string[],\n },\n },\n\n // ── Drafts & review ───────────────────────────────────────────\n {\n name: \"list_drafts\",\n description: \"Lists all unpublished draft documents across all (or one) collection.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n collection: { type: \"string\", description: \"Optional: filter by collection.\" },\n },\n required: [] as string[],\n },\n },\n {\n name: \"get_version_history\",\n description: \"Returns revision history for a document.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n collection: { type: \"string\" },\n slug: { type: \"string\" },\n limit: { type: \"number\", description: \"Number of versions. Default 10.\" },\n },\n required: [\"collection\", \"slug\"] as string[],\n },\n },\n] as const;\n\nexport type AdminToolName = (typeof ADMIN_TOOLS)[number][\"name\"];\n\n// Which scopes are required per tool\nexport const TOOL_SCOPES: Record<AdminToolName, string[]> = {\n get_site_summary: [\"read\"],\n list_collection: [\"read\"],\n search_content: [\"read\"],\n get_page: [\"read\"],\n get_schema: [\"read\"],\n export_all: [\"read\"],\n create_document: [\"write\"],\n update_document: [\"write\"],\n publish_document: [\"publish\"],\n unpublish_document: [\"publish\"],\n generate_with_ai: [\"write\", \"ai\"],\n rewrite_field: [\"write\", \"ai\"],\n trigger_build: [\"deploy\"],\n list_drafts: [\"read\"],\n get_version_history: [\"read\"],\n};\n","import { timingSafeEqual } from \"node:crypto\";\n\nexport interface ApiKeyConfig {\n key: string;\n label: string;\n scopes: string[];\n}\n\nexport type AuthResult =\n | { authenticated: true; label: string; scopes: string[] }\n | { authenticated: false; error: string };\n\n// Timing-safe string compare to prevent timing attacks\nfunction safeEqual(a: string, b: string): boolean {\n try {\n const ab = Buffer.from(a);\n const bb = Buffer.from(b);\n if (ab.length !== bb.length) return false;\n return timingSafeEqual(ab, bb);\n } catch {\n return false;\n }\n}\n\nexport function validateApiKey(\n authHeader: string | null | undefined,\n keys: ApiKeyConfig[],\n): { authenticated: true; label: string; scopes: string[] } | { authenticated: false; error: string } {\n if (!authHeader || !authHeader.startsWith(\"Bearer \")) {\n return { authenticated: false, error: \"Missing Authorization: Bearer <key>\" };\n }\n\n const provided = authHeader.slice(7).trim();\n\n for (const k of keys) {\n if (safeEqual(provided, k.key)) {\n return { authenticated: true, label: k.label, scopes: k.scopes };\n }\n }\n\n return { authenticated: false, error: \"Invalid API key\" };\n}\n\nexport function hasScope(userScopes: string[], required: string[]): boolean {\n return required.every((r) => userScopes.includes(r));\n}\n","import { appendFileSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\nexport interface AuditEntry {\n timestamp: string;\n tool: string;\n actor: string;\n result: \"success\" | \"error\";\n documentRef?: string;\n error?: string;\n}\n\nlet auditPath: string | null = null;\n\nexport function initAuditLog(dataDir: string): void {\n auditPath = `${dataDir}/mcp-audit.jsonl`;\n mkdirSync(dirname(auditPath), { recursive: true });\n}\n\nexport function writeAudit(entry: AuditEntry): void {\n if (!auditPath) return;\n try {\n appendFileSync(auditPath, JSON.stringify(entry) + \"\\n\", \"utf-8\");\n } catch {\n // Non-fatal — don't crash the server if audit fails\n }\n}\n","import { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { ContentReader } from \"@webhouse/cms-mcp-client\";\nimport { ADMIN_TOOLS, TOOL_SCOPES } from \"./tools.js\";\nimport { hasScope } from \"./auth.js\";\nimport { writeAudit } from \"./audit.js\";\nimport type { ContentService, CmsConfig } from \"@webhouse/cms\";\nimport type { AdminToolName } from \"./tools.js\";\n\nexport interface AiGenerator {\n generate(intent: string, collectionName: string): Promise<{ fields: Record<string, string>; slug: string }>;\n rewriteField(collection: string, slug: string, field: string, instruction: string, currentValue: string): Promise<string>;\n}\n\nexport interface AdminServerOptions {\n content: ContentService;\n config: CmsConfig;\n /** Resolved scopes for this session — determined by auth middleware before connect */\n scopes: string[];\n /** Label identifying the API key / user for audit log */\n actor: string;\n /** Optional AI generator — if not provided, AI tools return a \"not configured\" error */\n ai?: AiGenerator;\n /** Called when a write tool needs to trigger a build */\n onBuild?: (mode: \"full\" | \"incremental\") => Promise<{ ok: boolean; message: string }>;\n}\n\nexport function createAdminMcpServer(opts: AdminServerOptions): Server {\n const reader = new ContentReader(opts.content, opts.config);\n const { content, config, scopes, actor, ai, onBuild } = opts;\n\n const server = new Server(\n { name: \"cms-admin\", version: \"1.0.0\" },\n { capabilities: { tools: {} } },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: ADMIN_TOOLS as unknown as unknown[],\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (req) => {\n const { name, arguments: args = {} } = req.params;\n const a = args as Record<string, unknown>;\n const toolName = name as AdminToolName;\n\n // ── Scope check ───────────────────────────────────────────────\n const required = TOOL_SCOPES[toolName] ?? [\"read\"];\n if (!hasScope(scopes, required)) {\n writeAudit({ timestamp: new Date().toISOString(), tool: name, actor, result: \"error\", error: \"Insufficient scope\" } satisfies import(\"./audit.js\").AuditEntry);\n return {\n content: [{ type: \"text\" as const, text: `Forbidden: requires scopes [${required.join(\", \")}]` }],\n isError: true,\n };\n }\n\n const audit = (auditResult: \"success\" | \"error\", docRef?: string, auditError?: string) => {\n const entry: import(\"./audit.js\").AuditEntry = { timestamp: new Date().toISOString(), tool: name, actor, result: auditResult };\n if (docRef !== undefined) entry.documentRef = docRef;\n if (auditError !== undefined) entry.error = auditError;\n writeAudit(entry);\n };\n\n try {\n let result: unknown;\n\n switch (name) {\n // ── Inherited public read tools ──────────────────────────\n case \"get_site_summary\":\n result = await reader.getSiteSummary();\n break;\n case \"list_collection\": {\n const lArgs: Parameters<typeof reader.listCollection>[0] = { collection: String(a[\"collection\"] ?? \"\") };\n if (a[\"limit\"] !== undefined) lArgs.limit = a[\"limit\"] as number;\n if (a[\"offset\"] !== undefined) lArgs.offset = a[\"offset\"] as number;\n if (a[\"sort\"] !== undefined) lArgs.sort = a[\"sort\"] as \"date_desc\" | \"date_asc\" | \"title_asc\";\n result = await reader.listCollection(lArgs);\n break;\n }\n case \"search_content\": {\n const sArgs: Parameters<typeof reader.search>[0] = { query: String(a[\"query\"] ?? \"\") };\n if (a[\"collection\"] !== undefined) sArgs.collection = a[\"collection\"] as string;\n if (a[\"limit\"] !== undefined) sArgs.limit = a[\"limit\"] as number;\n result = await reader.search(sArgs);\n break;\n }\n case \"get_page\": {\n const pArgs: Parameters<typeof reader.getPage>[0] = { slug: String(a[\"slug\"] ?? \"\") };\n if (a[\"collection\"] !== undefined) pArgs.collection = a[\"collection\"] as string;\n result = await reader.getPage(pArgs);\n break;\n }\n case \"get_schema\":\n result = await reader.getSchema(String(a[\"collection\"] ?? \"\"));\n break;\n case \"export_all\": {\n const eArgs: Parameters<typeof reader.exportAll>[0] = {};\n if (a[\"include_body\"] !== undefined) eArgs.include_body = a[\"include_body\"] as boolean;\n result = await reader.exportAll(eArgs);\n break;\n }\n\n // ── Write tools ──────────────────────────────────────────\n case \"create_document\": {\n const col = String(a[\"collection\"] ?? \"\");\n const fields = (a[\"fields\"] as Record<string, unknown>) ?? {};\n const status = (a[\"status\"] as \"draft\" | \"published\") ?? \"draft\";\n const doc = await content.create(col, { data: fields, status }, { actor: \"ai\", aiModel: \"mcp\" });\n result = { slug: doc.slug, id: doc.id, collection: col, status: doc.status };\n audit(\"success\", `${col}/${doc.slug}`);\n break;\n }\n\n case \"update_document\": {\n const col = String(a[\"collection\"] ?? \"\");\n const slug = String(a[\"slug\"] ?? \"\");\n const fields = (a[\"fields\"] as Record<string, unknown>) ?? {};\n const existing = await content.findBySlug(col, slug);\n if (!existing) {\n result = { error: `Document \"${slug}\" not found in \"${col}\"` };\n break;\n }\n const { document: updated, skippedFields } = await content.updateWithContext(\n col, existing.id, { data: fields }, { actor: \"ai\", aiModel: \"mcp\" }\n );\n result = { slug: updated.slug, skippedFields };\n audit(\"success\", `${col}/${slug}`);\n break;\n }\n\n case \"publish_document\": {\n const col = String(a[\"collection\"] ?? \"\");\n const slug = String(a[\"slug\"] ?? \"\");\n const existing = await content.findBySlug(col, slug);\n if (!existing) { result = { error: `Document \"${slug}\" not found` }; break; }\n await content.update(col, existing.id, { status: \"published\" }, { actor: \"user\" });\n result = { slug, status: \"published\" };\n audit(\"success\", `${col}/${slug}`);\n if (a[\"auto_build\"] && onBuild) {\n const buildResult = await onBuild(\"incremental\");\n result = { ...result as object, build: buildResult };\n }\n break;\n }\n\n case \"unpublish_document\": {\n const col = String(a[\"collection\"] ?? \"\");\n const slug = String(a[\"slug\"] ?? \"\");\n const existing = await content.findBySlug(col, slug);\n if (!existing) { result = { error: `Document \"${slug}\" not found` }; break; }\n await content.update(col, existing.id, { status: \"draft\" }, { actor: \"user\" });\n result = { slug, status: \"draft\" };\n audit(\"success\", `${col}/${slug}`);\n break;\n }\n\n // ── AI tools ─────────────────────────────────────────────\n case \"generate_with_ai\": {\n if (!ai) { result = { error: \"AI provider not configured on this server\" }; break; }\n const col = String(a[\"collection\"] ?? \"\");\n const intent = String(a[\"intent\"] ?? \"\");\n const status = (a[\"status\"] as \"draft\" | \"published\") ?? \"draft\";\n const generated = await ai.generate(intent, col);\n const doc = await content.create(col, { data: generated.fields, slug: generated.slug, status }, { actor: \"ai\", aiModel: \"mcp-generate\" });\n result = { slug: doc.slug, id: doc.id, collection: col, status: doc.status, fields: doc.data };\n audit(\"success\", `${col}/${doc.slug}`);\n break;\n }\n\n case \"rewrite_field\": {\n if (!ai) { result = { error: \"AI provider not configured on this server\" }; break; }\n const col = String(a[\"collection\"] ?? \"\");\n const slug = String(a[\"slug\"] ?? \"\");\n const field = String(a[\"field\"] ?? \"\");\n const instruction = String(a[\"instruction\"] ?? \"\");\n const existing = await content.findBySlug(col, slug);\n if (!existing) { result = { error: `Document \"${slug}\" not found` }; break; }\n\n // Check AI lock\n const { isFieldLocked } = await import(\"@webhouse/cms\");\n if (isFieldLocked(existing._fieldMeta ?? {}, field)) {\n result = { error: `FIELD_LOCKED: Field '${field}' is AI-locked and cannot be modified by agents` };\n audit(\"error\", `${col}/${slug}`, \"FIELD_LOCKED\");\n break;\n }\n\n const currentValue = String(existing.data[field] ?? \"\");\n const rewritten = await ai.rewriteField(col, slug, field, instruction, currentValue);\n await content.updateWithContext(col, existing.id, { data: { ...existing.data, [field]: rewritten } }, { actor: \"ai\", aiModel: \"mcp-rewrite\" });\n result = { slug, field, rewritten };\n audit(\"success\", `${col}/${slug}`);\n break;\n }\n\n // ── Build tools ──────────────────────────────────────────\n case \"trigger_build\": {\n if (!onBuild) { result = { error: \"Build not configured on this server\" }; break; }\n const mode = (a[\"mode\"] as \"full\" | \"incremental\") ?? \"incremental\";\n result = await onBuild(mode);\n audit(\"success\");\n break;\n }\n\n // ── Draft tools ──────────────────────────────────────────\n case \"list_drafts\": {\n const collections = a[\"collection\"]\n ? [String(a[\"collection\"])]\n : config.collections.map((c) => c.name);\n const drafts = [];\n for (const col of collections) {\n const { documents } = await content.findMany(col, { status: \"draft\", limit: 100 });\n for (const doc of documents) {\n drafts.push({\n collection: col,\n slug: doc.slug,\n title: String(doc.data[\"title\"] ?? doc.data[\"name\"] ?? doc.slug),\n updatedAt: doc.updatedAt,\n });\n }\n }\n result = { total: drafts.length, drafts };\n break;\n }\n\n case \"get_version_history\": {\n const col = String(a[\"collection\"] ?? \"\");\n const slug = String(a[\"slug\"] ?? \"\");\n const doc = await content.findBySlug(col, slug);\n if (!doc) { result = { error: `Document \"${slug}\" not found` }; break; }\n result = { collection: col, slug, id: doc.id, note: \"Version history is stored in _data/revisions/ by the admin UI\" };\n break;\n }\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 const msg = (err as Error).message;\n audit(\"error\", undefined, msg ?? \"unknown error\");\n return { content: [{ type: \"text\" as const, text: `Error: ${msg}` }], isError: true };\n }\n });\n\n return server;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@webhouse/cms-mcp-server",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Authenticated read+write MCP server for @webhouse/cms content production",
|
|
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-server"
|
|
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
|
+
"content-management",
|
|
42
|
+
"authenticated",
|
|
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
|
+
"@webhouse/cms-mcp-client": "^0.1.1"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/node": "^22.10.2",
|
|
58
|
+
"tsup": "^8.3.5",
|
|
59
|
+
"typescript": "^5.7.2"
|
|
60
|
+
}
|
|
61
|
+
}
|