nitrostack 1.0.0 → 1.0.2
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 +30 -0
- package/dist/cli/index.js +4 -1
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
- package/src/studio/README.md +140 -0
- package/src/studio/app/api/auth/fetch-metadata/route.ts +71 -0
- package/src/studio/app/api/auth/register-client/route.ts +67 -0
- package/src/studio/app/api/chat/route.ts +123 -0
- package/src/studio/app/api/health/checks/route.ts +42 -0
- package/src/studio/app/api/health/route.ts +13 -0
- package/src/studio/app/api/init/route.ts +85 -0
- package/src/studio/app/api/ping/route.ts +13 -0
- package/src/studio/app/api/prompts/[name]/route.ts +21 -0
- package/src/studio/app/api/prompts/route.ts +13 -0
- package/src/studio/app/api/resources/[...uri]/route.ts +18 -0
- package/src/studio/app/api/resources/route.ts +13 -0
- package/src/studio/app/api/roots/route.ts +13 -0
- package/src/studio/app/api/sampling/route.ts +14 -0
- package/src/studio/app/api/tools/[name]/call/route.ts +41 -0
- package/src/studio/app/api/tools/route.ts +23 -0
- package/src/studio/app/api/widget-examples/route.ts +44 -0
- package/src/studio/app/auth/callback/page.tsx +160 -0
- package/src/studio/app/auth/page.tsx +543 -0
- package/src/studio/app/chat/page.tsx +530 -0
- package/src/studio/app/chat/page.tsx.backup +390 -0
- package/src/studio/app/globals.css +410 -0
- package/src/studio/app/health/page.tsx +177 -0
- package/src/studio/app/layout.tsx +48 -0
- package/src/studio/app/page.tsx +337 -0
- package/src/studio/app/page.tsx.backup +346 -0
- package/src/studio/app/ping/page.tsx +204 -0
- package/src/studio/app/prompts/page.tsx +228 -0
- package/src/studio/app/resources/page.tsx +313 -0
- package/src/studio/components/EnlargeModal.tsx +116 -0
- package/src/studio/components/Sidebar.tsx +133 -0
- package/src/studio/components/ToolCard.tsx +108 -0
- package/src/studio/components/WidgetRenderer.tsx +99 -0
- package/src/studio/lib/api.ts +207 -0
- package/src/studio/lib/llm-service.ts +361 -0
- package/src/studio/lib/mcp-client.ts +168 -0
- package/src/studio/lib/store.ts +192 -0
- package/src/studio/lib/theme-provider.tsx +50 -0
- package/src/studio/lib/types.ts +107 -0
- package/src/studio/lib/widget-loader.ts +90 -0
- package/src/studio/middleware.ts +27 -0
- package/src/studio/next.config.js +16 -0
- package/src/studio/package-lock.json +2696 -0
- package/src/studio/package.json +34 -0
- package/src/studio/postcss.config.mjs +10 -0
- package/src/studio/tailwind.config.ts +67 -0
- package/src/studio/tsconfig.json +41 -0
- 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,157 @@
|
|
|
1
|
+
import { Guard, ExecutionContext, ApiKeyModule } from 'nitrostack';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Multi-Auth Guard
|
|
5
|
+
*
|
|
6
|
+
* Accepts EITHER JWT token OR API key authentication.
|
|
7
|
+
* This demonstrates how to support multiple authentication methods in a single tool.
|
|
8
|
+
*
|
|
9
|
+
* Use Cases:
|
|
10
|
+
* - Tools that can be accessed by users (JWT) or services (API key)
|
|
11
|
+
* - Migration from one auth method to another
|
|
12
|
+
* - Supporting legacy clients
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* ```typescript
|
|
16
|
+
* @Tool({
|
|
17
|
+
* name: 'flexible_auth_tool',
|
|
18
|
+
* description: 'Works with JWT or API key'
|
|
19
|
+
* })
|
|
20
|
+
* @UseGuards(MultiAuthGuard)
|
|
21
|
+
* async flexibleTool() {
|
|
22
|
+
* // Accessible with EITHER JWT OR API key
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export class MultiAuthGuard implements Guard {
|
|
27
|
+
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
28
|
+
let authenticated = false;
|
|
29
|
+
let authMethod = 'none';
|
|
30
|
+
|
|
31
|
+
// Try JWT authentication first
|
|
32
|
+
const jwtToken = context.metadata?._jwt;
|
|
33
|
+
const authHeader = context.metadata?.authorization;
|
|
34
|
+
|
|
35
|
+
if (jwtToken || authHeader) {
|
|
36
|
+
// For this demo, we accept any JWT token
|
|
37
|
+
// In production, you'd validate it with JWTModule
|
|
38
|
+
const token = jwtToken || authHeader?.toString().replace('Bearer ', '');
|
|
39
|
+
if (token) {
|
|
40
|
+
context.auth = {
|
|
41
|
+
subject: 'jwt_user',
|
|
42
|
+
scopes: ['read', 'write'],
|
|
43
|
+
};
|
|
44
|
+
authenticated = true;
|
|
45
|
+
authMethod = 'JWT';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// If JWT didn't work, try API key
|
|
50
|
+
if (!authenticated) {
|
|
51
|
+
const apiKey = context.metadata?.apiKey || context.metadata?.['x-api-key'];
|
|
52
|
+
|
|
53
|
+
if (apiKey) {
|
|
54
|
+
const isValid = await ApiKeyModule.validate(apiKey as string);
|
|
55
|
+
|
|
56
|
+
if (isValid) {
|
|
57
|
+
context.auth = {
|
|
58
|
+
subject: `apikey_${(apiKey as string).substring(0, 12)}`,
|
|
59
|
+
scopes: ['*'],
|
|
60
|
+
};
|
|
61
|
+
authenticated = true;
|
|
62
|
+
authMethod = 'API Key';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!authenticated) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
'Authentication required. Please provide either:\n' +
|
|
70
|
+
' 1. JWT token (in Studio: Auth → JWT Token)\n' +
|
|
71
|
+
' 2. API key (in Studio: Auth → API Key)'
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Store auth method in context for debugging/logging
|
|
76
|
+
if (!context.metadata) {
|
|
77
|
+
(context as any).metadata = {};
|
|
78
|
+
}
|
|
79
|
+
(context.metadata as any)._authMethod = authMethod;
|
|
80
|
+
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Dual-Auth Guard
|
|
87
|
+
*
|
|
88
|
+
* Requires BOTH JWT token AND API key authentication.
|
|
89
|
+
* This is for highly sensitive operations that need multiple authentication factors.
|
|
90
|
+
*
|
|
91
|
+
* Use Cases:
|
|
92
|
+
* - Financial transactions
|
|
93
|
+
* - Account deletion
|
|
94
|
+
* - Admin operations
|
|
95
|
+
* - Critical data access
|
|
96
|
+
*
|
|
97
|
+
* Usage:
|
|
98
|
+
* ```typescript
|
|
99
|
+
* @Tool({
|
|
100
|
+
* name: 'critical_operation',
|
|
101
|
+
* description: 'Requires both JWT and API key'
|
|
102
|
+
* })
|
|
103
|
+
* @UseGuards(DualAuthGuard)
|
|
104
|
+
* async criticalOperation() {
|
|
105
|
+
* // Only accessible with BOTH JWT AND API key
|
|
106
|
+
* }
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export class DualAuthGuard implements Guard {
|
|
110
|
+
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
111
|
+
const errors: string[] = [];
|
|
112
|
+
|
|
113
|
+
// Check JWT token
|
|
114
|
+
const jwtToken = context.metadata?._jwt;
|
|
115
|
+
const authHeader = context.metadata?.authorization;
|
|
116
|
+
const hasJWT = !!(jwtToken || authHeader);
|
|
117
|
+
|
|
118
|
+
if (!hasJWT) {
|
|
119
|
+
errors.push('JWT token is required (set in Studio: Auth → JWT Token)');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Check API key
|
|
123
|
+
const apiKey = context.metadata?.apiKey || context.metadata?.['x-api-key'];
|
|
124
|
+
|
|
125
|
+
if (!apiKey) {
|
|
126
|
+
errors.push('API key is required (set in Studio: Auth → API Key)');
|
|
127
|
+
} else {
|
|
128
|
+
const isValidKey = await ApiKeyModule.validate(apiKey as string);
|
|
129
|
+
if (!isValidKey) {
|
|
130
|
+
errors.push('Invalid API key');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (errors.length > 0) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
'Dual authentication required:\n' +
|
|
137
|
+
errors.map(e => ` - ${e}`).join('\n')
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Both auth methods present and valid
|
|
142
|
+
context.auth = {
|
|
143
|
+
subject: 'dual_auth_user',
|
|
144
|
+
scopes: ['*'], // Full access with dual auth
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// Store auth info
|
|
148
|
+
if (!context.metadata) {
|
|
149
|
+
(context as any).metadata = {};
|
|
150
|
+
}
|
|
151
|
+
(context.metadata as any)._authMethod = 'JWT + API Key';
|
|
152
|
+
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { HealthCheck, HealthCheckInterface, HealthCheckResult } from 'nitrostack';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* System Health Check
|
|
5
|
+
*
|
|
6
|
+
* Monitors system resources and uptime
|
|
7
|
+
*/
|
|
8
|
+
@HealthCheck({
|
|
9
|
+
name: 'system',
|
|
10
|
+
description: 'System resource and uptime check',
|
|
11
|
+
interval: 30 // Check every 30 seconds
|
|
12
|
+
})
|
|
13
|
+
export class SystemHealthCheck implements HealthCheckInterface {
|
|
14
|
+
private startTime: number;
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
this.startTime = Date.now();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async check(): Promise<HealthCheckResult> {
|
|
21
|
+
try {
|
|
22
|
+
const memoryUsage = process.memoryUsage();
|
|
23
|
+
const uptime = Date.now() - this.startTime;
|
|
24
|
+
const uptimeSeconds = Math.floor(uptime / 1000);
|
|
25
|
+
|
|
26
|
+
// Convert memory to MB
|
|
27
|
+
const memoryUsedMB = Math.round(memoryUsage.heapUsed / 1024 / 1024);
|
|
28
|
+
const memoryTotalMB = Math.round(memoryUsage.heapTotal / 1024 / 1024);
|
|
29
|
+
|
|
30
|
+
// Consider unhealthy if memory usage is > 90%
|
|
31
|
+
const memoryPercent = (memoryUsage.heapUsed / memoryUsage.heapTotal) * 100;
|
|
32
|
+
const isHealthy = memoryPercent < 90;
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
status: isHealthy ? 'up' : 'degraded',
|
|
36
|
+
message: isHealthy
|
|
37
|
+
? 'System is healthy'
|
|
38
|
+
: 'High memory usage detected',
|
|
39
|
+
details: {
|
|
40
|
+
uptime: `${uptimeSeconds}s`,
|
|
41
|
+
memory: `${memoryUsedMB}MB / ${memoryTotalMB}MB (${Math.round(memoryPercent)}%)`,
|
|
42
|
+
pid: process.pid,
|
|
43
|
+
nodeVersion: process.version,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
} catch (error: any) {
|
|
47
|
+
return {
|
|
48
|
+
status: 'down',
|
|
49
|
+
message: 'System health check failed',
|
|
50
|
+
details: error.message,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpApplicationFactory } from 'nitrostack';
|
|
3
|
+
import { AppModule } from './app.module.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* API Key Authentication MCP Server
|
|
7
|
+
*
|
|
8
|
+
* This template demonstrates how to build an MCP server with API key authentication.
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Public tools (no authentication)
|
|
12
|
+
* - Protected tools (API key required)
|
|
13
|
+
* - Easy integration with NitroStack Studio
|
|
14
|
+
* - Environment-based key management
|
|
15
|
+
*
|
|
16
|
+
* Test API Keys (provided in .env):
|
|
17
|
+
* - sk_test_public_demo_key_12345
|
|
18
|
+
* - sk_test_admin_demo_key_67890
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
async function bootstrap() {
|
|
22
|
+
try {
|
|
23
|
+
console.error('🔑 Starting API Key Auth MCP Server...\n');
|
|
24
|
+
|
|
25
|
+
// Create the MCP application
|
|
26
|
+
const app = await McpApplicationFactory.create(AppModule);
|
|
27
|
+
|
|
28
|
+
console.error('✅ API Key Module configured');
|
|
29
|
+
console.error(' - Keys loaded from environment variables (API_KEY_*)');
|
|
30
|
+
console.error(' - Protected tools require valid API key');
|
|
31
|
+
console.error(' - Public tools accessible without authentication\n');
|
|
32
|
+
|
|
33
|
+
// Start the MCP server
|
|
34
|
+
await app.start();
|
|
35
|
+
|
|
36
|
+
console.error('🚀 Server started successfully!');
|
|
37
|
+
console.error(' Open NitroStack Studio to test the server');
|
|
38
|
+
console.error(' Set your API key in Studio → Auth → API Key section\n');
|
|
39
|
+
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error('❌ Failed to start server:', error);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
bootstrap();
|
|
47
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Module } from 'nitrostack';
|
|
2
|
+
import { CalculatorTools } from './calculator.tools.js';
|
|
3
|
+
import { CalculatorResources } from './calculator.resources.js';
|
|
4
|
+
import { CalculatorPrompts } from './calculator.prompts.js';
|
|
5
|
+
|
|
6
|
+
@Module({
|
|
7
|
+
name: 'calculator',
|
|
8
|
+
description: 'Basic arithmetic calculator',
|
|
9
|
+
controllers: [CalculatorTools, CalculatorResources, CalculatorPrompts]
|
|
10
|
+
})
|
|
11
|
+
export class CalculatorModule {}
|
|
12
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { PromptDecorator as Prompt, ExecutionContext } from 'nitrostack';
|
|
2
|
+
|
|
3
|
+
export class CalculatorPrompts {
|
|
4
|
+
@Prompt({
|
|
5
|
+
name: 'calculator_help',
|
|
6
|
+
description: 'Get help with calculator operations',
|
|
7
|
+
arguments: [
|
|
8
|
+
{
|
|
9
|
+
name: 'operation',
|
|
10
|
+
description: 'The operation to get help with (optional)',
|
|
11
|
+
required: false
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
})
|
|
15
|
+
async getHelp(args: any, ctx: ExecutionContext) {
|
|
16
|
+
ctx.logger.info('Generating calculator help prompt');
|
|
17
|
+
|
|
18
|
+
const operation = args.operation;
|
|
19
|
+
|
|
20
|
+
if (operation) {
|
|
21
|
+
// Help for specific operation
|
|
22
|
+
const helpText = this.getOperationHelp(operation);
|
|
23
|
+
return [
|
|
24
|
+
{
|
|
25
|
+
role: 'user' as const,
|
|
26
|
+
content: `How do I use the ${operation} operation in the calculator?`
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
role: 'assistant' as const,
|
|
30
|
+
content: helpText
|
|
31
|
+
}
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// General help
|
|
36
|
+
return [
|
|
37
|
+
{
|
|
38
|
+
role: 'user' as const,
|
|
39
|
+
content: 'How do I use the calculator?'
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
role: 'assistant' as const,
|
|
43
|
+
content: `The calculator supports four basic operations:
|
|
44
|
+
|
|
45
|
+
1. **Addition** - Add two numbers together
|
|
46
|
+
Example: calculate(operation="add", a=5, b=3) = 8
|
|
47
|
+
|
|
48
|
+
2. **Subtraction** - Subtract one number from another
|
|
49
|
+
Example: calculate(operation="subtract", a=10, b=4) = 6
|
|
50
|
+
|
|
51
|
+
3. **Multiplication** - Multiply two numbers
|
|
52
|
+
Example: calculate(operation="multiply", a=6, b=7) = 42
|
|
53
|
+
|
|
54
|
+
4. **Division** - Divide one number by another
|
|
55
|
+
Example: calculate(operation="divide", a=20, b=5) = 4
|
|
56
|
+
|
|
57
|
+
Just call the 'calculate' tool with the operation and two numbers!`
|
|
58
|
+
}
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private getOperationHelp(operation: string): string {
|
|
63
|
+
const helps: Record<string, string> = {
|
|
64
|
+
add: 'Use addition to sum two numbers. Call calculate(operation="add", a=5, b=3) to get 8.',
|
|
65
|
+
subtract: 'Use subtraction to find the difference. Call calculate(operation="subtract", a=10, b=4) to get 6.',
|
|
66
|
+
multiply: 'Use multiplication to find the product. Call calculate(operation="multiply", a=6, b=7) to get 42.',
|
|
67
|
+
divide: 'Use division to find the quotient. Call calculate(operation="divide", a=20, b=5) to get 4. Note: Cannot divide by zero!'
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return helps[operation] || 'Unknown operation. Available operations: add, subtract, multiply, divide.';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ResourceDecorator as Resource, Widget, ExecutionContext } from 'nitrostack';
|
|
2
|
+
|
|
3
|
+
export class CalculatorResources {
|
|
4
|
+
@Resource({
|
|
5
|
+
uri: 'calculator://operations',
|
|
6
|
+
name: 'Calculator Operations',
|
|
7
|
+
description: 'List of available calculator operations',
|
|
8
|
+
mimeType: 'application/json',
|
|
9
|
+
examples: {
|
|
10
|
+
response: {
|
|
11
|
+
operations: [
|
|
12
|
+
{ name: 'add', symbol: '+', description: 'Addition' },
|
|
13
|
+
{ name: 'subtract', symbol: '-', description: 'Subtraction' },
|
|
14
|
+
{ name: 'multiply', symbol: '×', description: 'Multiplication' },
|
|
15
|
+
{ name: 'divide', symbol: '÷', description: 'Division' }
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
@Widget('calculator-operations')
|
|
21
|
+
async getOperations(uri: string, ctx: ExecutionContext) {
|
|
22
|
+
ctx.logger.info('Fetching calculator operations');
|
|
23
|
+
|
|
24
|
+
const operations = [
|
|
25
|
+
{
|
|
26
|
+
name: 'add',
|
|
27
|
+
symbol: '+',
|
|
28
|
+
description: 'Addition',
|
|
29
|
+
example: '5 + 3 = 8'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'subtract',
|
|
33
|
+
symbol: '-',
|
|
34
|
+
description: 'Subtraction',
|
|
35
|
+
example: '10 - 4 = 6'
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'multiply',
|
|
39
|
+
symbol: '×',
|
|
40
|
+
description: 'Multiplication',
|
|
41
|
+
example: '6 × 7 = 42'
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'divide',
|
|
45
|
+
symbol: '÷',
|
|
46
|
+
description: 'Division',
|
|
47
|
+
example: '20 ÷ 5 = 4'
|
|
48
|
+
}
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
contents: [{
|
|
53
|
+
uri,
|
|
54
|
+
mimeType: 'application/json',
|
|
55
|
+
text: JSON.stringify({ operations }, null, 2)
|
|
56
|
+
}]
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ToolDecorator as Tool, Widget, ExecutionContext, z } from 'nitrostack';
|
|
2
|
+
|
|
3
|
+
export class CalculatorTools {
|
|
4
|
+
@Tool({
|
|
5
|
+
name: 'calculate',
|
|
6
|
+
description: 'Perform basic arithmetic calculations',
|
|
7
|
+
inputSchema: z.object({
|
|
8
|
+
operation: z.enum(['add', 'subtract', 'multiply', 'divide']).describe('The operation to perform'),
|
|
9
|
+
a: z.number().describe('First number'),
|
|
10
|
+
b: z.number().describe('Second number')
|
|
11
|
+
}),
|
|
12
|
+
examples: {
|
|
13
|
+
request: {
|
|
14
|
+
operation: 'add',
|
|
15
|
+
a: 5,
|
|
16
|
+
b: 3
|
|
17
|
+
},
|
|
18
|
+
response: {
|
|
19
|
+
operation: 'add',
|
|
20
|
+
a: 5,
|
|
21
|
+
b: 3,
|
|
22
|
+
result: 8,
|
|
23
|
+
expression: '5 + 3 = 8'
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
@Widget('calculator-result')
|
|
28
|
+
async calculate(input: any, ctx: ExecutionContext) {
|
|
29
|
+
ctx.logger.info('Performing calculation', {
|
|
30
|
+
operation: input.operation,
|
|
31
|
+
a: input.a,
|
|
32
|
+
b: input.b
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
let result: number;
|
|
36
|
+
let symbol: string;
|
|
37
|
+
|
|
38
|
+
switch (input.operation) {
|
|
39
|
+
case 'add':
|
|
40
|
+
result = input.a + input.b;
|
|
41
|
+
symbol = '+';
|
|
42
|
+
break;
|
|
43
|
+
case 'subtract':
|
|
44
|
+
result = input.a - input.b;
|
|
45
|
+
symbol = '-';
|
|
46
|
+
break;
|
|
47
|
+
case 'multiply':
|
|
48
|
+
result = input.a * input.b;
|
|
49
|
+
symbol = '×';
|
|
50
|
+
break;
|
|
51
|
+
case 'divide':
|
|
52
|
+
if (input.b === 0) {
|
|
53
|
+
throw new Error('Cannot divide by zero');
|
|
54
|
+
}
|
|
55
|
+
result = input.a / input.b;
|
|
56
|
+
symbol = '÷';
|
|
57
|
+
break;
|
|
58
|
+
default:
|
|
59
|
+
throw new Error('Invalid operation');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
operation: input.operation,
|
|
64
|
+
a: input.a,
|
|
65
|
+
b: input.b,
|
|
66
|
+
result,
|
|
67
|
+
expression: `${input.a} ${symbol} ${input.b} = ${result}`
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Module } from 'nitrostack';
|
|
2
|
+
import { DemoTools } from './demo.tools.js';
|
|
3
|
+
import { MultiAuthTools } from './multi-auth.tools.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Demo Module
|
|
7
|
+
*
|
|
8
|
+
* Contains example tools demonstrating:
|
|
9
|
+
* - API key authentication
|
|
10
|
+
* - Multi-auth patterns (JWT + API Key)
|
|
11
|
+
*/
|
|
12
|
+
@Module({
|
|
13
|
+
name: 'demo',
|
|
14
|
+
description: 'Demo module with API key authentication examples',
|
|
15
|
+
controllers: [DemoTools, MultiAuthTools],
|
|
16
|
+
})
|
|
17
|
+
export class DemoModule {}
|
|
18
|
+
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { Injectable, ToolDecorator as Tool, UseGuards, z, ExecutionContext } from 'nitrostack';
|
|
2
|
+
import { ApiKeyGuard } from '../../guards/apikey.guard.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Demo Tools Module
|
|
6
|
+
*
|
|
7
|
+
* Demonstrates:
|
|
8
|
+
* 1. Public tools (no authentication required)
|
|
9
|
+
* 2. Protected tools (API key required)
|
|
10
|
+
* 3. Multi-auth scenarios
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
@Injectable()
|
|
14
|
+
export class DemoTools {
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* PUBLIC TOOL - No authentication required
|
|
18
|
+
* Anyone can call this tool without an API key
|
|
19
|
+
*/
|
|
20
|
+
@Tool({
|
|
21
|
+
name: 'get_public_info',
|
|
22
|
+
description: 'Get public information (no authentication required)',
|
|
23
|
+
inputSchema: z.object({
|
|
24
|
+
topic: z.string().describe('The topic to get information about'),
|
|
25
|
+
}),
|
|
26
|
+
})
|
|
27
|
+
async getPublicInfo(args: { topic: string }) {
|
|
28
|
+
return {
|
|
29
|
+
message: `Public information about ${args.topic}`,
|
|
30
|
+
data: {
|
|
31
|
+
topic: args.topic,
|
|
32
|
+
info: 'This is public information that anyone can access without authentication.',
|
|
33
|
+
timestamp: new Date().toISOString(),
|
|
34
|
+
authRequired: false,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* PROTECTED TOOL - API key required
|
|
41
|
+
* Only accessible with a valid API key
|
|
42
|
+
*/
|
|
43
|
+
@Tool({
|
|
44
|
+
name: 'get_protected_data',
|
|
45
|
+
description: 'Get protected data (requires API key authentication)',
|
|
46
|
+
inputSchema: z.object({
|
|
47
|
+
dataType: z.enum(['user', 'account', 'settings']).describe('Type of protected data to retrieve'),
|
|
48
|
+
}),
|
|
49
|
+
})
|
|
50
|
+
@UseGuards(ApiKeyGuard)
|
|
51
|
+
async getProtectedData(args: { dataType: string }, context?: ExecutionContext) {
|
|
52
|
+
// Access auth information populated by the guard
|
|
53
|
+
const apiKeySubject = context?.auth?.subject || 'unknown';
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
message: `Protected ${args.dataType} data accessed successfully`,
|
|
57
|
+
data: {
|
|
58
|
+
dataType: args.dataType,
|
|
59
|
+
accessedBy: apiKeySubject,
|
|
60
|
+
timestamp: new Date().toISOString(),
|
|
61
|
+
protectedInfo: `This is sensitive ${args.dataType} data that requires API key authentication.`,
|
|
62
|
+
// In real apps, you'd fetch actual data from a database
|
|
63
|
+
sampleData: {
|
|
64
|
+
user: { id: 1, name: 'John Doe', role: 'admin' },
|
|
65
|
+
account: { id: 'acc_123', balance: 1000, currency: 'USD' },
|
|
66
|
+
settings: { theme: 'dark', notifications: true, language: 'en' },
|
|
67
|
+
}[args.dataType],
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* PROTECTED TOOL - API key required
|
|
74
|
+
* Performs an action that requires authentication
|
|
75
|
+
*/
|
|
76
|
+
@Tool({
|
|
77
|
+
name: 'perform_protected_action',
|
|
78
|
+
description: 'Perform a protected action (requires API key authentication)',
|
|
79
|
+
inputSchema: z.object({
|
|
80
|
+
action: z.enum(['create', 'update', 'delete']).describe('The action to perform'),
|
|
81
|
+
resourceType: z.string().describe('Type of resource (e.g., "user", "post", "file")'),
|
|
82
|
+
resourceId: z.string().optional().describe('Resource ID (for update/delete)'),
|
|
83
|
+
}),
|
|
84
|
+
})
|
|
85
|
+
@UseGuards(ApiKeyGuard)
|
|
86
|
+
async performProtectedAction(
|
|
87
|
+
args: { action: string; resourceType: string; resourceId?: string },
|
|
88
|
+
context?: ExecutionContext
|
|
89
|
+
) {
|
|
90
|
+
const apiKeySubject = context?.auth?.subject || 'unknown';
|
|
91
|
+
|
|
92
|
+
let result;
|
|
93
|
+
switch (args.action) {
|
|
94
|
+
case 'create':
|
|
95
|
+
result = {
|
|
96
|
+
status: 'success',
|
|
97
|
+
message: `Created new ${args.resourceType}`,
|
|
98
|
+
resourceId: `${args.resourceType}_${Date.now()}`,
|
|
99
|
+
};
|
|
100
|
+
break;
|
|
101
|
+
case 'update':
|
|
102
|
+
result = {
|
|
103
|
+
status: 'success',
|
|
104
|
+
message: `Updated ${args.resourceType} ${args.resourceId}`,
|
|
105
|
+
resourceId: args.resourceId,
|
|
106
|
+
};
|
|
107
|
+
break;
|
|
108
|
+
case 'delete':
|
|
109
|
+
result = {
|
|
110
|
+
status: 'success',
|
|
111
|
+
message: `Deleted ${args.resourceType} ${args.resourceId}`,
|
|
112
|
+
resourceId: args.resourceId,
|
|
113
|
+
};
|
|
114
|
+
break;
|
|
115
|
+
default:
|
|
116
|
+
result = { status: 'error', message: 'Invalid action' };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
message: `Action performed by ${apiKeySubject}`,
|
|
121
|
+
action: args.action,
|
|
122
|
+
resourceType: args.resourceType,
|
|
123
|
+
timestamp: new Date().toISOString(),
|
|
124
|
+
result,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* UTILITY TOOL - Check API key status
|
|
130
|
+
* Helps developers test their API key configuration
|
|
131
|
+
*/
|
|
132
|
+
@Tool({
|
|
133
|
+
name: 'check_api_key_status',
|
|
134
|
+
description: 'Check if your API key is valid and working (requires API key)',
|
|
135
|
+
inputSchema: z.object({}),
|
|
136
|
+
})
|
|
137
|
+
@UseGuards(ApiKeyGuard)
|
|
138
|
+
async checkApiKeyStatus(args: {}, context?: ExecutionContext) {
|
|
139
|
+
const apiKeySubject = context?.auth?.subject || 'unknown';
|
|
140
|
+
const scopes = context?.auth?.scopes || [];
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
message: 'API key is valid and working! ✓',
|
|
144
|
+
status: 'authenticated',
|
|
145
|
+
apiKeyInfo: {
|
|
146
|
+
subject: apiKeySubject,
|
|
147
|
+
scopes: scopes,
|
|
148
|
+
hasFullAccess: scopes.includes('*'),
|
|
149
|
+
},
|
|
150
|
+
timestamp: new Date().toISOString(),
|
|
151
|
+
hint: 'Your API key is properly configured in the Studio Auth tab.',
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|