nitrostack 1.0.14 → 1.0.16
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 +29 -295
- package/README.md +9 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +2 -1
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/mcp-dev-wrapper.js +30 -17
- package/dist/cli/mcp-dev-wrapper.js.map +1 -1
- package/dist/core/transports/http-server.d.ts.map +1 -1
- package/dist/core/transports/http-server.js +21 -1
- package/dist/core/transports/http-server.js.map +1 -1
- package/package.json +8 -6
- package/src/studio/app/api/chat/route.ts +12 -1
- package/src/studio/app/api/init/route.ts +28 -4
- package/src/studio/app/auth/page.tsx +13 -9
- package/src/studio/app/chat/page.tsx +544 -133
- package/src/studio/app/health/page.tsx +101 -99
- package/src/studio/app/layout.tsx +23 -3
- package/src/studio/app/page.tsx +61 -56
- package/src/studio/app/ping/page.tsx +13 -8
- package/src/studio/app/prompts/page.tsx +72 -70
- package/src/studio/app/resources/page.tsx +88 -86
- package/src/studio/app/settings/page.tsx +270 -0
- package/src/studio/components/Sidebar.tsx +197 -35
- package/src/studio/lib/http-client-transport.ts +222 -0
- package/src/studio/lib/llm-service.ts +97 -0
- package/src/studio/lib/log-manager.ts +76 -0
- package/src/studio/lib/mcp-client.ts +103 -13
- package/src/studio/package-lock.json +622 -189
- package/src/studio/package.json +1 -0
- package/templates/typescript-auth/README.md +3 -1
- package/templates/typescript-auth/src/db/database.ts +5 -8
- package/templates/typescript-auth/src/index.ts +13 -2
- package/templates/typescript-auth/src/modules/addresses/addresses.tools.ts +49 -6
- package/templates/typescript-auth/src/modules/cart/cart.tools.ts +13 -17
- package/templates/typescript-auth/src/modules/orders/orders.tools.ts +38 -16
- package/templates/typescript-auth/src/modules/products/products.tools.ts +4 -4
- package/templates/typescript-auth/src/widgets/app/order-confirmation/page.tsx +25 -0
- package/templates/typescript-auth/src/widgets/app/products-grid/page.tsx +26 -1
- package/templates/typescript-auth-api-key/README.md +3 -1
- package/templates/typescript-auth-api-key/src/index.ts +11 -3
- package/templates/typescript-starter/README.md +3 -1
- package/templates/typescript-auth-api-key/.env +0 -15
- package/templates/typescript-auth-api-key/package-lock.json +0 -124
package/src/studio/package.json
CHANGED
|
@@ -57,10 +57,12 @@ npm run dev
|
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
This starts:
|
|
60
|
-
- **MCP Server** (
|
|
60
|
+
- **MCP Server** (dual transport: STDIO + HTTP on port 3002) - Hot reloads on code changes
|
|
61
61
|
- **Studio** on http://localhost:3000 - Visual testing environment
|
|
62
62
|
- **Widget Dev Server** on http://localhost:3001 - Hot module replacement
|
|
63
63
|
|
|
64
|
+
> 💡 **Dual Transport**: Your server exposes tools via both STDIO (for direct connections) and HTTP (for remote access on port 3002). Switch between transports in Studio → Settings.
|
|
65
|
+
|
|
64
66
|
The `nitrostack dev` command handles everything automatically:
|
|
65
67
|
- ✅ Auto-detects widget directory
|
|
66
68
|
- ✅ Installs dependencies (if needed)
|
|
@@ -109,14 +109,13 @@ export function initializeSchema(): void {
|
|
|
109
109
|
CREATE TABLE IF NOT EXISTS orders (
|
|
110
110
|
id TEXT PRIMARY KEY,
|
|
111
111
|
user_id TEXT NOT NULL,
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
shipping_address_id TEXT NOT NULL,
|
|
113
|
+
total REAL NOT NULL,
|
|
114
114
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
115
|
-
payment_method TEXT NOT NULL DEFAULT '
|
|
115
|
+
payment_method TEXT NOT NULL DEFAULT 'credit_card',
|
|
116
116
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
117
|
-
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
118
117
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
|
119
|
-
FOREIGN KEY (
|
|
118
|
+
FOREIGN KEY (shipping_address_id) REFERENCES addresses(id)
|
|
120
119
|
)
|
|
121
120
|
`);
|
|
122
121
|
|
|
@@ -126,10 +125,8 @@ export function initializeSchema(): void {
|
|
|
126
125
|
id TEXT PRIMARY KEY,
|
|
127
126
|
order_id TEXT NOT NULL,
|
|
128
127
|
product_id TEXT NOT NULL,
|
|
129
|
-
product_name TEXT NOT NULL,
|
|
130
|
-
product_price REAL NOT NULL,
|
|
131
128
|
quantity INTEGER NOT NULL,
|
|
132
|
-
|
|
129
|
+
price REAL NOT NULL,
|
|
133
130
|
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE,
|
|
134
131
|
FOREIGN KEY (product_id) REFERENCES products(id)
|
|
135
132
|
)
|
|
@@ -15,8 +15,19 @@ async function bootstrap() {
|
|
|
15
15
|
// Create and start the MCP server from AppModule
|
|
16
16
|
const server = await McpApplicationFactory.create(AppModule);
|
|
17
17
|
|
|
18
|
-
// Start the server
|
|
19
|
-
|
|
18
|
+
// Start the server with dual transport (STDIO + HTTP)
|
|
19
|
+
// This allows both direct stdio connections and HTTP-based connections
|
|
20
|
+
// Note: Studio runs on 3000, Widgets on 3001, so MCP HTTP uses 3002
|
|
21
|
+
const port = parseInt(process.env.PORT || '3002');
|
|
22
|
+
await server.start('dual', {
|
|
23
|
+
port,
|
|
24
|
+
host: '0.0.0.0',
|
|
25
|
+
basePath: '/mcp',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
console.error(`\n🚀 Server started successfully (DUAL: STDIO + HTTP)`);
|
|
29
|
+
console.error(`📡 MCP Protocol: STDIO (for direct connections)`);
|
|
30
|
+
console.error(`🌐 HTTP Server: http://0.0.0.0:${port}/mcp`);
|
|
20
31
|
}
|
|
21
32
|
|
|
22
33
|
// Start the application
|
|
@@ -13,14 +13,14 @@ export class AddressesTools {
|
|
|
13
13
|
*/
|
|
14
14
|
@Tool({
|
|
15
15
|
name: 'add_address',
|
|
16
|
-
description: 'Add a new shipping address. Requires authentication.',
|
|
16
|
+
description: 'Add a new shipping address. Requires authentication. SMART TIP: If user mentions a well-known city, infer the state and country (e.g., Bangalore → Karnataka, India; New York → NY, USA; London → England, UK).',
|
|
17
17
|
inputSchema: z.object({
|
|
18
18
|
full_name: z.string().describe('Full name for delivery'),
|
|
19
19
|
street: z.string().describe('Street address'),
|
|
20
|
-
city: z.string().describe('City'),
|
|
21
|
-
state: z.string().describe('State/Province'),
|
|
22
|
-
zip_code: z.string().describe('ZIP/Postal code'),
|
|
23
|
-
country: z.string().default('USA').describe('Country'),
|
|
20
|
+
city: z.string().describe('City name (e.g., Bangalore, San Francisco, London)'),
|
|
21
|
+
state: z.string().optional().describe('State/Province (e.g., Karnataka, CA, England). Can be inferred from city in many cases.'),
|
|
22
|
+
zip_code: z.string().optional().describe('ZIP/Postal code (optional, can be left empty)'),
|
|
23
|
+
country: z.string().default('USA').describe('Country (e.g., India, USA, UK). Can be inferred from well-known cities.'),
|
|
24
24
|
phone: z.string().describe('Contact phone number'),
|
|
25
25
|
is_default: z.boolean().default(false).describe('Set as default address'),
|
|
26
26
|
}),
|
|
@@ -49,6 +49,44 @@ export class AddressesTools {
|
|
|
49
49
|
const userId = context.auth?.subject;
|
|
50
50
|
const addressId = uuidv4();
|
|
51
51
|
|
|
52
|
+
// Smart defaults for common locations
|
|
53
|
+
const cityLower = input.city?.toLowerCase() || '';
|
|
54
|
+
|
|
55
|
+
// If state not provided, try to infer from well-known cities
|
|
56
|
+
if (!input.state) {
|
|
57
|
+
const cityToState: Record<string, { state: string; country: string }> = {
|
|
58
|
+
'bangalore': { state: 'Karnataka', country: 'India' },
|
|
59
|
+
'bengaluru': { state: 'Karnataka', country: 'India' },
|
|
60
|
+
'mumbai': { state: 'Maharashtra', country: 'India' },
|
|
61
|
+
'delhi': { state: 'Delhi', country: 'India' },
|
|
62
|
+
'hyderabad': { state: 'Telangana', country: 'India' },
|
|
63
|
+
'chennai': { state: 'Tamil Nadu', country: 'India' },
|
|
64
|
+
'pune': { state: 'Maharashtra', country: 'India' },
|
|
65
|
+
'kolkata': { state: 'West Bengal', country: 'India' },
|
|
66
|
+
'san francisco': { state: 'CA', country: 'USA' },
|
|
67
|
+
'new york': { state: 'NY', country: 'USA' },
|
|
68
|
+
'los angeles': { state: 'CA', country: 'USA' },
|
|
69
|
+
'chicago': { state: 'IL', country: 'USA' },
|
|
70
|
+
'boston': { state: 'MA', country: 'USA' },
|
|
71
|
+
'seattle': { state: 'WA', country: 'USA' },
|
|
72
|
+
'london': { state: 'England', country: 'UK' },
|
|
73
|
+
'manchester': { state: 'England', country: 'UK' },
|
|
74
|
+
'toronto': { state: 'Ontario', country: 'Canada' },
|
|
75
|
+
'vancouver': { state: 'British Columbia', country: 'Canada' },
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const inferred = cityToState[cityLower];
|
|
79
|
+
if (inferred) {
|
|
80
|
+
input.state = inferred.state;
|
|
81
|
+
input.country = inferred.country;
|
|
82
|
+
context.logger.info(`Smart inference: ${input.city} → ${input.state}, ${input.country}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Set defaults if still missing
|
|
87
|
+
input.state = input.state || 'N/A';
|
|
88
|
+
input.zip_code = input.zip_code || '000000';
|
|
89
|
+
|
|
52
90
|
// If this is set as default, unset other defaults
|
|
53
91
|
if (input.is_default) {
|
|
54
92
|
db.prepare(`
|
|
@@ -72,12 +110,17 @@ export class AddressesTools {
|
|
|
72
110
|
input.is_default ? 1 : 0
|
|
73
111
|
);
|
|
74
112
|
|
|
75
|
-
context.logger.info(`Address added: ${input.city}, ${input.state}`);
|
|
113
|
+
context.logger.info(`Address added: ${input.city}, ${input.state}, ${input.country}`);
|
|
76
114
|
|
|
77
115
|
return {
|
|
78
116
|
message: 'Address added successfully!',
|
|
79
117
|
addressId,
|
|
80
118
|
isDefault: input.is_default,
|
|
119
|
+
address: {
|
|
120
|
+
city: input.city,
|
|
121
|
+
state: input.state,
|
|
122
|
+
country: input.country,
|
|
123
|
+
},
|
|
81
124
|
};
|
|
82
125
|
}
|
|
83
126
|
|
|
@@ -13,9 +13,9 @@ export class CartTools {
|
|
|
13
13
|
*/
|
|
14
14
|
@Tool({
|
|
15
15
|
name: 'add_to_cart',
|
|
16
|
-
description: 'Add a product to the shopping cart. Requires authentication.',
|
|
16
|
+
description: 'Add a product to the shopping cart. Requires authentication. IMPORTANT: Use the product ID from browse_products or get_product_details results. If user says "add apple", find the product with name "Apple" from previous results and use its ID.',
|
|
17
17
|
inputSchema: z.object({
|
|
18
|
-
product_id: z.string().describe('The ID of the product to add'),
|
|
18
|
+
product_id: z.string().describe('The ID of the product to add (e.g., "prod-3"). Get this from browse_products results by matching the product name.'),
|
|
19
19
|
quantity: z.number().min(1).default(1).describe('Quantity to add'),
|
|
20
20
|
}),
|
|
21
21
|
examples: {
|
|
@@ -77,13 +77,11 @@ export class CartTools {
|
|
|
77
77
|
|
|
78
78
|
return {
|
|
79
79
|
message: `Added to cart!`,
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
image_url: product.image_url,
|
|
86
|
-
},
|
|
80
|
+
product_id: input.product_id,
|
|
81
|
+
product_name: product.name,
|
|
82
|
+
price: product.price,
|
|
83
|
+
quantity: input.quantity,
|
|
84
|
+
image_url: product.image_url,
|
|
87
85
|
cartItemCount: cartCount.total || 0,
|
|
88
86
|
};
|
|
89
87
|
} else {
|
|
@@ -104,13 +102,11 @@ export class CartTools {
|
|
|
104
102
|
|
|
105
103
|
return {
|
|
106
104
|
message: `Added to cart!`,
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
image_url: product.image_url,
|
|
113
|
-
},
|
|
105
|
+
product_id: input.product_id,
|
|
106
|
+
product_name: product.name,
|
|
107
|
+
price: product.price,
|
|
108
|
+
quantity: input.quantity,
|
|
109
|
+
image_url: product.image_url,
|
|
114
110
|
cartItemCount: cartCount.total || 0,
|
|
115
111
|
};
|
|
116
112
|
}
|
|
@@ -156,7 +152,7 @@ export class CartTools {
|
|
|
156
152
|
const userId = context.auth?.subject;
|
|
157
153
|
|
|
158
154
|
const items: any[] = db.prepare(`
|
|
159
|
-
SELECT c.id, c.quantity, p.id as product_id, p.name, p.price, p.image_url
|
|
155
|
+
SELECT c.id, c.quantity, p.id as product_id, p.name, p.price, p.image_url, p.description, p.stock
|
|
160
156
|
FROM carts c
|
|
161
157
|
JOIN products p ON c.product_id = p.id
|
|
162
158
|
WHERE c.user_id = ?
|
|
@@ -13,10 +13,10 @@ export class OrdersTools {
|
|
|
13
13
|
*/
|
|
14
14
|
@Tool({
|
|
15
15
|
name: 'create_order',
|
|
16
|
-
description: 'Create an order from the current shopping cart. Requires authentication and at least one item in cart.',
|
|
16
|
+
description: 'Create an order from the current shopping cart. Requires authentication and at least one item in cart. A default shipping address will be automatically created if the user doesn\'t have one.',
|
|
17
17
|
inputSchema: z.object({
|
|
18
18
|
payment_method: z.enum(['credit_card', 'paypal', 'apple_pay']).describe('Payment method'),
|
|
19
|
-
shipping_address_id: z.string().optional().describe('Address ID for shipping.
|
|
19
|
+
shipping_address_id: z.string().optional().describe('Optional: Address ID for shipping. If not provided, uses or creates a default address automatically.'),
|
|
20
20
|
}),
|
|
21
21
|
examples: {
|
|
22
22
|
request: {
|
|
@@ -57,16 +57,21 @@ export class OrdersTools {
|
|
|
57
57
|
@Widget('order-confirmation')
|
|
58
58
|
@UseGuards(JWTGuard)
|
|
59
59
|
async createOrder(input: any, context: ExecutionContext) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
60
|
+
try {
|
|
61
|
+
const db = getDatabase();
|
|
62
|
+
const userId = context.auth?.subject;
|
|
63
|
+
|
|
64
|
+
context.logger.info(`Creating order for user: ${userId}, payment_method: ${input.payment_method}`);
|
|
65
|
+
|
|
66
|
+
// Get cart items
|
|
67
|
+
const cartItems: any[] = db.prepare(`
|
|
68
|
+
SELECT c.*, p.name, p.price, p.stock, p.image_url
|
|
69
|
+
FROM carts c
|
|
70
|
+
JOIN products p ON c.product_id = p.id
|
|
71
|
+
WHERE c.user_id = ?
|
|
72
|
+
`).all(userId);
|
|
73
|
+
|
|
74
|
+
context.logger.info(`Found ${cartItems.length} items in cart`);
|
|
70
75
|
|
|
71
76
|
if (cartItems.length === 0) {
|
|
72
77
|
throw new Error('Cart is empty. Add items to cart before creating an order.');
|
|
@@ -79,7 +84,7 @@ export class OrdersTools {
|
|
|
79
84
|
}
|
|
80
85
|
}
|
|
81
86
|
|
|
82
|
-
// Get shipping address
|
|
87
|
+
// Get shipping address (create a temporary one if none exists)
|
|
83
88
|
let addressId = input.shipping_address_id;
|
|
84
89
|
if (!addressId) {
|
|
85
90
|
const defaultAddress: any = db.prepare(`
|
|
@@ -87,10 +92,18 @@ export class OrdersTools {
|
|
|
87
92
|
`).get(userId);
|
|
88
93
|
|
|
89
94
|
if (!defaultAddress) {
|
|
90
|
-
|
|
95
|
+
// Create a temporary default address for the user
|
|
96
|
+
const tempAddressId = uuidv4();
|
|
97
|
+
db.prepare(`
|
|
98
|
+
INSERT INTO addresses (id, user_id, full_name, street, city, state, zip_code, country, phone, is_default)
|
|
99
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
100
|
+
`).run(tempAddressId, userId, 'Default User', '123 Main St', 'City', 'State', '00000', 'USA', '000-000-0000', 1);
|
|
101
|
+
|
|
102
|
+
context.logger.info('Created temporary default address for user');
|
|
103
|
+
addressId = tempAddressId;
|
|
104
|
+
} else {
|
|
105
|
+
addressId = defaultAddress.id;
|
|
91
106
|
}
|
|
92
|
-
|
|
93
|
-
addressId = defaultAddress.id;
|
|
94
107
|
}
|
|
95
108
|
|
|
96
109
|
// Calculate total
|
|
@@ -143,6 +156,10 @@ export class OrdersTools {
|
|
|
143
156
|
})),
|
|
144
157
|
},
|
|
145
158
|
};
|
|
159
|
+
} catch (error: any) {
|
|
160
|
+
context.logger.error(`Order creation failed: ${error.message}`, { error, stack: error.stack });
|
|
161
|
+
throw new Error(`Failed to create order: ${error.message}`);
|
|
162
|
+
}
|
|
146
163
|
}
|
|
147
164
|
|
|
148
165
|
/**
|
|
@@ -200,6 +217,11 @@ export class OrdersTools {
|
|
|
200
217
|
return {
|
|
201
218
|
orders,
|
|
202
219
|
totalOrders: orders.length,
|
|
220
|
+
pagination: {
|
|
221
|
+
page: 1,
|
|
222
|
+
totalPages: 1,
|
|
223
|
+
totalResults: orders.length,
|
|
224
|
+
},
|
|
203
225
|
};
|
|
204
226
|
}
|
|
205
227
|
|
|
@@ -39,10 +39,10 @@ export class ProductsTools {
|
|
|
39
39
|
*/
|
|
40
40
|
@Tool({
|
|
41
41
|
name: 'browse_products',
|
|
42
|
-
description: 'Browse available products. Can filter by category, search by name, and paginate results.',
|
|
42
|
+
description: 'Browse available products. Can filter by category, search by name, and paginate results. Returns products with their IDs which can be used with other tools like add_to_cart.',
|
|
43
43
|
inputSchema: z.object({
|
|
44
44
|
category: z.enum(['Electronics', 'Clothing', 'Home', 'Books', 'Sports', 'All']).default('All').describe('Filter by product category'),
|
|
45
|
-
search: z.string().optional().describe('Search products by name'),
|
|
45
|
+
search: z.string().optional().describe('Search products by name (e.g., "apple" will find products containing "apple")'),
|
|
46
46
|
page: z.number().min(1).default(1).describe('Page number for pagination'),
|
|
47
47
|
limit: z.number().min(1).max(50).default(10).describe('Number of products per page'),
|
|
48
48
|
}),
|
|
@@ -174,9 +174,9 @@ export class ProductsTools {
|
|
|
174
174
|
*/
|
|
175
175
|
@Tool({
|
|
176
176
|
name: 'get_product_details',
|
|
177
|
-
description: 'Get detailed information about a specific product by ID.',
|
|
177
|
+
description: 'Get detailed information about a specific product by ID. Use this if you need more details about a product from browse_products results.',
|
|
178
178
|
inputSchema: z.object({
|
|
179
|
-
product_id: z.string().describe('The ID of the product to retrieve'),
|
|
179
|
+
product_id: z.string().describe('The ID of the product to retrieve (e.g., "prod-3" from browse_products results)'),
|
|
180
180
|
}),
|
|
181
181
|
examples: {
|
|
182
182
|
request: {
|
|
@@ -5,8 +5,33 @@ import { OrderConfirmationData } from '../../types/tool-data';
|
|
|
5
5
|
import * as styles from '../../styles/ecommerce';
|
|
6
6
|
|
|
7
7
|
function OrderConfirmationWidget({ data }: { data: OrderConfirmationData }) {
|
|
8
|
+
// Handle missing or undefined data
|
|
9
|
+
if (!data) {
|
|
10
|
+
return (
|
|
11
|
+
<div style={styles.containerStyle}>
|
|
12
|
+
<div style={styles.cardStyle}>
|
|
13
|
+
<p style={{ color: styles.colors.error }}>No order data available</p>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
8
19
|
const { order, message } = data;
|
|
9
20
|
|
|
21
|
+
// Handle missing order
|
|
22
|
+
if (!order || !order.id) {
|
|
23
|
+
return (
|
|
24
|
+
<div style={styles.containerStyle}>
|
|
25
|
+
<div style={styles.cardStyle}>
|
|
26
|
+
<p style={{ color: styles.colors.error }}>Order data incomplete</p>
|
|
27
|
+
<pre style={{ fontSize: '12px', marginTop: '10px' }}>
|
|
28
|
+
{JSON.stringify(data, null, 2)}
|
|
29
|
+
</pre>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
10
35
|
return (
|
|
11
36
|
<div style={styles.containerStyle}>
|
|
12
37
|
<div style={{
|
|
@@ -5,8 +5,33 @@ import { ProductsGridData } from '../../types/tool-data';
|
|
|
5
5
|
import * as styles from '../../styles/ecommerce';
|
|
6
6
|
|
|
7
7
|
function ProductsGridWidget({ data }: { data: ProductsGridData }) {
|
|
8
|
+
// Handle missing or undefined data
|
|
9
|
+
if (!data) {
|
|
10
|
+
return (
|
|
11
|
+
<div style={styles.containerStyle}>
|
|
12
|
+
<div style={styles.cardStyle}>
|
|
13
|
+
<p style={{ color: styles.colors.error }}>No data available</p>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
8
19
|
const { products, pagination } = data;
|
|
9
20
|
|
|
21
|
+
// Handle missing products array
|
|
22
|
+
if (!products || !Array.isArray(products)) {
|
|
23
|
+
return (
|
|
24
|
+
<div style={styles.containerStyle}>
|
|
25
|
+
<div style={styles.cardStyle}>
|
|
26
|
+
<p style={{ color: styles.colors.error }}>Invalid product data</p>
|
|
27
|
+
<pre style={{ fontSize: '12px', marginTop: '10px' }}>
|
|
28
|
+
{JSON.stringify(data, null, 2)}
|
|
29
|
+
</pre>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
10
35
|
if (products.length === 0) {
|
|
11
36
|
return (
|
|
12
37
|
<div style={styles.containerStyle}>
|
|
@@ -152,7 +177,7 @@ function ProductsGridWidget({ data }: { data: ProductsGridData }) {
|
|
|
152
177
|
))}
|
|
153
178
|
</div>
|
|
154
179
|
|
|
155
|
-
{pagination.totalPages > 1 && (
|
|
180
|
+
{pagination && pagination.totalPages > 1 && (
|
|
156
181
|
<div style={{
|
|
157
182
|
marginTop: styles.spacing['2xl'],
|
|
158
183
|
textAlign: 'center',
|
|
@@ -43,7 +43,9 @@ npm run build
|
|
|
43
43
|
nitrostack dev
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
The server will start with API key authentication enabled.
|
|
46
|
+
The server will start with API key authentication enabled in dual transport mode (STDIO + HTTP on port 3002).
|
|
47
|
+
|
|
48
|
+
> 💡 **Dual Transport**: Your server exposes tools via both STDIO (for direct connections) and HTTP (for remote access on port 3002). Switch between transports in Studio → Settings.
|
|
47
49
|
|
|
48
50
|
### 2. Open NitroStack Studio
|
|
49
51
|
|
|
@@ -30,10 +30,18 @@ async function bootstrap() {
|
|
|
30
30
|
console.error(' - Protected tools require valid API key');
|
|
31
31
|
console.error(' - Public tools accessible without authentication\n');
|
|
32
32
|
|
|
33
|
-
// Start the MCP server
|
|
34
|
-
|
|
33
|
+
// Start the MCP server with dual transport (STDIO + HTTP)
|
|
34
|
+
// Note: Studio runs on 3000, Widgets on 3001, so MCP HTTP uses 3002
|
|
35
|
+
const port = parseInt(process.env.PORT || '3002');
|
|
36
|
+
await app.start('dual', {
|
|
37
|
+
port,
|
|
38
|
+
host: '0.0.0.0',
|
|
39
|
+
basePath: '/mcp',
|
|
40
|
+
});
|
|
35
41
|
|
|
36
|
-
console.error('🚀 Server started successfully!');
|
|
42
|
+
console.error('🚀 Server started successfully (DUAL: STDIO + HTTP)!');
|
|
43
|
+
console.error(`📡 MCP Protocol: STDIO (for direct connections)`);
|
|
44
|
+
console.error(`🌐 HTTP Server: http://0.0.0.0:${port}/mcp`);
|
|
37
45
|
console.error(' Open NitroStack Studio to test the server');
|
|
38
46
|
console.error(' Set your API key in Studio → Auth → API Key section\n');
|
|
39
47
|
|
|
@@ -46,10 +46,12 @@ npm run dev
|
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
This starts:
|
|
49
|
-
- **MCP Server** (
|
|
49
|
+
- **MCP Server** (dual transport: STDIO + HTTP on port 3002) - Hot reloads on code changes
|
|
50
50
|
- **Studio** on http://localhost:3000 - Visual testing environment
|
|
51
51
|
- **Widget Dev Server** on http://localhost:3001 - Hot module replacement
|
|
52
52
|
|
|
53
|
+
> 💡 **Dual Transport**: Your server exposes tools via both STDIO (for direct connections) and HTTP (for remote access on port 3002). Switch between transports in Studio → Settings.
|
|
54
|
+
|
|
53
55
|
The `nitrostack dev` command handles everything automatically:
|
|
54
56
|
- ✅ Auto-detects widget directory
|
|
55
57
|
- ✅ Installs dependencies (if needed)
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# API Key Authentication - Test Keys
|
|
2
|
-
# ⚠️ These are demo keys for testing only - DO NOT use in production!
|
|
3
|
-
|
|
4
|
-
# Test API Keys
|
|
5
|
-
API_KEY_1=sk_test_public_demo_key_12345
|
|
6
|
-
API_KEY_2=sk_test_admin_demo_key_67890
|
|
7
|
-
|
|
8
|
-
# You can add more keys by incrementing the number
|
|
9
|
-
# API_KEY_3=your_custom_key_here
|
|
10
|
-
# API_KEY_4=another_key_here
|
|
11
|
-
|
|
12
|
-
# Note: Keys are loaded automatically by ApiKeyModule
|
|
13
|
-
# The module reads all environment variables matching the pattern API_KEY_*
|
|
14
|
-
|
|
15
|
-
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "api-key-auth-mcp-server",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"lockfileVersion": 3,
|
|
5
|
-
"requires": true,
|
|
6
|
-
"packages": {
|
|
7
|
-
"": {
|
|
8
|
-
"name": "api-key-auth-mcp-server",
|
|
9
|
-
"version": "1.0.0",
|
|
10
|
-
"license": "MIT",
|
|
11
|
-
"dependencies": {
|
|
12
|
-
"nitrostack": "file:../../",
|
|
13
|
-
"zod": "^3.23.8"
|
|
14
|
-
},
|
|
15
|
-
"devDependencies": {
|
|
16
|
-
"@types/node": "^20",
|
|
17
|
-
"typescript": "^5"
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
"../..": {
|
|
21
|
-
"version": "1.0.0",
|
|
22
|
-
"license": "MIT",
|
|
23
|
-
"dependencies": {
|
|
24
|
-
"@google/generative-ai": "^0.24.1",
|
|
25
|
-
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
26
|
-
"bcryptjs": "^2.4.3",
|
|
27
|
-
"better-sqlite3": "^11.8.1",
|
|
28
|
-
"chokidar": "^3.6.0",
|
|
29
|
-
"commander": "^12.1.0",
|
|
30
|
-
"cors": "^2.8.5",
|
|
31
|
-
"dotenv": "^17.2.3",
|
|
32
|
-
"express": "^4.21.2",
|
|
33
|
-
"fs-extra": "^11.3.2",
|
|
34
|
-
"http-proxy-middleware": "^3.0.3",
|
|
35
|
-
"inquirer": "^12.10.0",
|
|
36
|
-
"jose": "^6.1.0",
|
|
37
|
-
"jsonwebtoken": "^9.0.2",
|
|
38
|
-
"open": "^10.2.0",
|
|
39
|
-
"openai": "^6.5.0",
|
|
40
|
-
"ora": "^9.0.0",
|
|
41
|
-
"reflect-metadata": "^0.2.1",
|
|
42
|
-
"uuid": "^11.0.5",
|
|
43
|
-
"winston": "^3.17.0",
|
|
44
|
-
"ws": "^8.18.3",
|
|
45
|
-
"zod": "^3.24.1",
|
|
46
|
-
"zod-to-json-schema": "^3.24.6"
|
|
47
|
-
},
|
|
48
|
-
"bin": {
|
|
49
|
-
"nitrostack": "dist/cli/index.js"
|
|
50
|
-
},
|
|
51
|
-
"devDependencies": {
|
|
52
|
-
"@types/bcryptjs": "^2.4.6",
|
|
53
|
-
"@types/better-sqlite3": "^7.6.12",
|
|
54
|
-
"@types/cors": "^2.8.19",
|
|
55
|
-
"@types/express": "^5.0.0",
|
|
56
|
-
"@types/fs-extra": "^11.0.4",
|
|
57
|
-
"@types/jest": "^29.5.14",
|
|
58
|
-
"@types/jsonwebtoken": "^9.0.7",
|
|
59
|
-
"@types/node": "^22.10.5",
|
|
60
|
-
"@types/react": "^19.2.2",
|
|
61
|
-
"@types/uuid": "^10.0.0",
|
|
62
|
-
"@types/ws": "^8.18.1",
|
|
63
|
-
"jest": "^29.7.0",
|
|
64
|
-
"ts-jest": "^29.2.5",
|
|
65
|
-
"typescript": "^5.7.2"
|
|
66
|
-
},
|
|
67
|
-
"engines": {
|
|
68
|
-
"node": ">=18.0.0"
|
|
69
|
-
},
|
|
70
|
-
"peerDependencies": {
|
|
71
|
-
"react": "^18.0.0 || ^19.0.0"
|
|
72
|
-
},
|
|
73
|
-
"peerDependenciesMeta": {
|
|
74
|
-
"react": {
|
|
75
|
-
"optional": true
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
"node_modules/@types/node": {
|
|
80
|
-
"version": "20.19.23",
|
|
81
|
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.23.tgz",
|
|
82
|
-
"integrity": "sha512-yIdlVVVHXpmqRhtyovZAcSy0MiPcYWGkoO4CGe/+jpP0hmNuihm4XhHbADpK++MsiLHP5MVlv+bcgdF99kSiFQ==",
|
|
83
|
-
"dev": true,
|
|
84
|
-
"license": "MIT",
|
|
85
|
-
"dependencies": {
|
|
86
|
-
"undici-types": "~6.21.0"
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
"node_modules/nitrostack": {
|
|
90
|
-
"resolved": "../..",
|
|
91
|
-
"link": true
|
|
92
|
-
},
|
|
93
|
-
"node_modules/typescript": {
|
|
94
|
-
"version": "5.9.3",
|
|
95
|
-
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
|
96
|
-
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
|
97
|
-
"dev": true,
|
|
98
|
-
"license": "Apache-2.0",
|
|
99
|
-
"bin": {
|
|
100
|
-
"tsc": "bin/tsc",
|
|
101
|
-
"tsserver": "bin/tsserver"
|
|
102
|
-
},
|
|
103
|
-
"engines": {
|
|
104
|
-
"node": ">=14.17"
|
|
105
|
-
}
|
|
106
|
-
},
|
|
107
|
-
"node_modules/undici-types": {
|
|
108
|
-
"version": "6.21.0",
|
|
109
|
-
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
|
110
|
-
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
|
111
|
-
"dev": true,
|
|
112
|
-
"license": "MIT"
|
|
113
|
-
},
|
|
114
|
-
"node_modules/zod": {
|
|
115
|
-
"version": "3.25.76",
|
|
116
|
-
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
|
117
|
-
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
|
118
|
-
"license": "MIT",
|
|
119
|
-
"funding": {
|
|
120
|
-
"url": "https://github.com/sponsors/colinhacks"
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|