@jaypie/mcp 0.1.9 → 0.1.10
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jaypie/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Jaypie MCP",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@modelcontextprotocol/sdk": "^1.17.0",
|
|
38
38
|
"commander": "^14.0.0",
|
|
39
39
|
"gray-matter": "^4.0.3",
|
|
40
|
-
"zod": "^
|
|
40
|
+
"zod": "^4.1.13"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/express": "^5.0.3",
|
|
@@ -46,5 +46,5 @@
|
|
|
46
46
|
"publishConfig": {
|
|
47
47
|
"access": "public"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "45fe048c6687a807ed52bbc0b25963f45450f9c9"
|
|
50
50
|
}
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Complete guide to using Jaypie Express features including expressHandler, CORS, lifecycle hooks, and pre-built routes
|
|
3
|
+
globs: packages/express/**
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Jaypie Express Package
|
|
7
|
+
|
|
8
|
+
Jaypie provides Express utilities through `@jaypie/express` (also available via `jaypie`). The primary export is `expressHandler`, a wrapper that adds error handling, logging, lifecycle hooks, and response formatting to Express route handlers.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install jaypie
|
|
14
|
+
# or
|
|
15
|
+
npm install @jaypie/express
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## expressHandler
|
|
19
|
+
|
|
20
|
+
The core of Jaypie Express. Wraps route handlers with error handling, logging, and lifecycle management.
|
|
21
|
+
|
|
22
|
+
### Basic Usage
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { expressHandler } from "jaypie";
|
|
26
|
+
import type { Request, Response } from "express";
|
|
27
|
+
|
|
28
|
+
const myRoute = expressHandler(async (req: Request, res: Response) => {
|
|
29
|
+
return { message: "Hello, World!" };
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Use in Express
|
|
33
|
+
app.get("/hello", myRoute);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Return Value Handling
|
|
37
|
+
|
|
38
|
+
expressHandler automatically formats responses:
|
|
39
|
+
|
|
40
|
+
| Return Value | HTTP Status | Response |
|
|
41
|
+
|--------------|-------------|----------|
|
|
42
|
+
| Object | 200 | JSON body |
|
|
43
|
+
| Array | 200 | JSON body |
|
|
44
|
+
| String (JSON) | 200 | Parsed JSON |
|
|
45
|
+
| String (other) | 200 | Text body |
|
|
46
|
+
| `true` | 201 Created | Empty |
|
|
47
|
+
| `null`, `undefined`, `false` | 204 No Content | Empty |
|
|
48
|
+
| Object with `.json()` method | 200 | Result of `.json()` |
|
|
49
|
+
|
|
50
|
+
### Options
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { expressHandler } from "jaypie";
|
|
54
|
+
import type { ExpressHandlerOptions } from "jaypie";
|
|
55
|
+
|
|
56
|
+
const options: ExpressHandlerOptions = {
|
|
57
|
+
name: "myHandler", // Handler name for logging
|
|
58
|
+
chaos: "low", // Chaos testing level
|
|
59
|
+
unavailable: false, // Return 503 if true
|
|
60
|
+
setup: [], // Setup function(s)
|
|
61
|
+
teardown: [], // Teardown function(s)
|
|
62
|
+
validate: [], // Validation function(s)
|
|
63
|
+
locals: {}, // Values to set on req.locals
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const handler = expressHandler(async (req, res) => {
|
|
67
|
+
return { success: true };
|
|
68
|
+
}, options);
|
|
69
|
+
|
|
70
|
+
// Alternative: options first
|
|
71
|
+
const handler2 = expressHandler(options, async (req, res) => {
|
|
72
|
+
return { success: true };
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Lifecycle Hooks
|
|
77
|
+
|
|
78
|
+
### Setup Functions
|
|
79
|
+
|
|
80
|
+
Run before the main handler. Use for initialization, authentication checks, or setting up request context.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import { expressHandler } from "jaypie";
|
|
84
|
+
import type { JaypieHandlerSetup } from "jaypie";
|
|
85
|
+
|
|
86
|
+
const authenticateUser: JaypieHandlerSetup = async (req, res) => {
|
|
87
|
+
const token = req.headers.authorization;
|
|
88
|
+
// Validate token, throw UnauthorizedError if invalid
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const loadTenant: JaypieHandlerSetup = async (req, res) => {
|
|
92
|
+
req.locals.tenant = await getTenant(req.params.tenantId);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const handler = expressHandler(
|
|
96
|
+
async (req, res) => {
|
|
97
|
+
// req.locals.tenant is available here
|
|
98
|
+
return { tenant: req.locals.tenant };
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
setup: [authenticateUser, loadTenant],
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Teardown Functions
|
|
107
|
+
|
|
108
|
+
Run after the main handler completes (success or error). Use for cleanup.
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import type { JaypieHandlerTeardown } from "jaypie";
|
|
112
|
+
|
|
113
|
+
const closeConnection: JaypieHandlerTeardown = async (req, res) => {
|
|
114
|
+
await req.locals.dbConnection?.close();
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const handler = expressHandler(
|
|
118
|
+
async (req, res) => {
|
|
119
|
+
req.locals.dbConnection = await openConnection();
|
|
120
|
+
return await doWork(req.locals.dbConnection);
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
teardown: closeConnection,
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Validation Functions
|
|
129
|
+
|
|
130
|
+
Run before the main handler. Return `true` to continue or `false`/throw to reject.
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { ForbiddenError } from "jaypie";
|
|
134
|
+
import type { JaypieHandlerValidate } from "jaypie";
|
|
135
|
+
|
|
136
|
+
const requireAdmin: JaypieHandlerValidate = (req, res) => {
|
|
137
|
+
if (!req.locals.user?.isAdmin) {
|
|
138
|
+
throw new ForbiddenError();
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const validateBody: JaypieHandlerValidate = (req, res) => {
|
|
144
|
+
return req.body?.email && req.body?.name;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const handler = expressHandler(
|
|
148
|
+
async (req, res) => {
|
|
149
|
+
// Only runs if user is admin and body is valid
|
|
150
|
+
return { success: true };
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
validate: [requireAdmin, validateBody],
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Locals
|
|
159
|
+
|
|
160
|
+
Set values on `req.locals`. Values can be static or functions that receive `(req, res)`.
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import type { ExpressHandlerLocals } from "jaypie";
|
|
164
|
+
|
|
165
|
+
const getUser: ExpressHandlerLocals = async (req, res) => {
|
|
166
|
+
return await User.findById(req.params.userId);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const handler = expressHandler(
|
|
170
|
+
async (req, res) => {
|
|
171
|
+
// Access via req.locals
|
|
172
|
+
console.log(req.locals.apiVersion); // "v1"
|
|
173
|
+
console.log(req.locals.user); // User object
|
|
174
|
+
return req.locals.user;
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
locals: {
|
|
178
|
+
apiVersion: "v1", // Static value
|
|
179
|
+
user: getUser, // Function called during setup
|
|
180
|
+
},
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## CORS Helper
|
|
186
|
+
|
|
187
|
+
Configure CORS middleware with Jaypie conventions.
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
import { cors } from "jaypie";
|
|
191
|
+
import type { CorsConfig } from "jaypie";
|
|
192
|
+
|
|
193
|
+
// Default: uses BASE_URL or PROJECT_BASE_URL env vars
|
|
194
|
+
app.use(cors());
|
|
195
|
+
|
|
196
|
+
// Wildcard origin
|
|
197
|
+
app.use(cors({ origin: "*" }));
|
|
198
|
+
|
|
199
|
+
// Specific origin
|
|
200
|
+
app.use(cors({ origin: "https://example.com" }));
|
|
201
|
+
|
|
202
|
+
// Custom configuration
|
|
203
|
+
const corsConfig: CorsConfig = {
|
|
204
|
+
origin: "https://api.example.com",
|
|
205
|
+
// Additional cors options
|
|
206
|
+
};
|
|
207
|
+
app.use(cors(corsConfig));
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Environment variables:
|
|
211
|
+
- `BASE_URL` or `PROJECT_BASE_URL`: Default allowed origin
|
|
212
|
+
- `PROJECT_ENV=sandbox` or `PROJECT_SANDBOX_MODE=true`: Allows localhost
|
|
213
|
+
|
|
214
|
+
## Pre-built Routes
|
|
215
|
+
|
|
216
|
+
Ready-to-use route handlers for common responses.
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
import {
|
|
220
|
+
badRequestRoute, // 400 Bad Request
|
|
221
|
+
echoRoute, // 200 with request echo
|
|
222
|
+
forbiddenRoute, // 403 Forbidden
|
|
223
|
+
goneRoute, // 410 Gone
|
|
224
|
+
methodNotAllowedRoute, // 405 Method Not Allowed
|
|
225
|
+
noContentRoute, // 204 No Content
|
|
226
|
+
notFoundRoute, // 404 Not Found
|
|
227
|
+
notImplementedRoute, // 501 Not Implemented
|
|
228
|
+
} from "jaypie";
|
|
229
|
+
|
|
230
|
+
// Use as catch-all or placeholder routes
|
|
231
|
+
app.all("/deprecated/*", goneRoute);
|
|
232
|
+
app.use("*", notFoundRoute);
|
|
233
|
+
|
|
234
|
+
// Echo route for debugging
|
|
235
|
+
app.get("/debug/echo", echoRoute);
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## HTTP Code Handler
|
|
239
|
+
|
|
240
|
+
Create custom HTTP status code handlers.
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
import { expressHttpCodeHandler, HTTP } from "jaypie";
|
|
244
|
+
|
|
245
|
+
// Returns 200 OK with empty body
|
|
246
|
+
const okRoute = expressHttpCodeHandler(HTTP.CODE.OK);
|
|
247
|
+
|
|
248
|
+
// Returns 202 Accepted
|
|
249
|
+
const acceptedRoute = expressHttpCodeHandler(202, { name: "accepted" });
|
|
250
|
+
|
|
251
|
+
app.post("/jobs", acceptedRoute);
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Error Handling
|
|
255
|
+
|
|
256
|
+
Throw Jaypie errors for proper HTTP responses.
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
import {
|
|
260
|
+
expressHandler,
|
|
261
|
+
BadRequestError,
|
|
262
|
+
NotFoundError,
|
|
263
|
+
UnauthorizedError,
|
|
264
|
+
ForbiddenError,
|
|
265
|
+
InternalError,
|
|
266
|
+
log,
|
|
267
|
+
} from "jaypie";
|
|
268
|
+
|
|
269
|
+
const handler = expressHandler(async (req, res) => {
|
|
270
|
+
const item = await findItem(req.params.id);
|
|
271
|
+
|
|
272
|
+
if (!item) {
|
|
273
|
+
log.warn("Item not found");
|
|
274
|
+
throw new NotFoundError();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (!canAccess(req.user, item)) {
|
|
278
|
+
throw new ForbiddenError();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return item;
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Errors return JSON:API compliant error responses:
|
|
286
|
+
|
|
287
|
+
```json
|
|
288
|
+
{
|
|
289
|
+
"errors": [{
|
|
290
|
+
"status": 404,
|
|
291
|
+
"title": "Not Found",
|
|
292
|
+
"detail": "The requested resource was not found"
|
|
293
|
+
}]
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## TypeScript Types
|
|
298
|
+
|
|
299
|
+
All lifecycle function types are exported for type safety:
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
import type {
|
|
303
|
+
ExpressHandlerOptions,
|
|
304
|
+
ExpressHandlerLocals,
|
|
305
|
+
JaypieHandlerSetup,
|
|
306
|
+
JaypieHandlerTeardown,
|
|
307
|
+
JaypieHandlerValidate,
|
|
308
|
+
CorsConfig,
|
|
309
|
+
} from "jaypie";
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Complete Example
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
import express from "express";
|
|
316
|
+
import {
|
|
317
|
+
cors,
|
|
318
|
+
expressHandler,
|
|
319
|
+
notFoundRoute,
|
|
320
|
+
NotFoundError,
|
|
321
|
+
ForbiddenError,
|
|
322
|
+
log,
|
|
323
|
+
} from "jaypie";
|
|
324
|
+
import type {
|
|
325
|
+
JaypieHandlerSetup,
|
|
326
|
+
JaypieHandlerValidate,
|
|
327
|
+
ExpressHandlerLocals,
|
|
328
|
+
} from "jaypie";
|
|
329
|
+
|
|
330
|
+
const app = express();
|
|
331
|
+
app.use(express.json());
|
|
332
|
+
app.use(cors());
|
|
333
|
+
|
|
334
|
+
// Lifecycle functions
|
|
335
|
+
const authenticate: JaypieHandlerSetup = async (req, res) => {
|
|
336
|
+
const token = req.headers.authorization?.replace("Bearer ", "");
|
|
337
|
+
if (!token) throw new UnauthorizedError();
|
|
338
|
+
req.locals.user = await verifyToken(token);
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const requireOwner: JaypieHandlerValidate = (req, res) => {
|
|
342
|
+
return req.locals.resource?.ownerId === req.locals.user?.id;
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const loadResource: ExpressHandlerLocals = async (req, res) => {
|
|
346
|
+
const resource = await Resource.findById(req.params.id);
|
|
347
|
+
if (!resource) throw new NotFoundError();
|
|
348
|
+
return resource;
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// Route handler
|
|
352
|
+
const updateResource = expressHandler(
|
|
353
|
+
async (req, res) => {
|
|
354
|
+
const { resource, user } = req.locals;
|
|
355
|
+
|
|
356
|
+
resource.name = req.body.name;
|
|
357
|
+
resource.updatedBy = user.id;
|
|
358
|
+
await resource.save();
|
|
359
|
+
|
|
360
|
+
log.trace("Resource updated");
|
|
361
|
+
return resource;
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
name: "updateResource",
|
|
365
|
+
setup: authenticate,
|
|
366
|
+
validate: requireOwner,
|
|
367
|
+
locals: {
|
|
368
|
+
resource: loadResource,
|
|
369
|
+
},
|
|
370
|
+
}
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
app.put("/resources/:id", updateResource);
|
|
374
|
+
app.use("*", notFoundRoute);
|
|
375
|
+
|
|
376
|
+
export default app;
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## Response Headers
|
|
380
|
+
|
|
381
|
+
expressHandler automatically sets:
|
|
382
|
+
- `X-Powered-By: @jaypie/express`
|
|
383
|
+
- `X-Project-Handler: {name}` (if name option provided)
|
|
384
|
+
- `X-Project-Invocation: {uuid}` (request tracking ID)
|
|
385
|
+
|
|
386
|
+
## Datadog Integration
|
|
387
|
+
|
|
388
|
+
When Datadog environment variables are configured, expressHandler automatically submits metrics for each request including status code and path.
|