@jaypie/mcp 0.2.2 → 0.2.4

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/llm.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * LLM debugging utilities for inspecting raw provider responses
3
+ */
4
+ export type LlmProvider = "anthropic" | "gemini" | "openai" | "openrouter";
5
+ export interface LlmDebugCallParams {
6
+ provider: LlmProvider;
7
+ model?: string;
8
+ message: string;
9
+ }
10
+ export interface LlmDebugCallResult {
11
+ success: boolean;
12
+ provider: string;
13
+ model: string;
14
+ content?: string;
15
+ reasoning?: string[];
16
+ reasoningTokens?: number;
17
+ history?: unknown[];
18
+ rawResponses?: unknown[];
19
+ usage?: unknown[];
20
+ error?: string;
21
+ }
22
+ interface Logger {
23
+ info: (message: string, ...args: unknown[]) => void;
24
+ error: (message: string, ...args: unknown[]) => void;
25
+ }
26
+ export declare const REASONING_MODELS: Record<string, string>;
27
+ /**
28
+ * Make a debug LLM call and return the raw response data for inspection
29
+ */
30
+ export declare function debugLlmCall(params: LlmDebugCallParams, log: Logger): Promise<LlmDebugCallResult>;
31
+ /**
32
+ * List available providers and their default/reasoning models
33
+ */
34
+ export declare function listLlmProviders(): {
35
+ providers: Array<{
36
+ name: LlmProvider;
37
+ defaultModel: string;
38
+ reasoningModels: string[];
39
+ }>;
40
+ };
41
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jaypie/mcp",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Jaypie MCP",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,7 +25,7 @@
25
25
  "prompts"
26
26
  ],
27
27
  "scripts": {
28
- "build": "rollup --config",
28
+ "build": "rollup --config && chmod +x dist/index.js",
29
29
  "format": "eslint . --fix",
30
30
  "lint": "eslint .",
31
31
  "prepare": "npm run build",
@@ -34,6 +34,7 @@
34
34
  "typecheck": "tsc --noEmit"
35
35
  },
