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
@@ -24,12 +24,131 @@ export interface ChatResponse {
24
24
  }
25
25
 
26
26
  export class LLMService {
27
+ // System prompt to improve tool usage (especially for Gemini)
28
+ private getSystemPrompt(tools: any[]): string {
29
+ return `You are an intelligent AI assistant with access to ${tools.length} powerful tools. Your goal is to help users accomplish their tasks efficiently and intelligently.
30
+
31
+ **CORE PRINCIPLES FOR TOOL USAGE:**
32
+
33
+ 1. **Be Proactive & Infer Context**:
34
+ - Infer obvious information instead of asking (e.g., "Bangalore" → Karnataka, India; "New York" → NY, USA)
35
+ - Use common sense defaults when reasonable
36
+ - Don't ask for information you can deduce from context
37
+
38
+ 2. **Chain Tools Intelligently**:
39
+ - Use multiple tools in sequence to accomplish complex tasks
40
+ - If a task requires multiple steps, execute them automatically
41
+ - Example: login → fetch data → process → display results
42
+ - Don't ask permission for each step in an obvious workflow
43
+
44
+ 3. **Maintain Context Awareness**:
45
+ - Remember information from previous tool calls in THIS conversation
46
+ - Extract and reuse data (IDs, names, values) from previous tool results
47
+ - Example: If browse_products returned products with IDs, use those IDs for add_to_cart
48
+ - Match user requests to previous data (e.g., "apple" → find product with name "Apple" → use its ID)
49
+ - Track state across the conversation (logged in status, product IDs, order IDs, etc.)
50
+ - NEVER ask for information you already have from a previous tool call
51
+
52
+ 4. **Use Smart Defaults**:
53
+ - Apply sensible default values when parameters are optional
54
+ - Common defaults: page=1, limit=10, sort=recent, etc.
55
+ - Only ask for clarification when truly ambiguous
56
+
57
+ 5. **Minimize User Friction**:
58
+ - Don't ask for every detail - use inference and defaults
59
+ - Chain related operations seamlessly
60
+ - Provide concise summaries after multi-step operations
61
+ - Be conversational but efficient
62
+
63
+ 6. **Handle Errors Gracefully**:
64
+ - If authentication required, guide user to login
65
+ - If prerequisite missing, suggest the required step
66
+ - If operation fails, explain why and suggest alternatives
67
+ - Always provide a helpful next step
68
+
69
+ 7. **Tool Call Best Practices**:
70
+ - Read tool descriptions carefully to understand their purpose
71
+ - Use exact parameter names as specified in the schema
72
+ - Pay attention to required vs optional parameters
73
+ - If a tool says "Requires authentication" - CALL IT ANYWAY! Auth is handled automatically
74
+ - Don't ask for credentials preemptively - only if a tool explicitly fails
75
+ - Look for examples in tool schemas for guidance
76
+
77
+ **AUTHENTICATION HANDLING:**
78
+
79
+ - Authentication tokens are AUTOMATICALLY handled in the background
80
+ - If a tool says "Requires authentication", you can STILL call it directly - auth is transparent
81
+ - NEVER ask users for credentials unless a tool explicitly fails with an auth error
82
+ - If you get an auth error, THEN suggest the user login using the login tool
83
+ - Once a user logs in (or if already logged in), the session persists automatically
84
+ - You don't need to check if user is authenticated - just call the tool and let the system handle it
85
+
86
+ **EXAMPLES:**
87
+
88
+ **Authentication:**
89
+ User: "whoami" → Call whoami tool directly (don't ask for login)
90
+ User: "show my orders" → Call get_order_history directly (don't ask for login)
91
+ User: "what's in my cart" → Call view_cart directly (don't ask for login)
92
+
93
+ **Context Awareness:**
94
+ 1. browse_products returns: [{id: "prod-3", name: "Apple", price: 0.99}, ...]
95
+ 2. User: "add apple to cart" → Extract ID "prod-3" from previous result → Call add_to_cart({product_id: "prod-3", quantity: 1})
96
+ 3. User: "add 2 more" → Remember prod-3 → Call add_to_cart({product_id: "prod-3", quantity: 2})
97
+
98
+ **NEVER do this:**
99
+ ❌ User: "add apple to cart" → "What is the product ID?" (You already have it!)
100
+ ❌ User: "add to cart" → "Which product?" (Look at conversation context!)
101
+
102
+ Only if tool returns auth error → THEN suggest: "Please login with your credentials"
103
+
104
+ **PRESENTING TOOL RESULTS:**
105
+
106
+ When you call a tool and receive results, you MUST present the information to the user in a clear, formatted way:
107
+
108
+ - For **list_resources**: Show each resource's URI, name, description, and mime type in a formatted list
109
+ - For **list_prompts**: Show each prompt's name, description, and required arguments
110
+ - For **read_resource**: Display the resource content in an appropriate format
111
+ - For **execute_prompt**: Show the prompt result clearly
112
+ - For ANY tool result: Extract and format the key information for the user to understand
113
+
114
+ **Example:**
115
+ User: "list all resources"
116
+ 1. Call list_resources tool
117
+ 2. Receive JSON array of resources
118
+ 3. Format and present: "Here are the available resources:
119
+ - **Resource Name** (uri) - Description
120
+ - **Another Resource** (uri) - Description"
121
+
122
+ **NEVER** just say "I have the results" without showing them!
123
+ **ALWAYS** format and display the actual data you receive from tools!
124
+
125
+ **REMEMBER:**
126
+
127
+ - You have access to real, functional tools - use them!
128
+ - Call tools directly - don't ask for permission or credentials first
129
+ - **After calling a tool, ALWAYS present the results to the user clearly**
130
+ - Your goal is to be helpful, efficient, and reduce user friction
131
+ - Think through multi-step workflows and execute them seamlessly
132
+ - Use your intelligence to fill gaps rather than always asking questions`;
133
+ }
134
+
27
135
  async chat(
28
136
  provider: LLMProvider,
29
137
  messages: ChatMessage[],
30
138
  tools: any[],
31
139
  apiKey: string
32
140
  ): Promise<ChatResponse> {
141
+ // Inject system prompt at the beginning if not already present
142
+ if (messages.length > 0 && messages[0].role !== 'system') {
143
+ messages = [
144
+ {
145
+ role: 'system',
146
+ content: this.getSystemPrompt(tools),
147
+ },
148
+ ...messages,
149
+ ];
150
+ }
151
+
33
152
  if (provider === 'openai') {
34
153
  return this.chatOpenAI(messages, tools, apiKey);
35
154
  } else if (provider === 'gemini') {
@@ -0,0 +1,76 @@
1
+ // Log Manager for MCP Server
2
+ // Captures stdout/stderr from MCP server process and streams to clients
3
+
4
+ export interface LogEntry {
5
+ timestamp: number;
6
+ level: 'info' | 'error' | 'warn' | 'debug';
7
+ message: string;
8
+ source: 'stdout' | 'stderr' | 'system';
9
+ }
10
+
11
+ class LogManager {
12
+ private logs: LogEntry[] = [];
13
+ private maxLogs = 1000; // Keep last 1000 logs in memory
14
+ private listeners: Set<(log: LogEntry) => void> = new Set();
15
+
16
+ addLog(message: string, level: LogEntry['level'] = 'info', source: LogEntry['source'] = 'system') {
17
+ const entry: LogEntry = {
18
+ timestamp: Date.now(),
19
+ level,
20
+ message: message.trim(),
21
+ source,
22
+ };
23
+
24
+ this.logs.push(entry);
25
+
26
+ // Keep only last maxLogs entries
27
+ if (this.logs.length > this.maxLogs) {
28
+ this.logs = this.logs.slice(-this.maxLogs);
29
+ }
30
+
31
+ // Notify all listeners
32
+ this.listeners.forEach(listener => {
33
+ try {
34
+ listener(entry);
35
+ } catch (error) {
36
+ console.error('Error in log listener:', error);
37
+ }
38
+ });
39
+ }
40
+
41
+ getLogs(limit?: number): LogEntry[] {
42
+ if (limit) {
43
+ return this.logs.slice(-limit);
44
+ }
45
+ return [...this.logs];
46
+ }
47
+
48
+ clearLogs() {
49
+ this.logs = [];
50
+ this.addLog('Logs cleared', 'info', 'system');
51
+ }
52
+
53
+ subscribe(listener: (log: LogEntry) => void): () => void {
54
+ this.listeners.add(listener);
55
+ return () => {
56
+ this.listeners.delete(listener);
57
+ };
58
+ }
59
+
60
+ getLogCount(): number {
61
+ return this.logs.length;
62
+ }
63
+ }
64
+
65
+ // Global singleton
66
+ declare global {
67
+ var __logManager: LogManager | undefined;
68
+ }
69
+
70
+ export function getLogManager(): LogManager {
71
+ if (!global.__logManager) {
72
+ global.__logManager = new LogManager();
73
+ }
74
+ return global.__logManager;
75
+ }
76
+
@@ -1,20 +1,36 @@
1
1
  // MCP Client for Studio
2
- // Communicates with MCP server via stdio
2
+ // Communicates with MCP server via stdio or HTTP
3
3
 
4
4
  import { spawn, ChildProcess } from 'child_process';
5
5
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
6
6
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
7
+ import { HttpClientTransport } from '@/lib/http-client-transport';
8
+ import { getLogManager } from '@/lib/log-manager';
7
9
 
8
- export interface McpClientConfig {
10
+ export type TransportType = 'stdio' | 'http';
11
+
12
+ export interface StdioClientConfig {
13
+ type: 'stdio';
9
14
  command: string;
10
15
  args: string[];
11
16
  env?: Record<string, string>;
12
17
  cwd?: string;
13
18
  }
14
19
 
20
+ export interface HttpClientConfig {
21
+ type: 'http';
22
+ baseUrl: string;
23
+ basePath?: string;
24
+ headers?: Record<string, string>;
25
+ }
26
+
27
+ export type McpClientConfig = StdioClientConfig | HttpClientConfig;
28
+
15
29
  export class McpClient {
16
30
  private client: Client | null = null;
17
- private transport: StdioClientTransport | null = null;
31
+ private transport: StdioClientTransport | HttpClientTransport | null = null;
32
+ private transportType: TransportType | null = null;
33
+ private childProcess: ChildProcess | null = null;
18
34
 
19
35
  async connect(config: McpClientConfig): Promise<void> {
20
36
  if (this.client && this.transport) {
@@ -35,16 +51,73 @@ export class McpClient {
35
51
  this.transport = null;
36
52
  }
37
53
 
38
- console.log('🚀 Connecting to MCP server:', config.command, config.args);
39
- console.log('📝 Environment vars:', Object.keys(config.env || {}).join(', '));
54
+ this.transportType = config.type;
55
+
56
+ // Create appropriate transport based on type
57
+ if (config.type === 'stdio') {
58
+ console.log('🚀 Connecting to MCP server via STDIO:', config.command, config.args);
59
+ console.log('📝 Environment vars:', Object.keys(config.env || {}).join(', '));
60
+
61
+ const logManager = getLogManager();
62
+ logManager.addLog(`Starting MCP server: ${config.command} ${config.args.join(' ')}`, 'info', 'system');
63
+
64
+ // Create STDIO transport (it will spawn the process internally)
65
+ this.transport = new StdioClientTransport({
66
+ command: config.command,
67
+ args: config.args,
68
+ env: { ...process.env, ...config.env },
69
+ cwd: config.cwd,
70
+ });
71
+
72
+ // Access the child process from the transport to capture logs
73
+ // The transport stores the process in a private property, so we need to access it
74
+ setTimeout(() => {
75
+ const transport = this.transport as any;
76
+ if (transport._process || transport.process) {
77
+ this.childProcess = transport._process || transport.process;
78
+
79
+ // Capture stdout
80
+ this.childProcess?.stdout?.on('data', (data: Buffer) => {
81
+ const message = data.toString();
82
+ logManager.addLog(message, 'info', 'stdout');
83
+ });
84
+
85
+ // Capture stderr
86
+ this.childProcess?.stderr?.on('data', (data: Buffer) => {
87
+ const message = data.toString();
88
+ logManager.addLog(message, 'error', 'stderr');
89
+ });
90
+
91
+ // Handle process errors
92
+ this.childProcess?.on('error', (error: Error) => {
93
+ logManager.addLog(`Process error: ${error.message}`, 'error', 'system');
94
+ });
40
95
 
41
- // Create transport (it will spawn the process internally)
42
- this.transport = new StdioClientTransport({
43
- command: config.command,
44
- args: config.args,
45
- env: { ...process.env, ...config.env },
46
- cwd: config.cwd, // Set working directory for the child process
47
- });
96
+ // Handle process exit
97
+ this.childProcess?.on('exit', (code: number | null, signal: string | null) => {
98
+ if (code !== null) {
99
+ logManager.addLog(`MCP server exited with code ${code}`, code === 0 ? 'info' : 'error', 'system');
100
+ } else if (signal) {
101
+ logManager.addLog(`MCP server killed with signal ${signal}`, 'warn', 'system');
102
+ }
103
+ });
104
+ }
105
+ }, 100); // Small delay to ensure transport has spawned the process
106
+ } else if (config.type === 'http') {
107
+ console.log('🌐 Connecting to MCP server via HTTP:', config.baseUrl);
108
+
109
+ // Create HTTP transport
110
+ this.transport = new HttpClientTransport({
111
+ baseUrl: config.baseUrl,
112
+ basePath: config.basePath,
113
+ headers: config.headers,
114
+ });
115
+
116
+ // Start the HTTP transport (establish SSE connection)
117
+ await this.transport.start();
118
+ } else {
119
+ throw new Error(`Unknown transport type: ${(config as any).type}`);
120
+ }
48
121
 
49
122
  // Create client
50
123
  this.client = new Client(
@@ -65,22 +138,25 @@ export class McpClient {
65
138
  console.error('❌ MCP Client error:', error);
66
139
  this.client = null;
67
140
  this.transport = null;
141
+ this.transportType = null;
68
142
  };
69
143
 
70
144
  this.client.onclose = () => {
71
145
  console.log('🔌 MCP Client connection closed');
72
146
  this.client = null;
73
147
  this.transport = null;
148
+ this.transportType = null;
74
149
  };
75
150
 
76
151
  // Connect
77
152
  try {
78
153
  await this.client.connect(this.transport);
79
- console.log('✅ MCP Client connected and ready');
154
+ console.log(`✅ MCP Client connected and ready (${config.type.toUpperCase()} transport)`);
80
155
  } catch (error) {
81
156
  console.error('❌ Failed to connect MCP client:', error);
82
157
  this.client = null;
83
158
  this.transport = null;
159
+ this.transportType = null;
84
160
  throw error;
85
161
  }
86
162
  }
@@ -88,6 +164,8 @@ export class McpClient {
88
164
  async disconnect(): Promise<void> {
89
165
  console.log('🛑 Disconnecting MCP client...');
90
166
 
167
+ const logManager = getLogManager();
168
+
91
169
  if (this.client) {
92
170
  await this.client.close();
93
171
  this.client = null;
@@ -97,12 +175,24 @@ export class McpClient {
97
175
  await this.transport.close();
98
176
  this.transport = null;
99
177
  }
178
+
179
+ if (this.childProcess) {
180
+ logManager.addLog('Stopping MCP server process', 'info', 'system');
181
+ this.childProcess.kill('SIGTERM');
182
+ this.childProcess = null;
183
+ }
184
+
185
+ this.transportType = null;
100
186
  }
101
187
 
102
188
  isConnected(): boolean {
103
189
  return this.client !== null;
104
190
  }
105
191
 
192
+ getTransportType(): TransportType | null {
193
+ return this.transportType;
194
+ }
195
+
106
196
  async listTools() {
107
197
  if (!this.client) throw new Error('Not connected');
108
198
  return await this.client.listTools();