@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
|
|
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
|
|
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
|
-
|
|
643
|
-
|
|
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