@usestratus/mcp-aws 0.1.0

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.
Files changed (110) hide show
  1. package/README.md +610 -0
  2. package/dist/auth/api-key.d.ts +29 -0
  3. package/dist/auth/api-key.d.ts.map +1 -0
  4. package/dist/auth/api-key.js +42 -0
  5. package/dist/auth/api-key.js.map +1 -0
  6. package/dist/auth/cognito.d.ts +26 -0
  7. package/dist/auth/cognito.d.ts.map +1 -0
  8. package/dist/auth/cognito.js +58 -0
  9. package/dist/auth/cognito.js.map +1 -0
  10. package/dist/auth/index.d.ts +11 -0
  11. package/dist/auth/index.d.ts.map +1 -0
  12. package/dist/auth/index.js +21 -0
  13. package/dist/auth/index.js.map +1 -0
  14. package/dist/auth/metadata.d.ts +30 -0
  15. package/dist/auth/metadata.d.ts.map +1 -0
  16. package/dist/auth/metadata.js +25 -0
  17. package/dist/auth/metadata.js.map +1 -0
  18. package/dist/auth/types.d.ts +8 -0
  19. package/dist/auth/types.d.ts.map +1 -0
  20. package/dist/auth/types.js +2 -0
  21. package/dist/auth/types.js.map +1 -0
  22. package/dist/codemode/executor.d.ts +29 -0
  23. package/dist/codemode/executor.d.ts.map +1 -0
  24. package/dist/codemode/executor.js +154 -0
  25. package/dist/codemode/executor.js.map +1 -0
  26. package/dist/codemode/index.d.ts +4 -0
  27. package/dist/codemode/index.d.ts.map +1 -0
  28. package/dist/codemode/index.js +3 -0
  29. package/dist/codemode/index.js.map +1 -0
  30. package/dist/codemode/types.d.ts +10 -0
  31. package/dist/codemode/types.d.ts.map +1 -0
  32. package/dist/codemode/types.js +195 -0
  33. package/dist/codemode/types.js.map +1 -0
  34. package/dist/compose.d.ts +57 -0
  35. package/dist/compose.d.ts.map +1 -0
  36. package/dist/compose.js +138 -0
  37. package/dist/compose.js.map +1 -0
  38. package/dist/context.d.ts +22 -0
  39. package/dist/context.d.ts.map +1 -0
  40. package/dist/context.js +39 -0
  41. package/dist/context.js.map +1 -0
  42. package/dist/deploy.d.ts +57 -0
  43. package/dist/deploy.d.ts.map +1 -0
  44. package/dist/deploy.js +281 -0
  45. package/dist/deploy.js.map +1 -0
  46. package/dist/disclosure/index.d.ts +3 -0
  47. package/dist/disclosure/index.d.ts.map +1 -0
  48. package/dist/disclosure/index.js +3 -0
  49. package/dist/disclosure/index.js.map +1 -0
  50. package/dist/disclosure/search.d.ts +15 -0
  51. package/dist/disclosure/search.d.ts.map +1 -0
  52. package/dist/disclosure/search.js +73 -0
  53. package/dist/disclosure/search.js.map +1 -0
  54. package/dist/disclosure/tier.d.ts +16 -0
  55. package/dist/disclosure/tier.d.ts.map +1 -0
  56. package/dist/disclosure/tier.js +69 -0
  57. package/dist/disclosure/tier.js.map +1 -0
  58. package/dist/errors.d.ts +34 -0
  59. package/dist/errors.d.ts.map +1 -0
  60. package/dist/errors.js +56 -0
  61. package/dist/errors.js.map +1 -0
  62. package/dist/events.d.ts +93 -0
  63. package/dist/events.d.ts.map +1 -0
  64. package/dist/events.js +28 -0
  65. package/dist/events.js.map +1 -0
  66. package/dist/gating/combinators.d.ts +10 -0
  67. package/dist/gating/combinators.d.ts.map +1 -0
  68. package/dist/gating/combinators.js +34 -0
  69. package/dist/gating/combinators.js.map +1 -0
  70. package/dist/gating/gates.d.ts +21 -0
  71. package/dist/gating/gates.d.ts.map +1 -0
  72. package/dist/gating/gates.js +74 -0
  73. package/dist/gating/gates.js.map +1 -0
  74. package/dist/gating/index.d.ts +3 -0
  75. package/dist/gating/index.d.ts.map +1 -0
  76. package/dist/gating/index.js +3 -0
  77. package/dist/gating/index.js.map +1 -0
  78. package/dist/index.d.ts +21 -0
  79. package/dist/index.d.ts.map +1 -0
  80. package/dist/index.js +28 -0
  81. package/dist/index.js.map +1 -0
  82. package/dist/server.d.ts +161 -0
  83. package/dist/server.d.ts.map +1 -0
  84. package/dist/server.js +768 -0
  85. package/dist/server.js.map +1 -0
  86. package/dist/session/dynamo.d.ts +23 -0
  87. package/dist/session/dynamo.d.ts.map +1 -0
  88. package/dist/session/dynamo.js +92 -0
  89. package/dist/session/dynamo.js.map +1 -0
  90. package/dist/session/index.d.ts +4 -0
  91. package/dist/session/index.d.ts.map +1 -0
  92. package/dist/session/index.js +4 -0
  93. package/dist/session/index.js.map +1 -0
  94. package/dist/session/memory.d.ts +16 -0
  95. package/dist/session/memory.d.ts.map +1 -0
  96. package/dist/session/memory.js +43 -0
  97. package/dist/session/memory.js.map +1 -0
  98. package/dist/session/sqlite.d.ts +17 -0
  99. package/dist/session/sqlite.d.ts.map +1 -0
  100. package/dist/session/sqlite.js +79 -0
  101. package/dist/session/sqlite.js.map +1 -0
  102. package/dist/ssrf.d.ts +25 -0
  103. package/dist/ssrf.d.ts.map +1 -0
  104. package/dist/ssrf.js +88 -0
  105. package/dist/ssrf.js.map +1 -0
  106. package/dist/types.d.ts +110 -0
  107. package/dist/types.d.ts.map +1 -0
  108. package/dist/types.js +21 -0
  109. package/dist/types.js.map +1 -0
  110. package/package.json +50 -0
