@jaypie/mcp 0.2.3 → 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.
@@ -0,0 +1,312 @@
1
+ ---
2
+ description: LLM tool creation from serviceHandler for use with @jaypie/llm Toolkit
3
+ ---
4
+
5
+ # Jaypie Vocabulary LLM Adapter
6
+
7
+ The LLM adapter (`@jaypie/vocabulary/llm`) creates LLM tools from Jaypie service handlers for use with `@jaypie/llm` Toolkit, automatically generating JSON Schema from input definitions.
8
+
9
+ **See also:** [Jaypie_Vocabulary_Package.md](Jaypie_Vocabulary_Package.md) for core serviceHandler documentation.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install @jaypie/vocabulary @jaypie/llm
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```typescript
20
+ import { serviceHandler } from "@jaypie/vocabulary";
21
+ import { createLlmTool } from "@jaypie/vocabulary/llm";
22
+ import { Toolkit } from "@jaypie/llm";
23
+
24
+ const handler = serviceHandler({
25
+ alias: "greet",
26
+ description: "Greet a user by name",
27
+ input: {
28
+ userName: { type: String, description: "The user's name" },
29
+ loud: { type: Boolean, default: false, description: "Shout the greeting" },
30
+ },
31
+ service: ({ userName, loud }) => {
32
+ const greeting = `Hello, ${userName}!`;
33
+ return loud ? greeting.toUpperCase() : greeting;
34
+ },
35
+ });
36
+
37
+ const { tool } = createLlmTool({ handler });
38
+ const toolkit = new Toolkit([tool]);
39
+ ```
40
+
41
+ ## createLlmTool
42
+
43
+ Creates an LLM tool from a serviceHandler.
44
+
45
+ ### Options
46
+
47
+ | Option | Type | Description |
48
+ |--------|------|-------------|
49
+ | `handler` | `ServiceHandlerFunction` | Required. The service handler to adapt |
50
+ | `name` | `string` | Override tool name (defaults to handler.alias) |
51
+ | `description` | `string` | Override tool description (defaults to handler.description) |
52
+ | `message` | `string \| function` | Custom message for logging |
53
+ | `exclude` | `string[]` | Fields to exclude from tool parameters |
54
+ | `onComplete` | `OnCompleteCallback` | Called with tool's return value on success |
55
+ | `onError` | `OnErrorCallback` | Receives errors reported via `context.onError()` in service |
56
+ | `onFatal` | `OnFatalCallback` | Receives fatal errors (thrown or via `context.onFatal()`) |
57
+ | `onMessage` | `OnMessageCallback` | Receives messages from `context.sendMessage` in service |
58
+
59
+ ### Basic Usage
60
+
61
+ ```typescript
62
+ import { serviceHandler } from "@jaypie/vocabulary";
63
+ import { createLlmTool } from "@jaypie/vocabulary/llm";
64
+
65
+ const calculateHandler = serviceHandler({
66
+ alias: "calculate",
67
+ description: "Perform a mathematical calculation",
68
+ input: {
69
+ operation: { type: ["add", "subtract", "multiply", "divide"] },
70
+ a: { type: Number, description: "First operand" },
71
+ b: { type: Number, description: "Second operand" },
72
+ },
73
+ service: ({ operation, a, b }) => {
74
+ switch (operation) {
75
+ case "add": return a + b;
76
+ case "subtract": return a - b;
77
+ case "multiply": return a * b;
78
+ case "divide": return a / b;
79
+ }
80
+ },
81
+ });
82
+
83
+ const { tool } = createLlmTool({ handler: calculateHandler });
84
+
85
+ // Tool has these properties:
86
+ // tool.name: "calculate"
87
+ // tool.description: "Perform a mathematical calculation"
88
+ // tool.parameters: JSON Schema for inputs
89
+ // tool.function: The callable function
90
+ ```
91
+
92
+ ### Overriding Name and Description
93
+
94
+ ```typescript
95
+ const { tool } = createLlmTool({
96
+ handler,
97
+ name: "math_calculator",
98
+ description: "A tool for performing basic math operations",
99
+ });
100
+ ```
101
+
102
+ ### Excluding Fields
103
+
104
+ ```typescript
105
+ const handler = serviceHandler({
106
+ alias: "search",
107
+ input: {
108
+ query: { type: String },
109
+ limit: { type: Number, default: 10 },
110
+ _internalId: { type: String }, // Internal field
111
+ },
112
+ service: async (params) => { /* ... */ },
113
+ });
114
+
115
+ const { tool } = createLlmTool({
116
+ handler,
117
+ exclude: ["_internalId"], // Not exposed to LLM
118
+ });
119
+ ```
120
+
121
+ ### Lifecycle Callbacks
122
+
123
+ Services can use context callbacks to report progress, errors, and completion:
124
+
125
+ ```typescript
126
+ const handler = serviceHandler({
127
+ alias: "evaluate",
128
+ input: { jobId: { type: String } },
129
+ service: async ({ jobId }, context) => {
130
+ context?.sendMessage?.({ content: `Processing ${jobId}` });
131
+
132
+ try {
133
+ await riskyOperation();
134
+ } catch (err) {
135
+ context?.onError?.(err); // Reports error but continues
136
+ }
137
+
138
+ return { jobId, status: "complete" };
139
+ },
140
+ });
141
+
142
+ const { tool } = createLlmTool({
143
+ handler,
144
+ onComplete: (result) => console.log("Tool completed:", result),
145
+ onError: (error) => console.warn("Recoverable error:", error),
146
+ onFatal: (error) => console.error("Fatal error:", error),
147
+ onMessage: (msg) => console.log(`[${msg.level || "info"}] ${msg.content}`),
148
+ });
149
+ ```
150
+
151
+ **Error handling**: Services receive `context.onError()` and `context.onFatal()` callbacks to report errors without throwing. Any error that escapes the service (is thrown) is treated as fatal and routes to `onFatal`. If `onFatal` is not provided, thrown errors fall back to `onError`. Callback errors are swallowed to ensure failures never halt service execution.
152
+
153
+ ## inputToJsonSchema
154
+
155
+ Converts vocabulary input definitions to JSON Schema:
156
+
157
+ ```typescript
158
+ import { inputToJsonSchema } from "@jaypie/vocabulary/llm";
159
+
160
+ const schema = inputToJsonSchema({
161
+ userName: { type: String, description: "User's name" },
162
+ age: { type: Number, required: false },
163
+ role: { type: ["admin", "user", "guest"], default: "user" },
164
+ });
165
+
166
+ // Returns:
167
+ {
168
+ type: "object",
169
+ properties: {
170
+ userName: { type: "string", description: "User's name" },
171
+ age: { type: "number" },
172
+ role: { type: "string", enum: ["admin", "user", "guest"] },
173
+ },
174
+ required: ["userName"],
175
+ }
176
+ ```
177
+
178
+ ### Type Mappings
179
+
180
+ | Vocabulary Type | JSON Schema |
181
+ |-----------------|-------------|
182
+ | `String` | `{ type: "string" }` |
183
+ | `Number` | `{ type: "number" }` |
184
+ | `Boolean` | `{ type: "boolean" }` |
185
+ | `Array` | `{ type: "array" }` |
186
+ | `Object` | `{ type: "object" }` |
187
+ | `[String]` | `{ type: "array", items: { type: "string" } }` |
188
+ | `[Number]` | `{ type: "array", items: { type: "number" } }` |
189
+ | `/regex/` | `{ type: "string", pattern: "..." }` |
190
+ | `["a", "b"]` | `{ type: "string", enum: ["a", "b"] }` |
191
+ | `[1, 2, 3]` | `{ type: "number", enum: [1, 2, 3] }` |
192
+
193
+ ### Excluding Fields
194
+
195
+ ```typescript
196
+ const schema = inputToJsonSchema(handler.input, {
197
+ exclude: ["internalField", "debugMode"],
198
+ });
199
+ ```
200
+
201
+ ## Complete Example
202
+
203
+ ```typescript
204
+ import { serviceHandler } from "@jaypie/vocabulary";
205
+ import { createLlmTool } from "@jaypie/vocabulary/llm";
206
+ import { Llm, Toolkit } from "@jaypie/llm";
207
+
208
+ // Define service handlers
209
+ const weatherHandler = serviceHandler({
210
+ alias: "get_weather",
211
+ description: "Get current weather for a location",
212
+ input: {
213
+ location: { type: String, description: "City name or coordinates" },
214
+ units: { type: ["celsius", "fahrenheit"], default: "celsius" },
215
+ },
216
+ service: async ({ location, units }) => {
217
+ const weather = await fetchWeather(location);
218
+ return {
219
+ location,
220
+ temperature: units === "celsius" ? weather.tempC : weather.tempF,
221
+ units,
222
+ conditions: weather.conditions,
223
+ };
224
+ },
225
+ });
226
+
227
+ const searchHandler = serviceHandler({
228
+ alias: "search_web",
229
+ description: "Search the web for information",
230
+ input: {
231
+ query: { type: String, description: "Search query" },
232
+ maxResults: { type: Number, default: 5, description: "Maximum results" },
233
+ },
234
+ service: async ({ query, maxResults }) => {
235
+ return await performSearch(query, maxResults);
236
+ },
237
+ });
238
+
239
+ // Create tools
240
+ const { tool: weatherTool } = createLlmTool({ handler: weatherHandler });
241
+ const { tool: searchTool } = createLlmTool({ handler: searchHandler });
242
+
243
+ // Use with Toolkit
244
+ const toolkit = new Toolkit([weatherTool, searchTool]);
245
+ const llm = new Llm({ toolkit });
246
+
247
+ const response = await llm.ask("What's the weather in Tokyo?");
248
+ ```
249
+
250
+ ## TypeScript Types
251
+
252
+ ```typescript
253
+ import type {
254
+ CreateLlmToolConfig,
255
+ CreateLlmToolResult,
256
+ LlmTool,
257
+ OnCompleteCallback,
258
+ OnErrorCallback,
259
+ OnFatalCallback,
260
+ OnMessageCallback,
261
+ } from "@jaypie/vocabulary/llm";
262
+ ```
263
+
264
+ ### Type Definitions
265
+
266
+ ```typescript
267
+ interface LlmTool {
268
+ name: string;
269
+ description: string;
270
+ parameters: JsonSchema;
271
+ function: (params: Record<string, unknown>) => Promise<unknown>;
272
+ }
273
+
274
+ interface CreateLlmToolConfig {
275
+ handler: ServiceHandlerFunction;
276
+ name?: string;
277
+ description?: string;
278
+ message?: string | ((params: Record<string, unknown>) => string);
279
+ exclude?: string[];
280
+ onComplete?: OnCompleteCallback;
281
+ onError?: OnErrorCallback;
282
+ onFatal?: OnFatalCallback;
283
+ onMessage?: OnMessageCallback;
284
+ }
285
+
286
+ interface CreateLlmToolResult {
287
+ tool: LlmTool;
288
+ }
289
+ ```
290
+
291
+ ## Exports
292
+
293
+ ```typescript
294
+ // @jaypie/vocabulary/llm
295
+ export { createLlmTool } from "./createLlmTool.js";
296
+ export { inputToJsonSchema } from "./inputToJsonSchema.js";
297
+
298
+ export type {
299
+ CreateLlmToolConfig,
300
+ CreateLlmToolResult,
301
+ LlmTool,
302
+ OnCompleteCallback,
303
+ OnErrorCallback,
304
+ OnFatalCallback,
305
+ OnMessageCallback,
306
+ } from "./types.js";
307
+ ```
308
+
309
+ ## Related
310
+
311
+ - [Jaypie_Vocabulary_Package.md](Jaypie_Vocabulary_Package.md) - Core serviceHandler and type coercion
312
+ - [Jaypie_Llm_Tools.md](Jaypie_Llm_Tools.md) - LLM tools and Toolkit usage
@@ -0,0 +1,310 @@
1
+ ---
2
+ description: AWS Lambda integration with serviceHandler for event processing and callbacks
3
+ include: "**/lambda/**"
4
+ ---
5
+
6
+ # Jaypie Vocabulary Lambda Adapter
7
+
8
+ The Lambda adapter (`@jaypie/vocabulary/lambda`) wraps Jaypie service handlers for use as AWS Lambda handlers, providing automatic event parsing, secrets management, and lifecycle hooks.
9
+
10
+ **See also:** [Jaypie_Vocabulary_Package.md](Jaypie_Vocabulary_Package.md) for core serviceHandler documentation.
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install @jaypie/vocabulary
16
+ ```
17
+
18
+ ## Quick Start
19
+
20
+ ```typescript
21
+ import { serviceHandler } from "@jaypie/vocabulary";
22
+ import { lambdaServiceHandler } from "@jaypie/vocabulary/lambda";
23
+
24
+ const handler = serviceHandler({
25
+ alias: "processOrder",
26
+ input: { orderId: { type: String } },
27
+ service: async ({ orderId }) => {
28
+ return { orderId, status: "processed" };
29
+ },
30
+ });
31
+
32
+ export const lambdaHandler = lambdaServiceHandler({ handler });
33
+ ```
34
+
35
+ ## lambdaServiceHandler
36
+
37
+ Wraps a serviceHandler for use as an AWS Lambda handler.
38
+
39
+ ### Options
40
+
41
+ | Option | Type | Description |
42
+ |--------|------|-------------|
43
+ | `handler` | `ServiceHandlerFunction` | Required. The service handler to wrap |
44
+ | `chaos` | `string` | Chaos testing mode |
45
+ | `name` | `string` | Override handler name for logging (default: handler.alias) |
46
+ | `onComplete` | `OnCompleteCallback` | Called with handler's return value on success |
47
+ | `onError` | `OnErrorCallback` | Receives errors reported via `context.onError()` in service |
48
+ | `onFatal` | `OnFatalCallback` | Receives fatal errors (thrown or via `context.onFatal()`) |
49
+ | `onMessage` | `OnMessageCallback` | Receives messages from `context.sendMessage` |
50
+ | `secrets` | `string[]` | AWS secrets to load into process.env |
51
+ | `setup` | `LifecycleFunction[]` | Functions to run before handler |
52
+ | `teardown` | `LifecycleFunction[]` | Functions to run after handler (always runs) |
53
+ | `throw` | `boolean` | Re-throw errors instead of returning error response |
54
+ | `unavailable` | `boolean` | Return 503 Unavailable immediately |
55
+ | `validate` | `ValidatorFunction[]` | Validation functions to run before handler |
56
+
57
+ ## Lifecycle Callbacks
58
+
59
+ Services can use context callbacks to report progress, errors, and completion:
60
+
61
+ ```typescript
62
+ import { serviceHandler } from "@jaypie/vocabulary";
63
+ import { lambdaServiceHandler } from "@jaypie/vocabulary/lambda";
64
+
65
+ const handler = serviceHandler({
66
+ alias: "evaluate",
67
+ input: { jobId: { type: String } },
68
+ service: async ({ jobId }, context) => {
69
+ context?.sendMessage?.({ content: `Starting job ${jobId}` });
70
+
71
+ // Handle recoverable errors without throwing
72
+ try {
73
+ await riskyOperation();
74
+ } catch (err) {
75
+ context?.onError?.(err); // Reports error but continues
76
+ }
77
+
78
+ // For fatal errors, either throw or call context.onFatal()
79
+ if (criticalFailure) {
80
+ context?.onFatal?.(new Error("Cannot continue"));
81
+ }
82
+
83
+ return { jobId, status: "complete" };
84
+ },
85
+ });
86
+
87
+ export const lambdaHandler = lambdaServiceHandler({
88
+ handler,
89
+ onComplete: (response) => {
90
+ console.log("Done:", JSON.stringify(response, null, 2));
91
+ },
92
+ onError: (error) => {
93
+ // Recoverable errors reported via context.onError()
94
+ console.error("Warning:", error);
95
+ },
96
+ onFatal: (error) => {
97
+ // Fatal errors (thrown or via context.onFatal())
98
+ console.error("Fatal:", error);
99
+ },
100
+ onMessage: (msg) => {
101
+ console.log(`[${msg.level || "info"}] ${msg.content}`);
102
+ },
103
+ });
104
+ ```
105
+
106
+ **Error handling**: Services receive `context.onError()` and `context.onFatal()` callbacks to report errors without throwing. Any error that escapes the service (is thrown) is treated as fatal and routes to `onFatal`. If `onFatal` is not provided, thrown errors fall back to `onError`. Callback errors are swallowed to ensure failures never halt service execution.
107
+
108
+ ## Secrets Management
109
+
110
+ Automatically loads AWS Secrets Manager values into `process.env`:
111
+
112
+ ```typescript
113
+ export const lambdaHandler = lambdaServiceHandler({
114
+ handler,
115
+ secrets: ["ANTHROPIC_API_KEY", "DATABASE_URL"],
116
+ });
117
+ // Before handler runs, secrets are fetched and available as:
118
+ // process.env.ANTHROPIC_API_KEY
119
+ // process.env.DATABASE_URL
120
+ ```
121
+
122
+ ## Lifecycle Hooks
123
+
124
+ ### setup
125
+
126
+ Functions to run before the handler:
127
+
128
+ ```typescript
129
+ export const lambdaHandler = lambdaServiceHandler({
130
+ handler,
131
+ setup: [
132
+ async () => {
133
+ await initializeDatabase();
134
+ },
135
+ async () => {
136
+ await warmCache();
137
+ },
138
+ ],
139
+ });
140
+ ```
141
+
142
+ ### teardown
143
+
144
+ Functions to run after the handler (always runs, even on error):
145
+
146
+ ```typescript
147
+ export const lambdaHandler = lambdaServiceHandler({
148
+ handler,
149
+ teardown: [
150
+ async () => {
151
+ await closeConnections();
152
+ },
153
+ ],
154
+ });
155
+ ```
156
+
157
+ ### validate
158
+
159
+ Validation functions to run before handler. If any returns falsy or throws, handler is skipped:
160
+
161
+ ```typescript
162
+ export const lambdaHandler = lambdaServiceHandler({
163
+ handler,
164
+ validate: [
165
+ (event) => event.headers?.authorization !== undefined,
166
+ async (event) => {
167
+ const token = event.headers?.authorization;
168
+ return await verifyToken(token);
169
+ },
170
+ ],
171
+ });
172
+ ```
173
+
174
+ ## Event Handling
175
+
176
+ The adapter uses `getMessages()` from `@jaypie/aws` to extract messages from various event types:
177
+
178
+ - **SQS Events**: Extracts message body from each record
179
+ - **SNS Events**: Extracts message from SNS notification
180
+ - **Direct Invocation**: Uses event body directly
181
+
182
+ ### Single vs Multiple Messages
183
+
184
+ ```typescript
185
+ // Single message returns single response
186
+ const result = await lambdaHandler(singleMessageEvent);
187
+ // result: { orderId: "123", status: "processed" }
188
+
189
+ // Multiple messages return array of responses
190
+ const results = await lambdaHandler(sqsBatchEvent);
191
+ // results: [
192
+ // { orderId: "123", status: "processed" },
193
+ // { orderId: "456", status: "processed" },
194
+ // ]
195
+ ```
196
+
197
+ ## Complete Example
198
+
199
+ ```typescript
200
+ import { serviceHandler } from "@jaypie/vocabulary";
201
+ import { lambdaServiceHandler } from "@jaypie/vocabulary/lambda";
202
+ import log from "@jaypie/logger";
203
+
204
+ const processOrderHandler = serviceHandler({
205
+ alias: "processOrder",
206
+ description: "Process an incoming order",
207
+ input: {
208
+ orderId: { type: String, description: "Order ID to process" },
209
+ priority: { type: [1, 2, 3], default: 2 },
210
+ rush: { type: Boolean, default: false },
211
+ },
212
+ service: async ({ orderId, priority, rush }, context) => {
213
+ context?.sendMessage?.({ content: `Processing order ${orderId}` });
214
+
215
+ if (rush) {
216
+ context?.sendMessage?.({ content: "Rush order - expediting", level: "warn" });
217
+ }
218
+
219
+ // Process the order...
220
+ await processOrder(orderId, { priority, rush });
221
+
222
+ context?.sendMessage?.({ content: "Order processed successfully" });
223
+ return { orderId, status: "complete", priority, rush };
224
+ },
225
+ });
226
+
227
+ export const handler = lambdaServiceHandler({
228
+ handler: processOrderHandler,
229
+ name: "order-processor",
230
+ secrets: ["DATABASE_URL", "STRIPE_API_KEY"],
231
+ setup: [
232
+ async () => {
233
+ await connectToDatabase();
234
+ },
235
+ ],
236
+ teardown: [
237
+ async () => {
238
+ await flushMetrics();
239
+ },
240
+ ],
241
+ onMessage: (msg) => {
242
+ log[msg.level || "info"](msg.content);
243
+ },
244
+ });
245
+ ```
246
+
247
+ ## Error Handling
248
+
249
+ ### Default Behavior
250
+
251
+ Errors are caught and returned as structured error responses:
252
+
253
+ ```typescript
254
+ // On error, returns:
255
+ {
256
+ error: true,
257
+ message: "Error message",
258
+ statusCode: 500,
259
+ // ... additional error details
260
+ }
261
+ ```
262
+
263
+ ### Re-throwing Errors
264
+
265
+ Set `throw: true` to re-throw errors (useful for SQS retry behavior):
266
+
267
+ ```typescript
268
+ export const lambdaHandler = lambdaServiceHandler({
269
+ handler,
270
+ throw: true, // Errors will propagate, triggering SQS retry
271
+ });
272
+ ```
273
+
274
+ ## TypeScript Types
275
+
276
+ ```typescript
277
+ import type {
278
+ LambdaContext,
279
+ LambdaServiceHandlerConfig,
280
+ LambdaServiceHandlerOptions,
281
+ LambdaServiceHandlerResult,
282
+ OnCompleteCallback,
283
+ OnErrorCallback,
284
+ OnFatalCallback,
285
+ OnMessageCallback,
286
+ } from "@jaypie/vocabulary/lambda";
287
+ ```
288
+
289
+ ## Exports
290
+
291
+ ```typescript
292
+ // @jaypie/vocabulary/lambda
293
+ export { lambdaServiceHandler } from "./lambdaServiceHandler.js";
294
+
295
+ export type {
296
+ LambdaContext,
297
+ LambdaServiceHandlerConfig,
298
+ LambdaServiceHandlerOptions,
299
+ LambdaServiceHandlerResult,
300
+ OnCompleteCallback,
301
+ OnErrorCallback,
302
+ OnFatalCallback,
303
+ OnMessageCallback,
304
+ } from "./types.js";
305
+ ```
306
+
307
+ ## Related
308
+
309
+ - [Jaypie_Vocabulary_Package.md](Jaypie_Vocabulary_Package.md) - Core serviceHandler and type coercion
310
+ - [Jaypie_Init_Lambda_Package.md](Jaypie_Init_Lambda_Package.md) - Setting up Lambda projects