@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.
- package/dist/functions/mcp-handler.d.ts +5 -3
- package/dist/functions/mcp-handler.js +7 -19
- package/dist/index.d.ts +1 -0
- package/dist/index.js +27 -2
- package/package.json +17 -17
|
@@ -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
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
|
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:
|
|
1074
|
-
Key: {
|
|
1071
|
+
TableName: MCP_TOKEN_TABLE,
|
|
1072
|
+
Key: { hash }
|
|
1075
1073
|
})
|
|
1076
1074
|
);
|
|
1077
1075
|
const row = res.Item;
|
|
1078
|
-
if (!row
|
|
1079
|
-
|
|
1080
|
-
if (
|
|
1081
|
-
|
|
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
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: [
|
|
178
|
+
resources: [mcpTokenTable.tableArn]
|
|
178
179
|
})
|
|
179
180
|
);
|
|
180
|
-
mcpHandlerFn.addEnvironment("
|
|
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.
|
|
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.
|
|
58
|
-
"@aws-sdk/client-cognito-identity-provider": "^3.
|
|
59
|
-
"@aws-sdk/client-dynamodb": "^3.
|
|
60
|
-
"@aws-sdk/client-s3": "^3.
|
|
61
|
-
"@aws-sdk/client-sqs": "^3.
|
|
62
|
-
"@aws-sdk/credential-provider-node": "^3.
|
|
63
|
-
"@aws-sdk/lib-dynamodb": "^3.
|
|
64
|
-
"@aws-sdk/util-dynamodb": "^3.
|
|
65
|
-
"@smithy/protocol-http": "^5.
|
|
66
|
-
"@smithy/signature-v4": "^5.4.
|
|
67
|
-
"fflate": "^0.8.
|
|
68
|
-
"ampless": "1.0.0-alpha.
|
|
69
|
-
"@ampless/mcp-server": "1.0.0-alpha.
|
|
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.
|
|
77
|
-
"@types/aws-lambda": "^8.10.
|
|
78
|
-
"aws-cdk-lib": "^2.
|
|
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",
|