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.
Files changed (43) hide show
  1. package/CHANGELOG.md +29 -295
  2. package/README.md +9 -1
  3. package/dist/cli/commands/dev.d.ts.map +1 -1
  4. package/dist/cli/commands/dev.js +2 -1
  5. package/dist/cli/commands/dev.js.map +1 -1
  6. package/dist/cli/mcp-dev-wrapper.js +30 -17
  7. package/dist/cli/mcp-dev-wrapper.js.map +1 -1
  8. package/dist/core/transports/http-server.d.ts.map +1 -1
  9. package/dist/core/transports/http-server.js +21 -1
  10. package/dist/core/transports/http-server.js.map +1 -1
  11. package/package.json +8 -6
  12. package/src/studio/app/api/chat/route.ts +12 -1
  13. package/src/studio/app/api/init/route.ts +28 -4
  14. package/src/studio/app/auth/page.tsx +13 -9
  15. package/src/studio/app/chat/page.tsx +544 -133
  16. package/src/studio/app/health/page.tsx +101 -99
  17. package/src/studio/app/layout.tsx +23 -3
  18. package/src/studio/app/page.tsx +61 -56
  19. package/src/studio/app/ping/page.tsx +13 -8
  20. package/src/studio/app/prompts/page.tsx +72 -70
  21. package/src/studio/app/resources/page.tsx +88 -86
  22. package/src/studio/app/settings/page.tsx +270 -0
  23. package/src/studio/components/Sidebar.tsx +197 -35
  24. package/src/studio/lib/http-client-transport.ts +222 -0
  25. package/src/studio/lib/llm-service.ts +97 -0
  26. package/src/studio/lib/log-manager.ts +76 -0
  27. package/src/studio/lib/mcp-client.ts +103 -13
  28. package/src/studio/package-lock.json +622 -189
  29. package/src/studio/package.json +1 -0
  30. package/templates/typescript-auth/README.md +3 -1
  31. package/templates/typescript-auth/src/db/database.ts +5 -8
  32. package/templates/typescript-auth/src/index.ts +13 -2
  33. package/templates/typescript-auth/src/modules/addresses/addresses.tools.ts +49 -6
  34. package/templates/typescript-auth/src/modules/cart/cart.tools.ts +13 -17
  35. package/templates/typescript-auth/src/modules/orders/orders.tools.ts +38 -16
  36. package/templates/typescript-auth/src/modules/products/products.tools.ts +4 -4
  37. package/templates/typescript-auth/src/widgets/app/order-confirmation/page.tsx +25 -0
  38. package/templates/typescript-auth/src/widgets/app/products-grid/page.tsx +26 -1
  39. package/templates/typescript-auth-api-key/README.md +3 -1
  40. package/templates/typescript-auth-api-key/src/index.ts +11 -3
  41. package/templates/typescript-starter/README.md +3 -1
  42. package/templates/typescript-auth-api-key/.env +0 -15
  43. package/templates/typescript-auth-api-key/package-lock.json +0 -124
@@ -111,37 +111,37 @@ export default function ResourcesPage() {
111
111
  }, [selectedExamples, openEnlargeModal, getSelectedExample]);
112
112
 
