@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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 WebHouse
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @webhouse/cms-mcp-server
|
|
2
|
+
|
|
3
|
+
Authenticated read+write [Model Context Protocol](https://modelcontextprotocol.io/) server for `@webhouse/cms`. Enables AI assistants to create, update, and manage CMS content with API key authentication.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @webhouse/cms-mcp-server
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createMcpServer } from "@webhouse/cms-mcp-server";
|
|
15
|
+
|
|
16
|
+
const server = createMcpServer({
|
|
17
|
+
siteUrl: "https://my-site.example.com",
|
|
18
|
+
apiKey: process.env.CMS_MCP_API_KEY,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// The MCP server exposes authenticated tools for creating,
|
|
22
|
+
// updating, publishing, and managing CMS content.
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Documentation
|
|
26
|
+
|
|
27
|
+
See the [main repository](https://github.com/webhousecode/cms) for full documentation.
|
|
28
|
+
|
|
29
|
+
## License
|
|
30
|
+
|
|
31
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
ADMIN_TOOLS: () => ADMIN_TOOLS,
|
|
34
|
+
TOOL_SCOPES: () => TOOL_SCOPES,
|
|
35
|
+
createAdminMcpServer: () => createAdminMcpServer,
|
|
36
|
+
hasScope: () => hasScope,
|
|
37
|
+
initAuditLog: () => initAuditLog,
|
|
38
|
+
validateApiKey: () => validateApiKey,
|
|
39
|
+
writeAudit: () => writeAudit
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(index_exports);
|
|
42
|
+
|
|
43
|
+
// src/tools.ts
|
|
44
|
+
var import_cms_mcp_client = require("@webhouse/cms-mcp-client");
|
|
45
|
+
var ADMIN_TOOLS = [
|
|
46
|
+
...import_cms_mcp_client.PUBLIC_TOOLS,
|
|
47
|
+
// ── Content creation ──────────────────────────────────────────
|
|
48
|
+
{
|
|
49
|
+
name: "create_document",
|
|
50
|
+
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.",
|
|
51
|
+
inputSchema: {
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: {
|
|
54
|
+
collection: { type: "string" },
|
|
55
|
+
fields: { type: "object", description: "Document fields matching the collection schema." },
|
|
56
|
+
status: {
|
|
57
|
+
type: "string",
|
|
58
|
+
enum: ["draft", "published"],
|
|
59
|
+
description: "Initial status. Default: draft."
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
required: ["collection", "fields"]
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: "update_document",
|
|
67
|
+
description: "Updates specific fields on an existing document. Only provided fields change \u2014 others are preserved. AI-locked fields are skipped automatically.",
|
|
68
|
+
inputSchema: {
|
|
69
|
+
type: "object",
|
|
70
|
+
properties: {
|
|
71
|
+
collection: { type: "string" },
|
|
72
|
+
slug: { type: "string" },
|
|
73
|
+
fields: { type: "object", description: "Fields to update." }
|
|
74
|
+
},
|
|
75
|
+
required: ["collection", "slug", "fields"]
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "publish_document",
|
|
80
|
+
description: "Sets a document status to 'published'. Optionally triggers an immediate build.",
|
|
81
|
+
inputSchema: {
|
|
82
|
+
type: "object",
|
|
83
|
+
properties: {
|
|
84
|
+
collection: { type: "string" },
|
|
85
|
+
slug: { type: "string" },
|
|
86
|
+
auto_build: { type: "boolean", description: "Trigger site build after publishing. Default: false." }
|
|
87
|
+
},
|
|
88
|
+
required: ["collection", "slug"]
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "unpublish_document",
|
|
93
|
+
description: "Sets a document back to draft status.",
|
|
94
|
+
inputSchema: {
|
|
95
|
+
type: "object",
|
|
96
|
+
properties: {
|
|
97
|
+
collection: { type: "string" },
|
|
98
|
+
slug: { type: "string" }
|
|
99
|
+
},
|
|
100
|
+
required: ["collection", "slug"]
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
// ── AI content generation ─────────────────────────────────────
|
|
104
|
+
{
|
|
105
|
+
name: "generate_with_ai",
|
|
106
|
+
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.",
|
|
107
|
+
inputSchema: {
|
|
108
|
+
type: "object",
|
|
109
|
+
properties: {
|
|
110
|
+
collection: { type: "string" },
|
|
111
|
+
intent: {
|
|
112
|
+
type: "string",
|
|
113
|
+
description: "Natural language description of what to create. E.g. 'A blog post about sustainable packaging trends, around 600 words, friendly tone.'"
|
|
114
|
+
},
|
|
115
|
+
status: {
|
|
116
|
+
type: "string",
|
|
117
|
+
enum: ["draft", "published"],
|
|
118
|
+
description: "Status for the created document. Default: draft."
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
required: ["collection", "intent"]
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: "rewrite_field",
|
|
126
|
+
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.",
|
|
127
|
+
inputSchema: {
|
|
128
|
+
type: "object",
|
|
129
|
+
properties: {
|
|
130
|
+
collection: { type: "string" },
|
|
131
|
+
slug: { type: "string" },
|
|
132
|
+
field: { type: "string", description: "Field name to rewrite." },
|
|
133
|
+
instruction: {
|
|
134
|
+
type: "string",
|
|
135
|
+
description: "Rewrite instruction. E.g. 'Translate to Danish', 'Make 30% shorter', 'Rewrite for a technical audience'."
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
required: ["collection", "slug", "field", "instruction"]
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
// ── Builds ────────────────────────────────────────────────────
|
|
142
|
+
{
|
|
143
|
+
name: "trigger_build",
|
|
144
|
+
description: "Triggers a static site build to make published content live. Returns build status information.",
|
|
145
|
+
inputSchema: {
|
|
146
|
+
type: "object",
|
|
147
|
+
properties: {
|
|
148
|
+
mode: {
|
|
149
|
+
type: "string",
|
|
150
|
+
enum: ["full", "incremental"],
|
|
151
|
+
description: "Build mode. Default: incremental."
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
required: []
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
// ── Drafts & review ───────────────────────────────────────────
|
|
158
|
+
{
|
|
159
|
+
name: "list_drafts",
|
|
160
|
+
description: "Lists all unpublished draft documents across all (or one) collection.",
|
|
161
|
+
inputSchema: {
|
|
162
|
+
type: "object",
|
|
163
|
+
properties: {
|
|
164
|
+
collection: { type: "string", description: "Optional: filter by collection." }
|
|
165
|
+
},
|
|
166
|
+
required: []
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: "get_version_history",
|
|
171
|
+
description: "Returns revision history for a document.",
|
|
172
|
+
inputSchema: {
|
|
173
|
+
type: "object",
|
|
174
|
+
properties: {
|
|
175
|
+
collection: { type: "string" },
|
|
176
|
+
slug: { type: "string" },
|
|
177
|
+
limit: { type: "number", description: "Number of versions. Default 10." }
|
|
178
|
+
},
|
|
179
|
+
required: ["collection", "slug"]
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
];
|
|
183
|
+
var TOOL_SCOPES = {
|
|
184
|
+
get_site_summary: ["read"],
|
|
185
|
+
list_collection: ["read"],
|
|
186
|
+
search_content: ["read"],
|
|
187
|
+
get_page: ["read"],
|
|
188
|
+
get_schema: ["read"],
|
|
189
|
+
export_all: ["read"],
|
|
190
|
+
create_document: ["write"],
|
|
191
|
+
update_document: ["write"],
|
|
192
|
+
publish_document: ["publish"],
|
|
193
|
+
unpublish_document: ["publish"],
|
|
194
|
+
generate_with_ai: ["write", "ai"],
|
|
195
|
+
rewrite_field: ["write", "ai"],
|
|
196
|
+
trigger_build: ["deploy"],
|
|
197
|
+
list_drafts: ["read"],
|
|
198
|
+
get_version_history: ["read"]
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// src/auth.ts
|
|
202
|
+
var import_node_crypto = require("crypto");
|
|
203
|
+
function safeEqual(a, b) {
|
|
204
|
+
try {
|
|
205
|
+
const ab = Buffer.from(a);
|
|
206
|
+
const bb = Buffer.from(b);
|
|
207
|
+
if (ab.length !== bb.length) return false;
|
|
208
|
+
return (0, import_node_crypto.timingSafeEqual)(ab, bb);
|
|
209
|
+
} catch {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function validateApiKey(authHeader, keys) {
|
|
214
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
215
|
+
return { authenticated: false, error: "Missing Authorization: Bearer <key>" };
|
|
216
|
+
}
|
|
217
|
+
const provided = authHeader.slice(7).trim();
|
|
218
|
+
for (const k of keys) {
|
|
219
|
+
if (safeEqual(provided, k.key)) {
|
|
220
|
+
return { authenticated: true, label: k.label, scopes: k.scopes };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return { authenticated: false, error: "Invalid API key" };
|
|
224
|
+
}
|
|
225
|
+
function hasScope(userScopes, required) {
|
|
226
|
+
return required.every((r) => userScopes.includes(r));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/audit.ts
|
|
230
|
+
var import_node_fs = require("fs");
|
|
231
|
+
var import_node_path = require("path");
|
|
232
|
+
var auditPath = null;
|
|
233
|
+
function initAuditLog(dataDir) {
|
|
234
|
+
auditPath = `${dataDir}/mcp-audit.jsonl`;
|
|
235
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(auditPath), { recursive: true });
|
|
236
|
+
}
|
|
237
|
+
function writeAudit(entry) {
|
|
238
|
+
if (!auditPath) return;
|
|
239
|
+
try {
|
|
240
|
+
(0, import_node_fs.appendFileSync)(auditPath, JSON.stringify(entry) + "\n", "utf-8");
|
|
241
|
+
} catch {
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// src/server.ts
|
|
246
|
+
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
247
|
+
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
248
|
+
var import_cms_mcp_client2 = require("@webhouse/cms-mcp-client");
|
|
249
|
+
function createAdminMcpServer(opts) {
|
|
250
|
+
const reader = new import_cms_mcp_client2.ContentReader(opts.content, opts.config);
|
|
251
|
+
const { content, config, scopes, actor, ai, onBuild } = opts;
|
|
252
|
+
const server = new import_server.Server(
|
|
253
|
+
{ name: "cms-admin", version: "1.0.0" },
|
|
254
|
+
{ capabilities: { tools: {} } }
|
|
255
|
+
);
|
|
256
|
+
server.setRequestHandler(import_types.ListToolsRequestSchema, async () => ({
|
|
257
|
+
tools: ADMIN_TOOLS
|
|
258
|
+
}));
|
|
259
|
+
server.setRequestHandler(import_types.CallToolRequestSchema, async (req) => {
|
|
260
|
+
const { name, arguments: args = {} } = req.params;
|
|
261
|
+
const a = args;
|
|
262
|
+
const toolName = name;
|
|
263
|
+
const required = TOOL_SCOPES[toolName] ?? ["read"];
|
|
264
|
+
if (!hasScope(scopes, required)) {
|
|
265
|
+
writeAudit({ timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool: name, actor, result: "error", error: "Insufficient scope" });
|
|
266
|
+
return {
|
|
267
|
+
content: [{ type: "text", text: `Forbidden: requires scopes [${required.join(", ")}]` }],
|
|
268
|
+
isError: true
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
const audit = (auditResult, docRef, auditError) => {
|
|
272
|
+
const entry = { timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool: name, actor, result: auditResult };
|
|
273
|
+
if (docRef !== void 0) entry.documentRef = docRef;
|
|
274
|
+
if (auditError !== void 0) entry.error = auditError;
|
|
275
|
+
writeAudit(entry);
|
|
276
|
+
};
|
|
277
|
+
try {
|
|
278
|
+
let result;
|
|
279
|
+
switch (name) {
|
|
280
|
+
// ── Inherited public read tools ──────────────────────────
|
|
281
|
+
case "get_site_summary":
|
|
282
|
+
result = await reader.getSiteSummary();
|
|
283
|
+
break;
|
|
284
|
+
case "list_collection": {
|
|
285
|
+
const lArgs = { collection: String(a["collection"] ?? "") };
|
|
286
|
+
if (a["limit"] !== void 0) lArgs.limit = a["limit"];
|
|
287
|
+
if (a["offset"] !== void 0) lArgs.offset = a["offset"];
|
|
288
|
+
if (a["sort"] !== void 0) lArgs.sort = a["sort"];
|
|
289
|
+
result = await reader.listCollection(lArgs);
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
case "search_content": {
|
|
293
|
+
const sArgs = { query: String(a["query"] ?? "") };
|
|
294
|
+
if (a["collection"] !== void 0) sArgs.collection = a["collection"];
|
|
295
|
+
if (a["limit"] !== void 0) sArgs.limit = a["limit"];
|
|
296
|
+
result = await reader.search(sArgs);
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
case "get_page": {
|
|
300
|
+
const pArgs = { slug: String(a["slug"] ?? "") };
|
|
301
|
+
if (a["collection"] !== void 0) pArgs.collection = a["collection"];
|
|
302
|
+
result = await reader.getPage(pArgs);
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
case "get_schema":
|
|
306
|
+
result = await reader.getSchema(String(a["collection"] ?? ""));
|
|
307
|
+
break;
|
|
308
|
+
case "export_all": {
|
|
309
|
+
const eArgs = {};
|
|
310
|
+
if (a["include_body"] !== void 0) eArgs.include_body = a["include_body"];
|
|
311
|
+
result = await reader.exportAll(eArgs);
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
// ── Write tools ──────────────────────────────────────────
|
|
315
|
+
case "create_document": {
|
|
316
|
+
const col = String(a["collection"] ?? "");
|
|
317
|
+
const fields = a["fields"] ?? {};
|
|
318
|
+
const status = a["status"] ?? "draft";
|
|
319
|
+
const doc = await content.create(col, { data: fields, status }, { actor: "ai", aiModel: "mcp" });
|
|
320
|
+
result = { slug: doc.slug, id: doc.id, collection: col, status: doc.status };
|
|
321
|
+
audit("success", `${col}/${doc.slug}`);
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
case "update_document": {
|
|
325
|
+
const col = String(a["collection"] ?? "");
|
|
326
|
+
const slug = String(a["slug"] ?? "");
|
|
327
|
+
const fields = a["fields"] ?? {};
|
|
328
|
+
const existing = await content.findBySlug(col, slug);
|
|
329
|
+
if (!existing) {
|
|
330
|
+
result = { error: `Document "${slug}" not found in "${col}"` };
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
const { document: updated, skippedFields } = await content.updateWithContext(
|
|
334
|
+
col,
|
|
335
|
+
existing.id,
|
|
336
|
+
{ data: fields },
|
|
337
|
+
{ actor: "ai", aiModel: "mcp" }
|
|
338
|
+
);
|
|
339
|
+
result = { slug: updated.slug, skippedFields };
|
|
340
|
+
audit("success", `${col}/${slug}`);
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
case "publish_document": {
|
|
344
|
+
const col = String(a["collection"] ?? "");
|
|
345
|
+
const slug = String(a["slug"] ?? "");
|
|
346
|
+
const existing = await content.findBySlug(col, slug);
|
|
347
|
+
if (!existing) {
|
|
348
|
+
result = { error: `Document "${slug}" not found` };
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
await content.update(col, existing.id, { status: "published" }, { actor: "user" });
|
|
352
|
+
result = { slug, status: "published" };
|
|
353
|
+
audit("success", `${col}/${slug}`);
|
|
354
|
+
if (a["auto_build"] && onBuild) {
|
|
355
|
+
const buildResult = await onBuild("incremental");
|
|
356
|
+
result = { ...result, build: buildResult };
|
|
357
|
+
}
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
case "unpublish_document": {
|
|
361
|
+
const col = String(a["collection"] ?? "");
|
|
362
|
+
const slug = String(a["slug"] ?? "");
|
|
363
|
+
const existing = await content.findBySlug(col, slug);
|
|
364
|
+
if (!existing) {
|
|
365
|
+
result = { error: `Document "${slug}" not found` };
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
await content.update(col, existing.id, { status: "draft" }, { actor: "user" });
|
|
369
|
+
result = { slug, status: "draft" };
|
|
370
|
+
audit("success", `${col}/${slug}`);
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
// ── AI tools ─────────────────────────────────────────────
|
|
374
|
+
case "generate_with_ai": {
|
|
375
|
+
if (!ai) {
|
|
376
|
+
result = { error: "AI provider not configured on this server" };
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
const col = String(a["collection"] ?? "");
|
|
380
|
+
const intent = String(a["intent"] ?? "");
|
|
381
|
+
const status = a["status"] ?? "draft";
|
|
382
|
+
const generated = await ai.generate(intent, col);
|
|
383
|
+
const doc = await content.create(col, { data: generated.fields, slug: generated.slug, status }, { actor: "ai", aiModel: "mcp-generate" });
|
|
384
|
+
result = { slug: doc.slug, id: doc.id, collection: col, status: doc.status, fields: doc.data };
|
|
385
|
+
audit("success", `${col}/${doc.slug}`);
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
case "rewrite_field": {
|
|
389
|
+
if (!ai) {
|
|
390
|
+
result = { error: "AI provider not configured on this server" };
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
const col = String(a["collection"] ?? "");
|
|
394
|
+
const slug = String(a["slug"] ?? "");
|
|
395
|
+
const field = String(a["field"] ?? "");
|
|
396
|
+
const instruction = String(a["instruction"] ?? "");
|
|
397
|
+
const existing = await content.findBySlug(col, slug);
|
|
398
|
+
if (!existing) {
|
|
399
|
+
result = { error: `Document "${slug}" not found` };
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
const { isFieldLocked } = await import("@webhouse/cms");
|
|
403
|
+
if (isFieldLocked(existing._fieldMeta ?? {}, field)) {
|
|
404
|
+
result = { error: `FIELD_LOCKED: Field '${field}' is AI-locked and cannot be modified by agents` };
|
|
405
|
+
audit("error", `${col}/${slug}`, "FIELD_LOCKED");
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
const currentValue = String(existing.data[field] ?? "");
|
|
409
|
+
const rewritten = await ai.rewriteField(col, slug, field, instruction, currentValue);
|
|
410
|
+
await content.updateWithContext(col, existing.id, { data: { ...existing.data, [field]: rewritten } }, { actor: "ai", aiModel: "mcp-rewrite" });
|
|
411
|
+
result = { slug, field, rewritten };
|
|
412
|
+
audit("success", `${col}/${slug}`);
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
// ── Build tools ──────────────────────────────────────────
|
|
416
|
+
case "trigger_build": {
|
|
417
|
+
if (!onBuild) {
|
|
418
|
+
result = { error: "Build not configured on this server" };
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
const mode = a["mode"] ?? "incremental";
|
|
422
|
+
result = await onBuild(mode);
|
|
423
|
+
audit("success");
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
// ── Draft tools ──────────────────────────────────────────
|
|
427
|
+
case "list_drafts": {
|
|
428
|
+
const collections = a["collection"] ? [String(a["collection"])] : config.collections.map((c) => c.name);
|
|
429
|
+
const drafts = [];
|
|
430
|
+
for (const col of collections) {
|
|
431
|
+
const { documents } = await content.findMany(col, { status: "draft", limit: 100 });
|
|
432
|
+
for (const doc of documents) {
|
|
433
|
+
drafts.push({
|
|
434
|
+
collection: col,
|
|
435
|
+
slug: doc.slug,
|
|
436
|
+
title: String(doc.data["title"] ?? doc.data["name"] ?? doc.slug),
|
|
437
|
+
updatedAt: doc.updatedAt
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
result = { total: drafts.length, drafts };
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
case "get_version_history": {
|
|
445
|
+
const col = String(a["collection"] ?? "");
|
|
446
|
+
const slug = String(a["slug"] ?? "");
|
|
447
|
+
const doc = await content.findBySlug(col, slug);
|
|
448
|
+
if (!doc) {
|
|
449
|
+
result = { error: `Document "${slug}" not found` };
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
result = { collection: col, slug, id: doc.id, note: "Version history is stored in _data/revisions/ by the admin UI" };
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
default:
|
|
456
|
+
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
457
|
+
}
|
|
458
|
+
const text = typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
459
|
+
return { content: [{ type: "text", text }] };
|
|
460
|
+
} catch (err) {
|
|
461
|
+
const msg = err.message;
|
|
462
|
+
audit("error", void 0, msg ?? "unknown error");
|
|
463
|
+
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
return server;
|
|
467
|
+
}
|
|
468
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
469
|
+
0 && (module.exports = {
|
|
470
|
+
ADMIN_TOOLS,
|
|
471
|
+
TOOL_SCOPES,
|
|
472
|
+
createAdminMcpServer,
|
|
473
|
+
hasScope,
|
|
474
|
+
initAuditLog,
|
|
475
|
+
validateApiKey,
|
|
476
|
+
writeAudit
|
|
477
|
+
});
|
|
478
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/tools.ts","../src/auth.ts","../src/audit.ts","../src/server.ts"],"sourcesContent":["export { ADMIN_TOOLS, TOOL_SCOPES } from \"./tools.js\";\nexport type { AdminToolName } from \"./tools.js\";\nexport { validateApiKey, hasScope } from \"./auth.js\";\nexport type { ApiKeyConfig } from \"./auth.js\";\nexport { initAuditLog, writeAudit } from \"./audit.js\";\nexport type { AuditEntry } from \"./audit.js\";\nexport { createAdminMcpServer } from \"./server.js\";\nexport type { AdminServerOptions, AiGenerator } from \"./server.js\";\n","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":["import_cms_mcp_client"]}
|