nitrostack 1.0.65 → 1.0.67

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 (61) hide show
  1. package/package.json +3 -2
  2. package/src/studio/README.md +140 -0
  3. package/src/studio/app/api/auth/fetch-metadata/route.ts +71 -0
  4. package/src/studio/app/api/auth/register-client/route.ts +67 -0
  5. package/src/studio/app/api/chat/route.ts +250 -0
  6. package/src/studio/app/api/health/checks/route.ts +42 -0
  7. package/src/studio/app/api/health/route.ts +13 -0
  8. package/src/studio/app/api/init/route.ts +109 -0
  9. package/src/studio/app/api/ping/route.ts +13 -0
  10. package/src/studio/app/api/prompts/[name]/route.ts +21 -0
  11. package/src/studio/app/api/prompts/route.ts +13 -0
  12. package/src/studio/app/api/resources/[...uri]/route.ts +18 -0
  13. package/src/studio/app/api/resources/route.ts +13 -0
  14. package/src/studio/app/api/roots/route.ts +13 -0
  15. package/src/studio/app/api/sampling/route.ts +14 -0
  16. package/src/studio/app/api/tools/[name]/call/route.ts +41 -0
  17. package/src/studio/app/api/tools/route.ts +23 -0
  18. package/src/studio/app/api/widget-examples/route.ts +44 -0
  19. package/src/studio/app/auth/callback/page.tsx +175 -0
  20. package/src/studio/app/auth/page.tsx +560 -0
  21. package/src/studio/app/chat/page.tsx +1133 -0
  22. package/src/studio/app/chat/page.tsx.backup +390 -0
  23. package/src/studio/app/globals.css +486 -0
  24. package/src/studio/app/health/page.tsx +179 -0
  25. package/src/studio/app/layout.tsx +68 -0
  26. package/src/studio/app/logs/page.tsx +279 -0
  27. package/src/studio/app/page.tsx +351 -0
  28. package/src/studio/app/page.tsx.backup +346 -0
  29. package/src/studio/app/ping/page.tsx +209 -0
  30. package/src/studio/app/prompts/page.tsx +230 -0
  31. package/src/studio/app/resources/page.tsx +315 -0
  32. package/src/studio/app/settings/page.tsx +199 -0
  33. package/src/studio/branding.md +807 -0
  34. package/src/studio/components/EnlargeModal.tsx +138 -0
  35. package/src/studio/components/LogMessage.tsx +153 -0
  36. package/src/studio/components/MarkdownRenderer.tsx +410 -0
  37. package/src/studio/components/Sidebar.tsx +295 -0
  38. package/src/studio/components/ToolCard.tsx +139 -0
  39. package/src/studio/components/WidgetRenderer.tsx +346 -0
  40. package/src/studio/lib/api.ts +207 -0
  41. package/src/studio/lib/http-client-transport.ts +222 -0
  42. package/src/studio/lib/llm-service.ts +480 -0
  43. package/src/studio/lib/log-manager.ts +76 -0
  44. package/src/studio/lib/mcp-client.ts +258 -0
  45. package/src/studio/lib/store.ts +192 -0
  46. package/src/studio/lib/theme-provider.tsx +50 -0
  47. package/src/studio/lib/types.ts +107 -0
  48. package/src/studio/lib/widget-loader.ts +90 -0
  49. package/src/studio/middleware.ts +27 -0
  50. package/src/studio/next.config.js +38 -0
  51. package/src/studio/package.json +35 -0
  52. package/src/studio/postcss.config.mjs +10 -0
  53. package/src/studio/public/nitrocloud.png +0 -0
  54. package/src/studio/tailwind.config.ts +67 -0
  55. package/src/studio/tsconfig.json +42 -0
  56. package/templates/typescript-oauth/AI_AGENT_CLI_REFERENCE.md +0 -701
  57. package/templates/typescript-oauth/AI_AGENT_SDK_REFERENCE.md +0 -1260
  58. package/templates/typescript-oauth/package-lock.json +0 -4253
  59. package/templates/typescript-pizzaz/IMPLEMENTATION.md +0 -98
  60. package/templates/typescript-starter/AI_AGENT_CLI_REFERENCE.md +0 -701
  61. package/templates/typescript-starter/AI_AGENT_SDK_REFERENCE.md +0 -1260
