@ampless/backend 1.0.0-alpha.20 → 1.0.0-alpha.22
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 +7 -5
- package/dist/functions/mcp-handler.js +7 -19
- package/dist/index.d.ts +5 -6
- package/dist/index.js +27 -2
- package/package.json +3 -3
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP HTTP endpoint Lambda.
|
|
3
|
-
*
|
|
2
|
+
* MCP HTTP endpoint Lambda. Bearer validation + JSON-RPC 2.0 tool
|
|
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
|
@@ -18,10 +18,8 @@ interface DefineAmplessBackendOpts {
|
|
|
18
18
|
}
|
|
19
19
|
type AmplessBackend = any;
|
|
20
20
|
/**
|
|
21
|
-
* The end-to-end ampless backend wiring
|
|
22
|
-
*
|
|
23
|
-
* but parameterised on the resource objects so users only have to
|
|
24
|
-
* compose the imports.
|
|
21
|
+
* The end-to-end ampless backend wiring, parameterised on the resource
|
|
22
|
+
* objects so users only have to compose the imports.
|
|
25
23
|
*
|
|
26
24
|
* What it does, in order:
|
|
27
25
|
* 1. Calls `defineBackend` with every Ampless resource.
|
|
@@ -175,6 +173,7 @@ declare function amplessSchemaModels(a: any, opts?: AmplessSchemaModelsOpts): {
|
|
|
175
173
|
Taxonomy: any;
|
|
176
174
|
PostTag: any;
|
|
177
175
|
KvStore: any;
|
|
176
|
+
McpToken: any;
|
|
178
177
|
PublicPost: any;
|
|
179
178
|
PublicPostConnection: any;
|
|
180
179
|
listPublishedPosts: any;
|
|
@@ -201,8 +200,8 @@ interface AmplessSchemaAuthorizationOpts {
|
|
|
201
200
|
* destructure `resource` out of their `allow` parameter), so this
|
|
202
201
|
* grant applies broadly — every model the Lambda calls is reachable.
|
|
203
202
|
* That's wider than strictly necessary; the MCP tools' GraphQL
|
|
204
|
-
* operations narrow the effective surface to Post / PostTag
|
|
205
|
-
*
|
|
203
|
+
* operations narrow the effective surface to Post / PostTag for
|
|
204
|
+
* content tools and Media for `upload_media`.
|
|
206
205
|
*
|
|
207
206
|
* Typed as `unknown` for the same reason `userAdminFunction` /
|
|
208
207
|
* `mcpHandlerFunction` are in other helpers — `defineFunction`'s
|
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.22",
|
|
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",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
"@smithy/protocol-http": "^5.3.0",
|
|
66
66
|
"@smithy/signature-v4": "^5.4.0",
|
|
67
67
|
"fflate": "^0.8.2",
|
|
68
|
-
"ampless": "1.0.0-alpha.
|
|
69
|
-
"@ampless/mcp-server": "1.0.0-alpha.
|
|
68
|
+
"ampless": "1.0.0-alpha.11",
|
|
69
|
+
"@ampless/mcp-server": "1.0.0-alpha.12"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
72
72
|
"@aws-amplify/backend": "^1",
|