@ampless/backend 0.2.0-alpha.15 → 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 4: Bearer validation + JSON-RPC 2.0
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>;
@@ -571,6 +571,26 @@ function createMcpGraphqlClient(opts) {
571
571
  };
572
572
  }
573
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
+
574
594
  // src/functions/mcp-handler.ts
575
595
  var TOKENS_PK = "mcp-tokens";
576
596
  var JSON_RPC_PARSE_ERROR = -32700;
@@ -586,9 +606,10 @@ function requireEnv(name) {
586
606
  }
587
607
  var KV_TABLE = requireEnv("AMPLESS_KV_TABLE");
588
608
  var APPSYNC_URL = requireEnv("AMPLESS_APPSYNC_URL");
609
+ var BUCKET_NAME = requireEnv("AMPLESS_BUCKET_NAME");
589
610
  var AWS_REGION = process.env["AWS_REGION"] ?? "us-east-1";
590
611
  var ddb = DynamoDBDocumentClient.from(new DynamoDBClient({}));
591
- var HTTP_TOOLS = tools.filter((t) => t.name !== "upload_media");
612
+ var HTTP_TOOLS = tools;
592
613
  function hashToken(plain) {
593
614
  return createHash("sha256").update(plain).digest("hex");
594
615
  }
@@ -636,12 +657,14 @@ function makeContext(meta) {
636
657
  if (cachedCtx && cachedCtx.defaultSiteId === (meta.scope.siteId ?? "default")) {
637
658
  return cachedCtx;
638
659
  }
660
+ let storageClient = null;
639
661
  const ctx = {
640
662
  graphql: createMcpGraphqlClient({ endpoint: APPSYNC_URL, region: AWS_REGION }),
641
663
  storage: () => {
642
- throw new Error(
643
- "upload_media is not available on the HTTP MCP transport in v0.2 Phase 4 \u2014 use the stdio CLI or wait for Phase 5"
644
- );
664
+ if (!storageClient) {
665
+ storageClient = createMcpStorageClient({ bucket: BUCKET_NAME, region: AWS_REGION });
666
+ }
667
+ return storageClient;
645
668
  },
646
669
  defaultSiteId: meta.scope.siteId ?? "default"
647
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.15",
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",