113
113
  return (
114
- <div className="min-h-screen bg-background p-8">
115
- {/* Header */}
116
- <div className="mb-8">
117
- <div className="flex items-center justify-between mb-6">
118
- <div className="flex items-center gap-3">
119
- <div className="w-12 h-12 rounded-lg bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center">
120
- <Package className="w-6 h-6 text-white" />
121
- </div>
122
- <div>
123
- <h1 className="text-3xl font-bold text-foreground">Resources</h1>
124
- <p className="text-muted-foreground mt-1">Browse MCP resources and UI widgets</p>
125
- </div>
114
+ <div className="fixed inset-0 flex flex-col bg-background" style={{ left: 'var(--sidebar-width, 15rem)' }}>
115
+ {/* Sticky Header */}
116
+ <div className="sticky top-0 z-10 border-b border-border/50 px-6 py-3 flex items-center justify-between bg-card/80 backdrop-blur-md shadow-sm">
117
+ <div className="flex items-center gap-3">
118
+ <div className="w-8 h-8 rounded-lg bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center shadow-md">
119
+ <Package className="w-5 h-5 text-white" strokeWidth={2.5} />
120
+ </div>
121
+ <div>
122
+ <h1 className="text-lg font-bold text-foreground">Resources</h1>
126
123
  </div>
127
- <button onClick={loadResources} className="btn btn-primary gap-2">
128
- <RefreshCw className="w-4 h-4" />
129
- Refresh
130
- </button>
131
124
  </div>
132
-
133
- <input
134
- type="text"
135
- placeholder="Search resources..."
136
- value={searchQuery}
137
- onChange={(e) => setSearchQuery(e.target.value)}
138
- className="input"
139
- />
125
+ <button onClick={loadResources} className="btn btn-primary text-sm px-4 py-2 gap-2">
126
+ <RefreshCw className="w-4 h-4" />
127
+ Refresh
128
+ </button>
140
129
  </div>
141
130
 
142
- {/* UI Widgets Section */}
143
- {(loadingWidgets || widgets.length > 0) && (
144
- <div className="mb-12">
131
+ {/* Content - ONLY this scrolls */}
132
+ <div className="flex-1 overflow-y-auto overflow-x-hidden">
133
+ <div className="max-w-7xl mx-auto px-6 py-6">
134
+ <input
135
+ type="text"
136
+ placeholder="Search resources..."
137
+ value={searchQuery}
138
+ onChange={(e) => setSearchQuery(e.target.value)}
139
+ className="input mb-6"
140
+ />
141
+
142
+ {/* UI Widgets Section */}
143
+ {(loadingWidgets || widgets.length > 0) && (
144
+ <div className="mb-12">
145
145
  <h2 className="text-2xl font-bold text-foreground mb-6 flex items-center gap-2">
146
146
  <Palette className="w-6 h-6 text-primary" />
147
147
  UI Widgets {widgets.length > 0 && `(${filteredWidgets.length})`}
@@ -240,73 +240,75 @@ export default function ResourcesPage() {
240
240
  ))}
241
241
  </div>
242
242
  )}
243
- </div>
244
- )}
243
+ </div>
244
+ )}
245
245
 
246
- {/* MCP Resources Section */}
247
- <div>
248
- <h2 className="text-2xl font-bold text-foreground mb-6 flex items-center gap-2">
249
- <Package className="w-6 h-6 text-primary" />
250
- MCP Resources {resources.length > 0 && `(${filteredResources.length})`}
251
- </h2>
252
-
253
- {/* Resources Grid */}
254
- {loading.resources ? (
255
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
256
- {[1, 2, 3].map((i) => (
257
- <div key={i} className="card skeleton h-64"></div>
258
- ))}
259
- </div>
260
- ) : filteredResources.length === 0 ? (
261
- <div className="empty-state">
262
- <Package className="empty-state-icon" />
263
- <p className="empty-state-title">
264
- {searchQuery ? 'No resources found' : 'No resources available'}
265
- </p>
266
- <p className="empty-state-description">
267
- {searchQuery ? 'Try a different search term' : 'No MCP resources have been registered'}
268
- </p>
269
- </div>
270
- ) : (
271
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
272
- {filteredResources.map((resource) => (
273
- <div key={resource.uri} className="card card-hover p-6 animate-fade-in">
274
- <div className="flex items-start justify-between mb-4">
275
- <div className="flex items-center gap-3">
276
- <div className="w-10 h-10 rounded-lg bg-blue-500/10 flex items-center justify-center">
277
- <Package className="w-5 h-5 text-blue-500" />
278
- </div>
279
- <div>
280
- <h3 className="font-semibold text-foreground">{resource.name}</h3>
281
- <span className="badge badge-primary text-xs mt-1">
282
- Resource
283
- </span>
246
+ {/* MCP Resources Section */}
247
+ <div>
248
+ <h2 className="text-2xl font-bold text-foreground mb-6 flex items-center gap-2">
249
+ <Package className="w-6 h-6 text-primary" />
250
+ MCP Resources {resources.length > 0 && `(${filteredResources.length})`}
251
+ </h2>
252
+
253
+ {/* Resources Grid */}
254
+ {loading.resources ? (
255
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
256
+ {[1, 2, 3].map((i) => (
257
+ <div key={i} className="card skeleton h-64"></div>
258
+ ))}
259
+ </div>
260
+ ) : filteredResources.length === 0 ? (
261
+ <div className="empty-state">
262
+ <Package className="empty-state-icon" />
263
+ <p className="empty-state-title">
264
+ {searchQuery ? 'No resources found' : 'No resources available'}
265
+ </p>
266
+ <p className="empty-state-description">
267
+ {searchQuery ? 'Try a different search term' : 'No MCP resources have been registered'}
268
+ </p>
269
+ </div>
270
+ ) : (
271
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
272
+ {filteredResources.map((resource) => (
273
+ <div key={resource.uri} className="card card-hover p-6 animate-fade-in">
274
+ <div className="flex items-start justify-between mb-4">
275
+ <div className="flex items-center gap-3">
276
+ <div className="w-10 h-10 rounded-lg bg-blue-500/10 flex items-center justify-center">
277
+ <Package className="w-5 h-5 text-blue-500" />
278
+ </div>
279
+ <div>
280
+ <h3 className="font-semibold text-foreground">{resource.name}</h3>
281
+ <span className="badge badge-primary text-xs mt-1">
282
+ Resource
283
+ </span>
284
+ </div>
285
+ </div>
284
286
  </div>
285
- </div>
286
- </div>
287
287
 
288
- <p className="text-sm text-muted-foreground mb-4 line-clamp-2">
289
- {resource.description || 'No description'}
290
- </p>
288
+ <p className="text-sm text-muted-foreground mb-4 line-clamp-2">
289
+ {resource.description || 'No description'}
290
+ </p>
291
291
 
292
- <div className="bg-muted/30 rounded-lg p-3 border border-border">
293
- <p className="text-xs text-muted-foreground mb-1 font-semibold uppercase tracking-wide">URI</p>
294
- <p className="text-xs text-foreground font-mono truncate">
295
- {resource.uri}
296
- </p>
297
- </div>
292
+ <div className="bg-muted/30 rounded-lg p-3 border border-border">
293
+ <p className="text-xs text-muted-foreground mb-1 font-semibold uppercase tracking-wide">URI</p>
294
+ <p className="text-xs text-foreground font-mono truncate">
295
+ {resource.uri}
296
+ </p>
297
+ </div>
298
298
 
299
- {resource.mimeType && (
300
- <div className="mt-3">
301
- <span className="badge badge-secondary text-xs">
302
- {resource.mimeType}
303
- </span>
299
+ {resource.mimeType && (
300
+ <div className="mt-3">
301
+ <span className="badge badge-secondary text-xs">
302
+ {resource.mimeType}
303
+ </span>
304
+ </div>
305
+ )}
304
306
  </div>
305
- )}
307
+ ))}
306
308
  </div>
307
- ))}
309
+ )}
308
310
  </div>
309
- )}
311
+ </div>
310
312
  </div>
