nitrostack 1.0.15 → 1.0.17

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 (55) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/commands/dev.d.ts.map +1 -1
  3. package/dist/cli/commands/dev.js +2 -1
  4. package/dist/cli/commands/dev.js.map +1 -1
  5. package/dist/cli/mcp-dev-wrapper.js +30 -17
  6. package/dist/cli/mcp-dev-wrapper.js.map +1 -1
  7. package/dist/core/app-decorator.js +2 -2
  8. package/dist/core/app-decorator.js.map +1 -1
  9. package/dist/core/builders.js +2 -2
  10. package/dist/core/builders.js.map +1 -1
  11. package/dist/core/resource.js +1 -1
  12. package/dist/core/resource.js.map +1 -1
  13. package/dist/core/server.js +2 -2
  14. package/dist/core/server.js.map +1 -1
  15. package/dist/core/transports/http-server.d.ts.map +1 -1
  16. package/dist/core/transports/http-server.js +21 -1
  17. package/dist/core/transports/http-server.js.map +1 -1
  18. package/dist/core/types.d.ts +1 -1
  19. package/dist/core/types.d.ts.map +1 -1
  20. package/package.json +1 -1
  21. package/src/studio/app/api/chat/route.ts +155 -28
  22. package/src/studio/app/api/init/route.ts +28 -4
  23. package/src/studio/app/auth/page.tsx +13 -9
  24. package/src/studio/app/chat/page.tsx +599 -133
  25. package/src/studio/app/health/page.tsx +101 -99
  26. package/src/studio/app/layout.tsx +24 -4
  27. package/src/studio/app/page.tsx +61 -56
  28. package/src/studio/app/ping/page.tsx +13 -8
  29. package/src/studio/app/prompts/page.tsx +72 -70
  30. package/src/studio/app/resources/page.tsx +88 -86
  31. package/src/studio/app/settings/page.tsx +270 -0
  32. package/src/studio/components/EnlargeModal.tsx +21 -15
  33. package/src/studio/components/LogMessage.tsx +153 -0
  34. package/src/studio/components/MarkdownRenderer.tsx +410 -0
  35. package/src/studio/components/Sidebar.tsx +197 -35
  36. package/src/studio/components/ToolCard.tsx +27 -9
  37. package/src/studio/components/WidgetRenderer.tsx +4 -2
  38. package/src/studio/lib/http-client-transport.ts +222 -0
  39. package/src/studio/lib/llm-service.ts +119 -0
  40. package/src/studio/lib/log-manager.ts +76 -0
  41. package/src/studio/lib/mcp-client.ts +103 -13
  42. package/src/studio/package-lock.json +3129 -0
  43. package/src/studio/package.json +1 -0
  44. package/templates/typescript-auth/README.md +3 -1
  45. package/templates/typescript-auth/src/db/database.ts +5 -8
  46. package/templates/typescript-auth/src/index.ts +13 -2
  47. package/templates/typescript-auth/src/modules/addresses/addresses.tools.ts +49 -6
  48. package/templates/typescript-auth/src/modules/cart/cart.tools.ts +13 -17
  49. package/templates/typescript-auth/src/modules/orders/orders.tools.ts +38 -16
  50. package/templates/typescript-auth/src/modules/products/products.tools.ts +4 -4
  51. package/templates/typescript-auth/src/widgets/app/order-confirmation/page.tsx +25 -0
  52. package/templates/typescript-auth/src/widgets/app/products-grid/page.tsx +26 -1
  53. package/templates/typescript-auth-api-key/README.md +3 -1
  54. package/templates/typescript-auth-api-key/src/index.ts +11 -3
  55. package/templates/typescript-starter/README.md +3 -1
@@ -59,119 +59,121 @@ export default function HealthPage() {
59
59
  };
60
60
 
