nitrostack 1.0.71 → 1.0.72

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.
@@ -12,15 +12,17 @@
12
12
  "export": "next build && next export"
13
13
  },
14
14
  "dependencies": {
15
+ "@elevenlabs/elevenlabs-js": "^2.28.0",
15
16
  "@fontsource/inter": "^5.2.8",
16
17
  "@fontsource/jetbrains-mono": "^5.2.8",
18
+ "@heroicons/react": "^2.2.0",
17
19
  "@modelcontextprotocol/sdk": "^1.0.4",
18
20
  "clsx": "^2.1.0",
19
21
  "eventsource": "^2.0.2",
20
- "lucide-react": "^0.546.0",
21
22
  "next": "^14.2.5",
22
23
  "react": "^18.3.1",
23
24
  "react-dom": "^18.3.1",
25
+ "reactflow": "^11.11.4",
24
26
  "zustand": "^4.5.0"
25
27
  },
26
28
  "devDependencies": {
@@ -9,21 +9,42 @@ const config: Config = {
9
9
  ],
10
10
  theme: {
11
11
  extend: {
12
+ fontFamily: {
13
+ heading: ['Space Grotesk', 'sans-serif'],
14
+ body: ['IBM Plex Sans', 'sans-serif'],
15
+ mono: ['JetBrains Mono', 'Fira Code', 'Consolas', 'monospace'],
16
+ },
12
17
  colors: {
13
- // Brand colors - Gold & Black
14
- primary: {
15
- DEFAULT: '#d4af37', // Gold
16
- 50: '#fdfcf8',
17
- 100: '#f9f7ed',
18
- 200: '#f2ecd3',
19
- 300: '#ead9a1',
20
- 400: '#e2c870',
21
- 500: '#d4af37', // Main gold
22
- 600: '#b8962b',
23
- 700: '#967824',
24
- 800: '#7a6020',
25
- 900: '#654f1d',
26
- },
18
+ // MCP Brand Core Colors
19
+ slateInk: '#121217',
20
+ coolMint: '#2AB5A5',
21
+ signalBlue: '#3B82F6',
22
+
23
+ // MCP Extended Palette - Light Mode
24
+ 'light-canvas': '#F5F6FA',
25
+ 'light-white': '#FFFFFF',
26
+ 'light-text-primary': '#121217',
27
+ 'light-text-secondary': '#5F6475',
28
+ 'light-border-soft': '#E6E8F0',
29
+ 'light-border-subtle': '#EEF0F6',
30
+
31
+ // MCP Extended Palette - Dark Mode
32
+ 'dark-base': '#0E0F14',
33
+ 'dark-surface': '#1B1B20',
34
+ 'dark-text-primary': '#E9E9F0',
35
+ 'dark-text-muted': '#B4B4CC',
36
+ 'dark-border-panel': '#312626',
37
+
38
+ // MCP Status Colors (same light/dark)
39
+ 'status-success': '#2AB5A5',
40
+ 'status-warning': '#F59E0B',
41
+ 'status-error': '#EF4444',
42
+ 'status-info': '#0A9CA9',
43
+
44
+ // MCP Accent
45
+ 'accent-steel': '#4D777F',
46
+
47
+ // CSS Variable mapped colors (for compatibility)
27
48
  background: 'hsl(var(--background))',
28
49
  foreground: 'hsl(var(--foreground))',
29
50
  card: {
@@ -34,6 +55,10 @@ const config: Config = {
34
55
  DEFAULT: 'hsl(var(--popover))',
35
56
  foreground: 'hsl(var(--popover-foreground))',
36
57
  },
58
+ primary: {
59
+ DEFAULT: 'hsl(var(--primary))',
60
+ foreground: 'hsl(var(--primary-foreground))',
61
+ },
37
62
  secondary: {
38
63
  DEFAULT: 'hsl(var(--secondary))',
39
64
  foreground: 'hsl(var(--secondary-foreground))',
@@ -55,9 +80,30 @@ const config: Config = {
55
80
  ring: 'hsl(var(--ring))',
56
81
  },
57
82
  borderRadius: {
58
- lg: 'var(--radius)',
59
- md: 'calc(var(--radius) - 2px)',
60
- sm: 'calc(var(--radius) - 4px)',
83
+ // MCP Border Radius System
84
+ DEFAULT: '4px', // MCP default
85
+ 'none': '0',
86
+ 'sm': '2px',
87
+ 'md': '4px', // MCP default
88
+ 'lg': '6px',
89
+ 'xl': '8px',
90
+ '2xl': '12px',
91
+ '3xl': '16px',
92
+ 'pill': '999px', // MCP pill
93
+ 'full': '9999px', // Circle
94
+ },
95
+ boxShadow: {
96
+ // MCP Shadow System - Light Mode
97
+ 'low-light': '0px 1px 3px rgba(240, 100, 49, 0.08)',
98
+ 'medium-light': '0px 4px 12px rgba(240, 100, 49, 0.11)',
99
+ 'high-light': '0px 8px 28px rgba(240, 100, 49, 0.14)',
100
+ 'focus-light': '0px 0px 28px rgba(173, 62, 44, 0.30)',
101
+
102
+ // MCP Shadow System - Dark Mode
103
+ 'low-dark': '0px 1px 3px rgba(0, 0, 0, 0.07)',
104
+ 'medium-dark': '0px 4px 12px rgba(0, 0, 0, 0.10)',
105
+ 'high-dark': '0px 8px 28px rgba(0, 0, 0, 0.15)',
106
+ 'focus-dark': '0px 0px 28px rgba(217, 91, 60, 0.16)',
61
107
  },
62
108
  },
63
109
  },
@@ -1,560 +0,0 @@
1
- 'use client';
2
-
3
- import { useState } from 'react';
4
- import { useStudioStore } from '@/lib/store';
5
- import { api } from '@/lib/api';
6
- import { Shield, Key, CheckCircle2, XCircle, Lock, ExternalLink, AlertCircle } from 'lucide-react';
7
-
8
- export default function AuthPage() {
9
- const { oauthState, setOAuthState, jwtToken, setJwtToken, apiKey, setApiKey } = useStudioStore();
10
- const initialServerUrl =
11
- process.env.MCP_TRANSPORT_TYPE === 'stdio'
12
- ? `http://localhost:${process.env.MCP_SERVER_PORT || 3005}`
13
- : '';
14
- const [serverUrl, setServerUrl] = useState(initialServerUrl);
15
- const [clientName, setClientName] = useState('NitroStack Studio');
16
- const [redirectUri, setRedirectUri] = useState('http://localhost:3000/auth/callback');
17
- const [manualToken, setManualToken] = useState('');
18
- const [manualApiKey, setManualApiKey] = useState('');
19
- const [discovering, setDiscovering] = useState(false);
20
- const [registering, setRegistering] = useState(false);
21
- const [manualClientId, setManualClientId] = useState('');
22
- const [manualClientSecret, setManualClientSecret] = useState('');
23
-
24
- const handleDiscover = async () => {
25
- setDiscovering(true);
26
- try {
27
- const resourceMetadataUrl = new URL('/.well-known/oauth-protected-resource', serverUrl).toString();
28
- const resourceMetadata = await api.discoverAuth(resourceMetadataUrl, 'resource');
29
- const authServerUrl = resourceMetadata.authorization_servers[0];
30
- const authServerMetadataUrl = new URL('/.well-known/oauth-authorization-server', authServerUrl).toString();
31
- const authServerMetadata = await api.discoverAuth(authServerMetadataUrl, 'auth-server');
32
-
33
- setOAuthState({
34
- authServerUrl: serverUrl,
35
- resourceMetadata,
36
- authServerMetadata,
37
- });
38
-
39
- alert('Discovery successful!');
40
- } catch (error) {
41
- console.error('Discovery failed:', error);
42
- alert('Discovery failed. See console for details.');
43
- } finally {
44
- setDiscovering(false);
45
- }
46
- };
47
-
48
- const handleRegister = async () => {
49
- if (!oauthState.authServerMetadata?.registration_endpoint) {
50
- alert('Dynamic registration not supported');
51
- return;
52
- }
53
-
54
- setRegistering(true);
55
- try {
56
- const registration = await api.registerClient(
57
- oauthState.authServerMetadata.registration_endpoint,
58
- {
59
- client_name: clientName,
60
- redirect_uris: [redirectUri],
61
- grant_types: ['authorization_code', 'refresh_token'],
62
- response_types: ['code'],
63
- scope: oauthState.selectedScopes.join(' '),
64
- }
65
- );
66
-
67
- setOAuthState({ clientRegistration: registration });
68
- alert('Client registered successfully!');
69
- } catch (error) {
70
- console.error('Registration failed:', error);
71
- alert('Registration failed. See console for details.');
72
- } finally {
73
- setRegistering(false);
74
- }
75
- };
76
-
77
- const handleManualCredentials = () => {
78
- if (!manualClientId.trim()) {
79
- alert('Please enter Client ID');
80
- return;
81
- }
82
-
83
- // Store manual credentials
84
- setOAuthState({
85
- ...oauthState,
86
- clientRegistration: {
87
- client_id: manualClientId.trim(),
88
- client_secret: manualClientSecret.trim() || undefined,
89
- },
90
- });
91
-
92
- alert('Client credentials saved!');
93
- };
94
-
95
- const handleUseManualToken = () => {
96
- if (!manualToken.trim()) {
97
- alert('Please enter a token');
98
- return;
99
- }
100
-
101
- setJwtToken(manualToken);
102
- setManualToken('');
103
- alert('Token set successfully!');
104
- };
105
-
106
- const handleUseApiKey = () => {
107
- if (!manualApiKey.trim()) {
108
- alert('Please enter an API key');
109
- return;
110
- }
111
-
112
- setApiKey(manualApiKey);
113
- setManualApiKey('');
114
- alert('API key set successfully!');
115
- };
116
-
117
- const handleStartOAuthFlow = async () => {
118
- if (!oauthState.clientRegistration?.client_id) {
119
- alert('Please register a client or enter manual credentials first');
120
- return;
121
- }
122
-
123
- if (!oauthState.authServerMetadata?.authorization_endpoint) {
124
- alert('Authorization endpoint not found in server metadata');
125
- return;
126
- }
127
-
128
- try {
129
- // Generate PKCE challenge
130
- const codeVerifier = generateCodeVerifier();
131
- const codeChallenge = await generateCodeChallenge(codeVerifier);
132
-
133
- // Store verifier for callback
134
- sessionStorage.setItem('oauth_code_verifier', codeVerifier);
135
- sessionStorage.setItem('oauth_state', Math.random().toString(36).substring(7));
136
-
137
- // Build authorization URL
138
- const authUrl = new URL(oauthState.authServerMetadata.authorization_endpoint);
139
- authUrl.searchParams.set('client_id', oauthState.clientRegistration.client_id);
140
- authUrl.searchParams.set('response_type', 'code');
141
- authUrl.searchParams.set('redirect_uri', redirectUri);
142
- authUrl.searchParams.set('scope', oauthState.resourceMetadata?.scopes_supported?.join(' ') || 'openid profile');
143
- authUrl.searchParams.set('state', sessionStorage.getItem('oauth_state')!);
144
- authUrl.searchParams.set('code_challenge', codeChallenge);
145
- authUrl.searchParams.set('code_challenge_method', 'S256');
146
-
147
- // CRITICAL: Add audience parameter to request access token for the API
148
- // Without this, Auth0 returns an opaque/encrypted token instead of a JWT
149
- if (oauthState.resourceMetadata?.resource) {
150
- authUrl.searchParams.set('audience', oauthState.resourceMetadata.resource);
151
- console.log('🎯 Setting audience parameter:', oauthState.resourceMetadata.resource);
152
- }
153
-
154
- // Open authorization URL
155
- window.location.href = authUrl.toString();
156
- } catch (error) {
157
- console.error('Failed to start OAuth flow:', error);
158
- alert('Failed to start OAuth flow. See console for details.');
159
- }
160
- };
161
-
162
- // PKCE helpers
163
- const generateCodeVerifier = () => {
164
- const array = new Uint8Array(32);
165
- crypto.getRandomValues(array);
166
- return base64UrlEncode(array);
167
- };
168
-
169
- const generateCodeChallenge = async (verifier: string) => {
170
- const encoder = new TextEncoder();
171
- const data = encoder.encode(verifier);
172
- const hash = await crypto.subtle.digest('SHA-256', data);
173
- return base64UrlEncode(new Uint8Array(hash));
174
- };
175
-
176
- const base64UrlEncode = (buffer: Uint8Array) => {
177
- const base64 = btoa(String.fromCharCode(...buffer));
178
- return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
179
- };
180
-
181
- return (
182
- <div className="fixed inset-0 flex flex-col bg-background" style={{ left: 'var(--sidebar-width, 15rem)' }}>
183
- {/* Sticky Header */}
184
- <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">
185
- <div className="flex items-center gap-3">
186
- <div className="w-8 h-8 rounded-lg bg-gradient-to-br from-violet-500 to-purple-500 flex items-center justify-center shadow-md">
187
- <Shield className="w-5 h-5 text-white" strokeWidth={2.5} />
188
- </div>
189
- <div>
190
- <h1 className="text-lg font-bold text-foreground">OAuth 2.1</h1>
191
- </div>
192
- </div>
193
- </div>
194
-
195
- {/* Content - ONLY this scrolls */}
196
- <div className="flex-1 overflow-y-auto overflow-x-hidden">
197
- <div className="max-w-4xl mx-auto px-6 py-6">
198
- <div className="space-y-6">
199
- {/* JWT Token Management */}
200
- <div className="card card-hover p-6">
201
- <h2 className="text-xl font-semibold text-foreground mb-4 flex items-center gap-2">
202
- <Key className="w-5 h-5 text-primary" />
203
- JWT Token
204
- </h2>
205
-
206
- {/* Status Badge */}
207
- <div className="flex items-center gap-4 mb-6">
208
- <div className={`w-14 h-14 rounded-xl flex items-center justify-center ${jwtToken ? 'bg-emerald-500/10' : 'bg-rose-500/10'}`}>
209
- {jwtToken ? (
210
- <CheckCircle2 className="w-8 h-8 text-emerald-500" />
211
- ) : (
212
- <XCircle className="w-8 h-8 text-rose-500" />
213
- )}
214
- </div>
215
- <div className="flex-1">
216
- <p className="font-semibold text-foreground">
217
- {jwtToken ? 'Token Active' : 'No Token Set'}
218
- </p>
219
- <p className="text-sm text-muted-foreground mt-1">
220
- {jwtToken
221
- ? 'Token is automatically included in all tool calls and chat requests'
222
- : 'Set a token manually or login via tools to authenticate'}
223
- </p>
224
- </div>
225
- </div>
226
-
227
- {/* Token Input/Display */}
228
- <div className="space-y-3">
229
- <label className="block text-sm font-medium text-foreground">
230
- Token Value
231
- {jwtToken && <span className="ml-2 text-xs text-emerald-500">(Currently Active)</span>}
232
- </label>
233
- <div className="flex gap-2">
234
- <input
235
- type="text"
236
- value={manualToken || jwtToken || ''}
237
- onChange={(e) => setManualToken(e.target.value)}
238
- placeholder="Paste or edit JWT token here..."
239
- className="input flex-1 font-mono text-sm"
240
- />
241
- <button
242
- onClick={handleUseManualToken}
243
- className="btn btn-primary gap-2"
244
- disabled={!manualToken.trim()}
245
- >
246
- <Key className="w-4 h-4" />
247
- {jwtToken ? 'Update' : 'Set'} Token
248
- </button>
249
- </div>
250
-
251
- {jwtToken && (
252
- <div className="flex gap-2">
253
- <button
254
- onClick={() => {
255
- setManualToken(jwtToken);
256
- alert('Token copied to input for editing');
257
- }}
258
- className="btn btn-secondary btn-sm gap-2"
259
- >
260
- <Lock className="w-3 h-3" />
261
- Edit Current Token
262
- </button>
263
- <button
264
- onClick={() => {
265
- setJwtToken(null);
266
- setManualToken('');
267
- alert('Token cleared');
268
- }}
269
- className="btn btn-secondary btn-sm gap-2"
270
- >
271
- <XCircle className="w-3 h-3" />
272
- Clear Token
273
- </button>
274
- </div>
275
- )}
276
- </div>
277
-
278
- {/* Info Box */}
279
- <div className="mt-4 p-4 bg-blue-500/10 border border-blue-500/20 rounded-lg">
280
- <div className="flex items-start gap-2">
281
- <AlertCircle className="w-5 h-5 text-blue-400 mt-0.5 flex-shrink-0" />
282
- <div className="text-sm text-blue-200">
283
- <p className="font-medium mb-1">How Token Management Works:</p>
284
- <ul className="list-disc list-inside space-y-1 text-blue-300/80">
285
- <li>Login via tool execution automatically saves token here</li>
286
- <li>Login via chat automatically saves token here</li>
287
- <li>Manually paste token here to use it globally</li>
288
- <li>Token persists across page refresh</li>
289
- <li>Token is sent with all subsequent requests</li>
290
- </ul>
291
- </div>
292
- </div>
293
- </div>
294
- </div>
295
-
296
- {/* API Key Management */}
297
- <div className="card card-hover p-6">
298
- <h2 className="text-xl font-semibold text-foreground mb-4 flex items-center gap-2">
299
- <Key className="w-5 h-5 text-primary" />
300
- API Key
301
- </h2>
302
-
303
- {/* Status Badge */}
304
- <div className="flex items-center gap-4 mb-6">
305
- <div className={`w-14 h-14 rounded-xl flex items-center justify-center ${apiKey ? 'bg-emerald-500/10' : 'bg-rose-500/10'}`}>
306
- {apiKey ? (
307
- <CheckCircle2 className="w-8 h-8 text-emerald-500" />
308
- ) : (
309
- <XCircle className="w-8 h-8 text-rose-500" />
310
- )}
311
- </div>
312
- <div className="flex-1">
313
- <p className="font-semibold text-foreground">
314
- {apiKey ? 'API Key Active' : 'No API Key Set'}
315
- </p>
316
- <p className="text-sm text-muted-foreground mt-1">
317
- {apiKey
318
- ? 'API key is automatically included in all tool calls and chat requests'
319
- : 'Set an API key to access protected tools'}
320
- </p>
321
- </div>
322
- </div>
323
-
324
- {/* API Key Input/Display */}
325
- <div className="space-y-3">
326
- <label className="block text-sm font-medium text-foreground">
327
- API Key Value
328
- {apiKey && <span className="ml-2 text-xs text-emerald-500">(Currently Active)</span>}
329
- </label>
330
- <div className="flex gap-2">
331
- <input
332
- type="password"
333
- value={manualApiKey || apiKey || ''}
334
- onChange={(e) => setManualApiKey(e.target.value)}
335
- placeholder="Enter your API key here (e.g., sk_...)..."
336
- className="input flex-1 font-mono text-sm"
337
- />
338
- <button
339
- onClick={handleUseApiKey}
340
- className="btn btn-primary gap-2"
341
- disabled={!manualApiKey.trim()}
342
- >
343
- <Key className="w-4 h-4" />
344
- {apiKey ? 'Update' : 'Set'} Key
345
- </button>
346
- </div>
347
-
348
- {apiKey && (
349
- <div className="flex gap-2">
350
- <button
351
- onClick={() => {
352
- setManualApiKey(apiKey);
353
- alert('API key copied to input for editing');
354
- }}
355
- className="btn btn-secondary btn-sm gap-2"
356
- >
357
- <Lock className="w-3 h-3" />
358
- Edit Current Key
359
- </button>
360
- <button
361
- onClick={() => {
362
- setApiKey(null);
363
- setManualApiKey('');
364
- alert('API key cleared');
365
- }}
366
- className="btn btn-secondary btn-sm gap-2"
367
- >
368
- <XCircle className="w-3 h-3" />
369
- Clear Key
370
- </button>
371
- </div>
372
- )}
373
- </div>
374
-
375
- {/* Info Box */}
376
- <div className="mt-4 p-4 bg-purple-500/10 border border-purple-500/20 rounded-lg">
377
- <div className="flex items-start gap-2">
378
- <AlertCircle className="w-5 h-5 text-purple-400 mt-0.5 flex-shrink-0" />
379
- <div className="text-sm text-purple-200">
380
- <p className="font-medium mb-1">API Key Authentication:</p>
381
- <ul className="list-disc list-inside space-y-1 text-purple-300/80">
382
- <li>API keys are simpler than JWT tokens</li>
383
- <li>Common format: sk_xxx (secret key) or pk_xxx (public key)</li>
384
- <li>Keys are sent in X-API-Key header or _meta.apiKey field</li>
385
- <li>Ideal for service-to-service authentication</li>
386
- <li>Can be used together with JWT tokens for multi-auth</li>
387
- </ul>
388
- </div>
389
- </div>
390
- </div>
391
- </div>
392
-
393
- {/* OAuth 2.1 Flow */}
394
- <div className="card card-hover p-6">
395
- <h2 className="text-xl font-semibold text-foreground mb-4 flex items-center gap-2">
396
- <Shield className="w-5 h-5 text-primary" />
397
- OAuth 2.1 Flow
398
- </h2>
399
- <p className="text-sm text-muted-foreground mb-6">
400
- For OpenAI Apps SDK and OAuth 2.1 compliant servers
401
- </p>
402
-
403
- {/* Step 1: Discovery */}
404
- <div className="mb-6">
405
- <h3 className="font-medium text-foreground mb-3">1. Discover Server Auth</h3>
406
- <input
407
- type="url"
408
- value={serverUrl}
409
- onChange={(e) => setServerUrl(e.target.value)}
410
- placeholder="https://mcp.example.com"
411
- className="input mb-3"
412
- />
413
- <button
414
- onClick={handleDiscover}
415
- className="btn btn-primary gap-2"
416
- disabled={discovering || !serverUrl}
417
- >
418
- <ExternalLink className="w-4 h-4" />
419
- {discovering ? 'Discovering...' : 'Discover Auth Config'}
420
- </button>
421
-
422
- {oauthState.resourceMetadata && (
423
- <div className="mt-4 p-4 bg-emerald-500/10 border border-emerald-500/20 rounded-lg">
424
- <div className="flex items-center gap-2 mb-2">
425
- <CheckCircle2 className="w-4 h-4 text-emerald-500" />
426
- <p className="text-sm font-semibold text-emerald-600 dark:text-emerald-400">Discovery Successful</p>
427
- </div>
428
- <details className="text-sm text-muted-foreground">
429
- <summary className="cursor-pointer hover:text-foreground font-medium">
430
- View Metadata
431
- </summary>
432
- <pre className="mt-3 p-3 bg-background rounded-lg overflow-auto max-h-40 font-mono text-xs text-foreground border border-border">
433
- {JSON.stringify(oauthState.resourceMetadata, null, 2)}
434
- </pre>
435
- </details>
436
- </div>
437
- )}
438
- </div>
439
-
440
- {/* Step 2a: Manual Client Credentials (Alternative to Registration) */}
441
- {oauthState.resourceMetadata && !oauthState.clientRegistration && (
442
- <div className="mb-6 border-t border-border pt-6">
443
- <h3 className="font-medium text-foreground mb-3">2a. Use Existing Client (Optional)</h3>
444
- <p className="text-sm text-muted-foreground mb-4">
445
- If you already have a Client ID and Secret from your OAuth provider, enter them here instead of dynamic registration.
446
- </p>
447
- <div className="space-y-3 mb-4">
448
- <input
449
- type="text"
450
- value={manualClientId}
451
- onChange={(e) => setManualClientId(e.target.value)}
452
- placeholder="Client ID"
453
- className="input font-mono"
454
- />
455
- <input
456
- type="password"
457
- value={manualClientSecret}
458
- onChange={(e) => setManualClientSecret(e.target.value)}
459
- placeholder="Client Secret (optional for public clients)"
460
- className="input font-mono"
461
- />
462
- </div>
463
- <button
464
- onClick={handleManualCredentials}
465
- className="btn btn-primary gap-2"
466
- disabled={!manualClientId.trim()}
467
- >
468
- <Key className="w-4 h-4" />
469
- Save Client Credentials
470
- </button>
471
- </div>
472
- )}
473
-
474
- {/* Step 2b: Registration */}
475
- {oauthState.authServerMetadata && !oauthState.clientRegistration && (
476
- <div className="mb-6 border-t border-border pt-6">
477
- <h3 className="font-medium text-foreground mb-3">2b. Register New Client (Dynamic)</h3>
478
- <div className="space-y-3 mb-4">
479
- <input
480
- type="text"
481
- value={clientName}
482
- onChange={(e) => setClientName(e.target.value)}
483
- placeholder="Client Name"
484
- className="input"
485
- />
486
- <input
487
- type="url"
488
- value={redirectUri}
489
- onChange={(e) => setRedirectUri(e.target.value)}
490
- placeholder="Redirect URI"
491
- className="input"
492
- />
493
- </div>
494
- <button
495
- onClick={handleRegister}
496
- className="btn btn-primary gap-2"
497
- disabled={registering}
498
- >
499
- <Shield className="w-4 h-4" />
500
- {registering ? 'Registering...' : 'Register Client'}
501
- </button>
502
-
503
- {oauthState.clientRegistration && (
504
- <div className="mt-4 p-4 bg-emerald-500/10 border border-emerald-500/20 rounded-lg">
505
- <div className="flex items-center gap-2 mb-2">
506
- <CheckCircle2 className="w-4 h-4 text-emerald-500" />
507
- <p className="text-sm font-semibold text-emerald-600 dark:text-emerald-400">Registration Successful</p>
508
- </div>
509
- <p className="text-sm text-muted-foreground font-mono">
510
- Client ID: {oauthState.clientRegistration.client_id}
511
- </p>
512
- </div>
513
- )}
514
- </div>
515
- )}
516
-
517
- {/* Step 3: Start Flow */}
518
- {oauthState.clientRegistration && (
519
- <div className="border-t border-border pt-6">
520
- <h3 className="font-medium text-foreground mb-3">3. Start OAuth Flow</h3>
521
- <button
522
- onClick={handleStartOAuthFlow}
523
- className="btn btn-primary gap-2"
524
- >
525
- <ExternalLink className="w-4 h-4" />
526
- Start Authorization Flow
527
- </button>
528
- <p className="text-sm text-muted-foreground mt-2 flex items-center gap-1">
529
- <AlertCircle className="w-4 h-4" />
530
- This will redirect you to the authorization server for login
531
- </p>
532
- </div>
533
- )}
534
- </div>
535
-
536
- {/* API Keys */}
537
- <div className="card card-hover p-6">
538
- <h2 className="text-xl font-semibold text-foreground mb-4 flex items-center gap-2">
539
- <Key className="w-5 h-5 text-primary" />
540
- API Keys
541
- </h2>
542
- <p className="text-sm text-muted-foreground mb-3">
543
- For servers that use API key authentication
544
- </p>
545
- <div className="bg-blue-500/10 border border-blue-500/20 rounded-lg p-4">
546
- <div className="flex items-start gap-2">
547
- <AlertCircle className="w-5 h-5 text-blue-500 mt-0.5 flex-shrink-0" />
548
- <p className="text-sm text-foreground">
549
- API key authentication is typically configured per-tool in the server configuration.
550
- Refer to your server's documentation for details.
551
- </p>
552
- </div>
553
- </div>
554
- </div>
555
- </div>
556
- </div>
557
- </div>
558
- </div>
559
- );
560
- }