package/README.md ADDED
@@ -0,0 +1,610 @@
1
+ # @usestratus/mcp-aws
2
+
3
+ Build, deploy, and manage MCP servers on AWS in TypeScript. Progressive disclosure, tool gating, code mode, and one-line Lambda deploys.
4
+
5
+ ```ts
6
+ import { McpServer, apiKey, role, deploy } from "@usestratus/mcp-aws";
7
+ import { z } from "zod";
8
+
9
+ const server = new McpServer("my-tools@1.0.0")
10
+ .auth(apiKey({ "sk-live-xxx": { roles: ["admin"] } }))
11
+ .tool("greet", z.object({ name: z.string() }), async ({ name }) => {
12
+ return `Hello, ${name}!`;
13
+ });
14
+
15
+ export const handler = server.lambda();
16
+
17
+ // Or deploy directly from code:
18
+ // const { url } = await server.deploy({ entry: "./src/server.ts" });
19
+ ```
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ bun add @usestratus/mcp-aws zod
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ ### 5 lines to a working MCP server
30
+
31
+ ```ts
32
+ import { McpServer } from "@usestratus/mcp-aws";
33
+ import { z } from "zod";
34
+
35
+ const server = new McpServer("my-server@1.0.0");
36
+ server.tool("greet", z.object({ name: z.string() }), async ({ name }) => `Hello, ${name}!`);
37
+ export const handler = server.lambda();
38
+ ```
39
+
40
+ ### Local development with Claude Desktop
41
+
42
+ ```ts
43
+ await server.stdio();
44
+ ```
45
+
46
+ ### Deploy to AWS Lambda
47
+
48
+ ```ts
49
+ const { url } = await server.deploy({ entry: "./src/server.ts" });
50
+ // → https://xxx.lambda-url.us-east-1.on.aws/
51
+ ```
52
+
53
+ ---
54
+
55
+ ## API Reference
56
+
57
+ ### McpServer
58
+
59
+ ```ts
60
+ // String constructor
61
+ const server = new McpServer("my-server@1.0.0");
62
+
63
+ // Config object (for advanced options)
64
+ const server = new McpServer({
65
+ name: "my-server",
66
+ version: "1.0.0",
67
+ codeMode: { enabled: true, executor: "worker" },
68
+ });
69
+ ```
70
+
71
+ ### Tool Registration
72
+
73
+ Three overloads — pick the one that fits:
74
+
75
+ ```ts
76
+ // Simple: name + handler (no params)
77
+ server.tool("ping", async () => "pong");
78
+
79
+ // With params: name + Zod schema + handler
80
+ server.tool("greet", z.object({ name: z.string() }), async ({ name }) => {
81
+ return `Hello, ${name}!`;
82
+ });
83
+
84
+ // Full config: name + options + handler
85
+ server.tool("admin_action", {
86
+ description: "Reset a user account",
87
+ params: z.object({ userId: z.string() }),
88
+ tier: "hidden",
89
+ gate: role("admin"),
90
+ timeout: 5000,
91
+ tags: ["admin"],
92
+ }, async ({ userId }) => {
93
+ return { reset: true, userId };
94
+ });
95
+ ```
96
+
97
+ **Return values are auto-coerced:**
98
+ - `string` → text content
99
+ - `object` / `array` → JSON-serialized text content
100
+ - `undefined` → empty content
101
+ - `ToolResult` → pass-through (for full control)
102
+
103
+ ### Method Chaining
104
+
105
+ Everything returns `this`:
106
+
107
+ ```ts
108
+ const server = new McpServer("my-server@1.0.0")
109
+ .auth(apiKey({ "sk-123": { roles: ["admin"] } }))
110
+ .tool("ping", async () => "pong")
111
+ .tool("greet", z.object({ name: z.string() }), async ({ name }) => `Hello, ${name}!`)
112
+ .on("tool:call", (e) => console.log(e.toolName));
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Auth
118
+
119
+ Configure once on the server. All transports inherit it.
120
+
121
+ ### API Key
122
+
123
+ ```ts
124
+ import { apiKey } from "@usestratus/mcp-aws";
125
+
126
+ server.auth(apiKey({
127
+ "sk-live-abc123": { subject: "user-1", roles: ["admin"] },
128
+ "sk-live-xyz789": { subject: "user-2", roles: ["reader"] },
129
+ }));
130
+ ```
131
+
132
+ ### Cognito JWT
133
+
134
+ ```ts
135
+ import { cognito } from "@usestratus/mcp-aws";
136
+
137
+ server.auth(cognito({
138
+ userPoolId: "us-east-1_abc123",
139
+ region: "us-east-1",
140
+ audience: "my-client-id", // optional
141
+ }));
142
+ ```
143
+
144
+ ### Chain Multiple Providers
145
+
146
+ ```ts
147
+ server.auth(
148
+ apiKey({ "sk-123": { roles: ["admin"] } }),
149
+ cognito({ userPoolId: "...", region: "us-east-1" }),
150
+ );
151
+ // Tries each in order, returns first success
152
+ ```
153
+
154
+ ### AsyncLocalStorage Context
155
+
156
+ Tool handlers can access auth without explicit parameters:
157
+
158
+ ```ts
159
+ import { getAuthContext, getSession } from "@usestratus/mcp-aws";
160
+
161
+ server.tool("my_tool", async () => {
162
+ const auth = getAuthContext(); // works anywhere in the call stack
163
+ const session = getSession();
164
+ return `Hello, ${auth.subject}!`;
165
+ });
166
+ ```
167
+
168
+ ---
169
+
170
+ ## Progressive Disclosure
171
+
172
+ 59 tools? No problem. Only show what matters.
173
+
174
+ ### Tier System
175
+
176
+ | Tier | Behavior | Default? |
177
+ |------|----------|----------|
178
+ | `always` | Always in `tools/list`. The server's front door. | Yes |
179
+ | `discoverable` | Found via `search_tools`. Promoted on discovery. | |
180
+ | `hidden` | Invisible until a gate unlocks it. | |
181
+
182
+ ```ts
183
+ server
184
+ .tool("get_weather", async () => "sunny") // always (default)
185
+ .tool("get_forecast", { tier: "discoverable", tags: ["weather"] }, handler) // searchable
186
+ .tool("delete_account", { tier: "hidden", gate: requires("confirm") }, handler) // gated
187
+ ```
188
+
189
+ ### How It Works
190
+
191
+ 1. Client calls `tools/list` → gets only `always` tier tools + `search_tools`
192
+ 2. Agent calls `search_tools("weather forecast")` → BM25 search finds matches
193
+ 3. Matches promoted to session → server sends `tools/list_changed`
194
+ 4. Client re-fetches → now sees promoted tools
195
+ 5. Visibility is per-session, persisted in session store
196
+
197
+ ### Disclosure Modes
198
+
199
+ Auto-inferred from your tool tiers. Override with config:
200
+
201
+ | Mode | When | `tools/list` shows |
202
+ |------|------|--------------------|
203
+ | `all` | All tools are `always` | Everything |
204
+ | `progressive` | Any `discoverable`/`hidden` | `always` + `search_tools` |
205
+ | `code-first` | Explicit config | `search_tools` + `execute_workflow` only |
206
+
207
+ ---
208
+
209
+ ## Tool Gating
210
+
211
+ Access control at the tool level.
212
+
213
+ ### Role-Based
214
+
215
+ ```ts
216
+ import { role } from "@usestratus/mcp-aws";
217
+
218
+ server.tool("admin_action", { gate: role("admin") }, handler);
219
+ server.tool("write_action", { gate: role("admin", "editor") }, handler); // any of these roles
220
+ ```
221
+
222
+ ### Prerequisite (Workflow Enforcement)
223
+
224
+ ```ts
225
+ import { requires } from "@usestratus/mcp-aws";
226
+
227
+ server
228
+ .tool("review_trade", handler)
229
+ .tool("execute_trade", { tier: "hidden", gate: requires("review_trade") }, handler);
230
+ // execute_trade is invisible until review_trade is called
231
+ // Then it auto-promotes and tools/list_changed fires
232
+ ```
233
+
234
+ ### Dynamic Check
235
+
236
+ ```ts
237
+ import { check } from "@usestratus/mcp-aws";
238
+
239
+ server.tool("update_deal", {
240
+ gate: check((ctx) => ctx.auth.claims.org === "acme", "Wrong org"),
241
+ }, handler);
242
+ ```
243
+
244
+ ### Rate Limiting
245
+
246
+ ```ts
247
+ import { rateLimit } from "@usestratus/mcp-aws";
248
+
249
+ server.tool("expensive_op", {
250
+ gate: rateLimit({ max: 10, windowMs: 60_000 }),
251
+ }, handler);
252
+ ```
253
+
254
+ ### Composite Gates
255
+
256
+ ```ts
257
+ import { all, any, role, requires, rateLimit } from "@usestratus/mcp-aws";
258
+
259
+ server.tool("approve_discount", {
260
+ gate: all(
261
+ role("sales-manager"),
262
+ requires("review_discount"),
263
+ rateLimit({ max: 10, windowMs: 3_600_000 }),
264
+ ),
265
+ }, handler);
266
+ ```
267
+
268
+ ### Gate Denial
269
+
270
+ When a gate blocks, the agent gets a structured error it can self-correct from:
271
+
272
+ ```json
273
+ {
274
+ "error": "Permission denied",
275
+ "reason": "Requires \"review_trade\" to be called first",
276
+ "hint": "Call the \"review_trade\" tool before using \"execute_trade\"."
277
+ }
278
+ ```
279
+
280
+ ---
281
+
282
+ ## Code Mode
283
+
284
+ Reduce N tool calls to 1. The agent writes code that orchestrates tools.
285
+
286
+ ```ts
287
+ const server = new McpServer({
288
+ name: "tools",
289
+ version: "1.0.0",
290
+ codeMode: { enabled: true, executor: "worker" },
291
+ });
292
+ ```
293
+
294
+ This registers an `execute_workflow` tool. The agent:
295
+ 1. Calls `search_tools` to discover available tools + type signatures
296
+ 2. Writes an async arrow function using `codemode.toolName(args)`
297
+ 3. Calls `execute_workflow` with the code
298
+ 4. Server validates gates, executes in isolated V8, returns result
299
+
300
+ ### Wrap Any Existing MCP Server
301
+
302
+ ```ts
303
+ import { codeMcpServer } from "@usestratus/mcp-aws";
304
+
305
+ const codeServer = await codeMcpServer({ server: existingMcpServer });
306
+ // → codeServer has one tool: execute_code
307
+ ```
308
+
309
+ ---
310
+
311
+ ## Transports
312
+
313
+ ### Lambda (Serverless)
314
+
315
+ ```ts
316
+ export const handler = server.lambda();
317
+
318
+ // With session store
319
+ import { DynamoSessionStore } from "@usestratus/mcp-aws/dynamo";
320
+
321
+ export const handler = server.lambda({
322
+ sessionStore: new DynamoSessionStore({ tableName: "mcp-sessions" }),
323
+ });
324
+ ```
325
+
326
+ ### Express (Container/ECS/EC2)
327
+
328
+ ```ts
329
+ import express from "express";
330
+
331
+ const app = express();
332
+ app.use(express.json());
333
+ server.express({ mcpPath: "/mcp" }).setup(app);
334
+ app.listen(3000);
335
+ ```
336
+
337
+ ### Bun.serve (Zero Dependencies)
338
+
339
+ ```ts
340
+ const { url, stop } = server.bun({ port: 3000 });
341
+ // MCP server at http://localhost:3000/mcp
342
+ ```
343
+
344
+ Native `Bun.serve()` — no Express, no dependencies. Handles auth, routes, and cleanup.
345
+
346
+ ### Stdio (Claude Desktop)
347
+
348
+ ```ts
349
+ await server.stdio();
350
+ ```
351
+
352
+ ### Stateless Handler (Bun/Deno/Any Runtime)
353
+
354
+ ```ts
355
+ import { createMcpHandler } from "@usestratus/mcp-aws";
356
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
357
+
358
+ const handler = createMcpHandler({ server: myMcpServer });
359
+ Bun.serve({ fetch: handler });
360
+ ```
361
+
362
+ ---
363
+
364
+ ## Deploy
365
+
366
+ ### One-Line Deploy
367
+
368
+ ```ts
369
+ const { url } = await server.deploy({ entry: "./src/server.ts" });
370
+ console.log(url); // https://xxx.lambda-url.us-east-1.on.aws/
371
+ ```
372
+
373
+ ### Deploy with Options
374
+
375
+ ```ts
376
+ const { url, functionName, functionArn } = await server.deploy({
377
+ entry: "./src/server.ts",
378
+ region: "us-east-1",
379
+ functionName: "my-mcp-server",
380
+ memory: 512,
381
+ timeout: 30,
382
+ environment: { DATABASE_URL: "postgres://..." },
383
+ });
384
+ ```
385
+
386
+ ### Destroy
387
+
388
+ ```ts
389
+ await server.destroy();
390
+ // or
391
+ await server.destroy("custom-function-name", "us-east-1");
392
+ ```
393
+
394
+ ### Security Modes
395
+
396
+ | Mode | `urlAuth` | `vpc` | Who can call |
397
+ |------|-----------|-------|-------------|
398
+ | Public (default) | `"NONE"` | — | Anyone + MCP-level auth |
399
+ | IAM-signed | `"AWS_IAM"` | — | AWS services with SigV4 |
400
+ | VPC-only | `"none"` | set | Resources in the VPC |
401
+ | VPC + IAM | `"AWS_IAM"` | set | VPC with SigV4 |
402
+
403
+ ```ts
404
+ // Private deployment inside a VPC
405
+ await server.deploy({
406
+ entry: "./src/server.ts",
407
+ urlAuth: "none", // no public URL
408
+ vpc: {
409
+ subnetIds: ["subnet-abc123", "subnet-def456"],
410
+ securityGroupIds: ["sg-xyz789"],
411
+ },
412
+ });
413
+ ```
414
+
415
+ ---
416
+
417
+ ## SSRF Protection
418
+
419
+ Prevent tools from accessing internal infrastructure:
420
+
421
+ ```ts
422
+ import { isBlockedUrl, assertSafeUrl } from "@usestratus/mcp-aws";
423
+
424
+ server.tool("fetch", z.object({ url: z.string() }), async ({ url }) => {
425
+ assertSafeUrl(url); // throws if private IP, metadata endpoint, etc.
426
+ return (await fetch(url)).text();
427
+ });
428
+
429
+ // Or check manually
430
+ if (isBlockedUrl(url)) return "Blocked";
431
+ ```
432
+
433
+ Blocks: `127.0.0.0/8`, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `169.254.169.254` (AWS metadata), `::1`, link-local, non-HTTP schemes.
434
+
435
+ ---
436
+
437
+ ## Observability
438
+
439
+ Typed event system for monitoring:
440
+
441
+ ```ts
442
+ server
443
+ .on("tool:call", (e) => {
444
+ console.log(`${e.toolName} called by ${e.auth.subject}`);
445
+ })
446
+ .on("tool:result", (e) => {
447
+ metrics.histogram("tool.duration", e.durationMs);
448
+ if (e.isError) metrics.increment("tool.errors");
449
+ })
450
+ .on("gate:denied", (e) => {
451
+ audit.log(`${e.toolName} denied: ${e.reason}`);
452
+ })
453
+ .on("tools:unlocked", (e) => {
454
+ console.log(`${e.toolNames.join(", ")} unlocked via ${e.prerequisite}`);
455
+ });
456
+ ```
457
+
458
+ Events: `tool:call`, `tool:result`, `gate:denied`, `auth:success`, `auth:failure`, `tools:promoted`, `tools:unlocked`, `deploy:start`, `deploy:complete`.
459
+
460
+ ---
461
+
462
+ ## Session Stores
463
+
464
+ ### Memory (dev/test)
465
+
466
+ ```ts
467
+ import { MemorySessionStore } from "@usestratus/mcp-aws";
468
+
469
+ server.lambda({ sessionStore: new MemorySessionStore({ ttlMs: 3600_000 }) });
470
+ ```
471
+
472
+ ### DynamoDB (production)
473
+
474
+ ```ts
475
+ import { DynamoSessionStore } from "@usestratus/mcp-aws/dynamo";
476
+
477
+ server.lambda({
478
+ sessionStore: new DynamoSessionStore({
479
+ tableName: "mcp-sessions",
480
+ region: "us-east-1",
481
+ ttlSeconds: 86400,
482
+ }),
483
+ });
484
+ ```
485
+
486
+ ### SQLite (ECS/Fargate/EC2)
487
+
488
+ ```ts
489
+ import { SqliteSessionStore } from "@usestratus/mcp-aws";
490
+
491
+ server.lambda({
492
+ sessionStore: new SqliteSessionStore({
493
+ path: "/tmp/mcp-sessions.db",
494
+ ttlMs: 3_600_000,
495
+ }),
496
+ });
497
+ ```
498
+
499
+ Uses Bun's native `bun:sqlite`. Zero dependencies. Supports file persistence across restarts.
500
+
501
+ ### Custom
502
+
503
+ Implement the `SessionStore` interface:
504
+
505
+ ```ts
506
+ import type { SessionStore, McpSession } from "@usestratus/mcp-aws";
507
+
508
+ class RedisSessionStore implements SessionStore {
509
+ async get(sessionId: string): Promise<McpSession | undefined> { /* ... */ }
510
+ async set(session: McpSession): Promise<void> { /* ... */ }
511
+ async delete(sessionId: string): Promise<void> { /* ... */ }
512
+ }
513
+ ```
514
+
515
+ ---
516
+
517
+ ## RFC 9728 OAuth Metadata
518
+
519
+ Automatically serve `.well-known/oauth-protected-resource` for MCP clients that support OAuth discovery:
520
+
521
+ ```ts
522
+ server.lambda({
523
+ baseUrl: "https://api.example.com",
524
+ resourceMetadata: {
525
+ baseUrl: "https://api.example.com",
526
+ authorizationServers: ["https://cognito-idp.us-east-1.amazonaws.com/us-east-1_xxx"],
527
+ scopes: ["openid", "email"],
528
+ },
529
+ });
530
+ ```
531
+
532
+ 401 responses include `WWW-Authenticate: Bearer realm="mcp-server", resource_metadata="..."` per the spec.
533
+
534
+ ---
535
+
536
+ ## Error Classes
537
+
538
+ | Error | Description |
539
+ |---|---|
540
+ | `McpAwsError` | Base error |
541
+ | `GateDeniedError` | Tool gate blocked (has `toolName`, `reason`, `hint`) |
542
+ | `AuthenticationError` | Auth failed |
543
+ | `SessionNotFoundError` | Session expired/missing (has `sessionId`) |
544
+ | `ToolExecutionError` | Tool handler threw (has `toolName`, `cause`) |
545
+ | `ToolTimeoutError` | Tool exceeded timeout (has `toolName`, `timeoutMs`) |
546
+
547
+ ---
548
+
549
+ ## Full Example: Playwright MCP on Lambda
550
+
551
+ ```ts
552
+ import { McpServer, apiKey, role } from "@usestratus/mcp-aws";
553
+ import { z } from "zod";
554
+
555
+ const server = new McpServer("playwright@1.0.0")
556
+ .auth(apiKey({ "demo-key": { roles: ["user"] } }))
557
+
558
+ // Always visible (3 core tools)
559
+ .tool("browser_navigate", z.object({ url: z.string() }), async ({ url }) => {
560
+ return { navigated: true, url };
561
+ })
562
+ .tool("browser_snapshot", async () => {
563
+ return { title: "Page", elements: ["heading", "link", "form"] };
564
+ })
565
+ .tool("browser_close", async () => "closed")
566
+
567
+ // Discoverable via search (interaction tools)
568
+ .tool("browser_click", {
569
+ tier: "discoverable",
570
+ tags: ["interaction"],
571
+ params: z.object({ element: z.string() }),
572
+ }, async ({ element }) => `Clicked ${element}`)
573
+
574
+ .tool("browser_fill", {
575
+ tier: "discoverable",
576
+ tags: ["form", "input"],
577
+ params: z.object({ element: z.string(), value: z.string() }),
578
+ }, async ({ element, value }) => `Filled "${element}" with "${value}"`)
579
+
580
+ // Hidden until auth (debug tools)
581
+ .tool("browser_console", {
582
+ tier: "hidden",
583
+ gate: role("user"),
584
+ tags: ["debug"],
585
+ }, async () => [{ level: "log", text: "loaded" }])
586
+
587
+ // Observability
588
+ .on("tool:call", (e) => console.log(`[${e.toolName}] called`));
589
+
590
+ export const handler = server.lambda();
591
+ ```
592
+
593
+ 19 tools → only 3 + `search_tools` visible initially. The agent discovers the rest via search. Debug tools require authentication. All deployed with `server.lambda()`.
594
+
595
+ ---
596
+
597
+ ## Development
598
+
599
+ ```bash
600
+ bun install # Install dependencies
601
+ bun test # Run 248 tests (<1s)
602
+ bun run typecheck # TypeScript checking
603
+ bun run lint # Biome linting
604
+ ```
605
+
606
+ Tests are organized by type:
607
+ - **Unit** (125 tests) — types, gates, search, auth, session, codemode, context, ssrf, events
608
+ - **Local integration** (80 tests) — Lambda handler, MCP protocol, disclosure, PRD stories
609
+ - **Edge cases** (43 tests) — every branch covered
610
+ - **AWS integration** (optional) — real Lambda deploy + Function URL + Stratus agent E2E
@@ -0,0 +1,29 @@
1
+ import type { AuthContext } from "../types.js";
2
+ import type { AuthProvider, AuthRequest } from "./types.js";
3
+ export type ApiKeyEntry = {
4
+ subject?: string;
5
+ roles?: string[];
6
+ claims?: Record<string, unknown>;
7
+ };
8
+ export type ApiKeyAuthConfig = {
9
+ keys: Record<string, ApiKeyEntry>;
10
+ headerName?: string;
11
+ };
12
+ /**
13
+ * Factory: create an API key auth provider.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * server.auth(apiKey({ "sk-123": { roles: ["admin"] } }));
18
+ * ```
19
+ */
20
+ export declare function apiKey(keys: Record<string, ApiKeyEntry>, opts?: {
21
+ headerName?: string;
22
+ }): AuthProvider;
23
+ export declare class ApiKeyAuth implements AuthProvider {
24
+ private readonly keys;
25
+ private readonly headerName;
26
+ constructor(config: ApiKeyAuthConfig);
27
+ authenticate(request: AuthRequest): Promise<AuthContext>;
28
+ }
29
+ //# sourceMappingURL=api-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-key.d.ts","sourceRoot":"","sources":["../../src/auth/api-key.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE5D,MAAM,MAAM,WAAW,GAAG;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAQF;;;;;;;GAOG;AACH,wBAAgB,MAAM,CACrB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACjC,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5B,YAAY,CAEd;AAED,qBAAa,UAAW,YAAW,YAAY;IAC9C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA8B;IACnD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,MAAM,EAAE,gBAAgB;IAK9B,YAAY,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;CAoB9D"}
@@ -0,0 +1,42 @@
1
+ const UNAUTHENTICATED = {
2
+ authenticated: false,
3
+ roles: [],
4
+ claims: {},
5
+ };
6
+ /**
7
+ * Factory: create an API key auth provider.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * server.auth(apiKey({ "sk-123": { roles: ["admin"] } }));
12
+ * ```
13
+ */
14
+ export function apiKey(keys, opts) {
15
+ return new ApiKeyAuth({ keys, headerName: opts?.headerName });
16
+ }
17
+ export class ApiKeyAuth {
18
+ keys;
19
+ headerName;
20
+ constructor(config) {
21
+ this.keys = config.keys;
22
+ this.headerName = config.headerName ?? "x-api-key";
23
+ }
24
+ async authenticate(request) {
25
+ const raw = request.headers[this.headerName];
26
+ const value = Array.isArray(raw) ? raw[0] : raw;
27
+ if (!value) {
28
+ return UNAUTHENTICATED;
29
+ }
30
+ const entry = this.keys[value];
31
+ if (!entry) {
32
+ return UNAUTHENTICATED;
33
+ }
34
+ return {
35
+ authenticated: true,
36
+ subject: entry.subject,
37
+ roles: entry.roles ?? [],
38
+ claims: entry.claims ?? {},
39
+ };
40
+ }
41
+ }
42
+ //# sourceMappingURL=api-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-key.js","sourceRoot":"","sources":["../../src/auth/api-key.ts"],"names":[],"mappings":"AAcA,MAAM,eAAe,GAAgB;IACpC,aAAa,EAAE,KAAK;IACpB,KAAK,EAAE,EAAE;IACT,MAAM,EAAE,EAAE;CACV,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,MAAM,CACrB,IAAiC,EACjC,IAA8B;IAE9B,OAAO,IAAI,UAAU,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,OAAO,UAAU;IACL,IAAI,CAA8B;IAClC,UAAU,CAAS;IAEpC,YAAY,MAAwB;QACnC,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;QACxB,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,WAAW,CAAC;IACpD,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,OAAoB;QACtC,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC7C,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QAEhD,IAAI,CAAC,KAAK,EAAE,CAAC;YACZ,OAAO,eAAe,CAAC;QACxB,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;YACZ,OAAO,eAAe,CAAC;QACxB,CAAC;QAED,OAAO;YACN,aAAa,EAAE,IAAI;YACnB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,EAAE;YACxB,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,EAAE;SAC1B,CAAC;IACH,CAAC;CACD"}
@@ -0,0 +1,26 @@
1
+ import type { AuthContext } from "../types.js";
2
+ import type { AuthProvider, AuthRequest } from "./types.js";
3
+ export type CognitoAuthConfig = {
4
+ userPoolId: string;
5
+ region: string;
6
+ audience?: string;
7
+ rolesClaim?: string;
8
+ };
9
+ /**
10
+ * Factory: create a Cognito JWT auth provider.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * server.auth(cognito({ userPoolId: "us-east-1_abc", region: "us-east-1" }));
15
+ * ```
16
+ */
17
+ export declare function cognito(config: CognitoAuthConfig): AuthProvider;
18
+ export declare class CognitoAuth implements AuthProvider {
19
+ private readonly issuer;
20
+ private readonly jwks;
21
+ private readonly audience?;
22
+ private readonly rolesClaim;
23
+ constructor(config: CognitoAuthConfig);
24
+ authenticate(request: AuthRequest): Promise<AuthContext>;
25
+ }
26
+ //# sourceMappingURL=cognito.d.ts.map