61
61
  return (
62
- <div className="min-h-screen bg-background p-8">
63
- {/* Header */}
64
- <div className="mb-8">
65
- <div className="flex items-center justify-between mb-6">
66
- <div className="flex items-center gap-3">
67
- <div className="w-12 h-12 rounded-lg bg-gradient-to-br from-emerald-500 to-teal-500 flex items-center justify-center">
68
- <Activity className="w-6 h-6 text-white" />
69
- </div>
70
- <div>
71
- <h1 className="text-3xl font-bold text-foreground">Health Checks</h1>
72
- <p className="text-muted-foreground mt-1">System health monitoring</p>
73
- </div>
62
+ <div className="fixed inset-0 flex flex-col bg-background" style={{ left: 'var(--sidebar-width, 15rem)' }}>
63
+ {/* Sticky Header */}
64
+ <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">
65
+ <div className="flex items-center gap-3">
66
+ <div className="w-8 h-8 rounded-lg bg-gradient-to-br from-emerald-500 to-teal-500 flex items-center justify-center shadow-md">
67
+ <Activity className="w-5 h-5 text-white" strokeWidth={2.5} />
68
+ </div>
69
+ <div>
70
+ <h1 className="text-lg font-bold text-foreground">Health</h1>
74
71
  </div>
75
- <button onClick={loadHealth} className="btn btn-primary gap-2" disabled={loading}>
76
- <RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />
77
- {loading ? 'Checking...' : 'Refresh'}
78
- </button>
79
72
  </div>
73
+ <button onClick={loadHealth} className="btn btn-primary text-sm px-4 py-2 gap-2" disabled={loading}>
74
+ <RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />
75
+ {loading ? 'Checking...' : 'Refresh'}
76
+ </button>
77
+ </div>
80
78
 
81
- {/* Overall Status */}
82
- <div className="card p-8 bg-gradient-to-br from-card to-muted/20">
83
- <div className="flex items-center gap-6">
84
- {getStatusIcon(overallStatus)}
85
- <div>
86
- <h2 className="text-3xl font-bold capitalize text-foreground">{overallStatus}</h2>
87
- <p className="text-muted-foreground mt-1">
88
- {healthChecks.length} health check{healthChecks.length !== 1 ? 's' : ''} configured
89
- </p>
79
+ {/* Content - ONLY this scrolls */}
80
+ <div className="flex-1 overflow-y-auto overflow-x-hidden">
81
+ <div className="max-w-4xl mx-auto px-6 py-6">
82
+ {/* Overall Status */}
83
+ <div className="card p-8 bg-gradient-to-br from-card to-muted/20 mb-6">
84
+ <div className="flex items-center gap-6">
85
+ {getStatusIcon(overallStatus)}
86
+ <div>
87
+ <h2 className="text-3xl font-bold capitalize text-foreground">{overallStatus}</h2>
88
+ <p className="text-muted-foreground mt-1">
89
+ {healthChecks.length} health check{healthChecks.length !== 1 ? 's' : ''} configured
90
+ </p>
91
+ </div>
90
92
  </div>
91
93
  </div>
92
- </div>
93
- </div>
94
94
 
95
- {/* Health Checks Grid */}
96
- {loading && healthChecks.length === 0 ? (
97
- <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
98
- {[1, 2].map((i) => (
99
- <div key={i} className="card skeleton h-48"></div>
100
- ))}
101
- </div>
102
- ) : healthChecks.length === 0 ? (
103
- <div className="empty-state">
104
- <Activity className="empty-state-icon" />
105
- <p className="empty-state-title">No health checks configured</p>
106
- <p className="empty-state-description">
107
- Add health checks using the @HealthCheck decorator
108
- </p>
109
- </div>
110
- ) : (
111
- <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
112
- {healthChecks.map((check) => (
113
- <div key={check.name} className="card card-hover p-6 animate-fade-in">
114
- <div className="flex items-start justify-between mb-4">
115
- <div className="flex items-center gap-3">
116
- <div className={`w-12 h-12 rounded-lg flex items-center justify-center ${
117
- check.status === 'up'
118
- ? 'bg-emerald-500/10'
119
- : check.status === 'degraded'
120
- ? 'bg-amber-500/10'
121
- : 'bg-rose-500/10'
122
- }`}>
123
- {check.status === 'up' ? (
124
- <CheckCircle2 className="w-6 h-6 text-emerald-500" />
125
- ) : check.status === 'degraded' ? (
126
- <AlertTriangle className="w-6 h-6 text-amber-500" />
127
- ) : (
128
- <XCircle className="w-6 h-6 text-rose-500" />
129
- )}
130
- </div>
131
- <div>
132
- <h3 className="font-semibold capitalize text-foreground">{check.name}</h3>
133
- <span
134
- className={`badge text-xs mt-1 ${
95
+ {/* Health Checks Grid */}
96
+ {loading && healthChecks.length === 0 ? (
97
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
98
+ {[1, 2].map((i) => (
99
+ <div key={i} className="card skeleton h-48"></div>
100
+ ))}
101
+ </div>
102
+ ) : healthChecks.length === 0 ? (
103
+ <div className="empty-state">
104
+ <Activity className="empty-state-icon" />
105
+ <p className="empty-state-title">No health checks configured</p>
106
+ <p className="empty-state-description">
107
+ Add health checks using the @HealthCheck decorator
108
+ </p>
109
+ </div>
110
+ ) : (
111
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
112
+ {healthChecks.map((check) => (
113
+ <div key={check.name} className="card card-hover p-6 animate-fade-in">
114
+ <div className="flex items-start justify-between mb-4">
115
+ <div className="flex items-center gap-3">
116
+ <div className={`w-12 h-12 rounded-lg flex items-center justify-center ${
135
117
  check.status === 'up'
136
- ? 'badge-success'
118
+ ? 'bg-emerald-500/10'
137
119
  : check.status === 'degraded'
138
- ? 'badge-warning'
139
- : 'badge-error'
140
- }`}
141
- >
142
- {check.status}
143
- </span>
120
+ ? 'bg-amber-500/10'
121
+ : 'bg-rose-500/10'
122
+ }`}>
123
+ {check.status === 'up' ? (
124
+ <CheckCircle2 className="w-6 h-6 text-emerald-500" />
125
+ ) : check.status === 'degraded' ? (
126
+ <AlertTriangle className="w-6 h-6 text-amber-500" />
127
+ ) : (
128
+ <XCircle className="w-6 h-6 text-rose-500" />
129
+ )}
130
+ </div>
131
+ <div>
132
+ <h3 className="font-semibold capitalize text-foreground">{check.name}</h3>
133
+ <span
134
+ className={`badge text-xs mt-1 ${
135
+ check.status === 'up'
136
+ ? 'badge-success'
137
+ : check.status === 'degraded'
138
+ ? 'badge-warning'
139
+ : 'badge-error'
140
+ }`}
141
+ >
142
+ {check.status}
143
+ </span>
144
+ </div>
145
+ </div>
144
146
  </div>
145
- </div>
146
- </div>
147
147
 
148
- {check.message && (
149
- <p className="text-sm text-muted-foreground mb-3">{check.message}</p>
150
- )}
148
+ {check.message && (
149
+ <p className="text-sm text-muted-foreground mb-3">{check.message}</p>
150
+ )}
151
151
 
152
- {check.details && (
153
- <div className="mt-3 p-4 bg-muted/30 rounded-lg border border-border">
154
- <p className="text-xs font-semibold text-muted-foreground mb-3 uppercase tracking-wide">Details</p>
155
- <div className="space-y-2">
156
- {Object.entries(check.details).map(([key, value]) => (
157
- <div key={key} className="flex justify-between text-sm">
158
- <span className="text-muted-foreground capitalize">{key}:</span>
159
- <span className="text-foreground font-mono font-medium">{String(value)}</span>
152
+ {check.details && (
153
+ <div className="mt-3 p-4 bg-muted/30 rounded-lg border border-border">
154
+ <p className="text-xs font-semibold text-muted-foreground mb-3 uppercase tracking-wide">Details</p>
155
+ <div className="space-y-2">
156
+ {Object.entries(check.details).map(([key, value]) => (
157
+ <div key={key} className="flex justify-between text-sm">
158
+ <span className="text-muted-foreground capitalize">{key}:</span>
159
+ <span className="text-foreground font-mono font-medium">{String(value)}</span>
160
+ </div>
161
+ ))}
160
162
  </div>
161
- ))}
162
- </div>
163
- </div>
164
- )}
163
+ </div>
164
+ )}
165
165
 
166
- {check.timestamp && (
167
- <p className="text-xs text-muted-foreground mt-3">
168
- Last check: {new Date(check.timestamp).toLocaleString()}
169
- </p>
170
- )}
166
+ {check.timestamp && (
167
+ <p className="text-xs text-muted-foreground mt-3">
168
+ Last check: {new Date(check.timestamp).toLocaleString()}
169
+ </p>
170
+ )}
171
+ </div>
172
+ ))}
171
173
  </div>
172
- ))}
174
+ )}
173
175
  </div>
