@jaypie/mcp 0.2.3 → 0.2.5
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/index.js +1 -1
- package/package.json +1 -1
- package/prompts/Jaypie_CDK_Constructs_and_Patterns.md +51 -0
- package/prompts/Jaypie_DynamoDB_Package.md +662 -0
- package/prompts/Jaypie_Express_Package.md +91 -0
- package/prompts/Jaypie_Init_Lambda_Package.md +134 -1
- package/prompts/Jaypie_Llm_Calls.md +77 -0
- package/prompts/Jaypie_Llm_Tools.md +26 -3
- package/prompts/Jaypie_Vocabulary_Commander.md +411 -0
- package/prompts/Jaypie_Vocabulary_LLM.md +312 -0
- package/prompts/Jaypie_Vocabulary_Lambda.md +310 -0
- package/prompts/Jaypie_Vocabulary_MCP.md +296 -0
- package/prompts/Jaypie_Vocabulary_Package.md +141 -183
|
@@ -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
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: MCP server tool registration from serviceHandler
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Jaypie Vocabulary MCP Adapter
|
|
6
|
+
|
|
7
|
+
The MCP adapter (`@jaypie/vocabulary/mcp`) registers Jaypie service handlers as MCP (Model Context Protocol) tools for use with MCP servers.
|
|
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 @modelcontextprotocol/sdk
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
21
|
+
import { serviceHandler } from "@jaypie/vocabulary";
|
|
22
|
+
import { registerMcpTool } from "@jaypie/vocabulary/mcp";
|
|
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 server = new McpServer({ name: "my-server", version: "1.0.0" });
|
|
38
|
+
registerMcpTool({ handler, server });
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## registerMcpTool
|
|
42
|
+
|
|
43
|
+
Registers a serviceHandler as an MCP tool.
|
|
44
|
+
|
|
45
|
+
### Options
|
|
46
|
+
|
|
47
|
+
| Option | Type | Description |
|
|
48
|
+
|--------|------|-------------|
|
|
49
|
+
| `handler` | `ServiceHandlerFunction` | Required. The service handler to adapt |
|
|
50
|
+
| `server` | `McpServer` | Required. The MCP server to register with |
|
|
51
|
+
| `name` | `string` | Override tool name (defaults to handler.alias) |
|
|
52
|
+
| `description` | `string` | Override tool description (defaults to handler.description) |
|
|
53
|
+
| `onComplete` | `OnCompleteCallback` | Called with tool's return value on success |
|
|
54
|
+
| `onError` | `OnErrorCallback` | Receives errors reported via `context.onError()` in service |
|
|
55
|
+
| `onFatal` | `OnFatalCallback` | Receives fatal errors (thrown or via `context.onFatal()`) |
|
|
56
|
+
| `onMessage` | `OnMessageCallback` | Receives messages from `context.sendMessage` in service |
|
|
57
|
+
|
|
58
|
+
### Basic Usage
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
62
|
+
import { serviceHandler } from "@jaypie/vocabulary";
|
|
63
|
+
import { registerMcpTool } from "@jaypie/vocabulary/mcp";
|
|
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 server = new McpServer({ name: "calculator", version: "1.0.0" });
|
|
84
|
+
registerMcpTool({ handler: calculateHandler, server });
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Overriding Name and Description
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
registerMcpTool({
|
|
91
|
+
handler,
|
|
92
|
+
server,
|
|
93
|
+
name: "math_calculate",
|
|
94
|
+
description: "A tool for performing basic math operations",
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Lifecycle Callbacks
|
|
99
|
+
|
|
100
|
+
Services can use context callbacks to report progress, errors, and completion:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
const handler = serviceHandler({
|
|
104
|
+
alias: "evaluate",
|
|
105
|
+
input: { jobId: { type: String } },
|
|
106
|
+
service: async ({ jobId }, context) => {
|
|
107
|
+
context?.sendMessage?.({ content: `Processing ${jobId}` });
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
await riskyOperation();
|
|
111
|
+
} catch (err) {
|
|
112
|
+
context?.onError?.(err); // Reports error but continues
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { jobId, status: "complete" };
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
registerMcpTool({
|
|
120
|
+
handler,
|
|
121
|
+
server,
|
|
122
|
+
onComplete: (result) => console.log("Tool completed:", result),
|
|
123
|
+
onError: (error) => console.warn("Recoverable error:", error),
|
|
124
|
+
onFatal: (error) => console.error("Fatal error:", error),
|
|
125
|
+
onMessage: (msg) => console.log(`[${msg.level || "info"}] ${msg.content}`),
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**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.
|
|
130
|
+
|
|
131
|
+
## Response Format
|
|
132
|
+
|
|
133
|
+
The adapter formats handler responses as MCP text content:
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
// Handler returns:
|
|
137
|
+
{ result: 42, status: "complete" }
|
|
138
|
+
|
|
139
|
+
// MCP response:
|
|
140
|
+
{
|
|
141
|
+
content: [{
|
|
142
|
+
type: "text",
|
|
143
|
+
text: '{"result":42,"status":"complete"}'
|
|
144
|
+
}]
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
For string responses:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// Handler returns:
|
|
152
|
+
"Hello, World!"
|
|
153
|
+
|
|
154
|
+
// MCP response:
|
|
155
|
+
{
|
|
156
|
+
content: [{
|
|
157
|
+
type: "text",
|
|
158
|
+
text: "Hello, World!"
|
|
159
|
+
}]
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Complete Example
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
167
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
168
|
+
import { serviceHandler } from "@jaypie/vocabulary";
|
|
169
|
+
import { registerMcpTool } from "@jaypie/vocabulary/mcp";
|
|
170
|
+
|
|
171
|
+
// Define handlers
|
|
172
|
+
const weatherHandler = serviceHandler({
|
|
173
|
+
alias: "get_weather",
|
|
174
|
+
description: "Get current weather for a location",
|
|
175
|
+
input: {
|
|
176
|
+
location: { type: String, description: "City name" },
|
|
177
|
+
units: { type: ["celsius", "fahrenheit"], default: "celsius" },
|
|
178
|
+
},
|
|
179
|
+
service: async ({ location, units }) => {
|
|
180
|
+
const weather = await fetchWeather(location);
|
|
181
|
+
return {
|
|
182
|
+
location,
|
|
183
|
+
temperature: units === "celsius" ? weather.tempC : weather.tempF,
|
|
184
|
+
units,
|
|
185
|
+
conditions: weather.conditions,
|
|
186
|
+
};
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const searchHandler = serviceHandler({
|
|
191
|
+
alias: "search",
|
|
192
|
+
description: "Search for information",
|
|
193
|
+
input: {
|
|
194
|
+
query: { type: String, description: "Search query" },
|
|
195
|
+
limit: { type: Number, default: 10 },
|
|
196
|
+
},
|
|
197
|
+
service: async ({ query, limit }) => {
|
|
198
|
+
return await performSearch(query, { limit });
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Create server and register tools
|
|
203
|
+
const server = new McpServer({
|
|
204
|
+
name: "my-mcp-server",
|
|
205
|
+
version: "1.0.0",
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
registerMcpTool({ handler: weatherHandler, server });
|
|
209
|
+
registerMcpTool({ handler: searchHandler, server });
|
|
210
|
+
|
|
211
|
+
// Start server
|
|
212
|
+
const transport = new StdioServerTransport();
|
|
213
|
+
await server.connect(transport);
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Input Validation
|
|
217
|
+
|
|
218
|
+
The adapter delegates input validation to the service handler. Invalid inputs result in errors:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
// If handler expects:
|
|
222
|
+
input: {
|
|
223
|
+
priority: { type: [1, 2, 3, 4, 5] },
|
|
224
|
+
email: { type: /^[^@]+@[^@]+\.[^@]+$/ },
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Invalid calls throw BadRequestError:
|
|
228
|
+
// { priority: 10 } → Validation fails
|
|
229
|
+
// { email: "invalid" } → Pattern mismatch
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## TypeScript Types
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
import type {
|
|
236
|
+
McpToolContentItem,
|
|
237
|
+
McpToolResponse,
|
|
238
|
+
OnCompleteCallback,
|
|
239
|
+
OnErrorCallback,
|
|
240
|
+
OnFatalCallback,
|
|
241
|
+
OnMessageCallback,
|
|
242
|
+
RegisterMcpToolConfig,
|
|
243
|
+
RegisterMcpToolResult,
|
|
244
|
+
} from "@jaypie/vocabulary/mcp";
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Type Definitions
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
interface McpToolContentItem {
|
|
251
|
+
type: "text";
|
|
252
|
+
text: string;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
interface McpToolResponse {
|
|
256
|
+
content: McpToolContentItem[];
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
interface RegisterMcpToolConfig {
|
|
260
|
+
handler: ServiceHandlerFunction;
|
|
261
|
+
server: McpServer;
|
|
262
|
+
name?: string;
|
|
263
|
+
description?: string;
|
|
264
|
+
onComplete?: OnCompleteCallback;
|
|
265
|
+
onError?: OnErrorCallback;
|
|
266
|
+
onFatal?: OnFatalCallback;
|
|
267
|
+
onMessage?: OnMessageCallback;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
interface RegisterMcpToolResult {
|
|
271
|
+
name: string;
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Exports
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
// @jaypie/vocabulary/mcp
|
|
279
|
+
export { registerMcpTool } from "./registerMcpTool.js";
|
|
280
|
+
|
|
281
|
+
export type {
|
|
282
|
+
McpToolContentItem,
|
|
283
|
+
McpToolResponse,
|
|
284
|
+
OnCompleteCallback,
|
|
285
|
+
OnErrorCallback,
|
|
286
|
+
OnFatalCallback,
|
|
287
|
+
OnMessageCallback,
|
|
288
|
+
RegisterMcpToolConfig,
|
|
289
|
+
RegisterMcpToolResult,
|
|
290
|
+
} from "./types.js";
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## Related
|
|
294
|
+
|
|
295
|
+
- [Jaypie_Vocabulary_Package.md](Jaypie_Vocabulary_Package.md) - Core serviceHandler and type coercion
|
|
296
|
+
- [Jaypie_Vocabulary_LLM.md](Jaypie_Vocabulary_LLM.md) - LLM tool creation (similar pattern)
|