nitrostack 1.0.43 → 1.0.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nitrostack",
3
- "version": "1.0.43",
3
+ "version": "1.0.45",
4
4
  "description": "NitroStack - Build powerful MCP servers with TypeScript",
5
5
  "type": "module",
6
6
  "main": "dist/core/index.js",
@@ -15,7 +15,7 @@ export default function ToolsPage() {
15
15
  const [toolArgs, setToolArgs] = useState<Record<string, any>>({});
16
16
  const [toolResult, setToolResult] = useState<any>(null);
17
17
  const [executingTool, setExecutingTool] = useState(false);
18
-
18
+
19
19
  // Get effective token - check both jwtToken and OAuth token
20
20
  const effectiveToken = jwtToken || oauthState?.currentToken;
21
21
 
@@ -164,162 +164,162 @@ export default function ToolsPage() {
164
164
 
165
165
  {/* Tool Executor Modal */}
166
166
  {selectedTool && (
167
+ <div
168
+ className="fixed inset-0 z-50 flex items-center justify-center animate-fade-in"
169
+ style={{ backgroundColor: 'rgba(0, 0, 0, 0.85)' }}
170
+ onClick={() => setSelectedTool(null)}
171
+ >
167
172
  <div
168
- className="fixed inset-0 z-50 flex items-center justify-center animate-fade-in"
169
- style={{ backgroundColor: 'rgba(0, 0, 0, 0.85)' }}
170
- onClick={() => setSelectedTool(null)}
173
+ className="w-[700px] max-h-[90vh] overflow-auto rounded-2xl border border-border p-6 bg-card shadow-2xl animate-scale-in"
174
+ onClick={(e) => e.stopPropagation()}
171
175
  >
172
- <div
173
- className="w-[700px] max-h-[90vh] overflow-auto rounded-2xl border border-border p-6 bg-card shadow-2xl animate-scale-in"
174
- onClick={(e) => e.stopPropagation()}
175
- >
176
- <div className="flex items-center justify-between mb-4">
177
- <div className="flex items-center gap-3">
178
- <div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center">
179
- <Wrench className="w-5 h-5 text-primary" />
180
- </div>
181
- <h2 className="text-xl font-bold text-foreground">{selectedTool.name}</h2>
176
+ <div className="flex items-center justify-between mb-4">
177
+ <div className="flex items-center gap-3">
178
+ <div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center">
179
+ <Wrench className="w-5 h-5 text-primary" />
182
180
  </div>
183
- <button
184
- onClick={() => setSelectedTool(null)}
185
- className="btn btn-ghost w-10 h-10 p-0"
186
- >
187
- <X className="w-5 h-5" />
188
- </button>
181
+ <h2 className="text-xl font-bold text-foreground">{selectedTool.name}</h2>
189
182
  </div>
183
+ <button
184
+ onClick={() => setSelectedTool(null)}
185
+ className="btn btn-ghost w-10 h-10 p-0"
186
+ >
187
+ <X className="w-5 h-5" />
188
+ </button>
189
+ </div>
190
190
 
191
- <p className="text-sm text-muted-foreground mb-6">
192
- {selectedTool.description || 'No description'}
193
- </p>
191
+ <p className="text-sm text-muted-foreground mb-6">
192
+ {selectedTool.description || 'No description'}
193
+ </p>
194
194
 
195
- <form onSubmit={handleSubmitTool}>
196
- {/* Generate form inputs from schema */}
197
- {selectedTool.inputSchema?.properties && typeof selectedTool.inputSchema.properties === 'object' && Object.keys(selectedTool.inputSchema.properties).length > 0 ? (
198
- <>
199
- {Object.entries(selectedTool.inputSchema.properties).map(([key, prop]: [string, any]) => {
200
- const isRequired = selectedTool.inputSchema?.required?.includes(key);
201
-
202
- // Handle different input types
203
- if (prop.enum) {
204
- // Enum/Select field
205
- return (
206
- <div key={key} className="mb-4">
207
- <label className="block text-sm font-medium text-foreground mb-2">
208
- {prop.title || key}
209
- {isRequired && <span className="text-destructive ml-1">*</span>}
210
- </label>
211
- <select
212
- className="input"
213
- value={toolArgs[key] || prop.default || ''}
214
- onChange={(e) => setToolArgs({ ...toolArgs, [key]: e.target.value })}
215
- required={isRequired}
216
- >
217
- <option value="">Select...</option>
218
- {prop.enum.map((val: any) => (
219
- <option key={val} value={val}>
220
- {val}
221
- </option>
222
- ))}
223
- </select>
224
- {prop.description && (
225
- <p className="text-xs text-muted-foreground mt-1">{prop.description}</p>
226
- )}
227
- </div>
228
- );
229
- } else if (prop.type === 'boolean') {
230
- // Checkbox field
231
- return (
232
- <div key={key} className="mb-4">
233
- <label className="flex items-center gap-2 cursor-pointer">
234
- <input
235
- type="checkbox"
236
- className="w-4 h-4"
237
- checked={toolArgs[key] || false}
238
- onChange={(e) => setToolArgs({ ...toolArgs, [key]: e.target.checked })}
239
- />
240
- <span className="text-sm font-medium text-foreground">
241
- {prop.title || key}
242
- {isRequired && <span className="text-destructive ml-1">*</span>}
243
- </span>
244
- </label>
245
- {prop.description && (
246
- <p className="text-xs text-muted-foreground mt-1 ml-6">{prop.description}</p>
247
- )}
248
- </div>
249
- );
250
- } else {
251
- // Text/Number field
252
- return (
253
- <div key={key} className="mb-4">
254
- <label className="block text-sm font-medium text-foreground mb-2">
255
- {prop.title || key}
256
- {isRequired && <span className="text-destructive ml-1">*</span>}
257
- </label>
195
+ <form onSubmit={handleSubmitTool}>
196
+ {/* Generate form inputs from schema */}
197
+ {selectedTool.inputSchema?.properties && typeof selectedTool.inputSchema.properties === 'object' && Object.keys(selectedTool.inputSchema.properties).length > 0 ? (
198
+ <>
199
+ {Object.entries(selectedTool.inputSchema.properties).map(([key, prop]: [string, any]) => {
200
+ const isRequired = selectedTool.inputSchema?.required?.includes(key);
201
+
202
+ // Handle different input types
203
+ if (prop.enum) {
204
+ // Enum/Select field
205
+ return (
206
+ <div key={key} className="mb-4">
207
+ <label className="block text-sm font-medium text-foreground mb-2">
208
+ {prop.title || key}
209
+ {isRequired && <span className="text-destructive ml-1">*</span>}
210
+ </label>
211
+ <select
212
+ className="input"
213
+ value={toolArgs[key] || prop.default || ''}
214
+ onChange={(e) => setToolArgs({ ...toolArgs, [key]: e.target.value })}
215
+ required={isRequired}
216
+ >
217
+ <option value="">Select...</option>
218
+ {prop.enum.map((val: any) => (
219
+ <option key={val} value={val}>
220
+ {val}
221
+ </option>
222
+ ))}
223
+ </select>
224
+ {prop.description && (
225
+ <p className="text-xs text-muted-foreground mt-1">{prop.description}</p>
226
+ )}
227
+ </div>
228
+ );
229
+ } else if (prop.type === 'boolean') {
230
+ // Checkbox field
231
+ return (
232
+ <div key={key} className="mb-4">
233
+ <label className="flex items-center gap-2 cursor-pointer">
258
234
  <input
259
- type={prop.type === 'number' || prop.type === 'integer' ? 'number' : 'text'}
260
- className="input"
261
- value={toolArgs[key] || prop.default || ''}
262
- onChange={(e) => {
263
- const value = prop.type === 'number' || prop.type === 'integer'
264
- ? (e.target.value ? Number(e.target.value) : '')
265
- : e.target.value;
266
- setToolArgs({ ...toolArgs, [key]: value });
267
- }}
268
- required={isRequired}
269
- placeholder={prop.description}
270
- min={prop.minimum}
271
- max={prop.maximum}
235
+ type="checkbox"
236
+ className="w-4 h-4"
237
+ checked={toolArgs[key] || false}
238
+ onChange={(e) => setToolArgs({ ...toolArgs, [key]: e.target.checked })}
272
239
  />
273
- {prop.description && (
274
- <p className="text-xs text-muted-foreground mt-1">{prop.description}</p>
275
- )}
276
- </div>
277
- );
278
- }
279
- })}
280
- </>
281
- ) : (
282
- <div className="mb-4 p-4 bg-muted/30 rounded-lg border border-border">
283
- <p className="text-sm text-muted-foreground">No input required</p>
284
- </div>
285
- )}
240
+ <span className="text-sm font-medium text-foreground">
241
+ {prop.title || key}
242
+ {isRequired && <span className="text-destructive ml-1">*</span>}
243
+ </span>
244
+ </label>
245
+ {prop.description && (
246
+ <p className="text-xs text-muted-foreground mt-1 ml-6">{prop.description}</p>
247
+ )}
248
+ </div>
249
+ );
250
+ } else {
251
+ // Text/Number field
252
+ return (
253
+ <div key={key} className="mb-4">
254
+ <label className="block text-sm font-medium text-foreground mb-2">
255
+ {prop.title || key}
256
+ {isRequired && <span className="text-destructive ml-1">*</span>}
257
+ </label>
258
+ <input
259
+ type={prop.type === 'number' || prop.type === 'integer' ? 'number' : 'text'}
260
+ className="input"
261
+ value={toolArgs[key] || prop.default || ''}
262
+ onChange={(e) => {
263
+ const value = prop.type === 'number' || prop.type === 'integer'
264
+ ? (e.target.value ? Number(e.target.value) : '')
265
+ : e.target.value;
266
+ setToolArgs({ ...toolArgs, [key]: value });
267
+ }}
268
+ required={isRequired}
269
+ placeholder={prop.description}
270
+ min={prop.minimum}
271
+ max={prop.maximum}
272
+ />
273
+ {prop.description && (
274
+ <p className="text-xs text-muted-foreground mt-1">{prop.description}</p>
275
+ )}
276
+ </div>
277
+ );
278
+ }
279
+ })}
280
+ </>
281
+ ) : (
282
+ <div className="mb-4 p-4 bg-muted/30 rounded-lg border border-border">
283
+ <p className="text-sm text-muted-foreground">No input required</p>
284
+ </div>
285
+ )}
286
286
 
287
- <button
288
- type="submit"
289
- className="btn btn-primary w-full gap-2"
290
- disabled={executingTool}
291
- >
292
- <Play className="w-4 h-4" />
293
- {executingTool ? 'Executing...' : 'Execute Tool'}
294
- </button>
295
- </form>
287
+ <button
288
+ type="submit"
289
+ className="btn btn-primary w-full gap-2"
290
+ disabled={executingTool}
291
+ >
292
+ <Play className="w-4 h-4" />
293
+ {executingTool ? 'Executing...' : 'Execute Tool'}
294
+ </button>
295
+ </form>
296
+
297
+ {/* Result */}
298
+ {toolResult && (
299
+ <div className="mt-6 space-y-4">
300
+ <div>
301
+ <h3 className="font-semibold text-foreground mb-3">Result:</h3>
302
+ <pre className="bg-muted/30 p-4 rounded-lg text-sm overflow-auto max-h-64 text-foreground font-mono border border-border">
303
+ {JSON.stringify(toolResult, null, 2)}
304
+ </pre>
305
+ </div>
296
306
 
297
- {/* Result */}
298
- {toolResult && (
299
- <div className="mt-6 space-y-4">
300
- <div>
301
- <h3 className="font-semibold text-foreground mb-3">Result:</h3>
302
- <pre className="bg-muted/30 p-4 rounded-lg text-sm overflow-auto max-h-64 text-foreground font-mono border border-border">
303
- {JSON.stringify(toolResult, null, 2)}
304
- </pre>
305
- </div>
306
-
307
- {/* Widget UI Rendering */}
308
- {(() => {
309
- // Get widget URI from multiple sources (same as ToolCard)
310
- const widgetUri =
311
- selectedTool.widget?.route ||
312
- selectedTool.outputTemplate ||
313
- selectedTool._meta?.['ui/template'] ||
314
- selectedTool._meta?.['openai/outputTemplate'];
315
-
316
- return widgetUri && toolResult ? (
317
- <div>
318
- <h3 className="font-semibold text-foreground mb-3">UI Widget:</h3>
319
- <div className="border border-border rounded-lg overflow-hidden h-64 bg-background shadow-inner">
320
- <WidgetRenderer
321
- uri={widgetUri}
322
- data={(() => {
307
+ {/* Widget UI Rendering */}
308
+ {(() => {
309
+ // Get widget URI from multiple sources (same as ToolCard)
310
+ const widgetUri =
311
+ selectedTool.widget?.route ||
312
+ selectedTool.outputTemplate ||
313
+ selectedTool._meta?.['ui/template'] ||
314
+ selectedTool._meta?.['openai/outputTemplate'];
315
+
316
+ return widgetUri && toolResult ? (
317
+ <div>
318
+ <h3 className="font-semibold text-foreground mb-3">UI Widget:</h3>
319
+ <div className="border border-border rounded-lg overflow-hidden h-64 bg-background shadow-inner">
320
+ <WidgetRenderer
321
+ uri={widgetUri}
322
+ data={(() => {
323
323
  // Try to parse JSON from content[0].text, otherwise use raw result
324
324
  if (toolResult.content?.[0]?.text) {
325
325
  try {
@@ -335,17 +335,17 @@ export default function ToolsPage() {
335
335
  }
336
336
  return toolResult;
337
337
  })()}
338
- className="w-full h-full"
339
- />
340
- </div>
338
+ className="w-full h-full"
339
+ />
341
340
  </div>
342
- ) : null;
343
- })()}
344
- </div>
345
- )}
346
- </div>
341
+ </div>
342
+ ) : null;
343
+ })()}
344
+ </div>
345
+ )}
347
346
  </div>
348
- )}
347
+ </div>
348
+ )}
349
349
  </>
350
350
  );
351
351
  }
@@ -26,6 +26,6 @@ export function middleware(request: NextRequest) {
26
26
  }
27
27
 
28
28
  export const config = {
29
- matcher: ['/widgets/:path*'],
29
+ // matcher: ['/widgets/:path*'],
30
30
  };
31
31