@ampless/mcp-server 0.2.0-alpha.4 → 0.2.0-alpha.5

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.
@@ -0,0 +1,552 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/tools/post-mapping.ts
4
+ var POST_FIELDS = (
5
+ /* GraphQL */
6
+ `
7
+ fragment PostFields on Post {
8
+ siteId
9
+ postId
10
+ slug
11
+ title
12
+ excerpt
13
+ format
14
+ body
15
+ status
16
+ publishedAt
17
+ tags
18
+ }
19
+ `
20
+ );
21
+ function decodeBody(value) {
22
+ if (typeof value !== "string") return value;
23
+ try {
24
+ return JSON.parse(value);
25
+ } catch {
26
+ return value;
27
+ }
28
+ }
29
+ function encodeBody(value) {
30
+ if (typeof value === "string") return value;
31
+ return JSON.stringify(value ?? null);
32
+ }
33
+ function toCorePost(p) {
34
+ return {
35
+ siteId: p.siteId,
36
+ postId: p.postId,
37
+ slug: p.slug,
38
+ title: p.title,
39
+ excerpt: p.excerpt ?? void 0,
40
+ format: p.format ?? "markdown",
41
+ body: decodeBody(p.body),
42
+ status: p.status ?? "draft",
43
+ publishedAt: p.publishedAt ?? void 0,
44
+ tags: (p.tags ?? []).filter((t) => typeof t === "string")
45
+ };
46
+ }
47
+
48
+ // src/tools/list-posts.ts
49
+ var QUERY = (
50
+ /* GraphQL */
51
+ `
52
+ ${POST_FIELDS}
53
+ query ListPosts($filter: ModelPostFilterInput, $limit: Int, $nextToken: String) {
54
+ listPosts(filter: $filter, limit: $limit, nextToken: $nextToken) {
55
+ items {
56
+ ...PostFields
57
+ }
58
+ nextToken
59
+ }
60
+ }
61
+ `
62
+ );
63
+ var listPostsSchema = {
64
+ type: "object",
65
+ properties: {
66
+ siteId: { type: "string", description: 'Site identifier (defaults to "default")' },
67
+ status: {
68
+ type: "string",
69
+ enum: ["draft", "published", "all"],
70
+ description: 'Filter by status (default "all")'
71
+ },
72
+ limit: { type: "integer", minimum: 1, maximum: 100, description: "Max results (default 20)" },
73
+ nextToken: { type: "string", description: "Pagination cursor from a previous call" }
74
+ }
75
+ };
76
+ async function listPosts(client, defaultSiteId, args = {}) {
77
+ const siteId = args.siteId ?? defaultSiteId;
78
+ const status = args.status ?? "all";
79
+ const filter = { siteId: { eq: siteId } };
80
+ if (status !== "all") filter.status = { eq: status };
81
+ const data = await client.query(QUERY, { filter, limit: args.limit ?? 20, nextToken: args.nextToken });
82
+ return {
83
+ posts: data.listPosts.items.map(toCorePost),
84
+ nextToken: data.listPosts.nextToken
85
+ };
86
+ }
87
+
88
+ // src/tools/get-post.ts
89
+ var GET_BY_ID = (
90
+ /* GraphQL */
91
+ `
92
+ ${POST_FIELDS}
93
+ query GetPost($siteId: String!, $postId: ID!) {
94
+ getPost(siteId: $siteId, postId: $postId) {
95
+ ...PostFields
96
+ }
97
+ }
98
+ `
99
+ );
100
+ var LIST_BY_SLUG = (
101
+ /* GraphQL */
102
+ `
103
+ ${POST_FIELDS}
104
+ query GetPostBySlug($filter: ModelPostFilterInput!) {
105
+ listPosts(filter: $filter, limit: 1) {
106
+ items {
107
+ ...PostFields
108
+ }
109
+ }
110
+ }
111
+ `
112
+ );
113
+ var getPostSchema = {
114
+ type: "object",
115
+ properties: {
116
+ siteId: { type: "string", description: 'Site identifier (defaults to "default")' },
117
+ slug: { type: "string", description: "Post slug" },
118
+ postId: { type: "string", description: "Post id (used when slug is omitted)" }
119
+ }
120
+ };
121
+ async function getPost(client, defaultSiteId, args) {
122
+ if (!args.slug && !args.postId) {
123
+ throw new Error("get_post requires either `slug` or `postId`");
124
+ }
125
+ const siteId = args.siteId ?? defaultSiteId;
126
+ if (args.postId) {
127
+ const data2 = await client.query(GET_BY_ID, { siteId, postId: args.postId });
128
+ return data2.getPost ? toCorePost(data2.getPost) : null;
129
+ }
130
+ const data = await client.query(LIST_BY_SLUG, {
131
+ filter: { siteId: { eq: siteId }, slug: { eq: args.slug } }
132
+ });
133
+ const item = data.listPosts.items[0];
134
+ return item ? toCorePost(item) : null;
135
+ }
136
+
137
+ // src/tools/create-post.ts
138
+ import { composeSiteIdStatus, composeSiteIdSlug } from "ampless";
139
+
140
+ // src/posttag.ts
141
+ function entries(post) {
142
+ if (post.status !== "published" || !post.publishedAt || !post.tags?.length) return [];
143
+ return post.tags.map((tag) => ({
144
+ siteIdTag: `${post.siteId}#${tag}`,
145
+ publishedAtPostId: `${post.publishedAt}#${post.postId}`
146
+ }));
147
+ }
148
+ function entryKey(e) {
149
+ return `${e.siteIdTag}|${e.publishedAtPostId}`;
150
+ }
151
+ var CREATE_POST_TAG = (
152
+ /* GraphQL */
153
+ `
154
+ mutation CreatePostTag($input: CreatePostTagInput!) {
155
+ createPostTag(input: $input) {
156
+ siteIdTag
157
+ publishedAtPostId
158
+ }
159
+ }
160
+ `
161
+ );
162
+ var UPDATE_POST_TAG = (
163
+ /* GraphQL */
164
+ `
165
+ mutation UpdatePostTag($input: UpdatePostTagInput!) {
166
+ updatePostTag(input: $input) {
167
+ siteIdTag
168
+ publishedAtPostId
169
+ }
170
+ }
171
+ `
172
+ );
173
+ var DELETE_POST_TAG = (
174
+ /* GraphQL */
175
+ `
176
+ mutation DeletePostTag($input: DeletePostTagInput!) {
177
+ deletePostTag(input: $input) {
178
+ siteIdTag
179
+ publishedAtPostId
180
+ }
181
+ }
182
+ `
183
+ );
184
+ async function syncPostTags(client, post, oldPost) {
185
+ const oldEntries = oldPost ? entries(oldPost) : [];
186
+ const newEntries = entries(post);
187
+ const oldKeys = new Set(oldEntries.map(entryKey));
188
+ const newKeys = new Set(newEntries.map(entryKey));
189
+ await Promise.all(
190
+ oldEntries.filter((e) => !newKeys.has(entryKey(e))).map(
191
+ (e) => client.query(DELETE_POST_TAG, {
192
+ input: { siteIdTag: e.siteIdTag, publishedAtPostId: e.publishedAtPostId }
193
+ })
194
+ )
195
+ );
196
+ await Promise.all(
197
+ newEntries.filter((e) => !oldKeys.has(entryKey(e))).map(
198
+ (e) => client.query(CREATE_POST_TAG, {
199
+ input: {
200
+ siteIdTag: e.siteIdTag,
201
+ publishedAtPostId: e.publishedAtPostId,
202
+ siteId: post.siteId,
203
+ tag: e.siteIdTag.slice(post.siteId.length + 1),
204
+ postId: post.postId,
205
+ publishedAt: post.publishedAt,
206
+ slug: post.slug,
207
+ title: post.title,
208
+ excerpt: post.excerpt,
209
+ tags: post.tags ?? []
210
+ }
211
+ })
212
+ )
213
+ );
214
+ await Promise.all(
215
+ newEntries.filter((e) => oldKeys.has(entryKey(e))).map(
216
+ (e) => client.query(UPDATE_POST_TAG, {
217
+ input: {
218
+ siteIdTag: e.siteIdTag,
219
+ publishedAtPostId: e.publishedAtPostId,
220
+ slug: post.slug,
221
+ title: post.title,
222
+ excerpt: post.excerpt,
223
+ tags: post.tags ?? []
224
+ }
225
+ })
226
+ )
227
+ );
228
+ }
229
+
230
+ // src/tools/create-post.ts
231
+ var MUTATION = (
232
+ /* GraphQL */
233
+ `
234
+ ${POST_FIELDS}
235
+ mutation CreatePost($input: CreatePostInput!) {
236
+ createPost(input: $input) {
237
+ ...PostFields
238
+ }
239
+ }
240
+ `
241
+ );
242
+ var createPostSchema = {
243
+ type: "object",
244
+ required: ["slug", "title", "format", "body"],
245
+ properties: {
246
+ siteId: { type: "string", description: 'Site identifier (defaults to "default")' },
247
+ postId: { type: "string", description: "Optional explicit id; auto-generated if omitted" },
248
+ slug: { type: "string" },
249
+ title: { type: "string" },
250
+ format: { type: "string", enum: ["tiptap", "markdown", "html"] },
251
+ body: {
252
+ description: "tiptap JSON (when format=tiptap), markdown source string, or raw HTML string"
253
+ },
254
+ status: { type: "string", enum: ["draft", "published"], default: "draft" },
255
+ excerpt: { type: "string" },
256
+ publishedAt: { type: "string", description: "ISO 8601 timestamp; required when status=published" },
257
+ tags: { type: "array", items: { type: "string" } }
258
+ }
259
+ };
260
+ async function createPost(client, defaultSiteId, args) {
261
+ const siteId = args.siteId ?? defaultSiteId;
262
+ const postId = args.postId ?? `post-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
263
+ const status = args.status ?? "draft";
264
+ const publishedAt = args.publishedAt ?? (status === "published" ? (/* @__PURE__ */ new Date()).toISOString() : void 0);
265
+ const data = await client.query(MUTATION, {
266
+ input: {
267
+ siteId,
268
+ postId,
269
+ slug: args.slug,
270
+ title: args.title,
271
+ excerpt: args.excerpt,
272
+ format: args.format,
273
+ body: encodeBody(args.body),
274
+ status,
275
+ publishedAt,
276
+ tags: args.tags,
277
+ // Denormalized GSI keys.
278
+ siteIdStatus: composeSiteIdStatus(siteId, status),
279
+ siteIdSlug: composeSiteIdSlug(siteId, args.slug)
280
+ }
281
+ });
282
+ const created = toCorePost(data.createPost);
283
+ await syncPostTags(client, created, null);
284
+ return created;
285
+ }
286
+
287
+ // src/tools/update-post.ts
288
+ import { composeSiteIdStatus as composeSiteIdStatus2, composeSiteIdSlug as composeSiteIdSlug2 } from "ampless";
289
+ var MUTATION2 = (
290
+ /* GraphQL */
291
+ `
292
+ ${POST_FIELDS}
293
+ mutation UpdatePost($input: UpdatePostInput!) {
294
+ updatePost(input: $input) {
295
+ ...PostFields
296
+ }
297
+ }
298
+ `
299
+ );
300
+ var updatePostSchema = {
301
+ type: "object",
302
+ required: ["postId"],
303
+ properties: {
304
+ postId: { type: "string" },
305
+ siteId: { type: "string", description: 'Site identifier (defaults to "default")' },
306
+ slug: { type: "string" },
307
+ title: { type: "string" },
308
+ excerpt: { type: "string" },
309
+ format: { type: "string", enum: ["tiptap", "markdown", "html"] },
310
+ body: { description: "tiptap JSON, markdown source, or raw HTML" },
311
+ status: { type: "string", enum: ["draft", "published"] },
312
+ publishedAt: { type: "string", description: "ISO 8601 timestamp" },
313
+ tags: { type: "array", items: { type: "string" } }
314
+ }
315
+ };
316
+ async function updatePost(client, defaultSiteId, args) {
317
+ const siteId = args.siteId ?? defaultSiteId;
318
+ const oldPost = await getPost(client, defaultSiteId, { postId: args.postId, siteId });
319
+ const input = { siteId, postId: args.postId };
320
+ if (args.slug !== void 0) input.slug = args.slug;
321
+ if (args.title !== void 0) input.title = args.title;
322
+ if (args.excerpt !== void 0) input.excerpt = args.excerpt;
323
+ if (args.format !== void 0) input.format = args.format;
324
+ if (args.body !== void 0) input.body = encodeBody(args.body);
325
+ if (args.status !== void 0) input.status = args.status;
326
+ if (args.publishedAt !== void 0) input.publishedAt = args.publishedAt;
327
+ if (args.tags !== void 0) input.tags = args.tags;
328
+ if (args.status !== void 0) {
329
+ input.siteIdStatus = composeSiteIdStatus2(siteId, args.status);
330
+ }
331
+ if (args.slug !== void 0) {
332
+ input.siteIdSlug = composeSiteIdSlug2(siteId, args.slug);
333
+ }
334
+ const data = await client.query(MUTATION2, { input });
335
+ const updated = toCorePost(data.updatePost);
336
+ await syncPostTags(client, updated, oldPost);
337
+ return updated;
338
+ }
339
+
340
+ // src/tools/delete-post.ts
341
+ var MUTATION3 = (
342
+ /* GraphQL */
343
+ `
344
+ mutation DeletePost($input: DeletePostInput!) {
345
+ deletePost(input: $input) {
346
+ siteId
347
+ postId
348
+ }
349
+ }
350
+ `
351
+ );
352
+ var deletePostSchema = {
353
+ type: "object",
354
+ required: ["postId"],
355
+ properties: {
356
+ postId: { type: "string" },
357
+ siteId: { type: "string", description: 'Site identifier (defaults to "default")' }
358
+ }
359
+ };
360
+ async function deletePost(client, defaultSiteId, args) {
361
+ const siteId = args.siteId ?? defaultSiteId;
362
+ const oldPost = await getPost(client, defaultSiteId, { postId: args.postId, siteId });
363
+ if (oldPost) {
364
+ await syncPostTags(client, { ...oldPost, status: "draft" }, oldPost);
365
+ }
366
+ const data = await client.query(MUTATION3, { input: { siteId, postId: args.postId } });
367
+ return { deleted: data.deletePost };
368
+ }
369
+
370
+ // src/tools/media-key.ts
371
+ function sanitizeName(name) {
372
+ return name.replace(/[\x00-\x1f\x7f]/g, "").replace(/[\\/:*?"<>|]/g, "_").replace(/\s+/g, "_").replace(/^\.+/, "_").slice(0, 200) || "upload";
373
+ }
374
+ function buildMediaKey(filename, now = /* @__PURE__ */ new Date()) {
375
+ const safe = sanitizeName(filename);
376
+ const yyyy = now.getFullYear();
377
+ const mm = String(now.getMonth() + 1).padStart(2, "0");
378
+ return `public/media/${yyyy}/${mm}/${now.getTime()}-${safe}`;
379
+ }
380
+
381
+ // src/tools/upload-media.ts
382
+ var MUTATION4 = (
383
+ /* GraphQL */
384
+ `
385
+ mutation CreateMedia($input: CreateMediaInput!) {
386
+ createMedia(input: $input) {
387
+ siteId
388
+ mediaId
389
+ src
390
+ mimeType
391
+ size
392
+ delivery
393
+ }
394
+ }
395
+ `
396
+ );
397
+ var uploadMediaSchema = {
398
+ type: "object",
399
+ required: ["filename", "mimeType", "base64Data"],
400
+ properties: {
401
+ siteId: { type: "string", description: 'Site identifier (defaults to "default")' },
402
+ filename: { type: "string", description: "Original filename; sanitized server-side" },
403
+ mimeType: {
404
+ type: "string",
405
+ description: "IANA media type (e.g. image/webp, image/jpeg). The MCP server does not transcode \u2014 pass the MIME of the bytes you actually send."
406
+ },
407
+ base64Data: {
408
+ type: "string",
409
+ description: "Base64-encoded file contents. NO data:URL prefix. The MCP server uploads bytes verbatim \u2014 resize/encode client-side first if needed."
410
+ }
411
+ }
412
+ };
413
+ async function uploadMedia(graphql, storage, defaultSiteId, args) {
414
+ const siteId = args.siteId ?? defaultSiteId;
415
+ const body = Buffer.from(args.base64Data, "base64");
416
+ const key = buildMediaKey(args.filename);
417
+ const url = await storage.putObject(key, body, args.mimeType);
418
+ const mediaId = `media-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
419
+ const data = await graphql.query(MUTATION4, {
420
+ input: {
421
+ siteId,
422
+ mediaId,
423
+ src: key,
424
+ mimeType: args.mimeType,
425
+ size: body.length,
426
+ delivery: "nextjs"
427
+ }
428
+ });
429
+ return { media: data.createMedia, url };
430
+ }
431
+
432
+ // src/tools/get-schema.ts
433
+ var getSchemaSchema = {
434
+ type: "object",
435
+ properties: {}
436
+ };
437
+ function getSchema() {
438
+ return {
439
+ contentTypes: [
440
+ {
441
+ name: "post",
442
+ identifier: ["siteId", "postId"],
443
+ fields: {
444
+ siteId: { type: "string", required: true },
445
+ postId: { type: "string", required: true, description: "auto-generated if omitted on create" },
446
+ slug: { type: "string", required: true, description: "URL slug, unique per site" },
447
+ title: { type: "string", required: true },
448
+ excerpt: { type: "string" },
449
+ format: { type: "enum", values: ["tiptap", "markdown", "html"], required: true },
450
+ body: {
451
+ type: "json",
452
+ description: "tiptap node JSON when format=tiptap; markdown source string when format=markdown; raw HTML string when format=html"
453
+ },
454
+ status: { type: "enum", values: ["draft", "published"], default: "draft" },
455
+ publishedAt: { type: "datetime", description: "ISO 8601" },
456
+ tags: { type: "string[]" }
457
+ }
458
+ },
459
+ {
460
+ name: "page",
461
+ identifier: ["siteId", "pageId"],
462
+ fields: {
463
+ siteId: { type: "string", required: true },
464
+ pageId: { type: "string", required: true },
465
+ slug: { type: "string", required: true },
466
+ title: { type: "string", required: true },
467
+ format: { type: "enum", values: ["tiptap", "markdown", "html"], required: true },
468
+ body: { type: "json" },
469
+ status: { type: "enum", values: ["draft", "published"] },
470
+ publishedAt: { type: "datetime" }
471
+ }
472
+ },
473
+ {
474
+ name: "media",
475
+ identifier: ["siteId", "mediaId"],
476
+ fields: {
477
+ siteId: { type: "string", required: true },
478
+ mediaId: { type: "string", required: true },
479
+ src: { type: "string", required: true, description: "S3 key" },
480
+ mimeType: { type: "string", required: true },
481
+ size: { type: "integer" },
482
+ delivery: { type: "string" }
483
+ }
484
+ }
485
+ ],
486
+ formats: ["tiptap", "markdown", "html"],
487
+ notes: {
488
+ 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".',
489
+ tiptapBody: 'When format=tiptap, body is the tiptap document JSON: { type: "doc", content: [...] }. The renderer expects this shape.'
490
+ }
491
+ };
492
+ }
493
+
494
+ // src/tools/index.ts
495
+ var tools = [
496
+ {
497
+ name: "list_posts",
498
+ description: "List posts in the CMS with optional filters by status. Returns up to `limit` posts (default 20) plus a `nextToken` cursor for pagination.",
499
+ inputSchema: listPostsSchema,
500
+ handler: (args, ctx) => listPosts(ctx.graphql, ctx.defaultSiteId, args)
501
+ },
502
+ {
503
+ name: "get_post",
504
+ description: "Fetch a single post by slug or postId. Returns null if not found.",
505
+ inputSchema: getPostSchema,
506
+ handler: (args, ctx) => getPost(ctx.graphql, ctx.defaultSiteId, args)
507
+ },
508
+ {
509
+ name: "create_post",
510
+ 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.",
511
+ inputSchema: createPostSchema,
512
+ handler: (args, ctx) => createPost(ctx.graphql, ctx.defaultSiteId, args)
513
+ },
514
+ {
515
+ name: "update_post",
516
+ description: "Update an existing post by postId. Only the fields you pass are changed. Tag list / publishedAt changes also update the PostTag denormalized index.",
517
+ inputSchema: updatePostSchema,
518
+ handler: (args, ctx) => updatePost(ctx.graphql, ctx.defaultSiteId, args)
519
+ },
520
+ {
521
+ name: "delete_post",
522
+ description: "Delete a post by postId. Also drops associated PostTag index entries.",
523
+ inputSchema: deletePostSchema,
524
+ handler: (args, ctx) => deletePost(ctx.graphql, ctx.defaultSiteId, args)
525
+ },
526
+ {
527
+ name: "upload_media",
528
+ 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.",
529
+ inputSchema: uploadMediaSchema,
530
+ handler: (args, ctx) => uploadMedia(ctx.graphql, ctx.storage(), ctx.defaultSiteId, args)
531
+ },
532
+ {
533
+ name: "get_schema",
534
+ description: "Returns the CMS content schema (Post/Page/Media field shapes, format enum, notes). Useful as the first call to understand what fields are available.",
535
+ inputSchema: getSchemaSchema,
536
+ handler: async () => getSchema()
537
+ }
538
+ ];
539
+ async function dispatchToolCall(name, args, ctx) {
540
+ const tool = tools.find((t) => t.name === name);
541
+ if (!tool) return null;
542
+ return tool.handler(args, ctx);
543
+ }
544
+ function getTools() {
545
+ return tools;
546
+ }
547
+
548
+ export {
549
+ tools,
550
+ dispatchToolCall,
551
+ getTools
552
+ };
@@ -400,7 +400,7 @@ var resolveProcessCredentials = async (options, profile) => import("./dist-es-OO
400
400
 
401
401
  // ../../node_modules/.pnpm/@aws-sdk+credential-provider-ini@3.972.41/node_modules/@aws-sdk/credential-provider-ini/dist-es/resolveSsoCredentials.js
402
402
  var resolveSsoCredentials = async (profile, profileData, options = {}, callerClientConfig) => {
403
- const { fromSSO } = await import("./dist-es-NAMW5ZZ4.js");
403
+ const { fromSSO } = await import("./dist-es-OKPWYZO7.js");
404
404
  return fromSSO({
405
405
  profile,
406
406
  logger: options.logger,
package/dist/index.js CHANGED
@@ -5,6 +5,9 @@ import {
5
5
  XmlShapeDeserializer,
6
6
  XmlText
7
7
  } from "./chunk-IW52OUIR.js";
8
+ import {
9
+ tools
10
+ } from "./chunk-KM6F5QJH.js";
8
11
  import {
9
12
  AwsCrc32,
10
13
  __awaiter,
@@ -11093,9 +11096,9 @@ function assertNever(_x2) {
11093
11096
  }
11094
11097
  function assert(_) {
11095
11098
  }
11096
- function getEnumValues(entries2) {
11097
- const numericValues = Object.values(entries2).filter((v2) => typeof v2 === "number");
11098
- const values = Object.entries(entries2).filter(([k2, _]) => numericValues.indexOf(+k2) === -1).map(([_, v2]) => v2);
11099
+ function getEnumValues(entries) {
11100
+ const numericValues = Object.values(entries).filter((v2) => typeof v2 === "number");
11101
+ const values = Object.entries(entries).filter(([k2, _]) => numericValues.indexOf(+k2) === -1).map(([_, v2]) => v2);
11099
11102
  return values;
11100
11103
  }
11101
11104
  function joinValues(array2, separator = "|") {
@@ -15980,10 +15983,10 @@ var ZodEnum = /* @__PURE__ */ $constructor("ZodEnum", (inst, def) => {
15980
15983
  };
15981
15984
  });
15982
15985
  function _enum(values, params) {
15983
- const entries2 = Array.isArray(values) ? Object.fromEntries(values.map((v2) => [v2, v2])) : values;
15986
+ const entries = Array.isArray(values) ? Object.fromEntries(values.map((v2) => [v2, v2])) : values;
15984
15987
  return new ZodEnum({
15985
15988
  type: "enum",
15986
- entries: entries2,
15989
+ entries,
15987
15990
  ...util_exports.normalizeParams(params)
15988
15991
  });
15989
15992
  }
@@ -31745,12 +31748,12 @@ var defaultProvider = (init = {}) => memoizeChain([
31745
31748
  if (!ssoStartUrl && !ssoAccountId && !ssoRegion && !ssoRoleName && !ssoSession) {
31746
31749
  throw new CredentialsProviderError("Skipping SSO provider in default chain (inputs do not include SSO fields).", { logger: init.logger });
31747
31750
  }
31748
- const { fromSSO } = await import("./dist-es-NAMW5ZZ4.js");
31751
+ const { fromSSO } = await import("./dist-es-OKPWYZO7.js");
31749
31752
  return fromSSO(init)(awsIdentityProperties);
31750
31753
  },
31751
31754
  async (awsIdentityProperties) => {
31752
31755
  init.logger?.debug("@aws-sdk/credential-provider-node - defaultProvider::fromIni");
31753
- const { fromIni } = await import("./dist-es-ZP52SD4G.js");
31756
+ const { fromIni } = await import("./dist-es-J4X5E4TX.js");
31754
31757
  return fromIni(init)(awsIdentityProperties);
31755
31758
  },
31756
31759
  async (awsIdentityProperties) => {
@@ -32085,15 +32088,6 @@ var PutObjectCommand = class extends Command.classBuilder().ep({
32085
32088
 
32086
32089
  // src/s3.ts
32087
32090
  import { formatPublicAssetUrl } from "ampless";
32088
- function sanitizeName(name) {
32089
- return name.replace(/[\x00-\x1f\x7f]/g, "").replace(/[\\/:*?"<>|]/g, "_").replace(/\s+/g, "_").replace(/^\.+/, "_").slice(0, 200) || "upload";
32090
- }
32091
- function buildMediaKey(filename, now = /* @__PURE__ */ new Date()) {
32092
- const safe = sanitizeName(filename);
32093
- const yyyy = now.getFullYear();
32094
- const mm = String(now.getMonth() + 1).padStart(2, "0");
32095
- return `public/media/${yyyy}/${mm}/${now.getTime()}-${safe}`;
32096
- }
32097
32091
  var StorageClient = class {
32098
32092
  client;
32099
32093
  bucket;
@@ -32121,532 +32115,6 @@ var StorageClient = class {
32121
32115
  }
32122
32116
  };
32123
32117
 
32124
- // src/tools/post-mapping.ts
32125
- var POST_FIELDS = (
32126
- /* GraphQL */
32127
- `
32128
- fragment PostFields on Post {
32129
- siteId
32130
- postId
32131
- slug
32132
- title
32133
- excerpt
32134
- format
32135
- body
32136
- status
32137
- publishedAt
32138
- tags
32139
- }
32140
- `
32141
- );
32142
- function decodeBody(value) {
32143
- if (typeof value !== "string") return value;
32144
- try {
32145
- return JSON.parse(value);
32146
- } catch {
32147
- return value;
32148
- }
32149
- }
32150
- function encodeBody(value) {
32151
- if (typeof value === "string") return value;
32152
- return JSON.stringify(value ?? null);
32153
- }
32154
- function toCorePost(p2) {
32155
- return {
32156
- siteId: p2.siteId,
32157
- postId: p2.postId,
32158
- slug: p2.slug,
32159
- title: p2.title,
32160
- excerpt: p2.excerpt ?? void 0,
32161
- format: p2.format ?? "markdown",
32162
- body: decodeBody(p2.body),
32163
- status: p2.status ?? "draft",
32164
- publishedAt: p2.publishedAt ?? void 0,
32165
- tags: (p2.tags ?? []).filter((t8) => typeof t8 === "string")
32166
- };
32167
- }
32168
-
32169
- // src/tools/list-posts.ts
32170
- var QUERY = (
32171
- /* GraphQL */
32172
- `
32173
- ${POST_FIELDS}
32174
- query ListPosts($filter: ModelPostFilterInput, $limit: Int, $nextToken: String) {
32175
- listPosts(filter: $filter, limit: $limit, nextToken: $nextToken) {
32176
- items {
32177
- ...PostFields
32178
- }
32179
- nextToken
32180
- }
32181
- }
32182
- `
32183
- );
32184
- var listPostsSchema = {
32185
- type: "object",
32186
- properties: {
32187
- siteId: { type: "string", description: 'Site identifier (defaults to "default")' },
32188
- status: {
32189
- type: "string",
32190
- enum: ["draft", "published", "all"],
32191
- description: 'Filter by status (default "all")'
32192
- },
32193
- limit: { type: "integer", minimum: 1, maximum: 100, description: "Max results (default 20)" },
32194
- nextToken: { type: "string", description: "Pagination cursor from a previous call" }
32195
- }
32196
- };
32197
- async function listPosts(client, defaultSiteId, args = {}) {
32198
- const siteId = args.siteId ?? defaultSiteId;
32199
- const status = args.status ?? "all";
32200
- const filter = { siteId: { eq: siteId } };
32201
- if (status !== "all") filter.status = { eq: status };
32202
- const data = await client.query(QUERY, { filter, limit: args.limit ?? 20, nextToken: args.nextToken });
32203
- return {
32204
- posts: data.listPosts.items.map(toCorePost),
32205
- nextToken: data.listPosts.nextToken
32206
- };
32207
- }
32208
-
32209
- // src/tools/get-post.ts
32210
- var GET_BY_ID = (
32211
- /* GraphQL */
32212
- `
32213
- ${POST_FIELDS}
32214
- query GetPost($siteId: String!, $postId: ID!) {
32215
- getPost(siteId: $siteId, postId: $postId) {
32216
- ...PostFields
32217
- }
32218
- }
32219
- `
32220
- );
32221
- var LIST_BY_SLUG = (
32222
- /* GraphQL */
32223
- `
32224
- ${POST_FIELDS}
32225
- query GetPostBySlug($filter: ModelPostFilterInput!) {
32226
- listPosts(filter: $filter, limit: 1) {
32227
- items {
32228
- ...PostFields
32229
- }
32230
- }
32231
- }
32232
- `
32233
- );
32234
- var getPostSchema = {
32235
- type: "object",
32236
- properties: {
32237
- siteId: { type: "string", description: 'Site identifier (defaults to "default")' },
32238
- slug: { type: "string", description: "Post slug" },
32239
- postId: { type: "string", description: "Post id (used when slug is omitted)" }
32240
- }
32241
- };
32242
- async function getPost(client, defaultSiteId, args) {
32243
- if (!args.slug && !args.postId) {
32244
- throw new Error("get_post requires either `slug` or `postId`");
32245
- }
32246
- const siteId = args.siteId ?? defaultSiteId;
32247
- if (args.postId) {
32248
- const data2 = await client.query(GET_BY_ID, { siteId, postId: args.postId });
32249
- return data2.getPost ? toCorePost(data2.getPost) : null;
32250
- }
32251
- const data = await client.query(LIST_BY_SLUG, {
32252
- filter: { siteId: { eq: siteId }, slug: { eq: args.slug } }
32253
- });
32254
- const item = data.listPosts.items[0];
32255
- return item ? toCorePost(item) : null;
32256
- }
32257
-
32258
- // src/tools/create-post.ts
32259
- import { composeSiteIdStatus, composeSiteIdSlug } from "ampless";
32260
-
32261
- // src/posttag.ts
32262
- function entries(post) {
32263
- if (post.status !== "published" || !post.publishedAt || !post.tags?.length) return [];
32264
- return post.tags.map((tag) => ({
32265
- siteIdTag: `${post.siteId}#${tag}`,
32266
- publishedAtPostId: `${post.publishedAt}#${post.postId}`
32267
- }));
32268
- }
32269
- function entryKey(e2) {
32270
- return `${e2.siteIdTag}|${e2.publishedAtPostId}`;
32271
- }
32272
- var CREATE_POST_TAG = (
32273
- /* GraphQL */
32274
- `
32275
- mutation CreatePostTag($input: CreatePostTagInput!) {
32276
- createPostTag(input: $input) {
32277
- siteIdTag
32278
- publishedAtPostId
32279
- }
32280
- }
32281
- `
32282
- );
32283
- var UPDATE_POST_TAG = (
32284
- /* GraphQL */
32285
- `
32286
- mutation UpdatePostTag($input: UpdatePostTagInput!) {
32287
- updatePostTag(input: $input) {
32288
- siteIdTag
32289
- publishedAtPostId
32290
- }
32291
- }
32292
- `
32293
- );
32294
- var DELETE_POST_TAG = (
32295
- /* GraphQL */
32296
- `
32297
- mutation DeletePostTag($input: DeletePostTagInput!) {
32298
- deletePostTag(input: $input) {
32299
- siteIdTag
32300
- publishedAtPostId
32301
- }
32302
- }
32303
- `
32304
- );
32305
- async function syncPostTags(client, post, oldPost) {
32306
- const oldEntries = oldPost ? entries(oldPost) : [];
32307
- const newEntries = entries(post);
32308
- const oldKeys = new Set(oldEntries.map(entryKey));
32309
- const newKeys = new Set(newEntries.map(entryKey));
32310
- await Promise.all(
32311
- oldEntries.filter((e2) => !newKeys.has(entryKey(e2))).map(
32312
- (e2) => client.query(DELETE_POST_TAG, {
32313
- input: { siteIdTag: e2.siteIdTag, publishedAtPostId: e2.publishedAtPostId }
32314
- })
32315
- )
32316
- );
32317
- await Promise.all(
32318
- newEntries.filter((e2) => !oldKeys.has(entryKey(e2))).map(
32319
- (e2) => client.query(CREATE_POST_TAG, {
32320
- input: {
32321
- siteIdTag: e2.siteIdTag,
32322
- publishedAtPostId: e2.publishedAtPostId,
32323
- siteId: post.siteId,
32324
- tag: e2.siteIdTag.slice(post.siteId.length + 1),
32325
- postId: post.postId,
32326
- publishedAt: post.publishedAt,
32327
- slug: post.slug,
32328
- title: post.title,
32329
- excerpt: post.excerpt,
32330
- tags: post.tags ?? []
32331
- }
32332
- })
32333
- )
32334
- );
32335
- await Promise.all(
32336
- newEntries.filter((e2) => oldKeys.has(entryKey(e2))).map(
32337
- (e2) => client.query(UPDATE_POST_TAG, {
32338
- input: {
32339
- siteIdTag: e2.siteIdTag,
32340
- publishedAtPostId: e2.publishedAtPostId,
32341
- slug: post.slug,
32342
- title: post.title,
32343
- excerpt: post.excerpt,
32344
- tags: post.tags ?? []
32345
- }
32346
- })
32347
- )
32348
- );
32349
- }
32350
-
32351
- // src/tools/create-post.ts
32352
- var MUTATION = (
32353
- /* GraphQL */
32354
- `
32355
- ${POST_FIELDS}
32356
- mutation CreatePost($input: CreatePostInput!) {
32357
- createPost(input: $input) {
32358
- ...PostFields
32359
- }
32360
- }
32361
- `
32362
- );
32363
- var createPostSchema = {
32364
- type: "object",
32365
- required: ["slug", "title", "format", "body"],
32366
- properties: {
32367
- siteId: { type: "string", description: 'Site identifier (defaults to "default")' },
32368
- postId: { type: "string", description: "Optional explicit id; auto-generated if omitted" },
32369
- slug: { type: "string" },
32370
- title: { type: "string" },
32371
- format: { type: "string", enum: ["tiptap", "markdown", "html"] },
32372
- body: {
32373
- description: "tiptap JSON (when format=tiptap), markdown source string, or raw HTML string"
32374
- },
32375
- status: { type: "string", enum: ["draft", "published"], default: "draft" },
32376
- excerpt: { type: "string" },
32377
- publishedAt: { type: "string", description: "ISO 8601 timestamp; required when status=published" },
32378
- tags: { type: "array", items: { type: "string" } }
32379
- }
32380
- };
32381
- async function createPost(client, defaultSiteId, args) {
32382
- const siteId = args.siteId ?? defaultSiteId;
32383
- const postId = args.postId ?? `post-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
32384
- const status = args.status ?? "draft";
32385
- const publishedAt = args.publishedAt ?? (status === "published" ? (/* @__PURE__ */ new Date()).toISOString() : void 0);
32386
- const data = await client.query(MUTATION, {
32387
- input: {
32388
- siteId,
32389
- postId,
32390
- slug: args.slug,
32391
- title: args.title,
32392
- excerpt: args.excerpt,
32393
- format: args.format,
32394
- body: encodeBody(args.body),
32395
- status,
32396
- publishedAt,
32397
- tags: args.tags,
32398
- // Denormalized GSI keys.
32399
- siteIdStatus: composeSiteIdStatus(siteId, status),
32400
- siteIdSlug: composeSiteIdSlug(siteId, args.slug)
32401
- }
32402
- });
32403
- const created = toCorePost(data.createPost);
32404
- await syncPostTags(client, created, null);
32405
- return created;
32406
- }
32407
-
32408
- // src/tools/update-post.ts
32409
- import { composeSiteIdStatus as composeSiteIdStatus2, composeSiteIdSlug as composeSiteIdSlug2 } from "ampless";
32410
- var MUTATION2 = (
32411
- /* GraphQL */
32412
- `
32413
- ${POST_FIELDS}
32414
- mutation UpdatePost($input: UpdatePostInput!) {
32415
- updatePost(input: $input) {
32416
- ...PostFields
32417
- }
32418
- }
32419
- `
32420
- );
32421
- var updatePostSchema = {
32422
- type: "object",
32423
- required: ["postId"],
32424
- properties: {
32425
- postId: { type: "string" },
32426
- siteId: { type: "string", description: 'Site identifier (defaults to "default")' },
32427
- slug: { type: "string" },
32428
- title: { type: "string" },
32429
- excerpt: { type: "string" },
32430
- format: { type: "string", enum: ["tiptap", "markdown", "html"] },
32431
- body: { description: "tiptap JSON, markdown source, or raw HTML" },
32432
- status: { type: "string", enum: ["draft", "published"] },
32433
- publishedAt: { type: "string", description: "ISO 8601 timestamp" },
32434
- tags: { type: "array", items: { type: "string" } }
32435
- }
32436
- };
32437
- async function updatePost(client, defaultSiteId, args) {
32438
- const siteId = args.siteId ?? defaultSiteId;
32439
- const oldPost = await getPost(client, defaultSiteId, { postId: args.postId, siteId });
32440
- const input = { siteId, postId: args.postId };
32441
- if (args.slug !== void 0) input.slug = args.slug;
32442
- if (args.title !== void 0) input.title = args.title;
32443
- if (args.excerpt !== void 0) input.excerpt = args.excerpt;
32444
- if (args.format !== void 0) input.format = args.format;
32445
- if (args.body !== void 0) input.body = encodeBody(args.body);
32446
- if (args.status !== void 0) input.status = args.status;
32447
- if (args.publishedAt !== void 0) input.publishedAt = args.publishedAt;
32448
- if (args.tags !== void 0) input.tags = args.tags;
32449
- if (args.status !== void 0) {
32450
- input.siteIdStatus = composeSiteIdStatus2(siteId, args.status);
32451
- }
32452
- if (args.slug !== void 0) {
32453
- input.siteIdSlug = composeSiteIdSlug2(siteId, args.slug);
32454
- }
32455
- const data = await client.query(MUTATION2, { input });
32456
- const updated = toCorePost(data.updatePost);
32457
- await syncPostTags(client, updated, oldPost);
32458
- return updated;
32459
- }
32460
-
32461
- // src/tools/delete-post.ts
32462
- var MUTATION3 = (
32463
- /* GraphQL */
32464
- `
32465
- mutation DeletePost($input: DeletePostInput!) {
32466
- deletePost(input: $input) {
32467
- siteId
32468
- postId
32469
- }
32470
- }
32471
- `
32472
- );
32473
- var deletePostSchema = {
32474
- type: "object",
32475
- required: ["postId"],
32476
- properties: {
32477
- postId: { type: "string" },
32478
- siteId: { type: "string", description: 'Site identifier (defaults to "default")' }
32479
- }
32480
- };
32481
- async function deletePost(client, defaultSiteId, args) {
32482
- const siteId = args.siteId ?? defaultSiteId;
32483
- const oldPost = await getPost(client, defaultSiteId, { postId: args.postId, siteId });
32484
- if (oldPost) {
32485
- await syncPostTags(client, { ...oldPost, status: "draft" }, oldPost);
32486
- }
32487
- const data = await client.query(MUTATION3, { input: { siteId, postId: args.postId } });
32488
- return { deleted: data.deletePost };
32489
- }
32490
-
32491
- // src/tools/upload-media.ts
32492
- var MUTATION4 = (
32493
- /* GraphQL */
32494
- `
32495
- mutation CreateMedia($input: CreateMediaInput!) {
32496
- createMedia(input: $input) {
32497
- siteId
32498
- mediaId
32499
- src
32500
- mimeType
32501
- size
32502
- delivery
32503
- }
32504
- }
32505
- `
32506
- );
32507
- var uploadMediaSchema = {
32508
- type: "object",
32509
- required: ["filename", "mimeType", "base64Data"],
32510
- properties: {
32511
- siteId: { type: "string", description: 'Site identifier (defaults to "default")' },
32512
- filename: { type: "string", description: "Original filename; sanitized server-side" },
32513
- mimeType: {
32514
- type: "string",
32515
- description: "IANA media type (e.g. image/webp, image/jpeg). The MCP server does not transcode \u2014 pass the MIME of the bytes you actually send."
32516
- },
32517
- base64Data: {
32518
- type: "string",
32519
- description: "Base64-encoded file contents. NO data:URL prefix. The MCP server uploads bytes verbatim \u2014 resize/encode client-side first if needed."
32520
- }
32521
- }
32522
- };
32523
- async function uploadMedia(graphql, storage, defaultSiteId, args) {
32524
- const siteId = args.siteId ?? defaultSiteId;
32525
- const body = Buffer.from(args.base64Data, "base64");
32526
- const key = buildMediaKey(args.filename);
32527
- const url = await storage.putObject(key, body, args.mimeType);
32528
- const mediaId = `media-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
32529
- const data = await graphql.query(MUTATION4, {
32530
- input: {
32531
- siteId,
32532
- mediaId,
32533
- src: key,
32534
- mimeType: args.mimeType,
32535
- size: body.length,
32536
- delivery: "nextjs"
32537
- }
32538
- });
32539
- return { media: data.createMedia, url };
32540
- }
32541
-
32542
- // src/tools/get-schema.ts
32543
- var getSchemaSchema = {
32544
- type: "object",
32545
- properties: {}
32546
- };
32547
- function getSchema() {
32548
- return {
32549
- contentTypes: [
32550
- {
32551
- name: "post",
32552
- identifier: ["siteId", "postId"],
32553
- fields: {
32554
- siteId: { type: "string", required: true },
32555
- postId: { type: "string", required: true, description: "auto-generated if omitted on create" },
32556
- slug: { type: "string", required: true, description: "URL slug, unique per site" },
32557
- title: { type: "string", required: true },
32558
- excerpt: { type: "string" },
32559
- format: { type: "enum", values: ["tiptap", "markdown", "html"], required: true },
32560
- body: {
32561
- type: "json",
32562
- description: "tiptap node JSON when format=tiptap; markdown source string when format=markdown; raw HTML string when format=html"
32563
- },
32564
- status: { type: "enum", values: ["draft", "published"], default: "draft" },
32565
- publishedAt: { type: "datetime", description: "ISO 8601" },
32566
- tags: { type: "string[]" }
32567
- }
32568
- },
32569
- {
32570
- name: "page",
32571
- identifier: ["siteId", "pageId"],
32572
- fields: {
32573
- siteId: { type: "string", required: true },
32574
- pageId: { type: "string", required: true },
32575
- slug: { type: "string", required: true },
32576
- title: { type: "string", required: true },
32577
- format: { type: "enum", values: ["tiptap", "markdown", "html"], required: true },
32578
- body: { type: "json" },
32579
- status: { type: "enum", values: ["draft", "published"] },
32580
- publishedAt: { type: "datetime" }
32581
- }
32582
- },
32583
- {
32584
- name: "media",
32585
- identifier: ["siteId", "mediaId"],
32586
- fields: {
32587
- siteId: { type: "string", required: true },
32588
- mediaId: { type: "string", required: true },
32589
- src: { type: "string", required: true, description: "S3 key" },
32590
- mimeType: { type: "string", required: true },
32591
- size: { type: "integer" },
32592
- delivery: { type: "string" }
32593
- }
32594
- }
32595
- ],
32596
- formats: ["tiptap", "markdown", "html"],
32597
- notes: {
32598
- 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".',
32599
- tiptapBody: 'When format=tiptap, body is the tiptap document JSON: { type: "doc", content: [...] }. The renderer expects this shape.'
32600
- }
32601
- };
32602
- }
32603
-
32604
- // src/tools/index.ts
32605
- var tools = [
32606
- {
32607
- name: "list_posts",
32608
- description: "List posts in the CMS with optional filters by status. Returns up to `limit` posts (default 20) plus a `nextToken` cursor for pagination.",
32609
- inputSchema: listPostsSchema,
32610
- handler: (args, ctx) => listPosts(ctx.graphql, ctx.defaultSiteId, args)
32611
- },
32612
- {
32613
- name: "get_post",
32614
- description: "Fetch a single post by slug or postId. Returns null if not found.",
32615
- inputSchema: getPostSchema,
32616
- handler: (args, ctx) => getPost(ctx.graphql, ctx.defaultSiteId, args)
32617
- },
32618
- {
32619
- name: "create_post",
32620
- 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.",
32621
- inputSchema: createPostSchema,
32622
- handler: (args, ctx) => createPost(ctx.graphql, ctx.defaultSiteId, args)
32623
- },
32624
- {
32625
- name: "update_post",
32626
- description: "Update an existing post by postId. Only the fields you pass are changed. Tag list / publishedAt changes also update the PostTag denormalized index.",
32627
- inputSchema: updatePostSchema,
32628
- handler: (args, ctx) => updatePost(ctx.graphql, ctx.defaultSiteId, args)
32629
- },
32630
- {
32631
- name: "delete_post",
32632
- description: "Delete a post by postId. Also drops associated PostTag index entries.",
32633
- inputSchema: deletePostSchema,
32634
- handler: (args, ctx) => deletePost(ctx.graphql, ctx.defaultSiteId, args)
32635
- },
32636
- {
32637
- name: "upload_media",
32638
- 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.",
32639
- inputSchema: uploadMediaSchema,
32640
- handler: (args, ctx) => uploadMedia(ctx.graphql, ctx.storage(), ctx.defaultSiteId, args)
32641
- },
32642
- {
32643
- name: "get_schema",
32644
- description: "Returns the CMS content schema (Post/Page/Media field shapes, format enum, notes). Useful as the first call to understand what fields are available.",
32645
- inputSchema: getSchemaSchema,
32646
- handler: async () => getSchema()
32647
- }
32648
- ];
32649
-
32650
32118
  // src/server.ts
32651
32119
  async function startServer(config2) {
32652
32120
  const auth = new CognitoAuth(config2);
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Abstract contracts each tool handler depends on. Concrete
3
+ * implementations live elsewhere:
4
+ *
5
+ * - Stdio CLI: `src/appsync.ts` (Cognito-id-token bearer) and
6
+ * `src/s3.ts` (default-credential-chain S3 client).
7
+ * - HTTP transport: `@ampless/admin/api/mcp` wraps the consumer's
8
+ * existing `generateClient<Schema>()` + S3 client.
9
+ *
10
+ * Both routes satisfy these interfaces structurally (no `implements`
11
+ * needed) — TypeScript's structural typing checks the shape on the
12
+ * call site. Tools never instantiate these themselves; the caller
13
+ * passes a ready `ToolContext` into `dispatchToolCall`.
14
+ */
15
+ interface GraphqlClient {
16
+ query<T>(operation: string, variables?: Record<string, unknown>): Promise<T>;
17
+ }
18
+ interface StorageClient {
19
+ /**
20
+ * Upload `body` to the bucket at `key` with `contentType`. Returns
21
+ * the public URL of the stored object (same format both routes use:
22
+ * https://{bucket}.s3.{region}.amazonaws.com/{key}).
23
+ */
24
+ putObject(key: string, body: Uint8Array, contentType: string): Promise<string>;
25
+ }
26
+ interface ToolContext {
27
+ graphql: GraphqlClient;
28
+ storage: () => StorageClient;
29
+ defaultSiteId: string;
30
+ }
31
+
32
+ interface ToolDefinition {
33
+ name: string;
34
+ description: string;
35
+ inputSchema: Record<string, unknown>;
36
+ handler: (args: Record<string, unknown>, ctx: ToolContext) => Promise<unknown>;
37
+ }
38
+ declare const tools: ToolDefinition[];
39
+ /**
40
+ * Look up a tool definition by name and invoke its handler. Returns
41
+ * `null` when no tool with that name is registered — callers should
42
+ * surface that as a JSON-RPC "method not found" error.
43
+ *
44
+ * Shared between the stdio CLI (`src/index.ts`) and the HTTP transport
45
+ * factory (`@ampless/admin/api/mcp` → `createMcpRoute`) so both routes
46
+ * dispatch through the same code path.
47
+ */
48
+ declare function dispatchToolCall(name: string, args: Record<string, unknown>, ctx: ToolContext): Promise<unknown>;
49
+ /**
50
+ * `tools` is the canonical registry; `getTools()` returns it for
51
+ * callers that prefer a function over a top-level constant (admin's
52
+ * HTTP factory exposes this through its options shape).
53
+ */
54
+ declare function getTools(): readonly ToolDefinition[];
55
+
56
+ export { type GraphqlClient, type StorageClient, type ToolContext, type ToolDefinition, dispatchToolCall, getTools, tools };
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ dispatchToolCall,
4
+ getTools,
5
+ tools
6
+ } from "../chunk-KM6F5QJH.js";
7
+ import "../chunk-LMMQX4CK.js";
8
+ export {
9
+ dispatchToolCall,
10
+ getTools,
11
+ tools
12
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ampless/mcp-server",
3
- "version": "0.2.0-alpha.4",
3
+ "version": "0.2.0-alpha.5",
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",
@@ -11,6 +11,10 @@
11
11
  ".": {
12
12
  "import": "./dist/index.js",
13
13
  "types": "./dist/index.d.ts"
14
+ },
15
+ "./tools": {
16
+ "import": "./dist/tools/index.js",
17
+ "types": "./dist/tools/index.d.ts"
14
18
  }
15
19
  },
16
20
  "files": [
@@ -31,7 +35,7 @@
31
35
  "@modelcontextprotocol/sdk": "^1.0.0",
32
36
  "@aws-sdk/client-s3": "^3.1048.0",
33
37
  "amazon-cognito-identity-js": "^6.3.12",
34
- "ampless": "0.2.0-alpha.4"
38
+ "ampless": "0.2.0-alpha.5"
35
39
  },
36
40
  "keywords": [
37
41
  "ampless",