@ampless/mcp-server 0.2.0-alpha.9 → 1.0.0-alpha.11
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.
|
@@ -8,7 +8,6 @@ var POST_FIELDS = (
|
|
|
8
8
|
/* GraphQL */
|
|
9
9
|
`
|
|
10
10
|
fragment PostFields on Post {
|
|
11
|
-
siteId
|
|
12
11
|
postId
|
|
13
12
|
slug
|
|
14
13
|
title
|
|
@@ -25,7 +24,6 @@ var POST_FIELDS = (
|
|
|
25
24
|
function toCorePost(p) {
|
|
26
25
|
const metadata = decodeAwsJson(p.metadata);
|
|
27
26
|
return {
|
|
28
|
-
siteId: p.siteId,
|
|
29
27
|
postId: p.postId,
|
|
30
28
|
slug: p.slug,
|
|
31
29
|
title: p.title,
|
|
@@ -57,7 +55,6 @@ var QUERY = (
|
|
|
57
55
|
var listPostsSchema = {
|
|
58
56
|
type: "object",
|
|
59
57
|
properties: {
|
|
60
|
-
siteId: { type: "string", description: 'Site identifier (defaults to "default")' },
|
|
61
58
|
status: {
|
|
62
59
|
type: "string",
|
|
63
60
|
enum: ["draft", "published", "all"],
|
|
@@ -67,12 +64,16 @@ var listPostsSchema = {
|
|
|
67
64
|
nextToken: { type: "string", description: "Pagination cursor from a previous call" }
|
|
68
65
|
}
|
|
69
66
|
};
|
|
70
|
-
async function listPosts(client,
|
|
71
|
-
const siteId = args.siteId ?? defaultSiteId;
|
|
67
|
+
async function listPosts(client, args = {}) {
|
|
72
68
|
const status = args.status ?? "all";
|
|
73
|
-
const filter = {
|
|
69
|
+
const filter = {};
|
|
74
70
|
if (status !== "all") filter.status = { eq: status };
|
|
75
|
-
const
|
|
71
|
+
const hasFilter = Object.keys(filter).length > 0;
|
|
72
|
+
const data = await client.query(QUERY, {
|
|
73
|
+
filter: hasFilter ? filter : void 0,
|
|
74
|
+
limit: args.limit ?? 20,
|
|
75
|
+
nextToken: args.nextToken
|
|
76
|
+
});
|
|
76
77
|
return {
|
|
77
78
|
posts: data.listPosts.items.map(toCorePost),
|
|
78
79
|
nextToken: data.listPosts.nextToken
|
|
@@ -84,8 +85,8 @@ var GET_BY_ID = (
|
|
|
84
85
|
/* GraphQL */
|
|
85
86
|
`
|
|
86
87
|
${POST_FIELDS}
|
|
87
|
-
query GetPost($
|
|
88
|
-
getPost(
|
|
88
|
+
query GetPost($postId: ID!) {
|
|
89
|
+
getPost(postId: $postId) {
|
|
89
90
|
...PostFields
|
|
90
91
|
}
|
|
91
92
|
}
|
|
@@ -107,22 +108,20 @@ var LIST_BY_SLUG = (
|
|
|
107
108
|
var getPostSchema = {
|
|
108
109
|
type: "object",
|
|
109
110
|
properties: {
|
|
110
|
-
siteId: { type: "string", description: 'Site identifier (defaults to "default")' },
|
|
111
111
|
slug: { type: "string", description: "Post slug" },
|
|
112
112
|
postId: { type: "string", description: "Post id (used when slug is omitted)" }
|
|
113
113
|
}
|
|
114
114
|
};
|
|
115
|
-
async function getPost(client,
|
|
115
|
+
async function getPost(client, args) {
|
|
116
116
|
if (!args.slug && !args.postId) {
|
|
117
117
|
throw new Error("get_post requires either `slug` or `postId`");
|
|
118
118
|
}
|
|
119
|
-
const siteId = args.siteId ?? defaultSiteId;
|
|
120
119
|
if (args.postId) {
|
|
121
|
-
const data2 = await client.query(GET_BY_ID, {
|
|
120
|
+
const data2 = await client.query(GET_BY_ID, { postId: args.postId });
|
|
122
121
|
return data2.getPost ? toCorePost(data2.getPost) : null;
|
|
123
122
|
}
|
|
124
123
|
const data = await client.query(LIST_BY_SLUG, {
|
|
125
|
-
filter: {
|
|
124
|
+
filter: { slug: { eq: args.slug } }
|
|
126
125
|
});
|
|
127
126
|
const item = data.listPosts.items[0];
|
|
128
127
|
return item ? toCorePost(item) : null;
|
|
@@ -130,8 +129,6 @@ async function getPost(client, defaultSiteId, args) {
|
|
|
130
129
|
|
|
131
130
|
// src/tools/create-post.ts
|
|
132
131
|
import {
|
|
133
|
-
composeSiteIdStatus,
|
|
134
|
-
composeSiteIdSlug,
|
|
135
132
|
encodeAwsJson
|
|
136
133
|
} from "ampless";
|
|
137
134
|
|
|
@@ -139,19 +136,19 @@ import {
|
|
|
139
136
|
function entries(post) {
|
|
140
137
|
if (post.status !== "published" || !post.publishedAt || !post.tags?.length) return [];
|
|
141
138
|
return post.tags.map((tag) => ({
|
|
142
|
-
|
|
139
|
+
tag,
|
|
143
140
|
publishedAtPostId: `${post.publishedAt}#${post.postId}`
|
|
144
141
|
}));
|
|
145
142
|
}
|
|
146
143
|
function entryKey(e) {
|
|
147
|
-
return `${e.
|
|
144
|
+
return `${e.tag}|${e.publishedAtPostId}`;
|
|
148
145
|
}
|
|
149
146
|
var CREATE_POST_TAG = (
|
|
150
147
|
/* GraphQL */
|
|
151
148
|
`
|
|
152
149
|
mutation CreatePostTag($input: CreatePostTagInput!) {
|
|
153
150
|
createPostTag(input: $input) {
|
|
154
|
-
|
|
151
|
+
tag
|
|
155
152
|
publishedAtPostId
|
|
156
153
|
}
|
|
157
154
|
}
|
|
@@ -162,7 +159,7 @@ var UPDATE_POST_TAG = (
|
|
|
162
159
|
`
|
|
163
160
|
mutation UpdatePostTag($input: UpdatePostTagInput!) {
|
|
164
161
|
updatePostTag(input: $input) {
|
|
165
|
-
|
|
162
|
+
tag
|
|
166
163
|
publishedAtPostId
|
|
167
164
|
}
|
|
168
165
|
}
|
|
@@ -173,7 +170,7 @@ var DELETE_POST_TAG = (
|
|
|
173
170
|
`
|
|
174
171
|
mutation DeletePostTag($input: DeletePostTagInput!) {
|
|
175
172
|
deletePostTag(input: $input) {
|
|
176
|
-
|
|
173
|
+
tag
|
|
177
174
|
publishedAtPostId
|
|
178
175
|
}
|
|
179
176
|
}
|
|
@@ -187,7 +184,7 @@ async function syncPostTags(client, post, oldPost) {
|
|
|
187
184
|
await Promise.all(
|
|
188
185
|
oldEntries.filter((e) => !newKeys.has(entryKey(e))).map(
|
|
189
186
|
(e) => client.query(DELETE_POST_TAG, {
|
|
190
|
-
input: {
|
|
187
|
+
input: { tag: e.tag, publishedAtPostId: e.publishedAtPostId }
|
|
191
188
|
})
|
|
192
189
|
)
|
|
193
190
|
);
|
|
@@ -195,10 +192,8 @@ async function syncPostTags(client, post, oldPost) {
|
|
|
195
192
|
newEntries.filter((e) => !oldKeys.has(entryKey(e))).map(
|
|
196
193
|
(e) => client.query(CREATE_POST_TAG, {
|
|
197
194
|
input: {
|
|
198
|
-
|
|
195
|
+
tag: e.tag,
|
|
199
196
|
publishedAtPostId: e.publishedAtPostId,
|
|
200
|
-
siteId: post.siteId,
|
|
201
|
-
tag: e.siteIdTag.slice(post.siteId.length + 1),
|
|
202
197
|
postId: post.postId,
|
|
203
198
|
publishedAt: post.publishedAt,
|
|
204
199
|
slug: post.slug,
|
|
@@ -213,7 +208,7 @@ async function syncPostTags(client, post, oldPost) {
|
|
|
213
208
|
newEntries.filter((e) => oldKeys.has(entryKey(e))).map(
|
|
214
209
|
(e) => client.query(UPDATE_POST_TAG, {
|
|
215
210
|
input: {
|
|
216
|
-
|
|
211
|
+
tag: e.tag,
|
|
217
212
|
publishedAtPostId: e.publishedAtPostId,
|
|
218
213
|
slug: post.slug,
|
|
219
214
|
title: post.title,
|
|
@@ -241,7 +236,6 @@ var createPostSchema = {
|
|
|
241
236
|
type: "object",
|
|
242
237
|
required: ["slug", "title", "format", "body"],
|
|
243
238
|
properties: {
|
|
244
|
-
siteId: { type: "string", description: 'Site identifier (defaults to "default")' },
|
|
245
239
|
postId: { type: "string", description: "Optional explicit id; auto-generated if omitted" },
|
|
246
240
|
slug: { type: "string" },
|
|
247
241
|
title: { type: "string" },
|
|
@@ -259,25 +253,29 @@ var createPostSchema = {
|
|
|
259
253
|
tags: { type: "array", items: { type: "string" } },
|
|
260
254
|
metadata: {
|
|
261
255
|
type: "object",
|
|
262
|
-
description: "Free-form per-post metadata. Reserved well-known keys: `no_layout` (boolean) \u2014 when true, the public page is served as bare HTML with no theme chrome (
|
|
256
|
+
description: "Free-form per-post metadata. Reserved well-known keys: `no_layout` (boolean) \u2014 when true, the public page is served as bare HTML with no theme chrome (middleware rewrites /<slug> to the internal bare-HTML route); meaningful only with format=html. `cache` (auto|deep|hot) \u2014 override the per-post cache strategy. See `cacheStrategy` in get_schema.notes for details. Other keys pass through unchanged for themes/plugins.",
|
|
263
257
|
properties: {
|
|
264
258
|
no_layout: {
|
|
265
259
|
type: "boolean",
|
|
266
260
|
description: "Serve the post as bare HTML with no theme chrome. Only meaningful when format=html; ignored otherwise."
|
|
261
|
+
},
|
|
262
|
+
cache: {
|
|
263
|
+
type: "string",
|
|
264
|
+
enum: ["auto", "deep", "hot"],
|
|
265
|
+
default: "auto",
|
|
266
|
+
description: "Override the response Cache-Control strategy. 'auto' (default): no-store within `cms.config.cache.cooldownMs` of updatedAt, then `max-age=freshTtlSeconds`. 'deep': always `max-age=deepTtlSeconds`. 'hot': always no-store. Independent of no_layout; applies uniformly to themed, no_layout, and static posts."
|
|
267
267
|
}
|
|
268
268
|
},
|
|
269
269
|
additionalProperties: true
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
};
|
|
273
|
-
async function createPost(client,
|
|
274
|
-
const siteId = args.siteId ?? defaultSiteId;
|
|
273
|
+
async function createPost(client, args) {
|
|
275
274
|
const postId = args.postId ?? `post-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
276
275
|
const status = args.status ?? "draft";
|
|
277
276
|
const publishedAt = args.publishedAt ?? (status === "published" ? (/* @__PURE__ */ new Date()).toISOString() : void 0);
|
|
278
277
|
const data = await client.query(MUTATION, {
|
|
279
278
|
input: {
|
|
280
|
-
siteId,
|
|
281
279
|
postId,
|
|
282
280
|
slug: args.slug,
|
|
283
281
|
title: args.title,
|
|
@@ -287,10 +285,7 @@ async function createPost(client, defaultSiteId, args) {
|
|
|
287
285
|
status,
|
|
288
286
|
publishedAt,
|
|
289
287
|
tags: args.tags,
|
|
290
|
-
metadata: args.metadata !== void 0 ? encodeAwsJson(args.metadata) : void 0
|
|
291
|
-
// Denormalized GSI keys.
|
|
292
|
-
siteIdStatus: composeSiteIdStatus(siteId, status),
|
|
293
|
-
siteIdSlug: composeSiteIdSlug(siteId, args.slug)
|
|
288
|
+
metadata: args.metadata !== void 0 ? encodeAwsJson(args.metadata) : void 0
|
|
294
289
|
}
|
|
295
290
|
});
|
|
296
291
|
const created = toCorePost(data.createPost);
|
|
@@ -300,8 +295,6 @@ async function createPost(client, defaultSiteId, args) {
|
|
|
300
295
|
|
|
301
296
|
// src/tools/update-post.ts
|
|
302
297
|
import {
|
|
303
|
-
composeSiteIdStatus as composeSiteIdStatus2,
|
|
304
|
-
composeSiteIdSlug as composeSiteIdSlug2,
|
|
305
298
|
encodeAwsJson as encodeAwsJson2
|
|
306
299
|
} from "ampless";
|
|
307
300
|
var MUTATION2 = (
|
|
@@ -320,7 +313,6 @@ var updatePostSchema = {
|
|
|
320
313
|
required: ["postId"],
|
|
321
314
|
properties: {
|
|
322
315
|
postId: { type: "string" },
|
|
323
|
-
siteId: { type: "string", description: 'Site identifier (defaults to "default")' },
|
|
324
316
|
slug: { type: "string" },
|
|
325
317
|
title: { type: "string" },
|
|
326
318
|
excerpt: { type: "string" },
|
|
@@ -335,21 +327,26 @@ var updatePostSchema = {
|
|
|
335
327
|
tags: { type: "array", items: { type: "string" } },
|
|
336
328
|
metadata: {
|
|
337
329
|
type: "object",
|
|
338
|
-
description: "Free-form per-post metadata. Reserved well-known keys: `no_layout` (boolean) \u2014 when true, the public page is served as bare HTML with no theme chrome (
|
|
330
|
+
description: "Free-form per-post metadata. Reserved well-known keys: `no_layout` (boolean) \u2014 when true, the public page is served as bare HTML with no theme chrome (middleware rewrites /<slug> to the internal bare-HTML route); meaningful only with format=html. `cache` (auto|deep|hot) \u2014 override the per-post cache strategy. See `cacheStrategy` in get_schema.notes for details. Passing metadata replaces the existing metadata object \u2014 read the current post first if you only want to add a key.",
|
|
339
331
|
properties: {
|
|
340
332
|
no_layout: {
|
|
341
333
|
type: "boolean",
|
|
342
334
|
description: "Serve the post as bare HTML with no theme chrome. Only meaningful when format=html; ignored otherwise."
|
|
335
|
+
},
|
|
336
|
+
cache: {
|
|
337
|
+
type: "string",
|
|
338
|
+
enum: ["auto", "deep", "hot"],
|
|
339
|
+
default: "auto",
|
|
340
|
+
description: "Override the response Cache-Control strategy. 'auto' (default): no-store within `cms.config.cache.cooldownMs` of updatedAt, then `max-age=freshTtlSeconds`. 'deep': always `max-age=deepTtlSeconds`. 'hot': always no-store. Independent of no_layout; applies uniformly to themed, no_layout, and static posts."
|
|
343
341
|
}
|
|
344
342
|
},
|
|
345
343
|
additionalProperties: true
|
|
346
344
|
}
|
|
347
345
|
}
|
|
348
346
|
};
|
|
349
|
-
async function updatePost(client,
|
|
350
|
-
const
|
|
351
|
-
const
|
|
352
|
-
const input = { siteId, postId: args.postId };
|
|
347
|
+
async function updatePost(client, args) {
|
|
348
|
+
const oldPost = await getPost(client, { postId: args.postId });
|
|
349
|
+
const input = { postId: args.postId };
|
|
353
350
|
if (args.slug !== void 0) input.slug = args.slug;
|
|
354
351
|
if (args.title !== void 0) input.title = args.title;
|
|
355
352
|
if (args.excerpt !== void 0) input.excerpt = args.excerpt;
|
|
@@ -359,12 +356,6 @@ async function updatePost(client, defaultSiteId, args) {
|
|
|
359
356
|
if (args.publishedAt !== void 0) input.publishedAt = args.publishedAt;
|
|
360
357
|
if (args.tags !== void 0) input.tags = args.tags;
|
|
361
358
|
if (args.metadata !== void 0) input.metadata = encodeAwsJson2(args.metadata);
|
|
362
|
-
if (args.status !== void 0) {
|
|
363
|
-
input.siteIdStatus = composeSiteIdStatus2(siteId, args.status);
|
|
364
|
-
}
|
|
365
|
-
if (args.slug !== void 0) {
|
|
366
|
-
input.siteIdSlug = composeSiteIdSlug2(siteId, args.slug);
|
|
367
|
-
}
|
|
368
359
|
const data = await client.query(MUTATION2, { input });
|
|
369
360
|
const updated = toCorePost(data.updatePost);
|
|
370
361
|
await syncPostTags(client, updated, oldPost);
|
|
@@ -377,7 +368,6 @@ var MUTATION3 = (
|
|
|
377
368
|
`
|
|
378
369
|
mutation DeletePost($input: DeletePostInput!) {
|
|
379
370
|
deletePost(input: $input) {
|
|
380
|
-
siteId
|
|
381
371
|
postId
|
|
382
372
|
}
|
|
383
373
|
}
|
|
@@ -387,17 +377,15 @@ var deletePostSchema = {
|
|
|
387
377
|
type: "object",
|
|
388
378
|
required: ["postId"],
|
|
389
379
|
properties: {
|
|
390
|
-
postId: { type: "string" }
|
|
391
|
-
siteId: { type: "string", description: 'Site identifier (defaults to "default")' }
|
|
380
|
+
postId: { type: "string" }
|
|
392
381
|
}
|
|
393
382
|
};
|
|
394
|
-
async function deletePost(client,
|
|
395
|
-
const
|
|
396
|
-
const oldPost = await getPost(client, defaultSiteId, { postId: args.postId, siteId });
|
|
383
|
+
async function deletePost(client, args) {
|
|
384
|
+
const oldPost = await getPost(client, { postId: args.postId });
|
|
397
385
|
if (oldPost) {
|
|
398
386
|
await syncPostTags(client, { ...oldPost, status: "draft" }, oldPost);
|
|
399
387
|
}
|
|
400
|
-
const data = await client.query(MUTATION3, { input: {
|
|
388
|
+
const data = await client.query(MUTATION3, { input: { postId: args.postId } });
|
|
401
389
|
return { deleted: data.deletePost };
|
|
402
390
|
}
|
|
403
391
|
|
|
@@ -418,7 +406,6 @@ var MUTATION4 = (
|
|
|
418
406
|
`
|
|
419
407
|
mutation CreateMedia($input: CreateMediaInput!) {
|
|
420
408
|
createMedia(input: $input) {
|
|
421
|
-
siteId
|
|
422
409
|
mediaId
|
|
423
410
|
src
|
|
424
411
|
mimeType
|
|
@@ -432,7 +419,6 @@ var uploadMediaSchema = {
|
|
|
432
419
|
type: "object",
|
|
433
420
|
required: ["filename", "mimeType", "base64Data"],
|
|
434
421
|
properties: {
|
|
435
|
-
siteId: { type: "string", description: 'Site identifier (defaults to "default")' },
|
|
436
422
|
filename: { type: "string", description: "Original filename; sanitized server-side" },
|
|
437
423
|
mimeType: {
|
|
438
424
|
type: "string",
|
|
@@ -444,15 +430,13 @@ var uploadMediaSchema = {
|
|
|
444
430
|
}
|
|
445
431
|
}
|
|
446
432
|
};
|
|
447
|
-
async function uploadMedia(graphql, storage,
|
|
448
|
-
const siteId = args.siteId ?? defaultSiteId;
|
|
433
|
+
async function uploadMedia(graphql, storage, args) {
|
|
449
434
|
const body = Buffer.from(args.base64Data, "base64");
|
|
450
435
|
const key = buildMediaKey(args.filename);
|
|
451
436
|
const url = await storage.putObject(key, body, args.mimeType);
|
|
452
437
|
const mediaId = `media-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
453
438
|
const data = await graphql.query(MUTATION4, {
|
|
454
439
|
input: {
|
|
455
|
-
siteId,
|
|
456
440
|
mediaId,
|
|
457
441
|
src: key,
|
|
458
442
|
mimeType: args.mimeType,
|
|
@@ -473,11 +457,10 @@ function getSchema() {
|
|
|
473
457
|
contentTypes: [
|
|
474
458
|
{
|
|
475
459
|
name: "post",
|
|
476
|
-
identifier: ["
|
|
460
|
+
identifier: ["postId"],
|
|
477
461
|
fields: {
|
|
478
|
-
siteId: { type: "string", required: true },
|
|
479
462
|
postId: { type: "string", required: true, description: "auto-generated if omitted on create" },
|
|
480
|
-
slug: { type: "string", required: true, description: "URL slug, unique
|
|
463
|
+
slug: { type: "string", required: true, description: "URL slug, unique" },
|
|
481
464
|
title: { type: "string", required: true },
|
|
482
465
|
excerpt: { type: "string" },
|
|
483
466
|
format: { type: "enum", values: ["tiptap", "markdown", "html"], required: true },
|
|
@@ -490,15 +473,14 @@ function getSchema() {
|
|
|
490
473
|
tags: { type: "string[]" },
|
|
491
474
|
metadata: {
|
|
492
475
|
type: "json",
|
|
493
|
-
description: "Free-form per-post key/value bag. Reserved well-known keys (owned by ampless): `no_layout` (boolean). Other keys pass through unchanged for themes/plugins."
|
|
476
|
+
description: "Free-form per-post key/value bag. Reserved well-known keys (owned by ampless): `no_layout` (boolean), `cache` (auto|deep|hot). Other keys pass through unchanged for themes/plugins."
|
|
494
477
|
}
|
|
495
478
|
}
|
|
496
479
|
},
|
|
497
480
|
{
|
|
498
481
|
name: "page",
|
|
499
|
-
identifier: ["
|
|
482
|
+
identifier: ["pageId"],
|
|
500
483
|
fields: {
|
|
501
|
-
siteId: { type: "string", required: true },
|
|
502
484
|
pageId: { type: "string", required: true },
|
|
503
485
|
slug: { type: "string", required: true },
|
|
504
486
|
title: { type: "string", required: true },
|
|
@@ -510,9 +492,8 @@ function getSchema() {
|
|
|
510
492
|
},
|
|
511
493
|
{
|
|
512
494
|
name: "media",
|
|
513
|
-
identifier: ["
|
|
495
|
+
identifier: ["mediaId"],
|
|
514
496
|
fields: {
|
|
515
|
-
siteId: { type: "string", required: true },
|
|
516
497
|
mediaId: { type: "string", required: true },
|
|
517
498
|
src: { type: "string", required: true, description: "S3 key" },
|
|
518
499
|
mimeType: { type: "string", required: true },
|
|
@@ -525,8 +506,9 @@ function getSchema() {
|
|
|
525
506
|
notes: {
|
|
526
507
|
editorTrust: 'editor stores arbitrary HTML/JS verbatim \u2014 same trust shape as WordPress unfiltered_html capability. See docs/architecture/04-access-layer-mcp.md \xA7"editor \u306E\u4FE1\u983C\u30E2\u30C7\u30EB".',
|
|
527
508
|
tiptapBody: 'When format=tiptap, body is the tiptap document JSON: { type: "doc", content: [...] }. The renderer expects this shape.',
|
|
528
|
-
noLayout: "metadata.no_layout=true serves the post as bare HTML with no theme chrome \u2014 the public
|
|
529
|
-
staticFormat: "A fourth format value `static` exists on the underlying data model for posts whose body is a JSON manifest pointing to a pre-uploaded HTML/CSS/JS bundle in S3 at public/static/<
|
|
509
|
+
noLayout: "metadata.no_layout=true serves the post as bare HTML with no theme chrome \u2014 middleware rewrites the public /<slug> request to the internal bare-HTML handler, which renders the body verbatim with no wrapping <html>/<head>/layout. Use this for landing pages, embeds, or any post whose body is a full HTML document. Only meaningful with format=html (the other formats need the theme renderer).",
|
|
510
|
+
staticFormat: "A fourth format value `static` exists on the underlying data model for posts whose body is a JSON manifest pointing to a pre-uploaded HTML/CSS/JS bundle in S3 at public/static/<slug>/. Public URL pattern: /<slug>/ for the entrypoint and /<slug>/<file> for every bundle file (middleware rewrites both to an internal static-bundle handler). Static posts are created/edited through the dedicated tools `upload_static_bundle` (zip in one shot), `upload_static_file` / `delete_static_file` (incremental per-file ops), and `commit_static_post` (rebuild the manifest from the current S3 prefix). `create_post` / `update_post` intentionally do NOT accept format=static \u2014 the bundle tools are the only supported entry point so the Post manifest stays in sync with the S3 prefix.",
|
|
511
|
+
cacheStrategy: "metadata.cache controls the response Cache-Control header (set by middleware). Values:\n - 'auto' (default): cooldown by edit time. Posts updated within cms.config.cache.cooldownMs (default 1h) emit `public, max-age=0, must-revalidate, s-maxage=0` so editors see fresh content immediately; older posts emit `public, max-age=<freshTtlSeconds>, s-maxage=<freshTtlSeconds>` (default 300 sec / 5 min).\n - 'deep': always `public, max-age=<deepTtlSeconds>, s-maxage=<deepTtlSeconds>` (default 3600 sec / 1 hour). Use for posts whose content is fixed for the foreseeable future.\n - 'hot': always `public, max-age=0, must-revalidate, s-maxage=0`. Use for rapidly-evolving posts or posts whose body is computed per request.\nIndependent of metadata.no_layout and post.format \u2014 applies uniformly to themed, no_layout, and static posts. Override the defaults per project via `cms.config.cache` ({ cooldownMs, freshTtlSeconds, deepTtlSeconds })."
|
|
530
512
|
}
|
|
531
513
|
};
|
|
532
514
|
}
|
|
@@ -576,8 +558,6 @@ function decodeUtf8(data) {
|
|
|
576
558
|
|
|
577
559
|
// src/tools/upsert-static-post.ts
|
|
578
560
|
import {
|
|
579
|
-
composeSiteIdStatus as composeSiteIdStatus3,
|
|
580
|
-
composeSiteIdSlug as composeSiteIdSlug3,
|
|
581
561
|
encodeAwsJson as encodeAwsJson3
|
|
582
562
|
} from "ampless";
|
|
583
563
|
var CREATE_MUTATION = (
|
|
@@ -602,21 +582,17 @@ var UPDATE_MUTATION = (
|
|
|
602
582
|
}
|
|
603
583
|
`
|
|
604
584
|
);
|
|
605
|
-
async function upsertStaticPost(graphql,
|
|
606
|
-
const existing = await getPost(graphql,
|
|
585
|
+
async function upsertStaticPost(graphql, slug, body, fields) {
|
|
586
|
+
const existing = await getPost(graphql, { slug });
|
|
607
587
|
if (existing) {
|
|
608
588
|
const input2 = {
|
|
609
|
-
siteId,
|
|
610
589
|
postId: existing.postId,
|
|
611
590
|
format: "static",
|
|
612
591
|
body: encodeAwsJson3(body)
|
|
613
592
|
};
|
|
614
593
|
if (fields.title !== void 0) input2.title = fields.title;
|
|
615
594
|
if (fields.excerpt !== void 0) input2.excerpt = fields.excerpt;
|
|
616
|
-
if (fields.status !== void 0)
|
|
617
|
-
input2.status = fields.status;
|
|
618
|
-
input2.siteIdStatus = composeSiteIdStatus3(siteId, fields.status);
|
|
619
|
-
}
|
|
595
|
+
if (fields.status !== void 0) input2.status = fields.status;
|
|
620
596
|
if (fields.publishedAt !== void 0) input2.publishedAt = fields.publishedAt;
|
|
621
597
|
if (fields.tags !== void 0) input2.tags = fields.tags;
|
|
622
598
|
if (fields.metadata !== void 0) {
|
|
@@ -636,15 +612,12 @@ async function upsertStaticPost(graphql, siteId, slug, body, fields) {
|
|
|
636
612
|
const publishedAt = fields.publishedAt ?? (status === "published" ? (/* @__PURE__ */ new Date()).toISOString() : void 0);
|
|
637
613
|
const postId = fields.postId ?? `post-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
638
614
|
const input = {
|
|
639
|
-
siteId,
|
|
640
615
|
postId,
|
|
641
616
|
slug,
|
|
642
617
|
title: fields.title,
|
|
643
618
|
format: "static",
|
|
644
619
|
body: encodeAwsJson3(body),
|
|
645
|
-
status
|
|
646
|
-
siteIdStatus: composeSiteIdStatus3(siteId, status),
|
|
647
|
-
siteIdSlug: composeSiteIdSlug3(siteId, slug)
|
|
620
|
+
status
|
|
648
621
|
};
|
|
649
622
|
if (fields.excerpt !== void 0) input.excerpt = fields.excerpt;
|
|
650
623
|
if (publishedAt !== void 0) input.publishedAt = publishedAt;
|
|
@@ -661,7 +634,6 @@ var uploadStaticBundleSchema = {
|
|
|
661
634
|
type: "object",
|
|
662
635
|
required: ["slug", "title", "zipBase64"],
|
|
663
636
|
properties: {
|
|
664
|
-
siteId: { type: "string", description: 'Site identifier (defaults to "default")' },
|
|
665
637
|
postId: {
|
|
666
638
|
type: "string",
|
|
667
639
|
description: "Optional explicit Post id when creating a new post. Ignored if a post at this slug already exists."
|
|
@@ -687,8 +659,7 @@ var uploadStaticBundleSchema = {
|
|
|
687
659
|
}
|
|
688
660
|
}
|
|
689
661
|
};
|
|
690
|
-
async function uploadStaticBundle(graphql, storage,
|
|
691
|
-
const siteId = args.siteId ?? defaultSiteId;
|
|
662
|
+
async function uploadStaticBundle(graphql, storage, args) {
|
|
692
663
|
const slug = args.slug;
|
|
693
664
|
const zipBytes = Buffer.from(args.zipBase64, "base64");
|
|
694
665
|
if (zipBytes.length === 0) {
|
|
@@ -715,7 +686,7 @@ async function uploadStaticBundle(graphql, storage, defaultSiteId, args) {
|
|
|
715
686
|
`upload_static_bundle: entrypoint "${entrypoint}" is not present in the bundle.`
|
|
716
687
|
);
|
|
717
688
|
}
|
|
718
|
-
const prefix = bundlePrefix(
|
|
689
|
+
const prefix = bundlePrefix(slug);
|
|
719
690
|
const existing = await storage.listObjects(prefix).catch((err) => {
|
|
720
691
|
console.error("[upload_static_bundle] listObjects failed (proceeding)", err);
|
|
721
692
|
return [];
|
|
@@ -733,7 +704,7 @@ async function uploadStaticBundle(graphql, storage, defaultSiteId, args) {
|
|
|
733
704
|
files: files.map((f) => f.path).sort(),
|
|
734
705
|
uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
735
706
|
};
|
|
736
|
-
const { post } = await upsertStaticPost(graphql,
|
|
707
|
+
const { post } = await upsertStaticPost(graphql, slug, body, {
|
|
737
708
|
title: args.title,
|
|
738
709
|
postId: args.postId,
|
|
739
710
|
status: args.status,
|
|
@@ -757,8 +728,7 @@ var uploadStaticFileSchema = {
|
|
|
757
728
|
type: "object",
|
|
758
729
|
required: ["slug", "filename", "base64Data"],
|
|
759
730
|
properties: {
|
|
760
|
-
|
|
761
|
-
slug: { type: "string", description: "Bundle slug. Files land at public/static/<siteId>/<slug>/<filename>." },
|
|
731
|
+
slug: { type: "string", description: "Bundle slug. Files land at public/static/<slug>/<filename>." },
|
|
762
732
|
filename: {
|
|
763
733
|
type: "string",
|
|
764
734
|
description: "Relative path inside the bundle. Must NOT start with `/`, contain `..`, or include null bytes. macOS / Windows zip junk (`.DS_Store`, `__MACOSX/*`, `Thumbs.db`) is also rejected."
|
|
@@ -773,8 +743,7 @@ var uploadStaticFileSchema = {
|
|
|
773
743
|
}
|
|
774
744
|
}
|
|
775
745
|
};
|
|
776
|
-
async function uploadStaticFile(storage,
|
|
777
|
-
const siteId = args.siteId ?? defaultSiteId;
|
|
746
|
+
async function uploadStaticFile(storage, args) {
|
|
778
747
|
const filename = args.filename;
|
|
779
748
|
const reason = validateBundlePath2(filename);
|
|
780
749
|
if (reason) {
|
|
@@ -795,7 +764,7 @@ async function uploadStaticFile(storage, defaultSiteId, args) {
|
|
|
795
764
|
}
|
|
796
765
|
}
|
|
797
766
|
const contentType = args.contentType ?? mimeTypeFor2(filename);
|
|
798
|
-
const key = `${bundlePrefix2(
|
|
767
|
+
const key = `${bundlePrefix2(args.slug)}${filename}`;
|
|
799
768
|
const url = await storage.putObject(key, body, contentType);
|
|
800
769
|
return { key, url, size: body.length };
|
|
801
770
|
}
|
|
@@ -806,7 +775,6 @@ var deleteStaticFileSchema = {
|
|
|
806
775
|
type: "object",
|
|
807
776
|
required: ["slug", "filename"],
|
|
808
777
|
properties: {
|
|
809
|
-
siteId: { type: "string", description: 'Site identifier (defaults to "default")' },
|
|
810
778
|
slug: { type: "string" },
|
|
811
779
|
filename: {
|
|
812
780
|
type: "string",
|
|
@@ -814,13 +782,12 @@ var deleteStaticFileSchema = {
|
|
|
814
782
|
}
|
|
815
783
|
}
|
|
816
784
|
};
|
|
817
|
-
async function deleteStaticFile(storage,
|
|
818
|
-
const siteId = args.siteId ?? defaultSiteId;
|
|
785
|
+
async function deleteStaticFile(storage, args) {
|
|
819
786
|
const reason = validateBundlePath3(args.filename);
|
|
820
787
|
if (reason) {
|
|
821
788
|
throw new Error(`delete_static_file: invalid filename "${args.filename}" (${reason})`);
|
|
822
789
|
}
|
|
823
|
-
const prefix = bundlePrefix3(
|
|
790
|
+
const prefix = bundlePrefix3(args.slug);
|
|
824
791
|
const key = `${prefix}${args.filename}`;
|
|
825
792
|
const existing = await storage.listObjects(key);
|
|
826
793
|
const found = existing.some((o) => o.key === key);
|
|
@@ -840,7 +807,6 @@ var commitStaticPostSchema = {
|
|
|
840
807
|
type: "object",
|
|
841
808
|
required: ["slug"],
|
|
842
809
|
properties: {
|
|
843
|
-
siteId: { type: "string", description: 'Site identifier (defaults to "default")' },
|
|
844
810
|
slug: { type: "string" },
|
|
845
811
|
title: {
|
|
846
812
|
type: "string",
|
|
@@ -865,10 +831,9 @@ var commitStaticPostSchema = {
|
|
|
865
831
|
}
|
|
866
832
|
}
|
|
867
833
|
};
|
|
868
|
-
async function commitStaticPost(graphql, storage,
|
|
869
|
-
const siteId = args.siteId ?? defaultSiteId;
|
|
834
|
+
async function commitStaticPost(graphql, storage, args) {
|
|
870
835
|
const slug = args.slug;
|
|
871
|
-
const prefix = bundlePrefix4(
|
|
836
|
+
const prefix = bundlePrefix4(slug);
|
|
872
837
|
const objects = await storage.listObjects(prefix);
|
|
873
838
|
if (objects.length === 0) {
|
|
874
839
|
throw new Error(
|
|
@@ -887,7 +852,7 @@ async function commitStaticPost(graphql, storage, defaultSiteId, args) {
|
|
|
887
852
|
files: relPaths,
|
|
888
853
|
uploadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
889
854
|
};
|
|
890
|
-
const { post, created } = await upsertStaticPost(graphql,
|
|
855
|
+
const { post, created } = await upsertStaticPost(graphql, slug, body, {
|
|
891
856
|
title: args.title,
|
|
892
857
|
postId: args.postId,
|
|
893
858
|
excerpt: args.excerpt,
|
|
@@ -905,37 +870,37 @@ var tools = [
|
|
|
905
870
|
name: "list_posts",
|
|
906
871
|
description: "List posts in the CMS with optional filters by status. Returns up to `limit` posts (default 20) plus a `nextToken` cursor for pagination.",
|
|
907
872
|
inputSchema: listPostsSchema,
|
|
908
|
-
handler: (args, ctx) => listPosts(ctx.graphql,
|
|
873
|
+
handler: (args, ctx) => listPosts(ctx.graphql, args)
|
|
909
874
|
},
|
|
910
875
|
{
|
|
911
876
|
name: "get_post",
|
|
912
877
|
description: "Fetch a single post by slug or postId. Returns null if not found.",
|
|
913
878
|
inputSchema: getPostSchema,
|
|
914
|
-
handler: (args, ctx) => getPost(ctx.graphql,
|
|
879
|
+
handler: (args, ctx) => getPost(ctx.graphql, args)
|
|
915
880
|
},
|
|
916
881
|
{
|
|
917
882
|
name: "create_post",
|
|
918
|
-
description:
|
|
883
|
+
description: 'Create a new post. Title and slug are required. Body shape depends on format: tiptap=JSON node tree, markdown=source string, html=raw HTML string. Defaults to status=draft. Pass `metadata: { no_layout: true }` alongside format=html to publish the body as a bare HTML page with no theme chrome (middleware rewrites the /<slug> request to the internal bare-HTML handler). Pass `metadata: { cache: "deep" | "hot" }` to override the default cooldown-based cache strategy \u2014 see get_schema.notes.cacheStrategy for details.',
|
|
919
884
|
inputSchema: createPostSchema,
|
|
920
|
-
handler: (args, ctx) => createPost(ctx.graphql,
|
|
885
|
+
handler: (args, ctx) => createPost(ctx.graphql, args)
|
|
921
886
|
},
|
|
922
887
|
{
|
|
923
888
|
name: "update_post",
|
|
924
889
|
description: "Update an existing post by postId. Only the fields you pass are changed. Tag list / publishedAt changes also update the PostTag denormalized index. Passing `metadata` REPLACES the existing object \u2014 call get_post first if you only want to add or change one key.",
|
|
925
890
|
inputSchema: updatePostSchema,
|
|
926
|
-
handler: (args, ctx) => updatePost(ctx.graphql,
|
|
891
|
+
handler: (args, ctx) => updatePost(ctx.graphql, args)
|
|
927
892
|
},
|
|
928
893
|
{
|
|
929
894
|
name: "delete_post",
|
|
930
895
|
description: "Delete a post by postId. Also drops associated PostTag index entries.",
|
|
931
896
|
inputSchema: deletePostSchema,
|
|
932
|
-
handler: (args, ctx) => deletePost(ctx.graphql,
|
|
897
|
+
handler: (args, ctx) => deletePost(ctx.graphql, args)
|
|
933
898
|
},
|
|
934
899
|
{
|
|
935
900
|
name: "upload_media",
|
|
936
901
|
description: "Upload a file to the site's media S3 bucket. Pass base64-encoded bytes; the server stores them verbatim under public/media/YYYY/MM/. Returns the public URL and Media record. The server does not transcode \u2014 pre-process (e.g. resize/webp) on the client.",
|
|
937
902
|
inputSchema: uploadMediaSchema,
|
|
938
|
-
handler: (args, ctx) => uploadMedia(ctx.graphql, ctx.storage(),
|
|
903
|
+
handler: (args, ctx) => uploadMedia(ctx.graphql, ctx.storage(), args)
|
|
939
904
|
},
|
|
940
905
|
{
|
|
941
906
|
name: "get_schema",
|
|
@@ -945,12 +910,11 @@ var tools = [
|
|
|
945
910
|
},
|
|
946
911
|
{
|
|
947
912
|
name: "upload_static_bundle",
|
|
948
|
-
description: "Create or replace a `format: 'static'` post in one shot. Pass a base64-encoded zip; the server unpacks it, validates every path + lints HTML/CSS/SVG for absolute path refs (bundles must be self-contained via relative paths), wipes the existing S3 prefix at public/static/<
|
|
913
|
+
description: "Create or replace a `format: 'static'` post in one shot. Pass a base64-encoded zip; the server unpacks it, validates every path + lints HTML/CSS/SVG for absolute path refs (bundles must be self-contained via relative paths), wipes the existing S3 prefix at public/static/<slug>/, uploads every file, and upserts the Post row with a manifest pointing at the entrypoint. Use this when you have the whole bundle to submit at once. For incremental edits use upload_static_file / delete_static_file followed by commit_static_post.",
|
|
949
914
|
inputSchema: uploadStaticBundleSchema,
|
|
950
915
|
handler: (args, ctx) => uploadStaticBundle(
|
|
951
916
|
ctx.graphql,
|
|
952
917
|
ctx.storage(),
|
|
953
|
-
ctx.defaultSiteId,
|
|
954
918
|
args
|
|
955
919
|
)
|
|
956
920
|
},
|
|
@@ -960,7 +924,6 @@ var tools = [
|
|
|
960
924
|
inputSchema: uploadStaticFileSchema,
|
|
961
925
|
handler: (args, ctx) => uploadStaticFile(
|
|
962
926
|
ctx.storage(),
|
|
963
|
-
ctx.defaultSiteId,
|
|
964
927
|
args
|
|
965
928
|
)
|
|
966
929
|
},
|
|
@@ -970,18 +933,16 @@ var tools = [
|
|
|
970
933
|
inputSchema: deleteStaticFileSchema,
|
|
971
934
|
handler: (args, ctx) => deleteStaticFile(
|
|
972
935
|
ctx.storage(),
|
|
973
|
-
ctx.defaultSiteId,
|
|
974
936
|
args
|
|
975
937
|
)
|
|
976
938
|
},
|
|
977
939
|
{
|
|
978
940
|
name: "commit_static_post",
|
|
979
|
-
description: 'Rebuild a static post\'s Post row from whatever is currently in its S3 prefix. Scans `public/static/<
|
|
941
|
+
description: 'Rebuild a static post\'s Post row from whatever is currently in its S3 prefix. Scans `public/static/<slug>/`, picks the entrypoint (default `index.html` or override), and upserts the Post with a fresh manifest (sorted file list + uploadedAt timestamp). Use this as the "save" step after a series of `upload_static_file` / `delete_static_file` calls. `title` is required when creating a brand-new post; on update, existing fields are preserved unless explicitly overridden.',
|
|
980
942
|
inputSchema: commitStaticPostSchema,
|
|
981
943
|
handler: (args, ctx) => commitStaticPost(
|
|
982
944
|
ctx.graphql,
|
|
983
945
|
ctx.storage(),
|
|
984
|
-
ctx.defaultSiteId,
|
|
985
946
|
args
|
|
986
947
|
)
|
|
987
948
|
}
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "./chunk-IW52OUIR.js";
|
|
8
8
|
import {
|
|
9
9
|
tools
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-K4GTND6P.js";
|
|
11
11
|
import {
|
|
12
12
|
AwsCrc32,
|
|
13
13
|
__awaiter,
|
|
@@ -10895,10 +10895,6 @@ function parseArgs(argv) {
|
|
|
10895
10895
|
out.outputs = argv[++i2];
|
|
10896
10896
|
} else if (arg.startsWith("--outputs=")) {
|
|
10897
10897
|
out.outputs = arg.slice("--outputs=".length);
|
|
10898
|
-
} else if (arg === "--site-id") {
|
|
10899
|
-
out.defaultSiteId = argv[++i2];
|
|
10900
|
-
} else if (arg.startsWith("--site-id=")) {
|
|
10901
|
-
out.defaultSiteId = arg.slice("--site-id=".length);
|
|
10902
10898
|
}
|
|
10903
10899
|
}
|
|
10904
10900
|
return out;
|
|
@@ -10938,8 +10934,7 @@ async function loadConfig2(argv = process.argv.slice(2)) {
|
|
|
10938
10934
|
return {
|
|
10939
10935
|
outputs,
|
|
10940
10936
|
email: requireEnv("AMPLESS_MCP_EMAIL"),
|
|
10941
|
-
password: requireEnv("AMPLESS_MCP_PASSWORD")
|
|
10942
|
-
defaultSiteId: args.defaultSiteId ?? process.env.AMPLESS_MCP_SITE_ID ?? "default"
|
|
10937
|
+
password: requireEnv("AMPLESS_MCP_PASSWORD")
|
|
10943
10938
|
};
|
|
10944
10939
|
}
|
|
10945
10940
|
|
|
@@ -32181,8 +32176,7 @@ async function startServer(config2) {
|
|
|
32181
32176
|
};
|
|
32182
32177
|
const ctx = {
|
|
32183
32178
|
graphql,
|
|
32184
|
-
storage
|
|
32185
|
-
defaultSiteId: config2.defaultSiteId
|
|
32179
|
+
storage
|
|
32186
32180
|
};
|
|
32187
32181
|
const server = new Server(
|
|
32188
32182
|
{ name: "@ampless/mcp-server", version: "0.0.1" },
|
package/dist/tools/index.d.ts
CHANGED
package/dist/tools/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ampless/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-alpha.11",
|
|
4
4
|
"description": "MCP server for ampless — lets AI agents (Claude Desktop, Cursor, Claude Code) read and write posts and media via AppSync",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"@aws-sdk/client-s3": "^3.1048.0",
|
|
37
37
|
"amazon-cognito-identity-js": "^6.3.12",
|
|
38
38
|
"fflate": "^0.8.2",
|
|
39
|
-
"ampless": "0.
|
|
39
|
+
"ampless": "1.0.0-alpha.10"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
42
42
|
"ampless",
|