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
@@ -18,7 +18,10 @@ export async function POST(request: NextRequest) {
18
18
  provider,
19
19
  messagesCount: messages?.length,
20
20
  messages: JSON.stringify(messages),
21
- hasApiKey: !!apiKey
21
+ hasApiKey: !!apiKey,
22
+ hasJwtToken: !!jwtToken,
23
+ hasMcpApiKey: !!mcpApiKey,
24
+ jwtTokenPreview: jwtToken ? jwtToken.substring(0, 20) + '...' : null
22
25
  });
23
26
 
24
27
  if (!provider || !messages || !apiKey) {
@@ -38,11 +41,79 @@ export async function POST(request: NextRequest) {
38
41
  // Get available tools from MCP server
39
42
  const client = getMcpClient();
40
43
  const toolsList = await client.listTools();
41
- const tools = toolsList.tools?.map((tool: any) => ({
44
+ const mcpTools = toolsList.tools?.map((tool: any) => ({
42
45
  name: tool.name,
43
46
  description: tool.description || '',
44
47
  inputSchema: tool.inputSchema || {},
45
48
  })) || [];
49
+
50
+ // Add synthetic tools for prompts and resources
51
+ const promptsList = await client.listPrompts().catch(() => ({ prompts: [] }));
52
+ const resourcesList = await client.listResources().catch(() => ({ resources: [] }));
53
+
54
+ const syntheticTools = [];
55
+
56
+ // Add a tool to list available prompts
57
+ if (promptsList.prompts && promptsList.prompts.length > 0) {
58
+ syntheticTools.push({
59
+ name: 'list_prompts',
60
+ description: 'List all available MCP prompts with their names, descriptions, and required arguments. Call this when user asks what prompts are available. After calling this, present the complete list of prompts to the user.',
61
+ inputSchema: {
62
+ type: 'object',
63
+ properties: {},
64
+ },
65
+ });
66
+
67
+ // Add a tool to execute prompts
68
+ syntheticTools.push({
69
+ name: 'execute_prompt',
70
+ description: 'Execute an MCP prompt with given arguments. Returns the prompt result. After calling this, display the result to the user.',
71
+ inputSchema: {
72
+ type: 'object',
73
+ properties: {
74
+ name: {
75
+ type: 'string',
76
+ description: 'The name of the prompt to execute',
77
+ },
78
+ arguments: {
79
+ type: 'object',
80
+ description: 'Arguments to pass to the prompt (key-value pairs)',
81
+ },
82
+ },
83
+ required: ['name'],
84
+ },
85
+ });
86
+ }
87
+
88
+ // Add a tool to list available resources
89
+ if (resourcesList.resources && resourcesList.resources.length > 0) {
90
+ syntheticTools.push({
91
+ name: 'list_resources',
92
+ description: 'List all available MCP resources with their URIs, names, descriptions, and mime types. Call this when user asks to see what resources are available. After calling this, present the complete list of resources to the user.',
93
+ inputSchema: {
94
+ type: 'object',
95
+ properties: {},
96
+ },
97
+ });
98
+
99
+ // Add a tool to read resources
100
+ syntheticTools.push({
101
+ name: 'read_resource',
102
+ description: 'Read the contents of an MCP resource by its URI. Returns the full resource content. After calling this, display the resource content to the user.',
103
+ inputSchema: {
104
+ type: 'object',
105
+ properties: {
106
+ uri: {
107
+ type: 'string',
108
+ description: 'The URI of the resource to read (e.g., "widget://examples", "config://settings")',
109
+ },
110
+ },
111
+ required: ['uri'],
112
+ },
113
+ });
114
+ }
115
+
116
+ const tools = [...mcpTools, ...syntheticTools];
46
117
 
47
118
  // Call LLM
48
119
  const response = await llmService.chat(provider, messages, tools, apiKey);
@@ -53,35 +124,89 @@ export async function POST(request: NextRequest) {
53
124
 
54
125
  for (const toolCall of response.toolCalls) {
55
126
  try {
56
- // Inject auth tokens into tool arguments if available
57
- const toolArgs = { ...toolCall.arguments };
58
- if (jwtToken || mcpApiKey) {
59
- toolArgs._meta = {
60
- ...(toolArgs._meta || {}),
61
- };
62
-
63
- if (jwtToken) {
64
- toolArgs._meta._jwt = jwtToken;
65
- toolArgs._meta.authorization = `Bearer ${jwtToken}`;
127
+ let result: any;
128
+ let toolContent = '';
129
+
130
+ // Handle synthetic tools
131
+ if (toolCall.name === 'list_prompts') {
132
+ const prompts = await client.listPrompts();
133
+ const formattedPrompts = prompts.prompts?.map((p: any) => ({
134
+ name: p.name,
135
+ description: p.description,
136
+ arguments: p.arguments,
137
+ })) || [];
138
+ toolContent = JSON.stringify(formattedPrompts, null, 2);
139
+ console.log('📝 list_prompts result:', toolContent);
140
+ } else if (toolCall.name === 'execute_prompt') {
141
+ const promptResult = await client.getPrompt(
142
+ toolCall.arguments.name,
143
+ toolCall.arguments.arguments || {}
144
+ );
145
+ toolContent = JSON.stringify(promptResult.messages || promptResult, null, 2);
146
+ console.log('▶️ execute_prompt result:', toolContent.substring(0, 200) + '...');
147
+ } else if (toolCall.name === 'list_resources') {
148
+ const resources = await client.listResources();
149
+ const formattedResources = resources.resources?.map((r: any) => ({
150
+ uri: r.uri,
151
+ name: r.name,
152
+ description: r.description,
153
+ mimeType: r.mimeType,
154
+ })) || [];
155
+ toolContent = JSON.stringify(formattedResources, null, 2);
156
+ console.log('📋 list_resources result:', toolContent);
157
+ } else if (toolCall.name === 'read_resource') {
158
+ const resource = await client.readResource(toolCall.arguments.uri);
159
+ // Extract the actual content from the resource
160
+ let resourceContent = resource;
161
+ if (resource.contents && Array.isArray(resource.contents)) {
162
+ // If it's an array of contents, extract text from each
163
+ resourceContent = resource.contents.map((c: any) => {
164
+ if (c.text) return c.text;
165
+ if (c.blob) return `[Binary data: ${c.mimeType || 'unknown type'}]`;
166
+ return JSON.stringify(c);
167
+ }).join('\n\n');
66
168
  }
67
-
68
- if (mcpApiKey) {
69
- toolArgs._meta.apiKey = mcpApiKey;
70
- toolArgs._meta['x-api-key'] = mcpApiKey;
169
+ toolContent = typeof resourceContent === 'string' ? resourceContent : JSON.stringify(resourceContent, null, 2);
170
+ console.log('📖 read_resource result:', toolContent.substring(0, 200) + '...');
171
+ } else {
172
+ // Regular MCP tool execution
173
+ // Inject auth tokens into tool arguments if available
174
+ const toolArgs = { ...toolCall.arguments };
175
+ if (jwtToken || mcpApiKey) {
176
+ toolArgs._meta = {
177
+ ...(toolArgs._meta || {}),
178
+ };
179
+
180
+ if (jwtToken) {
181
+ toolArgs._meta._jwt = jwtToken;
182
+ toolArgs._meta.authorization = `Bearer ${jwtToken}`;
183
+ }
184
+
185
+ if (mcpApiKey) {
186
+ toolArgs._meta.apiKey = mcpApiKey;
187
+ toolArgs._meta['x-api-key'] = mcpApiKey;
188
+ }
189
+
190
+ console.log(`🔐 Executing tool "${toolCall.name}" with auth:`, {
191
+ hasJwt: !!jwtToken,
192
+ hasMcpApiKey: !!mcpApiKey,
193
+ metaKeys: Object.keys(toolArgs._meta)
194
+ });
195
+ } else {
196
+ console.log(`⚠️ Executing tool "${toolCall.name}" WITHOUT auth tokens`);
71
197
  }
72
- }
73
198
 
74
- // Execute tool via MCP client
75
- const result = await client.executeTool(toolCall.name, toolArgs);
199
+ // Execute tool via MCP client
200
+ result = await client.executeTool(toolCall.name, toolArgs);
76
201
 
77
- // Extract content from MCP result
78
- let toolContent = '';
79
- if (result.content && Array.isArray(result.content)) {
80
- toolContent = result.content
81
- .map((c: any) => c.text || JSON.stringify(c))
82
- .join('\n');
83
- } else {
84
- toolContent = JSON.stringify(result);
202
+ // Extract content from MCP result
203
+ if (result.content && Array.isArray(result.content)) {
204
+ toolContent = result.content
205
+ .map((c: any) => c.text || JSON.stringify(c))
206
+ .join('\n');
207
+ } else {
208
+ toolContent = JSON.stringify(result);
209
+ }
85
210
  }
86
211
 
87
212
  // Store tool result with both ID and NAME (Gemini needs the name!)
@@ -92,9 +217,11 @@ export async function POST(request: NextRequest) {
92
217
  toolName: toolCall.name, // Add tool name for Gemini function responses
93
218
  });
94
219
  } catch (error: any) {
220
+ console.error(`❌ Error executing tool "${toolCall.name}":`, error);
221
+ const errorMessage = `Error executing ${toolCall.name}: ${error.message}`;
95
222
  toolResults.push({
96
223
  role: 'tool',
97
- content: JSON.stringify({ error: error.message }),
224
+ content: JSON.stringify({ error: error.message, message: errorMessage }),
98
225
  toolCallId: toolCall.id,
99
226
  toolName: toolCall.name, // Add tool name for Gemini function responses
100
227
  });
@@ -24,11 +24,33 @@ function loadEnvFile(projectPath: string): Record<string, string> {
24
24
  return envVars;
25
25
  }
26
26
 
27
- export async function POST() {
27
+ export async function POST(request: Request) {
28
28
  try {
29
29
  const client = getMcpClient();
30
30
 
31
- // Get MCP server config from environment
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)
32
54
  const command = process.env.MCP_COMMAND || 'node';
33
55
  const argsString = process.env.MCP_ARGS || '';
34
56
 
@@ -44,7 +66,6 @@ export async function POST() {
44
66
  }
45
67
  }
46
68
 
47
- if (!client.isConnected()) {
48
69
  // Get project directory from the MCP server path
49
70
  // If using wrapper, the actual server path is the second argument
50
71
  const serverPath = args.length > 1 ? args[1] : args[0];
@@ -59,6 +80,7 @@ export async function POST() {
59
80
  console.log(`📝 Command: ${command} ${args.join(' ')}`);
60
81
 
61
82
  await client.connect({
83
+ type: 'stdio',
62
84
  command,
63
85
  args,
64
86
  env: {
@@ -68,12 +90,14 @@ export async function POST() {
68
90
  cwd: projectPath, // Set working directory to project root
69
91
  });
70
92
 
71
- console.log('✅ MCP client connected successfully');
93
+ console.log('✅ MCP client connected successfully via STDIO');
94
+ }
72
95
  }
73
96
 
74
97
  return NextResponse.json({
75
98
  success: true,
76
99
  message: 'MCP client connected',
100
+ transport: client.getTransportType(),
77
101
  });
78
102
  } catch (error: any) {
79
103
  console.error('Failed to connect MCP client:', error);
@@ -166,21 +166,23 @@ export default function AuthPage() {
166
166
  };
167
167
 
168
168
  return (
169
- <div className="min-h-screen bg-background p-8">
170
- {/* Header */}
171
- <div className="mb-8">
172
- <div className="flex items-center gap-3 mb-4">
173
- <div className="w-12 h-12 rounded-lg bg-gradient-to-br from-violet-500 to-purple-500 flex items-center justify-center">
174
- <Shield className="w-6 h-6 text-white" />
169
+ <div className="fixed inset-0 flex flex-col bg-background" style={{ left: 'var(--sidebar-width, 15rem)' }}>
170
+ {/* Sticky Header */}
171
+ <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">
172
+ <div className="flex items-center gap-3">
173
+ <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">
174
+ <Shield className="w-5 h-5 text-white" strokeWidth={2.5} />
175
175
  </div>
176
176
  <div>
177
- <h1 className="text-3xl font-bold text-foreground">Authentication</h1>
178
- <p className="text-muted-foreground mt-1">OAuth 2.1 / JWT / API Key authentication</p>
177
+ <h1 className="text-lg font-bold text-foreground">OAuth 2.1</h1>
179
178
  </div>
180
179
  </div>
181
180
  </div>
182
181
 
183
- <div className="max-w-4xl mx-auto space-y-6">
182
+ {/* Content - ONLY this scrolls */}
183
+ <div className="flex-1 overflow-y-auto overflow-x-hidden">
184
+ <div className="max-w-4xl mx-auto px-6 py-6">
185
+ <div className="space-y-6">
184
186
  {/* JWT Token Management */}
185
187
  <div className="card card-hover p-6">
186
188
  <h2 className="text-xl font-semibold text-foreground mb-4 flex items-center gap-2">
@@ -537,6 +539,8 @@ export default function AuthPage() {
537
539
  </div>
538
540
  </div>
539
541
  </div>
542
+ </div>
543
+ </div>
540
544
  </div>
541
545
  </div>
542
546
  );