36
36
  "dependencies": {
37
+ "@jaypie/llm": "^1.2.2",
37
38
  "@modelcontextprotocol/sdk": "^1.17.0",
38
39
  "commander": "^14.0.0",
39
40
  "gray-matter": "^4.0.3",
@@ -100,6 +100,57 @@ new JaypieExpressLambda(this, "ExpressApp", {
100
100
 
101
101
  Preconfigured with API-optimized timeouts and role tags.
102
102
 
103
+ ### Streaming Lambda Functions
104
+
105
+ Enable Lambda Response Streaming via Function URLs for real-time SSE responses:
106
+
107
+ ```typescript
108
+ import { FunctionUrlAuthType, InvokeMode } from "aws-cdk-lib/aws-lambda";
109
+
110
+ const streamingLambda = new JaypieLambda(this, "StreamingFunction", {
111
+ code: "dist",
112
+ handler: "stream.handler",
113
+ timeout: Duration.minutes(5), // Longer timeout for streaming
114
+ });
115
+
116
+ // Add Function URL with streaming enabled
117
+ const functionUrl = streamingLambda.addFunctionUrl({
118
+ authType: FunctionUrlAuthType.NONE, // Public access
119
+ // authType: FunctionUrlAuthType.AWS_IAM, // IAM authentication
120
+ invokeMode: InvokeMode.RESPONSE_STREAM, // Enable streaming
121
+ });
122
+
123
+ // Output the URL
124
+ new cdk.CfnOutput(this, "StreamingUrl", {
125
+ value: functionUrl.url,
126
+ });
127
+ ```
128
+
129
+ Features:
130
+ - `RESPONSE_STREAM` invoke mode enables real-time streaming
131
+ - Works with `lambdaStreamHandler` from `@jaypie/lambda`
132
+ - Use `FunctionUrlAuthType.AWS_IAM` for authenticated endpoints
133
+ - Combine with API Gateway for custom domains via `JaypieApiGateway`
134
+
135
+ For Express-based streaming with custom domains:
136
+ ```typescript
137
+ // Express app with streaming routes
138
+ const expressLambda = new JaypieExpressLambda(this, "ExpressStream", {
139
+ code: "dist",
140
+ handler: "app.handler",
141
+ timeout: Duration.minutes(5),
142
+ });
143
+
144
+ // API Gateway handles the domain routing
145
+ new JaypieApiGateway(this, "Api", {
146
+ handler: expressLambda,
147
+ host: "api.example.com",
148
+ zone: "example.com",
149
+ });
150
+ ```
151
+
152
+ Note: API Gateway has a 29-second timeout limit. For longer streaming operations, use Function URLs directly.
153
+
103
154
  ### Stack Types
104
155
 
105
156
  Use specialized stacks for different purposes:
@@ -0,0 +1,547 @@
1
+ ---
2
+ description: Complete guide to @jaypie/dynamodb single-table design utilities including GSI patterns, key builders, entity operations, and query functions
3
+ globs: packages/dynamodb/**
4
+ ---
5
+
6
+ # Jaypie DynamoDB Package
7
+
8
+ Jaypie provides DynamoDB single-table design utilities through `@jaypie/dynamodb`. The package implements a five-GSI pattern for hierarchical data with named access patterns (not gsi1, gsi2, etc.).
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install @jaypie/dynamodb
14
+ ```
15
+
16
+ ## Core Concepts
17
+
18
+ ### Single-Table Design
19
+
20
+ All entities share a single DynamoDB table with:
21
+ - **Primary Key**: `model` (partition) + `id` (sort)
22
+ - **Five GSIs**: All use `sequence` as sort key for chronological ordering
23
+
24
+ ### Organizational Unit (OU)
25
+
26
+ The `ou` field creates hierarchical relationships:
27
+ - Root entities: `ou = "@"` (APEX constant)
28
+ - Child entities: `ou = "{parent.model}#{parent.id}"`
29
+
30
+ ### GSI Pattern
31
+
32
+ | GSI Name | Partition Key | Use Case |
33
+ |----------|---------------|----------|
34
+ | `indexOu` | `{ou}#{model}` | List all entities under a parent |
35
+ | `indexAlias` | `{ou}#{model}#{alias}` | Human-friendly slug lookup |
36
+ | `indexClass` | `{ou}#{model}#{class}` | Category filtering |
37
+ | `indexType` | `{ou}#{model}#{type}` | Type filtering |
38
+ | `indexXid` | `{ou}#{model}#{xid}` | External system ID lookup |
39
+
40
+ ## Client Initialization
41
+
42
+ Initialize once at application startup:
43
+
44
+ ```typescript
45
+ import { initClient } from "@jaypie/dynamodb";
46
+
47
+ initClient({
48
+ tableName: process.env.DYNAMODB_TABLE_NAME,
49
+ region: process.env.AWS_REGION, // Optional, defaults to us-east-1
50
+ endpoint: process.env.DYNAMODB_ENDPOINT, // Optional, for local dev
51
+ });
52
+ ```
53
+
54
+ For local development with DynamoDB Local:
55
+
56
+ ```typescript
57
+ initClient({
58
+ tableName: "local-table",
59
+ endpoint: "http://127.0.0.1:8100",
60
+ // Credentials auto-detected for localhost endpoints
61
+ });
62
+ ```
63
+
64
+ ## FabricEntity Interface
65
+
66
+ All entities must implement `FabricEntity`:
67
+
68
+ ```typescript
69
+ import type { FabricEntity } from "@jaypie/dynamodb";
70
+
71
+ interface MyRecord extends FabricEntity {
72
+ // Primary Key (required)
73
+ model: string; // e.g., "record"
74
+ id: string; // UUID
75
+
76
+ // Required fields
77
+ name: string;
78
+ ou: string; // APEX or hierarchical
79
+ sequence: number; // Date.now()
80
+
81
+ // Timestamps (ISO 8601)
82
+ createdAt: string;
83
+ updatedAt: string;
84
+ archivedAt?: string; // Set by archiveEntity
85
+ deletedAt?: string; // Set by deleteEntity
86
+
87
+ // Optional - trigger GSI population
88
+ alias?: string; // Human-friendly slug
89
+ class?: string; // Category
90
+ type?: string; // Type classification
91
+ xid?: string; // External ID
92
+ }
93
+ ```
94
+
95
+ ## Entity Operations
96
+
97
+ ### Creating Entities
98
+
99
+ Use `putEntity` to create or replace entities. GSI keys are auto-populated:
100
+
101
+ ```typescript
102
+ import { APEX, putEntity } from "@jaypie/dynamodb";
103
+
104
+ const now = new Date().toISOString();
105
+
106
+ const record = await putEntity({
107
+ entity: {
108
+ model: "record",
109
+ id: crypto.randomUUID(),
110
+ name: "Daily Log",
111
+ ou: APEX,
112
+ sequence: Date.now(),
113
+ alias: "2026-01-07", // Optional
114
+ class: "memory", // Optional
115
+ createdAt: now,
116
+ updatedAt: now,
117
+ },
118
+ });
119
+
120
+ // Result includes auto-populated indexes:
121
+ // indexOu: "@#record"
122
+ // indexAlias: "@#record#2026-01-07"
123
+ // indexClass: "@#record#memory"
124
+ ```
125
+
126
+ ### Getting Entities
127
+
128
+ ```typescript
129
+ import { getEntity } from "@jaypie/dynamodb";
130
+
131
+ const record = await getEntity({ id: "123", model: "record" });
132
+ // Returns entity or null
133
+ ```
134
+
135
+ ### Updating Entities
136
+
137
+ Use `updateEntity` to update. Automatically sets `updatedAt` and re-indexes:
138
+
139
+ ```typescript
140
+ import { updateEntity } from "@jaypie/dynamodb";
141
+
142
+ const updated = await updateEntity({
143
+ entity: {
144
+ ...existingRecord,
145
+ name: "Updated Name",
146
+ alias: "new-alias",
147
+ },
148
+ });
149
+ // updatedAt is set automatically
150
+ // indexAlias is re-calculated
151
+ ```
152
+
153
+ ### Soft Delete
154
+
155
+ Use `deleteEntity` for soft delete (sets `deletedAt`):
156
+
157
+ ```typescript
158
+ import { deleteEntity } from "@jaypie/dynamodb";
159
+
160
+ await deleteEntity({ id: "123", model: "record" });
161
+ // Sets deletedAt and updatedAt timestamps
162
+ // Entity excluded from queries by default
163
+ ```
164
+
165
+ ### Archive
166
+
167
+ Use `archiveEntity` for archiving (sets `archivedAt`):
168
+
169
+ ```typescript
170
+ import { archiveEntity } from "@jaypie/dynamodb";
171
+
172
+ await archiveEntity({ id: "123", model: "record" });
173
+ // Sets archivedAt and updatedAt timestamps
174
+ // Entity excluded from queries by default
175
+ ```
176
+
177
+ ### Hard Delete
178
+
179
+ Use `destroyEntity` to permanently remove:
180
+
181
+ ```typescript
182
+ import { destroyEntity } from "@jaypie/dynamodb";
183
+
184
+ await destroyEntity({ id: "123", model: "record" });
185
+ // Permanently removes from table
186
+ ```
187
+
188
+ ### Hierarchical Entities
189
+
190
+ Use `calculateOu` to derive OU from parent:
191
+
192
+ ```typescript
193
+ import { calculateOu, putEntity, queryByOu } from "@jaypie/dynamodb";
194
+
195
+ // Parent reference
196
+ const chat = { model: "chat", id: "abc-123" };
197
+
198
+ // Calculate child OU
199
+ const messageOu = calculateOu(chat); // "chat#abc-123"
200
+
201
+ // Create child entity
202
+ const message = await putEntity({
203
+ entity: {
204
+ model: "message",
205
+ id: crypto.randomUUID(),
206
+ name: "First message",
207
+ ou: messageOu,
208
+ sequence: Date.now(),
209
+ createdAt: now,
210
+ updatedAt: now,
211
+ },
212
+ });
213
+ // indexOu: "chat#abc-123#message"
214
+
215
+ // Query all messages in chat
216
+ const { items } = await queryByOu({ model: "message", ou: messageOu });
217
+ ```
218
+
219
+ ## Query Functions
220
+
221
+ All queries use object parameters and filter soft-deleted and archived records by default.
222
+
223
+ ### queryByOu - List by Parent
224
+
225
+ List all entities of a model under a parent:
226
+
227
+ ```typescript
228
+ import { APEX, queryByOu } from "@jaypie/dynamodb";
229
+
230
+ // Root-level records
231
+ const { items, lastEvaluatedKey } = await queryByOu({
232
+ model: "record",
233
+ ou: APEX,
234
+ });
235
+
236
+ // Messages under a chat
237
+ const { items: messages } = await queryByOu({
238
+ model: "message",
239
+ ou: "chat#abc-123",
240
+ });
241
+ ```
242
+
243
+ ### queryByAlias - Human-Friendly Lookup
244
+
245
+ Single entity lookup by slug:
246
+
247
+ ```typescript
248
+ import { APEX, queryByAlias } from "@jaypie/dynamodb";
249
+
250
+ const record = await queryByAlias({
251
+ alias: "2026-01-07",
252
+ model: "record",
253
+ ou: APEX,
254
+ });
255
+ // Returns entity or null
256
+ ```
257
+
258
+ ### queryByClass / queryByType - Category Filtering
259
+
260
+ ```typescript
261
+ import { APEX, queryByClass, queryByType } from "@jaypie/dynamodb";
262
+
263
+ // All memory records
264
+ const { items } = await queryByClass({
265
+ model: "record",
266
+ ou: APEX,
267
+ recordClass: "memory",
268
+ });
269
+
270
+ // All note-type records
271
+ const { items: notes } = await queryByType({
272
+ model: "record",
273
+ ou: APEX,
274
+ type: "note",
275
+ });
276
+ ```
277
+
278
+ ### queryByXid - External ID Lookup
279
+
280
+ Single entity lookup by external system ID:
281
+
282
+ ```typescript
283
+ import { APEX, queryByXid } from "@jaypie/dynamodb";
284
+
285
+ const record = await queryByXid({
286
+ model: "record",
287
+ ou: APEX,
288
+ xid: "ext-12345",
289
+ });
290
+ // Returns entity or null
291
+ ```
292
+
293
+ ### Query Options
294
+
295
+ ```typescript
296
+ import type { BaseQueryOptions } from "@jaypie/dynamodb";
297
+
298
+ const result = await queryByOu({
299
+ model: "record",
300
+ ou: APEX,
301
+ // BaseQueryOptions:
302
+ limit: 25, // Max items to return
303
+ ascending: true, // Oldest first (default: false = newest first)
304
+ includeDeleted: true, // Include soft-deleted records
305
+ includeArchived: true, // Include archived records
306
+ startKey: lastEvaluatedKey, // Pagination cursor
307
+ });
308
+ ```
309
+
310
+ ### Pagination
311
+
312
+ ```typescript
313
+ import { APEX, queryByOu } from "@jaypie/dynamodb";
314
+
315
+ let startKey: Record<string, unknown> | undefined;
316
+ const allItems: FabricEntity[] = [];
317
+
318
+ do {
319
+ const { items, lastEvaluatedKey } = await queryByOu({
320
+ model: "record",
321
+ ou: APEX,
322
+ limit: 100,
323
+ startKey,
324
+ });
325
+ allItems.push(...items);
326
+ startKey = lastEvaluatedKey;
327
+ } while (startKey);
328
+ ```
329
+
330
+ ## Key Builder Functions
331
+
332
+ Use `indexEntity` to auto-populate GSI keys on an entity:
333
+
334
+ ```typescript
335
+ import { indexEntity } from "@jaypie/dynamodb";
336
+
337
+ const indexed = indexEntity({
338
+ model: "record",
339
+ id: "123",
340
+ ou: "@",
341
+ alias: "my-alias",
342
+ // ...
343
+ });
344
+ // indexOu: "@#record"
345
+ // indexAlias: "@#record#my-alias"
346
+ ```
347
+
348
+ Use individual key builders for manual key construction:
349
+
350
+ ```typescript
351
+ import {
352
+ buildIndexOu,
353
+ buildIndexAlias,
354
+ buildIndexClass,
355
+ buildIndexType,
356
+ buildIndexXid,
357
+ } from "@jaypie/dynamodb";
358
+
359
+ buildIndexOu("@", "record"); // "@#record"
360
+ buildIndexAlias("@", "record", "my-alias"); // "@#record#my-alias"
361
+ buildIndexClass("@", "record", "memory"); // "@#record#memory"
362
+ buildIndexType("@", "record", "note"); // "@#record#note"
363
+ buildIndexXid("@", "record", "ext-123"); // "@#record#ext-123"
364
+ ```
365
+
366
+ ## Constants
367
+
368
+ ```typescript
369
+ import {
370
+ APEX, // "@" - Root-level marker
371
+ SEPARATOR, // "#" - Composite key separator
372
+ INDEX_OU, // "indexOu"
373
+ INDEX_ALIAS, // "indexAlias"
374
+ INDEX_CLASS, // "indexClass"
375
+ INDEX_TYPE, // "indexType"
376
+ INDEX_XID, // "indexXid"
377
+ } from "@jaypie/dynamodb";
378
+ ```
379
+
380
+ ## Table Schema (CloudFormation/CDK Reference)
381
+
382
+ ```yaml
383
+ AttributeDefinitions:
384
+ - AttributeName: model
385
+ AttributeType: S
386
+ - AttributeName: id
387
+ AttributeType: S
388
+ - AttributeName: indexOu
389
+ AttributeType: S
390
+ - AttributeName: indexAlias
391
+ AttributeType: S
392
+ - AttributeName: indexClass
393
+ AttributeType: S
394
+ - AttributeName: indexType
395
+ AttributeType: S
396
+ - AttributeName: indexXid
397
+ AttributeType: S
398
+ - AttributeName: sequence
399
+ AttributeType: N
400
+
401
+ KeySchema:
402
+ - AttributeName: model
403
+ KeyType: HASH
404
+ - AttributeName: id
405
+ KeyType: RANGE
406
+
407
+ GlobalSecondaryIndexes:
408
+ - IndexName: indexOu
409
+ KeySchema:
410
+ - AttributeName: indexOu
411
+ KeyType: HASH
412
+ - AttributeName: sequence
413
+ KeyType: RANGE
414
+ Projection:
415
+ ProjectionType: ALL
416
+ # Repeat for indexAlias, indexClass, indexType, indexXid
417
+ ```
418
+
419
+ ## Error Handling
420
+
421
+ Functions throw `ConfigurationError` if client is not initialized:
422
+
423
+ ```typescript
424
+ import { getDocClient } from "@jaypie/dynamodb";
425
+
426
+ try {
427
+ const client = getDocClient();
428
+ } catch (error) {
429
+ // ConfigurationError: DynamoDB client not initialized. Call initClient() first.
430
+ }
431
+ ```
432
+
433
+ ## Testing
434
+
435
+ Mock implementations in `@jaypie/testkit`:
436
+
437
+ ```typescript
438
+ import { vi } from "vitest";
439
+
440
+ vi.mock("@jaypie/dynamodb", async () => {
441
+ const testkit = await import("@jaypie/testkit/mock");
442
+ return testkit;
443
+ });
444
+
445
+ // Key builders and indexEntity work correctly (delegate to real implementations)
446
+ // Query functions return empty results by default
447
+ // Entity operations return sensible defaults
448
+
449
+ // Customize mock behavior:
450
+ import { queryByOu, getEntity, putEntity } from "@jaypie/testkit/mock";
451
+
452
+ queryByOu.mockResolvedValue({
453
+ items: [{ id: "123", name: "Test" }],
454
+ lastEvaluatedKey: undefined,
455
+ });
456
+
457
+ getEntity.mockResolvedValue({ id: "123", model: "record", name: "Test" });
458
+ ```
459
+
460
+ ## Best Practices
461
+
462
+ ### Use putEntity, Not Direct Writes
463
+
464
+ Always use `putEntity` or `updateEntity` to ensure GSI keys are auto-populated:
465
+
466
+ ```typescript
467
+ // CORRECT - uses putEntity which calls indexEntity internally
468
+ const entity = await putEntity({
469
+ entity: {
470
+ model: "record",
471
+ alias: "my-alias",
472
+ // ...
473
+ },
474
+ });
475
+
476
+ // WRONG - bypasses index population
477
+ const entity = {
478
+ model: "record",
479
+ alias: "my-alias",
480
+ indexAlias: "@#record#my-alias", // Don't manually set index keys
481
+ };
482
+ ```
483
+
484
+ ### Use indexEntity for Raw Entities
485
+
486
+ If you need to prepare an entity before a batch write:
487
+
488
+ ```typescript
489
+ import { indexEntity } from "@jaypie/dynamodb";
490
+
491
+ // Use indexEntity to prepare entities for batch operations
492
+ const indexed = indexEntity(myEntity);
493
+ ```
494
+
495
+ ### Use Meaningful Model Names
496
+
497
+ Model names are part of every key. Use short, descriptive names:
498
+
499
+ ```typescript
500
+ // GOOD
501
+ { model: "record" }
502
+ { model: "message" }
503
+ { model: "chat" }
504
+
505
+ // AVOID
506
+ { model: "DailyLogRecord" }
507
+ { model: "ChatMessage_v2" }
508
+ ```
509
+
510
+ ### Sequence for Ordering
511
+
512
+ Always set `sequence: Date.now()` for chronological ordering in GSI queries:
513
+
514
+ ```typescript
515
+ const entity = await putEntity({
516
+ entity: {
517
+ // ...
518
+ sequence: Date.now(), // Required for proper ordering
519
+ },
520
+ });
521
+ ```
522
+
523
+ ### Soft Delete and Archive Patterns
524
+
525
+ Use `deleteEntity` for logical deletion and `archiveEntity` for archival:
526
+
527
+ ```typescript
528
+ // Soft delete - user action, can be recovered
529
+ await deleteEntity({ id: "123", model: "record" });
530
+
531
+ // Archive - system action, long-term storage
532
+ await archiveEntity({ id: "123", model: "record" });
533
+
534
+ // Queries exclude both by default
535
+ const { items } = await queryByOu({ model: "record", ou: APEX });
536
+
537
+ // Include if needed
538
+ const { items: all } = await queryByOu({
539
+ model: "record",
540
+ ou: APEX,
541
+ includeDeleted: true,
542
+ includeArchived: true,
543
+ });
544
+
545
+ // Permanent deletion (use sparingly)
546
+ await destroyEntity({ id: "123", model: "record" });
547
+ ```