@@ -0,0 +1,109 @@
1
+ // Initialize MCP client connection
2
+ import { NextResponse } from 'next/server';
3
+ import { getMcpClient } from '@/lib/mcp-client';
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+
7
+ function loadEnvFile(projectPath: string): Record<string, string> {
8
+ const envPath = path.join(projectPath, '.env');
9
+ const envVars: Record<string, string> = {};
10
+
11
+ if (fs.existsSync(envPath)) {
12
+ const envContent = fs.readFileSync(envPath, 'utf-8');
13
+ envContent.split('\n').forEach(line => {
14
+ line = line.trim();
15
+ if (line && !line.startsWith('#')) {
16
+ const [key, ...valueParts] = line.split('=');
17
+ if (key && valueParts.length > 0) {
18
+ envVars[key.trim()] = valueParts.join('=').trim().replace(/^["']|["']$/g, '');
19
+ }
20
+ }
21
+ });
22
+ }
23
+
24
+ return envVars;
25
+ }
26
+
27
+ export async function POST(request: Request) {
28
+ try {
29
+ const client = getMcpClient();
30
+
31
+ // Parse request body to get transport configuration
32
+ const body = await request.json().catch(() => ({}));
33
+ const transportType = body.transport || 'stdio'; // Default to stdio for backward compatibility
34
+
35
+ if (!client.isConnected()) {
36
+ if (transportType === 'http') {
37
+ // HTTP Transport Configuration
38
+ const baseUrl = body.baseUrl || process.env.MCP_HTTP_URL || 'http://localhost:3000';
39
+ const basePath = body.basePath || '/mcp';
40
+ const headers = body.headers || {};
41
+
42
+ console.log(`🌐 Connecting via HTTP transport to ${baseUrl}${basePath}`);
43
+
44
+ await client.connect({
45
+ type: 'http',
46
+ baseUrl,
47
+ basePath,
48
+ headers,
49
+ });
50
+
51
+ console.log('✅ MCP client connected successfully via HTTP');
52
+ } else {
53
+ // STDIO Transport Configuration (default)
54
+ const command = process.env.MCP_COMMAND || 'node';
55
+ const argsString = process.env.MCP_ARGS || '';
56
+
57
+ // Parse MCP_ARGS - it can be a JSON array string or a single path
58
+ let args: string[] = [];
59
+ if (argsString) {
60
+ try {
61
+ // Try to parse as JSON array
62
+ args = JSON.parse(argsString);
63
+ } catch {
64
+ // If not JSON, treat as a single argument
65
+ args = [argsString];
66
+ }
67
+ }
68
+
69
+ // Get project directory from the MCP server path
70
+ // If using wrapper, the actual server path is the second argument
71
+ const serverPath = args.length > 1 ? args[1] : args[0];
72
+ const projectPath = serverPath ? path.dirname(path.dirname(serverPath)) : process.cwd();
73
+
74
+ // Load environment variables from project's .env file
75
+ const projectEnv = loadEnvFile(projectPath);
76
+
77
+ console.log(`📂 Project path: ${projectPath}`);
78
+ console.log(`🔧 Loaded ${Object.keys(projectEnv).length} env vars from ${projectPath}/.env`);
79
+ console.log(`🔑 JWT_SECRET present: ${projectEnv.JWT_SECRET ? 'YES' : 'NO'}`);
80
+ console.log(`📝 Command: ${command} ${args.join(' ')}`);
81
+
82
+ await client.connect({
83
+ type: 'stdio',
84
+ command,
85
+ args,
86
+ env: {
87
+ ...projectEnv,
88
+ NODE_ENV: 'development',
89
+ },
90
+ cwd: projectPath, // Set working directory to project root
91
+ });
92
+
93
+ console.log('✅ MCP client connected successfully via STDIO');
94
+ }
95
+ }
96
+
97
+ return NextResponse.json({
98
+ success: true,
99
+ message: 'MCP client connected',
100
+ transport: client.getTransportType(),
101
+ });
102
+ } catch (error: any) {
103
+ console.error('Failed to connect MCP client:', error);
104
+ return NextResponse.json(
105
+ { error: error.message },
106
+ { status: 500 }
107
+ );
108
+ }
109
+ }
@@ -0,0 +1,13 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getMcpClient } from '@/lib/mcp-client';
3
+
4
+ export async function GET() {
5
+ try {
6
+ const client = getMcpClient();
7
+ const result = await client.ping();
8
+ return NextResponse.json(result);
9
+ } catch (error: any) {
10
+ return NextResponse.json({ error: error.message }, { status: 500 });
11
+ }
12
+ }
13
+
@@ -0,0 +1,21 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { getMcpClient } from '@/lib/mcp-client';
3
+
4
+ export async function POST(
5
+ request: NextRequest,
6
+ { params }: { params: { name: string } }
7
+ ) {
8
+ try {
9
+ const client = getMcpClient();
10
+ const args = await request.json();
11
+ console.log('🔍 Executing prompt:', params.name, 'with args:', args);
12
+ const result = await client.getPrompt(params.name, args);
13
+ console.log('✅ Prompt result:', result);
14
+ return NextResponse.json(result);
15
+ } catch (error: any) {
16
+ console.error('❌ Prompt execution error:', error);
17
+ console.error('Error stack:', error.stack);
18
+ return NextResponse.json({ error: error.message, stack: error.stack }, { status: 500 });
19
+ }
20
+ }
21
+
@@ -0,0 +1,13 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getMcpClient } from '@/lib/mcp-client';
3
+
4
+ export async function GET() {
5
+ try {
6
+ const client = getMcpClient();
7
+ const result = await client.listPrompts();
8
+ return NextResponse.json(result);
9
+ } catch (error: any) {
10
+ return NextResponse.json({ error: error.message }, { status: 500 });
11
+ }
12
+ }
13
+
@@ -0,0 +1,18 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { getMcpClient } from '@/lib/mcp-client';
3
+
4
+ export async function GET(
5
+ request: NextRequest,
6
+ { params }: { params: { uri: string[] } }
7
+ ) {
8
+ try {
9
+ const client = getMcpClient();
10
+ const uri = params.uri.join('/');
11
+ const decodedUri = decodeURIComponent(uri);
12
+ const result = await client.readResource(decodedUri);
13
+ return NextResponse.json(result);
14
+ } catch (error: any) {
15
+ return NextResponse.json({ error: error.message }, { status: 500 });
16
+ }
17
+ }
18
+
@@ -0,0 +1,13 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getMcpClient } from '@/lib/mcp-client';
3
+
4
+ export async function GET() {
5
+ try {
6
+ const client = getMcpClient();
7
+ const result = await client.listResources();
8
+ return NextResponse.json(result);
9
+ } catch (error: any) {
10
+ return NextResponse.json({ error: error.message }, { status: 500 });
11
+ }
12
+ }
13
+
@@ -0,0 +1,13 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getMcpClient } from '@/lib/mcp-client';
3
+
4
+ export async function GET() {
5
+ try {
6
+ const client = getMcpClient();
7
+ const result = await client.listRoots();
8
+ return NextResponse.json(result);
9
+ } catch (error: any) {
10
+ return NextResponse.json({ error: error.message }, { status: 500 });
11
+ }
12
+ }
13
+
@@ -0,0 +1,14 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { getMcpClient } from '@/lib/mcp-client';
3
+
4
+ export async function POST(request: NextRequest) {
5
+ try {
6
+ const client = getMcpClient();
7
+ const params = await request.json();
8
+ const result = await client.createCompletion(params);
9
+ return NextResponse.json(result);
10
+ } catch (error: any) {
11
+ return NextResponse.json({ error: error.message }, { status: 500 });
12
+ }
13
+ }
14
+
@@ -0,0 +1,41 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { getMcpClient } from '@/lib/mcp-client';
3
+
4
+ export async function POST(
5
+ request: NextRequest,
6
+ { params }: { params: { name: string } }
7
+ ) {
8
+ try {
9
+ const client = getMcpClient();
10
+ const body = await request.json();
11
+
12
+ // Extract args, JWT token, and API key from request body
13
+ const { args = {}, jwtToken, apiKey } = body;
14
+
15
+ // Inject auth tokens into tool arguments if available
16
+ const toolArgs = { ...args };
17
+
18
+ // Add _meta field for auth tokens
19
+ if (jwtToken || apiKey) {
20
+ toolArgs._meta = {
21
+ ...(toolArgs._meta || {}),
22
+ };
23
+
24
+ if (jwtToken) {
25
+ toolArgs._meta._jwt = jwtToken;
26
+ toolArgs._meta.authorization = `Bearer ${jwtToken}`;
27
+ }
28
+
29
+ if (apiKey) {
30
+ toolArgs._meta.apiKey = apiKey;
31
+ toolArgs._meta['x-api-key'] = apiKey;
32
+ }
33
+ }
34
+
35
+ const result = await client.callTool(params.name, toolArgs);
36
+ return NextResponse.json(result);
37
+ } catch (error: any) {
38
+ return NextResponse.json({ error: error.message }, { status: 500 });
39
+ }
40
+ }
41
+
@@ -0,0 +1,23 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getMcpClient } from '@/lib/mcp-client';
3
+
4
+ export async function GET() {
5
+ try {
6
+ const client = getMcpClient();
7
+
8
+ // Check if connected, if not, throw error to trigger re-init
9
+ if (!client.isConnected()) {
10
+ console.error('❌ MCP client not connected in /api/tools');
11
+ return NextResponse.json({
12
+ error: 'MCP client not connected. Please refresh the page.'
13
+ }, { status: 500 });
14
+ }
15
+
16
+ const result = await client.listTools();
17
+ return NextResponse.json(result);
18
+ } catch (error: any) {
19
+ console.error('❌ Error in /api/tools:', error.message);
20
+ return NextResponse.json({ error: error.message }, { status: 500 });
21
+ }
22
+ }
23
+
@@ -0,0 +1,44 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getMcpClient } from '@/lib/mcp-client';
3
+
4
+ export async function GET() {
5
+ try {
6
+ const client = getMcpClient();
7
+
8
+ if (!client.isConnected()) {
9
+ return NextResponse.json({
10
+ error: 'MCP client not connected',
11
+ widgets: []
12
+ }, { status: 500 });
13
+ }
14
+
15
+ // Read widget examples from MCP server as a resource
16
+ try {
17
+ const result = await client.readResource('widget://examples');
18
+
19
+ // Parse the widget examples data
20
+ const widgetData = JSON.parse(result.contents[0].text);
21
+
22
+ return NextResponse.json({
23
+ widgets: widgetData.widgets || [],
24
+ count: widgetData.widgets?.length || 0,
25
+ loaded: widgetData.loaded || false
26
+ });
27
+ } catch (resourceError: any) {
28
+ // Widget examples resource not available
29
+ console.log('No widget examples resource available');
30
+ return NextResponse.json({
31
+ widgets: [],
32
+ count: 0,
33
+ loaded: false
34
+ });
35
+ }
36
+ } catch (error: any) {
37
+ console.error('❌ Error in /api/widget-examples:', error.message);
38
+ return NextResponse.json(
39
+ { error: 'Failed to fetch widget examples', widgets: [] },
40
+ { status: 500 }
41
+ );
42
+ }
43
+ }
44
+
@@ -0,0 +1,175 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState, Suspense } from 'react';
4
+ import { useRouter, useSearchParams } from 'next/navigation';
5
+ import { useStudioStore } from '@/lib/store';
6
+ import { Loader2, CheckCircle2, XCircle } from 'lucide-react';
7
+
8
+ function OAuthCallback() {
9
+ const router = useRouter();
10
+ const searchParams = useSearchParams();
11
+ const { oauthState, setJwtToken } = useStudioStore();
12
+ const [status, setStatus] = useState<'processing' | 'success' | 'error'>('processing');
13
+ const [message, setMessage] = useState('Processing authorization...');
14
+
15
+ useEffect(() => {
16
+ const handleCallback = async () => {
17
+ try {
18
+ // Get authorization code and state from URL
19
+ const code = searchParams.get('code');
20
+ const state = searchParams.get('state');
21
+ const error = searchParams.get('error');
22
+ const errorDescription = searchParams.get('error_description');
23
+
24
+ // Check for errors
25
+ if (error) {
26
+ setStatus('error');
27
+ setMessage(`Authorization failed: ${errorDescription || error}`);
28
+ return;
29
+ }
30
+
31
+ // Validate state
32
+ const storedState = sessionStorage.getItem('oauth_state');
33
+ if (!state || state !== storedState) {
34
+ setStatus('error');
35
+ setMessage('Invalid state parameter. Possible CSRF attack.');
36
+ return;
37
+ }
38
+
39
+ // Validate code
40
+ if (!code) {
41
+ setStatus('error');
42
+ setMessage('Authorization code not received');
43
+ return;
44
+ }
45
+
46
+ // Get code verifier and client credentials
47
+ const codeVerifier = sessionStorage.getItem('oauth_code_verifier');
48
+ if (!codeVerifier) {
49
+ setStatus('error');
50
+ setMessage('Code verifier not found. Please try again.');
51
+ return;
52
+ }
53
+
54
+ if (!oauthState.clientRegistration?.client_id) {
55
+ setStatus('error');
56
+ setMessage('Client ID not found. Please register again.');
57
+ return;
58
+ }
59
+
60
+ if (!oauthState.authServerMetadata?.token_endpoint) {
61
+ setStatus('error');
62
+ setMessage('Token endpoint not found in server metadata');
63
+ return;
64
+ }
65
+
66
+ setMessage('Exchanging authorization code for tokens...');
67
+
68
+ // Exchange code for tokens
69
+ const tokenResponse = await fetch(oauthState.authServerMetadata.token_endpoint, {
70
+ method: 'POST',
71
+ headers: {
72
+ 'Content-Type': 'application/x-www-form-urlencoded',
73
+ },
74
+ body: new URLSearchParams({
75
+ grant_type: 'authorization_code',
76
+ code,
77
+ redirect_uri: 'http://localhost:3000/auth/callback',
78
+ client_id: oauthState.clientRegistration.client_id,
79
+ ...(oauthState.clientRegistration.client_secret && {
80
+ client_secret: oauthState.clientRegistration.client_secret,
81
+ }),
82
+ code_verifier: codeVerifier,
83
+ }),
84
+ });
85
+
86
+ if (!tokenResponse.ok) {
87
+ const errorData = await tokenResponse.json().catch(() => ({}));
88
+ setStatus('error');
89
+ setMessage(`Token exchange failed: ${errorData.error_description || errorData.error || tokenResponse.statusText}`);
90
+ return;
91
+ }
92
+
93
+ const tokens = await tokenResponse.json();
94
+
95
+ // Store access token as JWT token
96
+ if (tokens.access_token) {
97
+ setJwtToken(tokens.access_token);
98
+
99
+ // Clean up session storage
100
+ sessionStorage.removeItem('oauth_code_verifier');
101
+ sessionStorage.removeItem('oauth_state');
102
+
103
+ setStatus('success');
104
+ setMessage('Authorization successful! Redirecting...');
105
+
106
+ // Redirect back to auth page after 2 seconds
107
+ setTimeout(() => {
108
+ router.push('/auth');
109
+ }, 2000);
110
+ } else {
111
+ setStatus('error');
112
+ setMessage('Access token not received');
113
+ }
114
+ } catch (error: any) {
115
+ console.error('OAuth callback error:', error);
116
+ setStatus('error');
117
+ setMessage(`Error: ${error.message || 'Unknown error occurred'}`);
118
+ }
119
+ };
120
+
121
+ handleCallback();
122
+ }, [searchParams, oauthState, setJwtToken, router]);
123
+
124
+ return (
125
+ <div className="min-h-screen bg-background flex items-center justify-center p-8">
126
+ <div className="card p-8 max-w-md w-full text-center">
127
+ {status === 'processing' && (
128
+ <>
129
+ <Loader2 className="w-16 h-16 text-primary mx-auto mb-4 animate-spin" />
130
+ <h1 className="text-2xl font-bold text-foreground mb-2">Processing...</h1>
131
+ <p className="text-muted-foreground">{message}</p>
132
+ </>
133
+ )}
134
+
135
+ {status === 'success' && (
136
+ <>
137
+ <CheckCircle2 className="w-16 h-16 text-emerald-500 mx-auto mb-4" />
138
+ <h1 className="text-2xl font-bold text-foreground mb-2">Success!</h1>
139
+ <p className="text-muted-foreground">{message}</p>
140
+ </>
141
+ )}
142
+
143
+ {status === 'error' && (
144
+ <>
145
+ <XCircle className="w-16 h-16 text-red-500 mx-auto mb-4" />
146
+ <h1 className="text-2xl font-bold text-foreground mb-2">Authorization Failed</h1>
147
+ <p className="text-muted-foreground mb-4">{message}</p>
148
+ <button
149
+ onClick={() => router.push('/auth')}
150
+ className="btn btn-primary"
151
+ >
152
+ Back to Auth Page
153
+ </button>
154
+ </>
155
+ )}
156
+ </div>
157
+ </div>
158
+ );
159
+ }
160
+
161
+ export default function AuthCallbackPage() {
162
+ return (
163
+ <Suspense fallback={
164
+ <div className="fixed inset-0 flex items-center justify-center bg-background">
165
+ <div className="text-center">
166
+ <Loader2 className="w-8 h-8 text-primary animate-spin mx-auto mb-4" />
167
+ <p className="text-muted-foreground">Loading...</p>
168
+ </div>
169
+ </div>
170
+ }>
171
+ <OAuthCallback />
172
+ </Suspense>
173
+ );
174
+ }
175
+