@ampless/backend 1.0.0-alpha.21 → 1.0.0-alpha.23

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.
@@ -2,9 +2,11 @@
2
2
  * MCP HTTP endpoint Lambda. Bearer validation + JSON-RPC 2.0 tool
3
3
  * dispatch over AppSync IAM auth, including `upload_media`.
4
4
  *
5
- * 1. Reads KvStore directly (PK = 'mcp-tokens', SK = SHA-256 hex)
6
- * to validate `Authorization: Bearer amk_...`. The Lambda has a
7
- * narrow IAM grant: `dynamodb:GetItem` on the KvStore table.
5
+ * 1. Reads the admin-only `McpToken` DynamoDB table directly
6
+ * (identifier = SHA-256 hex of plaintext) to validate
7
+ * `Authorization: Bearer amk_...`. The Lambda has a narrow IAM
8
+ * grant: `dynamodb:GetItem` on the McpToken table only — no
9
+ * AppSync round-trip for token validation.
8
10
  * 2. Parses the incoming JSON-RPC envelope by hand (no MCP SDK
9
11
  * stdio transport in a Lambda runtime — overkill for the three
10
12
  * verbs we actually need).
@@ -4,7 +4,6 @@ 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";
8
7
 
9
8
  // ../mcp-server/dist/chunk-K4GTND6P.js
