@ampless/backend 0.2.0-alpha.14 → 0.2.0-alpha.16
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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP HTTP endpoint Lambda. Phase
|
|
3
|
-
* tool dispatch over AppSync IAM auth
|
|
2
|
+
* MCP HTTP endpoint Lambda. Phase 5: Bearer validation + JSON-RPC 2.0
|
|
3
|
+
* tool dispatch over AppSync IAM auth, including `upload_media`.
|
|
4
4
|
*
|
|
5
5
|
* 1. Reads KvStore directly (PK = 'mcp-tokens', SK = SHA-256 hex)
|
|
6
6
|
* to validate `Authorization: Bearer amk_...`. Same narrow IAM
|
|
@@ -12,14 +12,14 @@
|
|
|
12
12
|
* registry. The GraphqlClient implementation hits AppSync with
|
|
13
13
|
* SigV4 (`AWS_IAM` auth mode), gated by `allow.resource(mcpHandler)
|
|
14
14
|
* .to(['query', 'mutate'])` on Post / PostTag in the schema.
|
|
15
|
+
* 4. `upload_media` decodes the base64 body inline and uploads to S3
|
|
16
|
+
* using the Lambda execution role (s3:PutObject on public/media/*).
|
|
17
|
+
* Payload limit ~6 MB (base64-inflated) covers typical CMS images;
|
|
18
|
+
* large files should use the stdio CLI.
|
|
15
19
|
*
|
|
16
20
|
* Function URL event format: Lambda Function URLs emit API Gateway
|
|
17
21
|
* HTTP v2 events (https://docs.aws.amazon.com/lambda/latest/dg/urls-invocation.html#urls-payloads).
|
|
18
22
|
* Headers arrive lowercased.
|
|
19
|
-
*
|
|
20
|
-
* Note: `upload_media` is filtered out — the StorageClient flow needs
|
|
21
|
-
* presigned S3 PUT URLs (the Lambda doesn't accept the binary body
|
|
22
|
-
* itself), which lands in Phase 5.
|
|
23
23
|
*/
|
|
24
24
|
interface FunctionUrlEvent {
|
|
25
25
|
headers?: Record<string, string | undefined>;
|
|
@@ -4,10 +4,12 @@ import "../chunk-BYXBJQAS.js";
|
|
|
4
4
|
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
5
5
|
import { DynamoDBDocumentClient, GetCommand } from "@aws-sdk/lib-dynamodb";
|
|
6
6
|
import { createHash } from "crypto";
|
|
7
|
+
import { decodeAwsJson as decodeAwsJson2 } from "ampless";
|
|
7
8
|
|
|
8
|
-
// ../mcp-server/dist/chunk-
|
|
9
|
-
import {
|
|
10
|
-
import { composeSiteIdStatus
|
|
9
|
+
// ../mcp-server/dist/chunk-6LH275IC.js
|
|
10
|
+
import { decodeAwsJson } from "ampless";
|
|
11
|
+
import { composeSiteIdStatus, composeSiteIdSlug, encodeAwsJson } from "ampless";
|
|
12
|
+
import { composeSiteIdStatus as composeSiteIdStatus2, composeSiteIdSlug as composeSiteIdSlug2, encodeAwsJson as encodeAwsJson2 } from "ampless";
|
|
11
13
|
var POST_FIELDS = (
|
|
12
14
|
/* GraphQL */
|
|
13
15
|
`
|
|
@@ -25,18 +27,6 @@ var POST_FIELDS = (
|
|
|
25
27
|
}
|
|
26
28
|
`
|
|
27
29
|
);
|
|
28
|
-
function decodeBody(value) {
|
|
29
|
-
if (typeof value !== "string") return value;
|
|
30
|
-
try {
|
|
31
|
-
return JSON.parse(value);
|
|
32
|
-
} catch {
|
|
33
|
-
return value;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
function encodeBody(value) {
|
|
37
|
-
if (typeof value === "string") return value;
|
|
38
|
-
return JSON.stringify(value ?? null);
|
|
39
|
-
}
|
|
40
30
|
function toCorePost(p) {
|
|
41
31
|
return {
|
|
42
32
|
siteId: p.siteId,
|
|
@@ -45,7 +35,7 @@ function toCorePost(p) {
|
|
|
45
35
|
title: p.title,
|
|
46
36
|
excerpt: p.excerpt ?? void 0,
|
|
47
37
|
format: p.format ?? "markdown",
|
|
48
|
-
body:
|
|
38
|
+
body: decodeAwsJson(p.body),
|
|
49
39
|
status: p.status ?? "draft",
|
|
50
40
|
publishedAt: p.publishedAt ?? void 0,
|
|
51
41
|
tags: (p.tags ?? []).filter((t) => typeof t === "string")
|
|
@@ -266,7 +256,7 @@ async function createPost(client, defaultSiteId, args) {
|
|
|
266
256
|
title: args.title,
|
|
267
257
|
excerpt: args.excerpt,
|
|
268
258
|
format: args.format,
|
|
269
|
-
body:
|
|
259
|
+
body: encodeAwsJson(args.body),
|
|
270
260
|
status,
|
|
271
261
|
publishedAt,
|
|
272
262
|
tags: args.tags,
|
|
@@ -314,7 +304,7 @@ async function updatePost(client, defaultSiteId, args) {
|
|
|
314
304
|
if (args.title !== void 0) input.title = args.title;
|
|
315
305
|
if (args.excerpt !== void 0) input.excerpt = args.excerpt;
|
|
316
306
|
if (args.format !== void 0) input.format = args.format;
|
|
317
|
-
if (args.body !== void 0) input.body =
|
|
307
|
+
if (args.body !== void 0) input.body = encodeAwsJson2(args.body);
|
|
318
308
|
if (args.status !== void 0) input.status = args.status;
|
|
319
309
|
if (args.publishedAt !== void 0) input.publishedAt = args.publishedAt;
|
|
320
310
|
if (args.tags !== void 0) input.tags = args.tags;
|
|
@@ -581,6 +571,26 @@ function createMcpGraphqlClient(opts) {
|
|
|
581
571
|
};
|
|
582
572
|
}
|
|
583
573
|
|
|
574
|
+
// src/functions/mcp-storage-client.ts
|
|
575
|
+
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
576
|
+
import { formatPublicAssetUrl } from "ampless";
|
|
577
|
+
function createMcpStorageClient(opts) {
|
|
578
|
+
const client = new S3Client({ region: opts.region });
|
|
579
|
+
return {
|
|
580
|
+
async putObject(key, body, contentType) {
|
|
581
|
+
await client.send(
|
|
582
|
+
new PutObjectCommand({
|
|
583
|
+
Bucket: opts.bucket,
|
|
584
|
+
Key: key,
|
|
585
|
+
Body: body,
|
|
586
|
+
ContentType: contentType
|
|
587
|
+
})
|
|
588
|
+
);
|
|
589
|
+
return formatPublicAssetUrl(opts.bucket, opts.region, key);
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
|
|
584
594
|
// src/functions/mcp-handler.ts
|
|
585
595
|
var TOKENS_PK = "mcp-tokens";
|
|
586
596
|
var JSON_RPC_PARSE_ERROR = -32700;
|
|
@@ -596,9 +606,10 @@ function requireEnv(name) {
|
|
|
596
606
|
}
|
|
597
607
|
var KV_TABLE = requireEnv("AMPLESS_KV_TABLE");
|
|
598
608
|
var APPSYNC_URL = requireEnv("AMPLESS_APPSYNC_URL");
|
|
609
|
+
var BUCKET_NAME = requireEnv("AMPLESS_BUCKET_NAME");
|
|
599
610
|
var AWS_REGION = process.env["AWS_REGION"] ?? "us-east-1";
|
|
600
611
|
var ddb = DynamoDBDocumentClient.from(new DynamoDBClient({}));
|
|
601
|
-
var HTTP_TOOLS = tools
|
|
612
|
+
var HTTP_TOOLS = tools;
|
|
602
613
|
function hashToken(plain) {
|
|
603
614
|
return createHash("sha256").update(plain).digest("hex");
|
|
604
615
|
}
|
|
@@ -637,27 +648,23 @@ async function validateBearer(plaintext) {
|
|
|
637
648
|
return meta;
|
|
638
649
|
}
|
|
639
650
|
function decodeTokenMeta(value) {
|
|
640
|
-
|
|
641
|
-
if (typeof
|
|
642
|
-
|
|
643
|
-
return JSON.parse(value);
|
|
644
|
-
} catch {
|
|
645
|
-
return null;
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
return null;
|
|
651
|
+
const parsed = decodeAwsJson2(value);
|
|
652
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
653
|
+
return parsed;
|
|
649
654
|
}
|
|
650
655
|
var cachedCtx = null;
|
|
651
656
|
function makeContext(meta) {
|
|
652
657
|
if (cachedCtx && cachedCtx.defaultSiteId === (meta.scope.siteId ?? "default")) {
|
|
653
658
|
return cachedCtx;
|
|
654
659
|
}
|
|
660
|
+
let storageClient = null;
|
|
655
661
|
const ctx = {
|
|
656
662
|
graphql: createMcpGraphqlClient({ endpoint: APPSYNC_URL, region: AWS_REGION }),
|
|
657
663
|
storage: () => {
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
664
|
+
if (!storageClient) {
|
|
665
|
+
storageClient = createMcpStorageClient({ bucket: BUCKET_NAME, region: AWS_REGION });
|
|
666
|
+
}
|
|
667
|
+
return storageClient;
|
|
661
668
|
},
|
|
662
669
|
defaultSiteId: meta.scope.siteId ?? "default"
|
|
663
670
|
};
|
package/dist/index.js
CHANGED
|
@@ -182,6 +182,14 @@ function defineAmplessBackend(opts) {
|
|
|
182
182
|
"AMPLESS_APPSYNC_URL",
|
|
183
183
|
backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl
|
|
184
184
|
);
|
|
185
|
+
mcpHandlerFn.addToRolePolicy(
|
|
186
|
+
new PolicyStatement({
|
|
187
|
+
effect: Effect.ALLOW,
|
|
188
|
+
actions: ["s3:PutObject"],
|
|
189
|
+
resources: [`${backend.storage.resources.bucket.bucketArn}/public/media/*`]
|
|
190
|
+
})
|
|
191
|
+
);
|
|
192
|
+
mcpHandlerFn.addEnvironment("AMPLESS_BUCKET_NAME", backend.storage.resources.bucket.bucketName);
|
|
185
193
|
const mcpFunctionUrl = mcpHandlerFn.addFunctionUrl({
|
|
186
194
|
authType: FunctionUrlAuthType.NONE,
|
|
187
195
|
cors: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ampless/backend",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.16",
|
|
4
4
|
"description": "Amplify Gen 2 backend factories for ampless: auth, data, storage, event processors, API key renewer",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
"@aws-sdk/util-dynamodb": "^3.717.0",
|
|
65
65
|
"@smithy/protocol-http": "^5.3.0",
|
|
66
66
|
"@smithy/signature-v4": "^5.4.0",
|
|
67
|
-
"ampless": "0.2.0-alpha.
|
|
68
|
-
"@ampless/mcp-server": "0.2.0-alpha.
|
|
67
|
+
"ampless": "0.2.0-alpha.7",
|
|
68
|
+
"@ampless/mcp-server": "0.2.0-alpha.7"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"@aws-amplify/backend": "^1",
|