174
- )}
175
- </div>
176
+ </div>
177
+ </div>
176
178
  );
177
179
  }
@@ -10,8 +10,13 @@ import { Sidebar } from '@/components/Sidebar';
10
10
  import { EnlargeModal } from '@/components/EnlargeModal';
11
11
 
12
12
  export const metadata: Metadata = {
13
- title: 'NitroStack Studio',
14
- description: 'Professional development environment for NitroStack servers',
13
+ title: 'NitroStudio - MCP Development Suite',
14
+ description: 'Professional AI-powered development environment for Model Context Protocol (MCP) servers. Built with NitroStack.',
15
+ keywords: ['MCP', 'Model Context Protocol', 'AI Development', 'NitroStack', 'NitroStudio'],
16
+ authors: [{ name: 'NitroStack Team' }],
17
+ icons: {
18
+ icon: '/favicon.ico',
19
+ },
15
20
  };
16
21
 
17
22
  export default function RootLayout({
@@ -34,14 +39,29 @@ export default function RootLayout({
34
39
  <body className="min-h-screen font-sans">
35
40
  <div className="flex min-h-screen bg-gradient-to-br from-background via-background to-muted/20">
36
41
  <Sidebar />
37
- <main className="flex-1 ml-64">
38
- <div className="min-h-screen p-8">
42
+ <main className="flex-1 transition-all duration-300 ease-in-out" style={{ marginLeft: 'var(--sidebar-width, 15rem)' }}>
43
+ <div className="min-h-screen p-3 sm:p-6 md:p-8">
39
44
  {children}
40
45
  </div>
41
46
  </main>
42
47
  </div>
43
48
  {/* Global Modal - Available on all pages */}
44
49
  <EnlargeModal />
50
+ {/* CSS Variable for sidebar width */}
51
+ <script
52
+ dangerouslySetInnerHTML={{
53
+ __html: `
54
+ function updateSidebarWidth() {
55
+ const isCollapsed = localStorage.getItem('sidebar_collapsed') === 'true';
56
+ document.documentElement.style.setProperty('--sidebar-width', isCollapsed ? '4rem' : '15rem');
57
+ }
58
+ updateSidebarWidth();
59
+ window.addEventListener('storage', updateSidebarWidth);
60
+ // Update on custom event
61
+ window.addEventListener('sidebar-toggle', updateSidebarWidth);
62
+ `,
63
+ }}
64
+ />
45
65
  </body>
46
66
  </html>
47
67
  );
@@ -9,12 +9,15 @@ import type { Tool } from '@/lib/types';
9
9
  import { Wrench, RefreshCw, X, Play, AlertCircle } from 'lucide-react';
10
10
 
11
11
  export default function ToolsPage() {
12
- const { tools, setTools, loading, setLoading, connection, setConnection, jwtToken, apiKey } = useStudioStore();
12
+ const { tools, setTools, loading, setLoading, connection, setConnection, jwtToken, apiKey, oauthState } = useStudioStore();
13
13
  const [searchQuery, setSearchQuery] = useState('');
14
14
  const [selectedTool, setSelectedTool] = useState<Tool | null>(null);
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
+
19
+ // Get effective token - check both jwtToken and OAuth token
20
+ const effectiveToken = jwtToken || oauthState?.currentToken;
18
21
 
19
22
  // Initialize MCP, load tools and check connection on mount
20
23
  useEffect(() => {
@@ -64,8 +67,8 @@ export default function ToolsPage() {
64
67
  setToolResult(null);
65
68
 
66
69
  try {
67
- // Pass JWT token and API key if available
68
- const result = await api.callTool(selectedTool.name, toolArgs, jwtToken || undefined, apiKey || undefined);
70
+ // Pass JWT token and API key if available (check both jwtToken and OAuth token)
71
+ const result = await api.callTool(selectedTool.name, toolArgs, effectiveToken || undefined, apiKey || undefined);
69
72
  setToolResult(result);
70
73
 
71
74
  // Extract JWT token from ANY tool response (not just 'login')
@@ -100,64 +103,67 @@ export default function ToolsPage() {
100
103
 
101
104
  return (
102
105
  <>
103
- <div className="min-h-screen bg-background p-8">
104
- {/* Header */}
105
- <div className="mb-8">
106
- <div className="flex items-center justify-between mb-6">
107
- <div className="flex items-center gap-3">
108
- <div className="w-12 h-12 rounded-lg bg-gradient-to-br from-primary to-amber-500 flex items-center justify-center">
109
- <Wrench className="w-6 h-6 text-black" />
110
- </div>
111
- <div>
112
- <h1 className="text-3xl font-bold text-foreground">Tools</h1>
113
- <p className="text-muted-foreground mt-1">
114
- Browse and execute MCP tools
115
- </p>
116
- </div>
106
+ <div className="fixed inset-0 flex flex-col bg-background" style={{ left: 'var(--sidebar-width, 15rem)' }}>
107
+ {/* Sticky Header */}
108
+ <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">
109
+ <div className="flex items-center gap-3">
110
+ <div className="w-8 h-8 rounded-lg bg-gradient-to-br from-primary to-amber-500 flex items-center justify-center shadow-md">
111
+ <Wrench className="w-5 h-5 text-white" strokeWidth={2.5} />
112
+ </div>
113
+ <div>
114
+ <h1 className="text-lg font-bold text-foreground">Tools</h1>
117
115
  </div>
118
- <button onClick={loadTools} className="btn btn-primary gap-2">
119
- <RefreshCw className="w-4 h-4" />
120
- Refresh
121
- </button>
122
116
  </div>
123
-
124
- {/* Search */}
125
- <input
126
- type="text"
127
- placeholder="Search tools..."
128
- value={searchQuery}
129
- onChange={(e) => setSearchQuery(e.target.value)}
130
- className="input"
131
- />
117
+ <button onClick={loadTools} className="btn btn-primary text-sm px-4 py-2 gap-2">
118
+ <RefreshCw className="w-4 h-4" />
119
+ Refresh
120
+ </button>
132
121
  </div>
133
122
 
134
- {/* Tools Grid */}
135
- {loading.tools ? (
136
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
137
- {[1, 2, 3].map((i) => (
138
- <div key={i} className="card skeleton h-64"></div>
139
- ))}
140
- </div>
141
- ) : filteredTools.length === 0 ? (
142
- <div className="empty-state">
143
- <AlertCircle className="empty-state-icon" />
144
- <p className="empty-state-title">
145
- {searchQuery ? 'No tools found matching your search' : 'No tools available'}
146
- </p>
147
- <p className="empty-state-description">
148
- {searchQuery ? 'Try a different search term' : 'No MCP tools have been registered'}
149
- </p>
150
- </div>
151
- ) : (
152
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
153
- {filteredTools.map((tool) => (
154
- <ToolCard key={tool.name} tool={tool} onExecute={handleExecuteTool} />
155
- ))}
123
+ {/* Content - ONLY this scrolls */}
124
+ <div className="flex-1 overflow-y-auto overflow-x-hidden">
125
+ <div className="max-w-7xl mx-auto px-6 py-6">
126
+ {/* Search */}
127
+ <div className="mb-6">
128
+ <input
129
+ type="text"
130
+ placeholder="Search tools..."
131
+ value={searchQuery}
132
+ onChange={(e) => setSearchQuery(e.target.value)}
133
+ className="input w-full"
134
+ />
135
+ </div>
136
+
137
+ {/* Tools Grid */}
138
+ {loading.tools ? (
139
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
140
+ {[1, 2, 3].map((i) => (
141
+ <div key={i} className="card skeleton h-64"></div>
142
+ ))}
143
+ </div>
144
+ ) : filteredTools.length === 0 ? (
145
+ <div className="empty-state">
146
+ <AlertCircle className="empty-state-icon" />
147
+ <p className="empty-state-title">
148
+ {searchQuery ? 'No tools found matching your search' : 'No tools available'}
149
+ </p>
150
+ <p className="empty-state-description">
151
+ {searchQuery ? 'Try a different search term' : 'No MCP tools have been registered'}
152
+ </p>
153
+ </div>
154
+ ) : (
155
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
156
+ {filteredTools.map((tool) => (
157
+ <ToolCard key={tool.name} tool={tool} onExecute={handleExecuteTool} />
158
+ ))}
159
+ </div>
160
+ )}
156
161
  </div>
157
- )}
162
+ </div>
163
+ </div>
158
164
 
159
- {/* Tool Executor Modal */}
160
- {selectedTool && (
165
+ {/* Tool Executor Modal */}
166
+ {selectedTool && (
161
167
  <div
162
168
  className="fixed inset-0 z-50 flex items-center justify-center animate-fade-in"
163
169
  style={{ backgroundColor: 'rgba(0, 0, 0, 0.85)' }}
@@ -331,7 +337,6 @@ export default function ToolsPage() {
331
337
  </div>
332
338
  </div>
333
339
  )}
334
- </div>
335
340
  </>
336
341
  );
337
342
  }
@@ -60,20 +60,23 @@ export default function PingPage() {
60
60
  };
61
61
 
62
62
  return (
63
- <div className="min-h-screen bg-background p-8">
64
- {/* Header */}
65
- <div className="mb-8">
66
- <div className="flex items-center gap-3 mb-4">
67
- <div className="w-12 h-12 rounded-lg bg-gradient-to-br from-cyan-500 to-blue-500 flex items-center justify-center">
68
- <Wifi className="w-6 h-6 text-white" />
63
+ <div className="fixed inset-0 flex flex-col bg-background" style={{ left: 'var(--sidebar-width, 15rem)' }}>
64
+ {/* Sticky Header */}
65
+ <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">
66
+ <div className="flex items-center gap-3">
67
+ <div className="w-8 h-8 rounded-lg bg-gradient-to-br from-cyan-500 to-blue-500 flex items-center justify-center shadow-md">
68
+ <Wifi className="w-5 h-5 text-white" strokeWidth={2.5} />
69
69
  </div>
70
70
  <div>
71
- <h1 className="text-3xl font-bold text-foreground">Ping</h1>
72
- <p className="text-muted-foreground mt-1">Test server connectivity and latency</p>
71
+ <h1 className="text-lg font-bold text-foreground">Ping</h1>
73
72
  </div>
74
73
  </div>
75
74
  </div>
76
75
 
76
+ {/* Content - ONLY this scrolls */}
77
+ <div className="flex-1 overflow-y-auto overflow-x-hidden">
78
+ <div className="max-w-4xl mx-auto px-6 py-6">
79
+
77
80
  {/* Main Ping Card */}
78
81
  <div className="card card-hover p-8 mb-8 text-center bg-gradient-to-br from-card via-card to-muted/20">
79
82
  <button
@@ -199,6 +202,8 @@ export default function PingPage() {
199
202
  </p>
200
203
  </div>
201
204
  )}
205
+ </div>
206
+ </div>
202
207
  </div>
203
208
  );
204
209
  }
@@ -53,85 +53,87 @@ export default function PromptsPage() {
53
53
  );
54
54
 
55
55
  return (
56
- <div className="min-h-screen bg-background p-8">
57
- {/* Header */}
58
- <div className="mb-8">
59
- <div className="flex items-center justify-between mb-6">
60
- <div className="flex items-center gap-3">
61
- <div className="w-12 h-12 rounded-lg bg-gradient-to-br from-blue-500 to-cyan-500 flex items-center justify-center">
62
- <FileText className="w-6 h-6 text-white" />
63
- </div>
64
- <div>
65
- <h1 className="text-3xl font-bold text-foreground">Prompts</h1>
66
- <p className="text-muted-foreground mt-1">Browse and execute MCP prompts</p>
67
- </div>
56
+ <div className="fixed inset-0 flex flex-col bg-background" style={{ left: 'var(--sidebar-width, 15rem)' }}>
57
+ {/* Sticky Header */}
58
+ <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">
59
+ <div className="flex items-center gap-3">
60
+ <div className="w-8 h-8 rounded-lg bg-gradient-to-br from-blue-500 to-cyan-500 flex items-center justify-center shadow-md">
61
+ <FileText className="w-5 h-5 text-white" strokeWidth={2.5} />
62
+ </div>
63
+ <div>
64
+ <h1 className="text-lg font-bold text-foreground">Prompts</h1>
68
65
  </div>
69
- <button onClick={loadPrompts} className="btn btn-primary gap-2">
70
- <RefreshCw className="w-4 h-4" />
71
- Refresh
72
- </button>
73
66
  </div>
74
-
75
- <input
76
- type="text"
77
- placeholder="Search prompts..."
78
- value={searchQuery}
79
- onChange={(e) => setSearchQuery(e.target.value)}
80
- className="input"
81
- />
67
+ <button onClick={loadPrompts} className="btn btn-primary text-sm px-4 py-2 gap-2">
68
+ <RefreshCw className="w-4 h-4" />
69
+ Refresh
70
+ </button>
82
71
  </div>
83
72
 
84
- {/* Prompts Grid */}
85
- {loading.prompts ? (
86
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
87
- {[1, 2, 3].map((i) => (
88
- <div key={i} className="card skeleton h-40"></div>
89
- ))}
90
- </div>
91
- ) : filteredPrompts.length === 0 ? (
92
- <div className="empty-state">
93
- <AlertCircle className="empty-state-icon" />
94
- <p className="empty-state-title">
95
- {searchQuery ? 'No prompts found' : 'No prompts available'}
96
- </p>
97
- <p className="empty-state-description">
98
- {searchQuery ? 'Try a different search term' : 'No prompts have been registered'}
99
- </p>
100
- </div>
101
- ) : (
102
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
103
- {filteredPrompts.map((prompt) => (
104
- <div
105
- key={prompt.name}
106
- className="card card-hover p-6 cursor-pointer animate-fade-in"
107
- onClick={() => {
108
- setSelectedPrompt(prompt);
109
- setPromptArgs({});
110
- setPromptResult(null);
111
- }}
112
- >
113
- <div className="flex items-center gap-3 mb-3">
114
- <div className="w-10 h-10 rounded-lg bg-blue-500/10 flex items-center justify-center">
115
- <FileText className="w-5 h-5 text-blue-500" />
116
- </div>
117
- <h3 className="font-semibold text-foreground">{prompt.name}</h3>
118
- </div>
119
-
120
- <p className="text-sm text-muted-foreground line-clamp-2 mb-3">
121
- {prompt.description || 'No description'}
73
+ {/* Content - ONLY this scrolls */}
74
+ <div className="flex-1 overflow-y-auto overflow-x-hidden">
75
+ <div className="max-w-7xl mx-auto px-6 py-6">
76
+ <input
77
+ type="text"
78
+ placeholder="Search prompts..."
79
+ value={searchQuery}
80
+ onChange={(e) => setSearchQuery(e.target.value)}
81
+ className="input mb-6"
82
+ />
83
+
84
+ {/* Prompts Grid */}
85
+ {loading.prompts ? (
86
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
87
+ {[1, 2, 3].map((i) => (
88
+ <div key={i} className="card skeleton h-40"></div>
89
+ ))}
90
+ </div>
91
+ ) : filteredPrompts.length === 0 ? (
92
+ <div className="empty-state">
93
+ <AlertCircle className="empty-state-icon" />
94
+ <p className="empty-state-title">
95
+ {searchQuery ? 'No prompts found' : 'No prompts available'}
122
96
  </p>
97
+ <p className="empty-state-description">
98
+ {searchQuery ? 'Try a different search term' : 'No prompts have been registered'}
99
+ </p>
100
+ </div>
101
+ ) : (
102
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
103
+ {filteredPrompts.map((prompt) => (
104
+ <div
105
+ key={prompt.name}
106
+ className="card card-hover p-6 cursor-pointer animate-fade-in"
107
+ onClick={() => {
108
+ setSelectedPrompt(prompt);
109
+ setPromptArgs({});
110
+ setPromptResult(null);
111
+ }}
112
+ >
113
+ <div className="flex items-center gap-3 mb-3">
114
+ <div className="w-10 h-10 rounded-lg bg-blue-500/10 flex items-center justify-center">
115
+ <FileText className="w-5 h-5 text-blue-500" />
116
+ </div>
117
+ <h3 className="font-semibold text-foreground">{prompt.name}</h3>
118
+ </div>
123
119
 
124
- {prompt.arguments && prompt.arguments.length > 0 && (
125
- <div className="flex items-center gap-1 text-xs text-muted-foreground">
126
- <span className="badge badge-secondary">
127
- {prompt.arguments.length} argument{prompt.arguments.length !== 1 ? 's' : ''}
128
- </span>
120
+ <p className="text-sm text-muted-foreground line-clamp-2 mb-3">
121
+ {prompt.description || 'No description'}
122
+ </p>
123
+
124
+ {prompt.arguments && prompt.arguments.length > 0 && (
125
+ <div className="flex items-center gap-1 text-xs text-muted-foreground">
126
+ <span className="badge badge-secondary">
127
+ {prompt.arguments.length} argument{prompt.arguments.length !== 1 ? 's' : ''}
128
+ </span>
129
+ </div>
130
+ )}
129
131
  </div>
130
- )}
132
+ ))}
131
133
  </div>
132
- ))}
134
+ )}
133
135
  </div>
134
- )}
136
+ </div>
135
137
 
136
138
  {/* Prompt Executor Modal */}
137
139
  {selectedPrompt && (