10
9
  import {
@@ -1030,7 +1029,6 @@ function createMcpStorageClient(opts) {
1030
1029
  }
1031
1030
 
1032
1031
  // src/functions/mcp-handler.ts
1033
- var TOKENS_PK = "mcp-tokens";
1034
1032
  var JSON_RPC_PARSE_ERROR = -32700;
1035
1033
  var JSON_RPC_INVALID_REQUEST = -32600;
1036
1034
  var JSON_RPC_METHOD_NOT_FOUND = -32601;
@@ -1042,7 +1040,7 @@ function requireEnv(name) {
1042
1040
  if (!v) throw new Error(`[mcp-handler] missing required env var ${name}`);
1043
1041
  return v;
1044
1042
  }
1045
- var KV_TABLE = requireEnv("AMPLESS_KV_TABLE");
1043
+ var MCP_TOKEN_TABLE = requireEnv("AMPLESS_MCP_TOKEN_TABLE");
1046
1044
  var APPSYNC_URL = requireEnv("AMPLESS_APPSYNC_URL");
1047
1045
  var BUCKET_NAME = requireEnv("AMPLESS_BUCKET_NAME");
1048
1046
  var AWS_REGION = process.env["AWS_REGION"] ?? "us-east-1";
@@ -1070,25 +1068,15 @@ async function validateBearer(plaintext) {
1070
1068
  const hash = hashToken(plaintext);
1071
1069
  const res = await ddb.send(
1072
1070
  new GetCommand({
1073
- TableName: KV_TABLE,
1074
- Key: { pk: TOKENS_PK, sk: hash }
1071
+ TableName: MCP_TOKEN_TABLE,
1072
+ Key: { hash }
1075
1073
  })
1076
1074
  );
1077
1075
  const row = res.Item;
1078
- if (!row?.value) return null;
1079
- const meta = decodeTokenMeta(row.value);
1080
- if (!meta) {
1081
- console.error("[mcp-handler] could not decode token row", { hash, valueType: typeof row.value });
1082
- return null;
1083
- }
1084
- if (meta.revokedAt) return null;
1085
- if (meta.expiresAt && new Date(meta.expiresAt).getTime() <= Date.now()) return null;
1086
- return meta;
1087
- }
1088
- function decodeTokenMeta(value) {
1089
- const parsed = decodeAwsJson2(value);
1090
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
1091
- return parsed;
1076
+ if (!row) return null;
1077
+ if (row.revokedAt) return null;
1078
+ if (row.expiresAt && new Date(row.expiresAt).getTime() <= Date.now()) return null;
1079
+ return row;
1092
1080
  }
1093
1081
  var cachedCtx = null;
1094
1082
  function makeContext() {
package/dist/index.d.ts CHANGED
@@ -173,6 +173,7 @@ declare function amplessSchemaModels(a: any, opts?: AmplessSchemaModelsOpts): {
173
173
  Taxonomy: any;
174
174
  PostTag: any;
175
175
  KvStore: any;
176
+ McpToken: any;
176
177
  PublicPost: any;
177
178
  PublicPostConnection: any;
178
179
  listPublishedPosts: any;
package/dist/index.js CHANGED
@@ -88,6 +88,7 @@ function defineAmplessBackend(opts) {
88
88
  const cfnKvTable = backend.data.resources.cfnResources.amplifyDynamoDbTables["KvStore"];
89
89
  cfnKvTable.streamSpecification = { streamViewType: "NEW_AND_OLD_IMAGES" };
90
90
  cfnKvTable.timeToLiveSpecification = { attributeName: "ttl", enabled: true };
91
+ const mcpTokenTable = backend.data.resources.tables["McpToken"];
91
92
  const eventsStack = backend.createStack("amplessEvents");
92
93
  const eventsDlq = new Queue(eventsStack, "EventsDlq", {
93
94
  retentionPeriod: Duration.days(14)
@@ -174,10 +175,10 @@ function defineAmplessBackend(opts) {
174
175
  new PolicyStatement({
175
176
  effect: Effect.ALLOW,
176
177
  actions: ["dynamodb:GetItem"],
177
- resources: [kvTable.tableArn]
178
+ resources: [mcpTokenTable.tableArn]
178
179
  })
179
180
  );
180
- mcpHandlerFn.addEnvironment("AMPLESS_KV_TABLE", kvTable.tableName);
181
+ mcpHandlerFn.addEnvironment("AMPLESS_MCP_TOKEN_TABLE", mcpTokenTable.tableName);
181
182
  mcpHandlerFn.addEnvironment(
182
183
  "AMPLESS_APPSYNC_URL",
183
184
  backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl
@@ -362,6 +363,30 @@ function amplessSchemaModels(a, opts = {}) {
362
363
  }).identifier(["pk", "sk"]).authorization((allow) => [
363
364
  allow.groups(["ampless-admin", "ampless-editor"])
364
365
  ]),
366
+ // Admin-issued API tokens for the HTTP MCP endpoint. Identifier =
367
+ // SHA-256 hex of plaintext; the Lambda hashes incoming Bearers and
368
+ // GetItem's this table directly (bypassing AppSync) so token
369
+ // validation costs one DDB read.
370
+ //
371
+ // Admin-only authorization is the security boundary: the MCP
372
+ // Lambda runs every tool with its own IAM role independent of the
373
+ // issuer's Cognito group, so editors must not be able to mint
374
+ // tokens that bypass their own group restrictions. KvStore is
375
+ // shared with editors for site settings / caches; this model is
376
+ // deliberately separate so the namespace can't be smuggled into
377
+ // there.
378
+ McpToken: a.model({
379
+ hash: a.string().required(),
380
+ prefix: a.string().required(),
381
+ createdBy: a.string().required(),
382
+ createdByEmail: a.string().required(),
383
+ issuedAt: a.datetime().required(),
384
+ lastUsedAt: a.datetime(),
385
+ expiresAt: a.datetime(),
386
+ revokedAt: a.datetime()
387
+ }).identifier(["hash"]).authorization((allow) => [
388
+ allow.groups(["ampless-admin"])
389
+ ]),
365
390
  // Custom return type for public post reads. Decoupling from `Post` lets
366
391
  // AppSync skip the model-level (admin-only) auth check on fields.
367
392
  //
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ampless/backend",
3
- "version": "1.0.0-alpha.21",
3
+ "version": "1.0.0-alpha.23",
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",
@@ -54,28 +54,28 @@
54
54
  "bugs": "https://github.com/heavymoons/ampless/issues",
55
55
  "dependencies": {
56
56
  "@aws-crypto/sha256-js": "^5.2.0",
57
- "@aws-sdk/client-appsync": "^3.717.0",
58
- "@aws-sdk/client-cognito-identity-provider": "^3.717.0",
59
- "@aws-sdk/client-dynamodb": "^3.717.0",
60
- "@aws-sdk/client-s3": "^3.1048.0",
61
- "@aws-sdk/client-sqs": "^3.717.0",
62
- "@aws-sdk/credential-provider-node": "^3.717.0",
63
- "@aws-sdk/lib-dynamodb": "^3.717.0",
64
- "@aws-sdk/util-dynamodb": "^3.717.0",
65
- "@smithy/protocol-http": "^5.3.0",
66
- "@smithy/signature-v4": "^5.4.0",
67
- "fflate": "^0.8.2",
68
- "ampless": "1.0.0-alpha.11",
69
- "@ampless/mcp-server": "1.0.0-alpha.12"
57
+ "@aws-sdk/client-appsync": "^3.1053.0",
58
+ "@aws-sdk/client-cognito-identity-provider": "^3.1053.0",
59
+ "@aws-sdk/client-dynamodb": "^3.1053.0",
60
+ "@aws-sdk/client-s3": "^3.1053.0",
61
+ "@aws-sdk/client-sqs": "^3.1053.0",
62
+ "@aws-sdk/credential-provider-node": "^3.972.44",
63
+ "@aws-sdk/lib-dynamodb": "^3.1053.0",
64
+ "@aws-sdk/util-dynamodb": "^3.996.2",
65
+ "@smithy/protocol-http": "^5.4.4",
66
+ "@smithy/signature-v4": "^5.4.4",
67
+ "fflate": "^0.8.3",
68
+ "ampless": "1.0.0-alpha.12",
69
+ "@ampless/mcp-server": "1.0.0-alpha.13"
70
70
  },
71
71
  "peerDependencies": {
72
72
  "@aws-amplify/backend": "^1",
73
73
  "aws-cdk-lib": "^2"
74
74
  },
75
75
  "devDependencies": {
76
- "@aws-amplify/backend": "^1.13.0",
77
- "@types/aws-lambda": "^8.10.147",
78
- "aws-cdk-lib": "^2.174.0"
76
+ "@aws-amplify/backend": "^1.22.0",
77
+ "@types/aws-lambda": "^8.10.161",
78
+ "aws-cdk-lib": "^2.257.0"
79
79
  },
80
80
  "keywords": [
81
81
  "ampless",