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.
- package/README.md +1 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +2 -1
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/mcp-dev-wrapper.js +30 -17
- package/dist/cli/mcp-dev-wrapper.js.map +1 -1
- package/dist/core/app-decorator.js +2 -2
- package/dist/core/app-decorator.js.map +1 -1
- package/dist/core/builders.js +2 -2
- package/dist/core/builders.js.map +1 -1
- package/dist/core/resource.js +1 -1
- package/dist/core/resource.js.map +1 -1
- package/dist/core/server.js +2 -2
- package/dist/core/server.js.map +1 -1
- package/dist/core/transports/http-server.d.ts.map +1 -1
- package/dist/core/transports/http-server.js +21 -1
- package/dist/core/transports/http-server.js.map +1 -1
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/studio/app/api/chat/route.ts +155 -28
- package/src/studio/app/api/init/route.ts +28 -4
- package/src/studio/app/auth/page.tsx +13 -9
- package/src/studio/app/chat/page.tsx +599 -133
- package/src/studio/app/health/page.tsx +101 -99
- package/src/studio/app/layout.tsx +24 -4
- package/src/studio/app/page.tsx +61 -56
- package/src/studio/app/ping/page.tsx +13 -8
- package/src/studio/app/prompts/page.tsx +72 -70
- package/src/studio/app/resources/page.tsx +88 -86
- package/src/studio/app/settings/page.tsx +270 -0
- package/src/studio/components/EnlargeModal.tsx +21 -15
- package/src/studio/components/LogMessage.tsx +153 -0
- package/src/studio/components/MarkdownRenderer.tsx +410 -0
- package/src/studio/components/Sidebar.tsx +197 -35
- package/src/studio/components/ToolCard.tsx +27 -9
- package/src/studio/components/WidgetRenderer.tsx +4 -2
- package/src/studio/lib/http-client-transport.ts +222 -0
- package/src/studio/lib/llm-service.ts +119 -0
- package/src/studio/lib/log-manager.ts +76 -0
- package/src/studio/lib/mcp-client.ts +103 -13
- package/src/studio/package-lock.json +3129 -0
- package/src/studio/package.json +1 -0
- package/templates/typescript-auth/README.md +3 -1
- package/templates/typescript-auth/src/db/database.ts +5 -8
- package/templates/typescript-auth/src/index.ts +13 -2
- package/templates/typescript-auth/src/modules/addresses/addresses.tools.ts +49 -6
- package/templates/typescript-auth/src/modules/cart/cart.tools.ts +13 -17
- package/templates/typescript-auth/src/modules/orders/orders.tools.ts +38 -16
- package/templates/typescript-auth/src/modules/products/products.tools.ts +4 -4
- package/templates/typescript-auth/src/widgets/app/order-confirmation/page.tsx +25 -0
- package/templates/typescript-auth/src/widgets/app/products-grid/page.tsx +26 -1
- package/templates/typescript-auth-api-key/README.md +3 -1
- package/templates/typescript-auth-api-key/src/index.ts +11 -3
- 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
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
75
|
-
|
|
199
|
+
// Execute tool via MCP client
|
|
200
|
+
result = await client.executeTool(toolCall.name, toolArgs);
|
|
76
201
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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="
|
|
170
|
-
{/* Header */}
|
|
171
|
-
<div className="
|
|
172
|
-
<div className="flex items-center gap-3
|
|
173
|
-
<div className="w-
|
|
174
|
-
<Shield className="w-
|
|
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-
|
|
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
|
-
|
|
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
|
);
|