311
313
  </div>
312
314
  );
@@ -0,0 +1,270 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+ import { Settings as SettingsIcon, Wifi, CheckCircle, AlertCircle } from 'lucide-react';
5
+
6
+ export default function SettingsPage() {
7
+ const [transport, setTransport] = useState<'stdio' | 'http'>('stdio');
8
+ const [httpUrl, setHttpUrl] = useState('http://localhost:3002');
9
+ const [httpBasePath, setHttpBasePath] = useState('/mcp');
10
+ const [connecting, setConnecting] = useState(false);
11
+ const [connectionStatus, setConnectionStatus] = useState<'idle' | 'success' | 'error'>('idle');
12
+ const [errorMessage, setErrorMessage] = useState('');
13
+
14
+ useEffect(() => {
15
+ // Load saved settings from localStorage
16
+ const savedTransport = localStorage.getItem('mcp_transport');
17
+ const savedHttpUrl = localStorage.getItem('mcp_http_url');
18
+ const savedHttpBasePath = localStorage.getItem('mcp_http_base_path');
19
+
20
+ if (savedTransport) setTransport(savedTransport as 'stdio' | 'http');
21
+ if (savedHttpUrl) setHttpUrl(savedHttpUrl);
22
+ if (savedHttpBasePath) setHttpBasePath(savedHttpBasePath);
23
+ }, []);
24
+
25
+ const handleSaveSettings = async () => {
26
+ setConnecting(true);
27
+ setConnectionStatus('idle');
28
+ setErrorMessage('');
29
+
30
+ try {
31
+ // Save settings to localStorage
32
+ localStorage.setItem('mcp_transport', transport);
33
+ if (transport === 'http') {
34
+ localStorage.setItem('mcp_http_url', httpUrl);
35
+ localStorage.setItem('mcp_http_base_path', httpBasePath);
36
+ }
37
+
38
+ // Try to reconnect with new settings
39
+ const response = await fetch('/api/init', {
40
+ method: 'POST',
41
+ headers: { 'Content-Type': 'application/json' },
42
+ body: JSON.stringify({
43
+ transport,
44
+ baseUrl: httpUrl,
45
+ basePath: httpBasePath,
46
+ }),
47
+ });
48
+
49
+ if (!response.ok) {
50
+ const error = await response.json();
51
+ throw new Error(error.error || 'Failed to connect');
52
+ }
53
+
54
+ const data = await response.json();
55
+ setConnectionStatus('success');
56
+ console.log('✅ Settings saved and connection established:', data);
57
+ } catch (error: any) {
58
+ console.error('❌ Failed to save settings:', error);
59
+ setConnectionStatus('error');
60
+ setErrorMessage(error.message || 'Failed to connect');
61
+ } finally {
62
+ setConnecting(false);
63
+ }
64
+ };
65
+
66
+ return (
67
+ <div className="fixed inset-0 flex flex-col bg-background" style={{ left: 'var(--sidebar-width, 15rem)' }}>
68
+ {/* Sticky Header */}
69
+ <div className="sticky top-0 z-10 border-b border-border/50 px-6 py-3 flex items-center justify-between bg-card/80 backdrop-blur-md shadow-sm">
70
+ <div className="flex items-center gap-3">
71
+ <div className="w-8 h-8 rounded-lg bg-gradient-to-br from-primary to-amber-500 flex items-center justify-center shadow-md">
72
+ <SettingsIcon className="w-5 h-5 text-white" strokeWidth={2.5} />
73
+ </div>
74
+ <div>
75
+ <h1 className="text-lg font-bold text-foreground">Settings</h1>
76
+ </div>
77
+ </div>
78
+ </div>
79
+
80
+ {/* Content - ONLY this scrolls */}
81
+ <div className="flex-1 overflow-y-auto overflow-x-hidden">
82
+ <div className="max-w-4xl mx-auto px-6 py-6">
83
+
84
+ {/* Settings Card */}
85
+ <div className="max-w-2xl">
86
+ <div className="card p-6">
87
+ <h2 className="text-xl font-semibold text-foreground mb-6">Transport Configuration</h2>
88
+
89
+ {/* Transport Type Selection */}
90
+ <div className="mb-6">
91
+ <label className="block text-sm font-medium text-foreground mb-3">
92
+ Transport Type
93
+ </label>
94
+ <div className="grid grid-cols-2 gap-4">
95
+ <button
96
+ onClick={() => setTransport('stdio')}
97
+ className={`p-4 rounded-lg border-2 transition-all text-left ${
98
+ transport === 'stdio'
99
+ ? 'border-primary bg-primary/10'
100
+ : 'border-border hover:border-primary/50'
101
+ }`}
102
+ >
103
+ <div className="flex items-center gap-3 mb-2">
104
+ <div className={`w-3 h-3 rounded-full ${transport === 'stdio' ? 'bg-primary' : 'bg-muted'}`} />
105
+ <h3 className="font-semibold text-foreground">STDIO</h3>
106
+ </div>
107
+ <p className="text-sm text-muted-foreground">
108
+ Direct process communication (default)
109
+ </p>
110
+ </button>
111
+
112
+ <button
113
+ onClick={() => setTransport('http')}
114
+ className={`p-4 rounded-lg border-2 transition-all text-left ${
115
+ transport === 'http'
116
+ ? 'border-primary bg-primary/10'
117
+ : 'border-border hover:border-primary/50'
118
+ }`}
119
+ >
120
+ <div className="flex items-center gap-3 mb-2">
121
+ <div className={`w-3 h-3 rounded-full ${transport === 'http' ? 'bg-primary' : 'bg-muted'}`} />
122
+ <h3 className="font-semibold text-foreground">HTTP</h3>
123
+ </div>
124
+ <p className="text-sm text-muted-foreground">
125
+ HTTP/SSE transport for remote servers
126
+ </p>
127
+ </button>
128
+ </div>
129
+ </div>
130
+
131
+ {/* HTTP Configuration (shown only when HTTP is selected) */}
132
+ {transport === 'http' && (
133
+ <div className="space-y-4 mb-6 p-4 bg-muted/30 rounded-lg border border-border">
134
+ <div>
135
+ <label className="block text-sm font-medium text-foreground mb-2">
136
+ Server URL
137
+ </label>
138
+ <input
139
+ type="text"
140
+ value={httpUrl}
141
+ onChange={(e) => setHttpUrl(e.target.value)}
142
+ placeholder="http://localhost:3002"
143
+ className="input"
144
+ />
145
+ <p className="text-xs text-muted-foreground mt-1">
146
+ The base URL of your MCP HTTP server (default: port 3002)
147
+ </p>
148
+ </div>
149
+
150
+ <div>
151
+ <label className="block text-sm font-medium text-foreground mb-2">
152
+ Base Path
153
+ </label>
154
+ <input
155
+ type="text"
156
+ value={httpBasePath}
157
+ onChange={(e) => setHttpBasePath(e.target.value)}
158
+ placeholder="/mcp"
159
+ className="input"
160
+ />
161
+ <p className="text-xs text-muted-foreground mt-1">
162
+ The base path for MCP endpoints
163
+ </p>
164
+ </div>
165
+ </div>
166
+ )}
167
+
168
+ {/* Port Allocation Info */}
169
+ <div className="mb-6 p-4 bg-blue-500/10 rounded-lg border border-blue-500/20">
170
+ <h4 className="font-semibold text-foreground mb-2">Port Allocation</h4>
171
+ <div className="space-y-1 text-sm">
172
+ <div className="flex items-center gap-2">
173
+ <span className="text-muted-foreground">Studio UI:</span>
174
+ <code className="px-2 py-0.5 bg-muted rounded text-foreground">Port 3000</code>
175
+ </div>
176
+ <div className="flex items-center gap-2">
177
+ <span className="text-muted-foreground">Widget Server:</span>
178
+ <code className="px-2 py-0.5 bg-muted rounded text-foreground">Port 3001</code>
179
+ </div>
180
+ <div className="flex items-center gap-2">
181
+ <span className="text-muted-foreground">MCP HTTP Server:</span>
182
+ <code className="px-2 py-0.5 bg-primary/20 rounded text-primary font-semibold">Port 3002</code>
183
+ </div>
184
+ </div>
185
+ </div>
186
+
187
+ {/* Info Box */}
188
+ <div className="mb-6 p-4 bg-primary/5 rounded-lg border border-primary/20">
189
+ <div className="flex gap-3">
190
+ <Wifi className="w-5 h-5 text-primary flex-shrink-0 mt-0.5" />
191
+ <div>
192
+ <h4 className="font-semibold text-foreground mb-1">Transport Information</h4>
193
+ <p className="text-sm text-muted-foreground mb-2">
194
+ {transport === 'stdio' ? (
195
+ <>
196
+ <strong>STDIO</strong> (Default): Studio spawns the MCP server as a child process and communicates
197
+ via standard input/output. The MCP server still runs dual transport (STDIO + HTTP on port 3002).
198
+ </>
199
+ ) : (
200
+ <>
201
+ <strong>HTTP</strong>: Studio connects to the running MCP HTTP server on port 3002.
202
+ Use this when you want to inspect an already-running server or connect remotely.
203
+ </>
204
+ )}
205
+ </p>
206
+ {transport === 'http' && (
207
+ <p className="text-xs text-amber-600 dark:text-amber-500">
208
+ ⚠️ Make sure your MCP server is running with <code>npm run dev</code> or <code>npm start</code>
209
+ </p>
210
+ )}
211
+ </div>
212
+ </div>
213
+ </div>
214
+
215
+ {/* Connection Status */}
216
+ {connectionStatus !== 'idle' && (
217
+ <div className={`mb-6 p-4 rounded-lg border ${
218
+ connectionStatus === 'success'
219
+ ? 'bg-emerald-500/10 border-emerald-500/20'
220
+ : 'bg-rose-500/10 border-rose-500/20'
221
+ }`}>
222
+ <div className="flex gap-3">
223
+ {connectionStatus === 'success' ? (
224
+ <>
225
+ <CheckCircle className="w-5 h-5 text-emerald-500 flex-shrink-0 mt-0.5" />
226
+ <div>
227
+ <h4 className="font-semibold text-emerald-500 mb-1">Connected</h4>
228
+ <p className="text-sm text-emerald-600">
229
+ Settings saved and connection established successfully.
230
+ </p>
231
+ </div>
232
+ </>
233
+ ) : (
234
+ <>
235
+ <AlertCircle className="w-5 h-5 text-rose-500 flex-shrink-0 mt-0.5" />
236
+ <div>
237
+ <h4 className="font-semibold text-rose-500 mb-1">Connection Failed</h4>
238
+ <p className="text-sm text-rose-600">
239
+ {errorMessage || 'Failed to establish connection with the selected transport.'}
240
+ </p>
241
+ </div>
242
+ </>
243
+ )}
244
+ </div>
245
+ </div>
246
+ )}
247
+
248
+ {/* Save Button */}
249
+ <button
250
+ onClick={handleSaveSettings}
251
+ disabled={connecting}
252
+ className="btn btn-primary w-full"
253
+ >
254
+ {connecting ? (
255
+ <>
256
+ <div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
257
+ Connecting...
258
+ </>
259
+ ) : (
260
+ 'Save & Connect'
261
+ )}
262
+ </button>
263
+ </div>
264
+ </div>
265
+ </div>
266
+ </div>
267
+ </div>
268
+ );
269
+ }
270
+