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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nitrostack",
3
- "version": "1.0.65",
3
+ "version": "1.0.67",
4
4
  "description": "NitroStack - Build powerful MCP servers with TypeScript",
5
5
  "type": "module",
6
6
  "main": "dist/core/index.js",
@@ -27,6 +27,7 @@
27
27
  },
28
28
  "files": [
29
29
  "dist/",
30
+ "src/studio/",
30
31
  "templates/",
31
32
  "README.md",
32
33
  "LICENSE"
@@ -114,4 +115,4 @@
114
115
  "url": "https://github.com/abhishekpanditofficial/nitrostack/issues"
115
116
  },
116
117
  "homepage": "https://nitrostack.vercel.app"
117
- }
118
+ }
@@ -0,0 +1,140 @@
1
+ # NitroStack Studio 🎨
2
+
3
+ Modern, React-based visual inspector for MCP servers.
4
+
5
+ ## Features
6
+
7
+ - ⚡ **Tools Browser** - Execute tools with auto-generated forms
8
+ - 🎨 **Widget Rendering** - Preview UI components in dev and prod
9
+ - 🤖 **AI Chat** - Chat with tool integration (OpenAI/Gemini)
10
+ - 📦 **Resources** - Browse MCP resources and schemas
11
+ - 💬 **Prompts** - Execute MCP prompts
12
+ - 🔐 **Auth** - OAuth 2.1, JWT, and API key support
13
+ - 💚 **Health** - System health monitoring
14
+ - 📡 **Ping** - Latency testing
15
+ - 🎲 **Sampling** - Text completion testing
16
+ - 🌳 **Roots** - MCP server roots
17
+
18
+ ## Architecture
19
+
20
+ ```
21
+ src/studio/
22
+ ├── app/ # Next.js App Router pages
23
+ │ ├── page.tsx # Tools (default)
24
+ │ ├── chat/ # AI Chat
25
+ │ ├── resources/ # Resources browser
26
+ │ ├── prompts/ # Prompts executor
27
+ │ ├── auth/ # Authentication
28
+ │ ├── health/ # Health checks
29
+ │ ├── ping/ # Ping testing
30
+ │ ├── sampling/ # Sampling API
31
+ │ └── roots/ # Roots browser
32
+ ├── components/ # Reusable React components
33
+ │ ├── Sidebar.tsx # Navigation sidebar
34
+ │ ├── ToolCard.tsx # Tool display card
35
+ │ ├── WidgetRenderer.tsx # Widget iframe loader
36
+ │ └── EnlargeModal.tsx # Full-screen widget viewer
37
+ ├── lib/ # Utilities and shared logic
38
+ │ ├── api.ts # API client
39
+ │ ├── store.ts # Zustand state management
40
+ │ ├── types.ts # TypeScript types
41
+ │ ├── widget-loader.ts # Dev/prod widget loading
42
+ │ └── dummy-data.ts # Preview data generator
43
+ └── next.config.ts # Next.js configuration
44
+
45
+ ## Development
46
+
47
+ ```bash
48
+ # Install dependencies
49
+ cd src/studio
50
+ npm install
51
+
52
+ # Run dev server
53
+ npm run dev
54
+
55
+ # Build static export
56
+ npm run build
57
+ ```
58
+
59
+ ## Widget Rendering
60
+
61
+ The Studio supports two modes:
62
+
63
+ ### Dev Mode (Port 3001)
64
+ - Widgets load from Next.js dev server
65
+ - Hot reload enabled
66
+ - URL: `http://localhost:3001/widget-name`
67
+
68
+ ### Production Mode
69
+ - Widgets compile to static HTML
70
+ - Bundled with server
71
+ - Served from `/widgets/*`
72
+
73
+ ## Static Export
74
+
75
+ Studio builds to static HTML/CSS/JS for easy deployment:
76
+
77
+ ```bash
78
+ npm run build
79
+ # Output: ../../dist/studio/
80
+ ```
81
+
82
+ Deploy to:
83
+ - Vercel
84
+ - Netlify
85
+ - Cloudflare Pages
86
+ - GitHub Pages
87
+ - S3 + CloudFront
88
+ - Any static hosting
89
+
90
+ ## State Management
91
+
92
+ Uses Zustand for global state:
93
+ - Connection status
94
+ - Tools, resources, prompts
95
+ - Chat messages
96
+ - Auth tokens
97
+ - Modal state
98
+
99
+ ## API Integration
100
+
101
+ All API calls go through the unified `StudioAPI` client in `lib/api.ts`:
102
+ - Tools: `api.getTools()`, `api.callTool()`
103
+ - Resources: `api.getResources()`, `api.getResource()`
104
+ - Chat: `api.chat()`
105
+ - Auth: `api.discoverAuth()`, `api.registerClient()`
106
+ - Health: `api.getHealth()`
107
+
108
+ ## Styling
109
+
110
+ - **Tailwind CSS** - Utility-first styling
111
+ - **Dark Theme** - Optimized for developer experience
112
+ - **Custom CSS** - Global styles in `app/globals.css`
113
+ - **Animations** - Fade-in, slide-in, pulse, skeleton loading
114
+
115
+ ## Integration with NitroStack
116
+
117
+ When you run `nitrostack dev`, it starts:
118
+ 1. **MCP Server** (your project on stdio)
119
+ 2. **Widget Dev Server** (Next.js on port 3001)
120
+ 3. **Studio** (Next.js on port 3000)
121
+
122
+ Studio proxies API calls to the MCP server and loads widgets from the widget dev server.
123
+
124
+ ## Browser Support
125
+
126
+ - Modern browsers (Chrome, Firefox, Safari, Edge)
127
+ - ES2022+ JavaScript
128
+ - CSS Grid & Flexbox
129
+
130
+ ## Performance
131
+
132
+ - **Static Export** - Pre-rendered HTML
133
+ - **Code Splitting** - Automatic by Next.js
134
+ - **Tree Shaking** - Unused code removed
135
+ - **Lazy Loading** - Components load on demand
136
+
137
+ ## License
138
+
139
+ MIT - Part of NitroStack
140
+
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Fetch OAuth 2.1 Protected Resource Metadata
3
+ *
4
+ * This endpoint fetches OAuth metadata from an MCP server's
5
+ * /.well-known/oauth-protected-resource endpoint (RFC 9728)
6
+ */
7
+
8
+ import { NextRequest, NextResponse } from 'next/server';
9
+
10
+ export async function POST(request: NextRequest) {
11
+ try {
12
+ const { url, type } = await request.json();
13
+
14
+ if (!url) {
15
+ return NextResponse.json(
16
+ { error: 'url is required' },
17
+ { status: 400 }
18
+ );
19
+ }
20
+
21
+ let metadataUrl: string;
22
+
23
+ if (type === 'resource') {
24
+ // Fetch Protected Resource Metadata (RFC 9728)
25
+ metadataUrl = new URL(url).toString();
26
+ } else if (type === 'auth-server') {
27
+ // Fetch Authorization Server Metadata (RFC 8414)
28
+ metadataUrl = new URL(url).toString();
29
+ } else {
30
+ return NextResponse.json(
31
+ { error: 'type must be "resource" or "auth-server"' },
32
+ { status: 400 }
33
+ );
34
+ }
35
+
36
+ // Fetch the metadata
37
+ const response = await fetch(metadataUrl, {
38
+ method: 'GET',
39
+ headers: {
40
+ 'Accept': 'application/json',
41
+ },
42
+ signal: AbortSignal.timeout(5000), // 5 second timeout
43
+ });
44
+
45
+ if (!response.ok) {
46
+ return NextResponse.json(
47
+ { error: `Failed to fetch metadata: ${response.statusText}` },
48
+ { status: response.status }
49
+ );
50
+ }
51
+
52
+ const metadata = await response.json();
53
+
54
+ // Basic validation
55
+ if (type === 'resource' && (!metadata.resource || !metadata.authorization_servers)) {
56
+ return NextResponse.json(
57
+ { error: 'Invalid OAuth resource metadata format' },
58
+ { status: 400 }
59
+ );
60
+ }
61
+
62
+ return NextResponse.json(metadata);
63
+ } catch (error: any) {
64
+ console.error('Error fetching OAuth metadata:', error);
65
+ return NextResponse.json(
66
+ { error: error.message || 'Failed to fetch OAuth metadata' },
67
+ { status: 500 }
68
+ );
69
+ }
70
+ }
71
+
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Dynamic Client Registration (RFC 7591)
3
+ *
4
+ * This endpoint handles OAuth 2.1 Dynamic Client Registration
5
+ * by proxying requests to the authorization server's registration endpoint.
6
+ */
7
+
8
+ import { NextRequest, NextResponse } from 'next/server';
9
+
10
+ export async function POST(request: NextRequest) {
11
+ try {
12
+ const { endpoint, metadata } = await request.json();
13
+
14
+ if (!endpoint) {
15
+ return NextResponse.json(
16
+ { error: 'endpoint is required' },
17
+ { status: 400 }
18
+ );
19
+ }
20
+
21
+ if (!metadata) {
22
+ return NextResponse.json(
23
+ { error: 'metadata is required' },
24
+ { status: 400 }
25
+ );
26
+ }
27
+
28
+ // Register the client with the authorization server
29
+ const response = await fetch(endpoint, {
30
+ method: 'POST',
31
+ headers: {
32
+ 'Content-Type': 'application/json',
33
+ 'Accept': 'application/json',
34
+ },
35
+ body: JSON.stringify(metadata),
36
+ signal: AbortSignal.timeout(10000), // 10 second timeout
37
+ });
38
+
39
+ if (!response.ok) {
40
+ const errorText = await response.text();
41
+ console.error('Client registration failed:', response.status, errorText);
42
+ return NextResponse.json(
43
+ { error: `Registration failed: ${response.statusText}`, details: errorText },
44
+ { status: response.status }
45
+ );
46
+ }
47
+
48
+ const registrationResponse = await response.json();
49
+
50
+ // Validate the response contains required fields
51
+ if (!registrationResponse.client_id) {
52
+ return NextResponse.json(
53
+ { error: 'Invalid registration response: missing client_id' },
54
+ { status: 400 }
55
+ );
56
+ }
57
+
58
+ return NextResponse.json(registrationResponse);
59
+ } catch (error: any) {
60
+ console.error('Error during client registration:', error);
61
+ return NextResponse.json(
62
+ { error: error.message || 'Failed to register client' },
63
+ { status: 500 }
64
+ );
65
+ }
66
+ }
67
+
@@ -0,0 +1,250 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { getMcpClient } from '@/lib/mcp-client';
3
+ import { LLMService, type ChatMessage, type LLMProvider } from '@/lib/llm-service';
4
+
5
+ const llmService = new LLMService();
6
+
7
+ export async function POST(request: NextRequest) {
8
+ try {
9
+ const { provider, messages, apiKey, jwtToken, mcpApiKey } = await request.json() as {
10
+ provider: LLMProvider;
11
+ messages: ChatMessage[];
12
+ apiKey: string; // LLM API key
13
+ jwtToken?: string; // MCP server JWT
14
+ mcpApiKey?: string; // MCP server API key
15
+ };
16
+
17
+ console.log('Received chat request:', {
18
+ provider,
19
+ messagesCount: messages?.length,
20
+ messages: JSON.stringify(messages),
21
+ hasApiKey: !!apiKey,
22
+ hasJwtToken: !!jwtToken,
23
+ hasMcpApiKey: !!mcpApiKey,
24
+ jwtTokenPreview: jwtToken ? jwtToken.substring(0, 20) + '...' : null
25
+ });
26
+
27
+ if (!provider || !messages || !apiKey) {
28
+ return NextResponse.json(
29
+ { error: 'Missing required fields' },
30
+ { status: 400 }
31
+ );
32
+ }
33
+
34
+ if (messages.length === 0) {
35
+ return NextResponse.json(
36
+ { error: 'Messages array is empty' },
37
+ { status: 400 }
38
+ );
39
+ }
40
+
41
+ // Get available tools from MCP server
42
+ const client = getMcpClient();
43
+ const toolsList = await client.listTools();
44
+ const mcpTools = toolsList.tools?.map((tool: any) => ({
45
+ name: tool.name,
46
+ description: tool.description || '',
47
+ inputSchema: tool.inputSchema || {},
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];
117
+
118
+ // Call LLM
119
+ const response = await llmService.chat(provider, messages, tools, apiKey);
120
+
121
+ // If LLM wants to call tools, execute them
122
+ if (response.toolCalls && response.toolCalls.length > 0) {
123
+ const toolResults: ChatMessage[] = [];
124
+
125
+ for (const toolCall of response.toolCalls) {
126
+ try {
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');
168
+ }
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`);
197
+ }
198
+
199
+ // Execute tool via MCP client
200
+ result = await client.executeTool(toolCall.name, toolArgs);
201
+
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
+ }
210
+ }
211
+
212
+ // Store tool result with both ID and NAME (Gemini needs the name!)
213
+ toolResults.push({
214
+ role: 'tool',
215
+ content: toolContent,
216
+ toolCallId: toolCall.id,
217
+ toolName: toolCall.name, // Add tool name for Gemini function responses
218
+ });
219
+ } catch (error: any) {
220
+ console.error(`❌ Error executing tool "${toolCall.name}":`, error);
221
+ const errorMessage = `Error executing ${toolCall.name}: ${error.message}`;
222
+ toolResults.push({
223
+ role: 'tool',
224
+ content: JSON.stringify({ error: error.message, message: errorMessage }),
225
+ toolCallId: toolCall.id,
226
+ toolName: toolCall.name, // Add tool name for Gemini function responses
227
+ });
228
+ }
229
+ }
230
+
231
+ // Return response with tool results
232
+ return NextResponse.json({
233
+ message: response.message,
234
+ toolCalls: response.toolCalls,
235
+ toolResults,
236
+ finishReason: response.finishReason,
237
+ });
238
+ }
239
+
240
+ // No tool calls, just return the message
241
+ return NextResponse.json({
242
+ message: response.message,
243
+ finishReason: response.finishReason,
244
+ });
245
+ } catch (error: any) {
246
+ console.error('Chat error:', error);
247
+ return NextResponse.json({ error: error.message }, { status: 500 });
248
+ }
249
+ }
250
+
@@ -0,0 +1,42 @@
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
+ checks: []
12
+ }, { status: 500 });
13
+ }
14
+
15
+ // Read health checks from MCP server as a resource
16
+ try {
17
+ const result = await client.readResource('health://checks');
18
+
19
+ // Parse the health checks data
20
+ const healthData = JSON.parse(result.contents[0].text);
21
+
22
+ return NextResponse.json({
23
+ checks: healthData.checks || [],
24
+ count: healthData.checks?.length || 0
25
+ });
26
+ } catch (resourceError: any) {
27
+ // Health checks resource not available or no checks registered
28
+ console.log('No health checks resource available');
29
+ return NextResponse.json({
30
+ checks: [],
31
+ count: 0
32
+ });
33
+ }
34
+ } catch (error: any) {
35
+ console.error('❌ Error in /api/health/checks:', error.message);
36
+ return NextResponse.json(
37
+ { error: 'Failed to fetch health checks', checks: [] },
38
+ { status: 500 }
39
+ );
40
+ }
41
+ }
42
+
@@ -0,0 +1,13 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getMcpClient } from '@/lib/mcp-client';
3
+
4
+ export async function GET() {
5
+ const client = getMcpClient();
6
+
7
+ return NextResponse.json({
8
+ status: 'ok',
9
+ connected: client.isConnected(),
10
+ timestamp: new Date().toISOString(),
11
+ });
12
+ }
13
+