nitrostack 1.0.0 → 1.0.1
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/CHANGELOG.md +15 -0
- package/package.json +1 -1
- package/templates/typescript-auth/.env.example +23 -0
- package/templates/typescript-auth/src/app.module.ts +103 -0
- package/templates/typescript-auth/src/db/database.ts +163 -0
- package/templates/typescript-auth/src/db/seed.ts +374 -0
- package/templates/typescript-auth/src/db/setup.ts +87 -0
- package/templates/typescript-auth/src/events/analytics.service.ts +52 -0
- package/templates/typescript-auth/src/events/notification.service.ts +40 -0
- package/templates/typescript-auth/src/filters/global-exception.filter.ts +28 -0
- package/templates/typescript-auth/src/guards/README.md +75 -0
- package/templates/typescript-auth/src/guards/jwt.guard.ts +105 -0
- package/templates/typescript-auth/src/health/database.health.ts +41 -0
- package/templates/typescript-auth/src/index.ts +26 -0
- package/templates/typescript-auth/src/interceptors/transform.interceptor.ts +24 -0
- package/templates/typescript-auth/src/middleware/logging.middleware.ts +42 -0
- package/templates/typescript-auth/src/modules/addresses/addresses.module.ts +16 -0
- package/templates/typescript-auth/src/modules/addresses/addresses.prompts.ts +114 -0
- package/templates/typescript-auth/src/modules/addresses/addresses.resources.ts +40 -0
- package/templates/typescript-auth/src/modules/addresses/addresses.tools.ts +241 -0
- package/templates/typescript-auth/src/modules/auth/auth.module.ts +16 -0
- package/templates/typescript-auth/src/modules/auth/auth.prompts.ts +147 -0
- package/templates/typescript-auth/src/modules/auth/auth.resources.ts +84 -0
- package/templates/typescript-auth/src/modules/auth/auth.tools.ts +139 -0
- package/templates/typescript-auth/src/modules/cart/cart.module.ts +16 -0
- package/templates/typescript-auth/src/modules/cart/cart.prompts.ts +95 -0
- package/templates/typescript-auth/src/modules/cart/cart.resources.ts +44 -0
- package/templates/typescript-auth/src/modules/cart/cart.tools.ts +281 -0
- package/templates/typescript-auth/src/modules/orders/orders.module.ts +16 -0
- package/templates/typescript-auth/src/modules/orders/orders.prompts.ts +88 -0
- package/templates/typescript-auth/src/modules/orders/orders.resources.ts +48 -0
- package/templates/typescript-auth/src/modules/orders/orders.tools.ts +281 -0
- package/templates/typescript-auth/src/modules/products/products.module.ts +16 -0
- package/templates/typescript-auth/src/modules/products/products.prompts.ts +146 -0
- package/templates/typescript-auth/src/modules/products/products.resources.ts +98 -0
- package/templates/typescript-auth/src/modules/products/products.tools.ts +266 -0
- package/templates/typescript-auth/src/pipes/validation.pipe.ts +42 -0
- package/templates/typescript-auth/src/services/database.service.ts +90 -0
- package/templates/typescript-auth/src/widgets/app/add-to-cart/page.tsx +122 -0
- package/templates/typescript-auth/src/widgets/app/address-added/page.tsx +116 -0
- package/templates/typescript-auth/src/widgets/app/address-deleted/page.tsx +105 -0
- package/templates/typescript-auth/src/widgets/app/address-list/page.tsx +139 -0
- package/templates/typescript-auth/src/widgets/app/address-updated/page.tsx +153 -0
- package/templates/typescript-auth/src/widgets/app/cart-cleared/page.tsx +86 -0
- package/templates/typescript-auth/src/widgets/app/cart-updated/page.tsx +116 -0
- package/templates/typescript-auth/src/widgets/app/categories/page.tsx +134 -0
- package/templates/typescript-auth/src/widgets/app/layout.tsx +21 -0
- package/templates/typescript-auth/src/widgets/app/login-result/page.tsx +129 -0
- package/templates/typescript-auth/src/widgets/app/order-confirmation/page.tsx +206 -0
- package/templates/typescript-auth/src/widgets/app/order-details/page.tsx +225 -0
- package/templates/typescript-auth/src/widgets/app/order-history/page.tsx +218 -0
- package/templates/typescript-auth/src/widgets/app/product-card/page.tsx +121 -0
- package/templates/typescript-auth/src/widgets/app/products-grid/page.tsx +173 -0
- package/templates/typescript-auth/src/widgets/app/shopping-cart/page.tsx +187 -0
- package/templates/typescript-auth/src/widgets/app/whoami/page.tsx +165 -0
- package/templates/typescript-auth/src/widgets/next.config.js +38 -0
- package/templates/typescript-auth/src/widgets/package.json +18 -0
- package/templates/typescript-auth/src/widgets/styles/ecommerce.ts +169 -0
- package/templates/typescript-auth/src/widgets/tsconfig.json +28 -0
- package/templates/typescript-auth/src/widgets/types/tool-data.ts +141 -0
- package/templates/typescript-auth/src/widgets/widget-manifest.json +464 -0
- package/templates/typescript-auth/tsconfig.json +27 -0
- package/templates/typescript-auth-api-key/.env +15 -0
- package/templates/typescript-auth-api-key/.env.example +4 -0
- package/templates/typescript-auth-api-key/src/app.module.ts +38 -0
- package/templates/typescript-auth-api-key/src/guards/apikey.guard.ts +47 -0
- package/templates/typescript-auth-api-key/src/guards/multi-auth.guard.ts +157 -0
- package/templates/typescript-auth-api-key/src/health/system.health.ts +55 -0
- package/templates/typescript-auth-api-key/src/index.ts +47 -0
- package/templates/typescript-auth-api-key/src/modules/calculator/calculator.module.ts +12 -0
- package/templates/typescript-auth-api-key/src/modules/calculator/calculator.prompts.ts +73 -0
- package/templates/typescript-auth-api-key/src/modules/calculator/calculator.resources.ts +60 -0
- package/templates/typescript-auth-api-key/src/modules/calculator/calculator.tools.ts +71 -0
- package/templates/typescript-auth-api-key/src/modules/demo/demo.module.ts +18 -0
- package/templates/typescript-auth-api-key/src/modules/demo/demo.tools.ts +155 -0
- package/templates/typescript-auth-api-key/src/modules/demo/multi-auth.tools.ts +123 -0
- package/templates/typescript-auth-api-key/src/widgets/app/calculator-operations/page.tsx +133 -0
- package/templates/typescript-auth-api-key/src/widgets/app/calculator-result/page.tsx +134 -0
- package/templates/typescript-auth-api-key/src/widgets/app/layout.tsx +14 -0
- package/templates/typescript-auth-api-key/src/widgets/next.config.js +37 -0
- package/templates/typescript-auth-api-key/src/widgets/package.json +24 -0
- package/templates/typescript-auth-api-key/src/widgets/tsconfig.json +28 -0
- package/templates/typescript-auth-api-key/src/widgets/widget-manifest.json +48 -0
- package/templates/typescript-auth-api-key/tsconfig.json +23 -0
- package/templates/typescript-oauth/.env.example +91 -0
- package/templates/typescript-oauth/src/app.module.ts +89 -0
- package/templates/typescript-oauth/src/guards/oauth.guard.ts +127 -0
- package/templates/typescript-oauth/src/index.ts +74 -0
- package/templates/typescript-oauth/src/modules/demo/demo.module.ts +16 -0
- package/templates/typescript-oauth/src/modules/demo/demo.tools.ts +190 -0
- package/templates/typescript-oauth/src/widgets/app/calculator-operations/page.tsx +133 -0
- package/templates/typescript-oauth/src/widgets/app/calculator-result/page.tsx +134 -0
- package/templates/typescript-oauth/src/widgets/app/layout.tsx +14 -0
- package/templates/typescript-oauth/src/widgets/next.config.js +37 -0
- package/templates/typescript-oauth/src/widgets/package.json +24 -0
- package/templates/typescript-oauth/src/widgets/tsconfig.json +28 -0
- package/templates/typescript-oauth/src/widgets/widget-manifest.json +48 -0
- package/templates/typescript-oauth/tsconfig.json +23 -0
- package/templates/typescript-starter/.env.example +4 -0
- package/templates/typescript-starter/src/app.module.ts +34 -0
- package/templates/typescript-starter/src/health/system.health.ts +55 -0
- package/templates/typescript-starter/src/index.ts +27 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.module.ts +12 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.prompts.ts +73 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.resources.ts +60 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.tools.ts +71 -0
- package/templates/typescript-starter/src/widgets/app/calculator-operations/page.tsx +133 -0
- package/templates/typescript-starter/src/widgets/app/calculator-result/page.tsx +134 -0
- package/templates/typescript-starter/src/widgets/app/layout.tsx +14 -0
- package/templates/typescript-starter/src/widgets/next.config.js +37 -0
- package/templates/typescript-starter/src/widgets/package.json +24 -0
- package/templates/typescript-starter/src/widgets/tsconfig.json +28 -0
- package/templates/typescript-starter/src/widgets/widget-manifest.json +48 -0
- package/templates/typescript-starter/tsconfig.json +23 -0
- package/LICENSE_URLS_UPDATE_COMPLETE.md +0 -388
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as dotenv from 'dotenv';
|
|
2
|
+
import { initializeSchema, clearAllData } from './database.js';
|
|
3
|
+
import { seedAll } from './seed.js';
|
|
4
|
+
import crypto from 'crypto';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Database setup script
|
|
10
|
+
* Run with: npm run setup-db
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// Load environment variables
|
|
14
|
+
dotenv.config();
|
|
15
|
+
|
|
16
|
+
console.log('╔══════════════════════════════════════════════════════════════╗');
|
|
17
|
+
console.log('║ ║');
|
|
18
|
+
console.log('║ E-COMMERCE MCP SERVER - DATABASE SETUP ║');
|
|
19
|
+
console.log('║ ║');
|
|
20
|
+
console.log('╚══════════════════════════════════════════════════════════════╝\n');
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
// 1. Initialize schema
|
|
24
|
+
console.log('📦 Initializing database schema...');
|
|
25
|
+
initializeSchema();
|
|
26
|
+
console.log('✅ Schema initialized\n');
|
|
27
|
+
|
|
28
|
+
// 2. Clear existing data (optional)
|
|
29
|
+
console.log('🗑️ Clearing existing data...');
|
|
30
|
+
clearAllData();
|
|
31
|
+
console.log('✅ Data cleared\n');
|
|
32
|
+
|
|
33
|
+
// 3. Seed data
|
|
34
|
+
seedAll();
|
|
35
|
+
console.log('');
|
|
36
|
+
|
|
37
|
+
// 4. Generate JWT secret if not exists
|
|
38
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
39
|
+
const envExamplePath = path.join(process.cwd(), '.env.example');
|
|
40
|
+
|
|
41
|
+
if (!fs.existsSync(envPath) && fs.existsSync(envExamplePath)) {
|
|
42
|
+
console.log('📝 Creating .env file...');
|
|
43
|
+
let envContent = fs.readFileSync(envExamplePath, 'utf-8');
|
|
44
|
+
|
|
45
|
+
// Generate JWT secret (64 random bytes encoded as base64url)
|
|
46
|
+
const jwtSecret = 'jwt_secret_' + crypto.randomBytes(32).toString('base64url');
|
|
47
|
+
envContent = envContent.replace('your-jwt-secret-here-change-this', jwtSecret);
|
|
48
|
+
|
|
49
|
+
fs.writeFileSync(envPath, envContent);
|
|
50
|
+
console.log('✅ .env file created with JWT secret\n');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 5. Print test credentials
|
|
54
|
+
console.log('═══════════════════════════════════════════════════════════════');
|
|
55
|
+
console.log(' TEST CREDENTIALS ');
|
|
56
|
+
console.log('═══════════════════════════════════════════════════════════════\n');
|
|
57
|
+
|
|
58
|
+
console.log('🧪 Test Users (use these in the inspector):');
|
|
59
|
+
console.log('');
|
|
60
|
+
console.log(' 👤 Alice Johnson');
|
|
61
|
+
console.log(' Email: alice@example.com');
|
|
62
|
+
console.log(' Password: password123');
|
|
63
|
+
console.log('');
|
|
64
|
+
console.log(' 👤 Bob Smith');
|
|
65
|
+
console.log(' Email: bob@example.com');
|
|
66
|
+
console.log(' Password: password123');
|
|
67
|
+
console.log('');
|
|
68
|
+
console.log(' 👤 Charlie Brown');
|
|
69
|
+
console.log(' Email: charlie@example.com');
|
|
70
|
+
console.log(' Password: password123');
|
|
71
|
+
console.log('');
|
|
72
|
+
|
|
73
|
+
console.log('═══════════════════════════════════════════════════════════════\n');
|
|
74
|
+
|
|
75
|
+
console.log('🎉 Database setup complete!\n');
|
|
76
|
+
console.log('Next steps:');
|
|
77
|
+
console.log(' 1. npm install');
|
|
78
|
+
console.log(' 2. npm run build');
|
|
79
|
+
console.log(' 3. npm run dev');
|
|
80
|
+
console.log(' 4. Test in inspector with the credentials above\n');
|
|
81
|
+
|
|
82
|
+
process.exit(0);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error('❌ Setup failed:', error);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Injectable, OnEvent } from 'nitrostack';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Analytics Service
|
|
5
|
+
*
|
|
6
|
+
* Tracks events for analytics
|
|
7
|
+
*/
|
|
8
|
+
@Injectable()
|
|
9
|
+
export class AnalyticsService {
|
|
10
|
+
private events: any[] = [];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Track order creation
|
|
14
|
+
*/
|
|
15
|
+
@OnEvent('order.created')
|
|
16
|
+
async trackOrderCreated(order: any): Promise<void> {
|
|
17
|
+
this.events.push({
|
|
18
|
+
type: 'order_created',
|
|
19
|
+
timestamp: new Date().toISOString(),
|
|
20
|
+
data: {
|
|
21
|
+
order_id: order.id,
|
|
22
|
+
total: order.total,
|
|
23
|
+
item_count: order.items?.length || 0,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
console.log(`📊 Tracked: Order Created #${order.id}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Track product viewed
|
|
31
|
+
*/
|
|
32
|
+
@OnEvent('product.viewed')
|
|
33
|
+
async trackProductViewed(product: any): Promise<void> {
|
|
34
|
+
this.events.push({
|
|
35
|
+
type: 'product_viewed',
|
|
36
|
+
timestamp: new Date().toISOString(),
|
|
37
|
+
data: {
|
|
38
|
+
product_id: product.id,
|
|
39
|
+
product_name: product.name,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
console.log(`📊 Tracked: Product Viewed - ${product.name}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get analytics data
|
|
47
|
+
*/
|
|
48
|
+
getAnalytics(): any[] {
|
|
49
|
+
return this.events;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Injectable, OnEvent } from 'nitrostack';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Notification Service
|
|
5
|
+
*
|
|
6
|
+
* Listens to events and sends notifications
|
|
7
|
+
*/
|
|
8
|
+
@Injectable()
|
|
9
|
+
export class NotificationService {
|
|
10
|
+
/**
|
|
11
|
+
* Handle order created event
|
|
12
|
+
*/
|
|
13
|
+
@OnEvent('order.created')
|
|
14
|
+
async handleOrderCreated(order: any): Promise<void> {
|
|
15
|
+
console.log(`📧 Sending order confirmation email for order #${order.id}`);
|
|
16
|
+
// TODO: Implement actual email sending
|
|
17
|
+
console.log(` To: ${order.user_email}`);
|
|
18
|
+
console.log(` Subject: Order Confirmation #${order.id}`);
|
|
19
|
+
console.log(` Total: $${order.total}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Handle order shipped event
|
|
24
|
+
*/
|
|
25
|
+
@OnEvent('order.shipped')
|
|
26
|
+
async handleOrderShipped(order: any): Promise<void> {
|
|
27
|
+
console.log(`📦 Sending shipping notification for order #${order.id}`);
|
|
28
|
+
// TODO: Implement actual notification
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Handle user registered event
|
|
33
|
+
*/
|
|
34
|
+
@OnEvent('user.registered')
|
|
35
|
+
async handleUserRegistered(user: any): Promise<void> {
|
|
36
|
+
console.log(`👋 Sending welcome email to ${user.email}`);
|
|
37
|
+
// TODO: Implement actual welcome email
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ExceptionFilter, ExceptionFilterInterface, ExecutionContext } from 'nitrostack';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Global Exception Filter
|
|
5
|
+
*
|
|
6
|
+
* Handles all unhandled exceptions and formats error responses
|
|
7
|
+
*/
|
|
8
|
+
@ExceptionFilter()
|
|
9
|
+
export class GlobalExceptionFilter implements ExceptionFilterInterface {
|
|
10
|
+
catch(exception: any, context: ExecutionContext) {
|
|
11
|
+
context.logger.error('Unhandled exception', {
|
|
12
|
+
tool: context.toolName || 'unknown',
|
|
13
|
+
error: exception.message,
|
|
14
|
+
stack: exception.stack,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Format error response
|
|
18
|
+
return {
|
|
19
|
+
error: true,
|
|
20
|
+
message: exception.message || 'Internal server error',
|
|
21
|
+
code: exception.code || 'INTERNAL_ERROR',
|
|
22
|
+
timestamp: new Date().toISOString(),
|
|
23
|
+
requestId: context.requestId,
|
|
24
|
+
tool: context.toolName || 'unknown',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Guards
|
|
2
|
+
|
|
3
|
+
Guards are classes that implement the `Guard` interface and determine whether a tool can be executed.
|
|
4
|
+
|
|
5
|
+
## What are Guards?
|
|
6
|
+
|
|
7
|
+
Guards in NitroStack are similar to NestJS guards - they handle authorization logic before tool execution. Use the `@UseGuards()` decorator to apply guards to your tools.
|
|
8
|
+
|
|
9
|
+
## Available Guards
|
|
10
|
+
|
|
11
|
+
### JWTGuard
|
|
12
|
+
|
|
13
|
+
Validates JWT tokens and populates `context.auth` with user information.
|
|
14
|
+
|
|
15
|
+
**Usage:**
|
|
16
|
+
```typescript
|
|
17
|
+
import { JWTGuard } from '../guards/jwt.guard.js';
|
|
18
|
+
|
|
19
|
+
@Tool({ name: 'get_profile', ... })
|
|
20
|
+
@UseGuards(JWTGuard)
|
|
21
|
+
async getProfile(input: any, context: ExecutionContext) {
|
|
22
|
+
const userId = context.auth?.subject;
|
|
23
|
+
// User is already authenticated!
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Creating Custom Guards
|
|
28
|
+
|
|
29
|
+
Create a class that implements the `Guard` interface:
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { Guard, ExecutionContext } from 'nitrostack';
|
|
33
|
+
|
|
34
|
+
export class RoleGuard implements Guard {
|
|
35
|
+
constructor(private requiredRole: string) {}
|
|
36
|
+
|
|
37
|
+
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
38
|
+
const userRole = context.auth?.role;
|
|
39
|
+
|
|
40
|
+
if (userRole !== this.requiredRole) {
|
|
41
|
+
throw new Error(`Required role: ${this.requiredRole}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Usage:**
|
|
50
|
+
```typescript
|
|
51
|
+
@UseGuards(JWTGuard, new RoleGuard('admin'))
|
|
52
|
+
async deleteUser(input: any, context: ExecutionContext) {
|
|
53
|
+
// Only admins can execute this
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Guard Execution Order
|
|
58
|
+
|
|
59
|
+
Guards are executed in the order they are specified:
|
|
60
|
+
```typescript
|
|
61
|
+
@UseGuards(JWTGuard, RoleGuard, PermissionGuard)
|
|
62
|
+
```
|
|
63
|
+
1. JWTGuard validates token
|
|
64
|
+
2. RoleGuard checks user role
|
|
65
|
+
3. PermissionGuard checks specific permissions
|
|
66
|
+
|
|
67
|
+
If any guard returns `false` or throws an error, execution stops.
|
|
68
|
+
|
|
69
|
+
## Best Practices
|
|
70
|
+
|
|
71
|
+
1. **Keep guards focused** - Each guard should have one responsibility
|
|
72
|
+
2. **Reuse guards** - Create guards that can be used across multiple tools
|
|
73
|
+
3. **Clear error messages** - Help users understand why access was denied
|
|
74
|
+
4. **Log important events** - Use `context.logger` for debugging
|
|
75
|
+
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import jwt from 'jsonwebtoken';
|
|
2
|
+
import { Guard, ExecutionContext, JWTPayload, JWTModule } from 'nitrostack';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* JWT Guard - Validates JWT tokens and populates context.auth
|
|
6
|
+
*
|
|
7
|
+
* This is an example implementation. You can customize it for your needs.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* @Tool({ name: 'whoami', ... })
|
|
12
|
+
* @UseGuards(JWTGuard)
|
|
13
|
+
* async whoami(input: any, context: ExecutionContext) {
|
|
14
|
+
* // context.auth is populated with JWT payload
|
|
15
|
+
* return { user: context.auth };
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export class JWTGuard implements Guard {
|
|
20
|
+
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
21
|
+
try {
|
|
22
|
+
// Get JWT secret from JWTModule configuration
|
|
23
|
+
const secret = JWTModule.getSecret();
|
|
24
|
+
if (!secret) {
|
|
25
|
+
context.logger.warn('[JWTGuard] JWT secret not configured');
|
|
26
|
+
throw new Error('JWT authentication not configured. Set JWT_SECRET environment variable.');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Extract token from context
|
|
30
|
+
const token = this.extractToken(context);
|
|
31
|
+
if (!token) {
|
|
32
|
+
context.logger.info('[JWTGuard] No token found in request');
|
|
33
|
+
throw new Error('Authentication required. Please login first and include your JWT token.');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Verify token
|
|
37
|
+
const payload = await this.verifyToken(token, secret);
|
|
38
|
+
if (!payload) {
|
|
39
|
+
context.logger.warn('[JWTGuard] Token verification failed');
|
|
40
|
+
throw new Error('Invalid or expired token. Please login again.');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Populate context.auth
|
|
44
|
+
context.auth = {
|
|
45
|
+
subject: payload.sub,
|
|
46
|
+
scopes: [],
|
|
47
|
+
email: payload.email,
|
|
48
|
+
name: payload.name,
|
|
49
|
+
...payload,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
context.logger.info(`[JWTGuard] Authentication successful for user: ${payload.sub}`);
|
|
53
|
+
return true;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
context.logger.error('[JWTGuard] Authentication failed', error);
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Extract JWT token from execution context
|
|
62
|
+
* Supports multiple token locations for maximum compatibility
|
|
63
|
+
*/
|
|
64
|
+
private extractToken(context: ExecutionContext): string | null {
|
|
65
|
+
// Priority 1: Check metadata for _jwt field (MCP protocol)
|
|
66
|
+
if (context.metadata?._jwt) {
|
|
67
|
+
return context.metadata._jwt as string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Priority 2: Check Authorization header format in metadata
|
|
71
|
+
if (context.metadata?.authorization) {
|
|
72
|
+
const auth = context.metadata.authorization as string;
|
|
73
|
+
if (auth.startsWith('Bearer ')) {
|
|
74
|
+
return auth.substring(7);
|
|
75
|
+
}
|
|
76
|
+
return auth;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Priority 3: Check metadata.headers (if present)
|
|
80
|
+
const headers = context.metadata?.headers as any;
|
|
81
|
+
if (headers?.authorization) {
|
|
82
|
+
const auth = headers.authorization as string;
|
|
83
|
+
if (auth.startsWith('Bearer ')) {
|
|
84
|
+
return auth.substring(7);
|
|
85
|
+
}
|
|
86
|
+
return auth;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Verify JWT token
|
|
94
|
+
*/
|
|
95
|
+
private async verifyToken(token: string, secret: string): Promise<JWTPayload | null> {
|
|
96
|
+
try {
|
|
97
|
+
const cleanSecret = secret.trim();
|
|
98
|
+
const decoded = jwt.verify(token, cleanSecret) as JWTPayload;
|
|
99
|
+
return decoded;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { HealthCheck, HealthCheckInterface, HealthCheckResult } from 'nitrostack';
|
|
2
|
+
import { DatabaseService } from '../services/database.service.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Database Health Check
|
|
6
|
+
*
|
|
7
|
+
* Monitors database connectivity
|
|
8
|
+
*/
|
|
9
|
+
@HealthCheck({
|
|
10
|
+
name: 'database',
|
|
11
|
+
description: 'Database connectivity check',
|
|
12
|
+
interval: 30 // Check every 30 seconds
|
|
13
|
+
})
|
|
14
|
+
export class DatabaseHealthCheck implements HealthCheckInterface {
|
|
15
|
+
constructor(private database: DatabaseService) {}
|
|
16
|
+
|
|
17
|
+
async check(): Promise<HealthCheckResult> {
|
|
18
|
+
try {
|
|
19
|
+
const isConnected = await this.database.ping();
|
|
20
|
+
|
|
21
|
+
if (isConnected) {
|
|
22
|
+
return {
|
|
23
|
+
status: 'up',
|
|
24
|
+
message: 'Database is connected and responsive',
|
|
25
|
+
};
|
|
26
|
+
} else {
|
|
27
|
+
return {
|
|
28
|
+
status: 'down',
|
|
29
|
+
message: 'Database connection failed',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
} catch (error: any) {
|
|
33
|
+
return {
|
|
34
|
+
status: 'down',
|
|
35
|
+
message: 'Database health check failed',
|
|
36
|
+
details: error.message,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E-commerce MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Main entry point for the MCP server.
|
|
5
|
+
* Uses the new @McpApp decorator pattern for clean, NestJS-style architecture.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { McpApplicationFactory } from 'nitrostack';
|
|
9
|
+
import { AppModule } from './app.module.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Bootstrap the application
|
|
13
|
+
*/
|
|
14
|
+
async function bootstrap() {
|
|
15
|
+
// Create and start the MCP server from AppModule
|
|
16
|
+
const server = await McpApplicationFactory.create(AppModule);
|
|
17
|
+
|
|
18
|
+
// Start the server
|
|
19
|
+
await server.start();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Start the application
|
|
23
|
+
bootstrap().catch((error) => {
|
|
24
|
+
console.error('❌ Failed to start server:', error);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Interceptor, InterceptorInterface, ExecutionContext } from 'nitrostack';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Transform Interceptor
|
|
5
|
+
*
|
|
6
|
+
* Wraps all responses in a standard format with metadata
|
|
7
|
+
*/
|
|
8
|
+
@Interceptor()
|
|
9
|
+
export class TransformInterceptor implements InterceptorInterface {
|
|
10
|
+
async intercept(context: ExecutionContext, next: () => Promise<any>) {
|
|
11
|
+
const result = await next();
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
success: true,
|
|
15
|
+
data: result,
|
|
16
|
+
metadata: {
|
|
17
|
+
timestamp: new Date().toISOString(),
|
|
18
|
+
requestId: context.requestId,
|
|
19
|
+
tool: context.toolName || 'unknown',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Middleware, MiddlewareInterface, ExecutionContext } from 'nitrostack';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Logging Middleware
|
|
5
|
+
*
|
|
6
|
+
* Logs all tool executions with timing information
|
|
7
|
+
*/
|
|
8
|
+
@Middleware()
|
|
9
|
+
export class LoggingMiddleware implements MiddlewareInterface {
|
|
10
|
+
async use(context: ExecutionContext, next: () => Promise<any>) {
|
|
11
|
+
const start = performance.now();
|
|
12
|
+
const toolName = context.toolName || 'unknown';
|
|
13
|
+
|
|
14
|
+
context.logger.info(`[${toolName}] Started`, {
|
|
15
|
+
requestId: context.requestId,
|
|
16
|
+
user: context.auth?.subject || 'anonymous',
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const result = await next();
|
|
21
|
+
const duration = performance.now() - start;
|
|
22
|
+
|
|
23
|
+
context.logger.info(`[${toolName}] Completed in ${duration.toFixed(2)}ms`, {
|
|
24
|
+
requestId: context.requestId,
|
|
25
|
+
duration,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return result;
|
|
29
|
+
} catch (error: any) {
|
|
30
|
+
const duration = performance.now() - start;
|
|
31
|
+
|
|
32
|
+
context.logger.error(`[${toolName}] Failed after ${duration.toFixed(2)}ms`, {
|
|
33
|
+
requestId: context.requestId,
|
|
34
|
+
duration,
|
|
35
|
+
error: error.message,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Module } from 'nitrostack';
|
|
2
|
+
import { AddressesTools } from './addresses.tools.js';
|
|
3
|
+
import { AddressesResources } from './addresses.resources.js';
|
|
4
|
+
import { AddressesPrompts } from './addresses.prompts.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Addresses Module
|
|
8
|
+
* Provides shipping address management, resources, and prompts
|
|
9
|
+
*/
|
|
10
|
+
@Module({
|
|
11
|
+
name: 'addresses',
|
|
12
|
+
description: 'Shipping address management',
|
|
13
|
+
controllers: [AddressesTools, AddressesResources, AddressesPrompts],
|
|
14
|
+
})
|
|
15
|
+
export class AddressesModule {}
|
|
16
|
+
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { PromptDecorator as Prompt, ExecutionContext } from 'nitrostack';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Addresses Prompts
|
|
5
|
+
* Provides helpful prompts for address management
|
|
6
|
+
*/
|
|
7
|
+
export class AddressesPrompts {
|
|
8
|
+
/**
|
|
9
|
+
* Address management help prompt
|
|
10
|
+
*/
|
|
11
|
+
@Prompt({
|
|
12
|
+
name: 'address-help',
|
|
13
|
+
description: 'Get help managing shipping addresses',
|
|
14
|
+
})
|
|
15
|
+
async addressHelp(args: Record<string, any>, context: ExecutionContext) {
|
|
16
|
+
return [
|
|
17
|
+
{
|
|
18
|
+
role: 'user' as const,
|
|
19
|
+
content: 'I need help managing shipping addresses',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
role: 'assistant' as const,
|
|
23
|
+
content: `**Shipping Address Management Guide**
|
|
24
|
+
|
|
25
|
+
**Available Tools (Requires Authentication):**
|
|
26
|
+
1. \`add_address\` - Add new shipping address
|
|
27
|
+
2. \`get_addresses\` - View all your addresses
|
|
28
|
+
3. \`set_default_address\` - Set default for orders
|
|
29
|
+
4. \`delete_address\` - Remove an address
|
|
30
|
+
|
|
31
|
+
**Adding an Address:**
|
|
32
|
+
\`\`\`
|
|
33
|
+
add_address({
|
|
34
|
+
full_name: "John Doe",
|
|
35
|
+
street: "123 Main St",
|
|
36
|
+
city: "New York",
|
|
37
|
+
state: "NY",
|
|
38
|
+
zip_code: "10001",
|
|
39
|
+
country: "USA",
|
|
40
|
+
phone: "+1234567890",
|
|
41
|
+
is_default: true
|
|
42
|
+
})
|
|
43
|
+
\`\`\`
|
|
44
|
+
|
|
45
|
+
**Required Fields:**
|
|
46
|
+
- ✓ full_name
|
|
47
|
+
- ✓ street
|
|
48
|
+
- ✓ city
|
|
49
|
+
- ✓ state
|
|
50
|
+
- ✓ zip_code
|
|
51
|
+
- ✓ phone
|
|
52
|
+
|
|
53
|
+
**Optional Fields:**
|
|
54
|
+
- country (defaults to "USA")
|
|
55
|
+
- is_default (defaults to false)
|
|
56
|
+
|
|
57
|
+
**Default Address:**
|
|
58
|
+
- Used automatically for orders
|
|
59
|
+
- Only one default address per user
|
|
60
|
+
- Setting new default unsets old one
|
|
61
|
+
|
|
62
|
+
**Viewing Addresses:**
|
|
63
|
+
\`\`\`
|
|
64
|
+
get_addresses({})
|
|
65
|
+
\`\`\`
|
|
66
|
+
Returns all addresses, sorted by:
|
|
67
|
+
1. Default address first
|
|
68
|
+
2. Then by creation date
|
|
69
|
+
|
|
70
|
+
**Setting Default:**
|
|
71
|
+
\`\`\`
|
|
72
|
+
set_default_address({
|
|
73
|
+
address_id: "address-uuid"
|
|
74
|
+
})
|
|
75
|
+
\`\`\`
|
|
76
|
+
|
|
77
|
+
**Deleting Address:**
|
|
78
|
+
\`\`\`
|
|
79
|
+
delete_address({
|
|
80
|
+
address_id: "address-uuid"
|
|
81
|
+
})
|
|
82
|
+
\`\`\`
|
|
83
|
+
⚠️ Cannot be undone!
|
|
84
|
+
|
|
85
|
+
**Address Format Tips:**
|
|
86
|
+
📍 **Street:** Include apartment/unit if applicable
|
|
87
|
+
📍 **State:** Use 2-letter code (NY, CA, TX)
|
|
88
|
+
📍 **ZIP:** 5-digit or 9-digit format
|
|
89
|
+
📍 **Phone:** Include country code for international
|
|
90
|
+
|
|
91
|
+
**Common Operations:**
|
|
92
|
+
1. **First Time Setup:**
|
|
93
|
+
- Add address with is_default: true
|
|
94
|
+
- Will be used for all orders
|
|
95
|
+
|
|
96
|
+
2. **Multiple Addresses:**
|
|
97
|
+
- Add each address separately
|
|
98
|
+
- Set one as default
|
|
99
|
+
- Choose specific address when ordering
|
|
100
|
+
|
|
101
|
+
3. **Update Address:**
|
|
102
|
+
- Delete old address
|
|
103
|
+
- Add new address with updated info
|
|
104
|
+
|
|
105
|
+
**Tips:**
|
|
106
|
+
✓ Add address before first order
|
|
107
|
+
✓ Keep at least one address on file
|
|
108
|
+
✓ Verify address details before saving
|
|
109
|
+
✓ Use default for frequent shipping location`,
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|