nitrostack 1.0.1 → 1.0.3

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 (51) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/cli/index.js +4 -1
  3. package/dist/cli/index.js.map +1 -1
  4. package/package.json +1 -1
  5. package/src/studio/README.md +140 -0
  6. package/src/studio/app/api/auth/fetch-metadata/route.ts +71 -0
  7. package/src/studio/app/api/auth/register-client/route.ts +67 -0
  8. package/src/studio/app/api/chat/route.ts +123 -0
  9. package/src/studio/app/api/health/checks/route.ts +42 -0
  10. package/src/studio/app/api/health/route.ts +13 -0
  11. package/src/studio/app/api/init/route.ts +85 -0
  12. package/src/studio/app/api/ping/route.ts +13 -0
  13. package/src/studio/app/api/prompts/[name]/route.ts +21 -0
  14. package/src/studio/app/api/prompts/route.ts +13 -0
  15. package/src/studio/app/api/resources/[...uri]/route.ts +18 -0
  16. package/src/studio/app/api/resources/route.ts +13 -0
  17. package/src/studio/app/api/roots/route.ts +13 -0
  18. package/src/studio/app/api/sampling/route.ts +14 -0
  19. package/src/studio/app/api/tools/[name]/call/route.ts +41 -0
  20. package/src/studio/app/api/tools/route.ts +23 -0
  21. package/src/studio/app/api/widget-examples/route.ts +44 -0
  22. package/src/studio/app/auth/callback/page.tsx +160 -0
  23. package/src/studio/app/auth/page.tsx +543 -0
  24. package/src/studio/app/chat/page.tsx +530 -0
  25. package/src/studio/app/chat/page.tsx.backup +390 -0
  26. package/src/studio/app/globals.css +410 -0
  27. package/src/studio/app/health/page.tsx +177 -0
  28. package/src/studio/app/layout.tsx +48 -0
  29. package/src/studio/app/page.tsx +337 -0
  30. package/src/studio/app/page.tsx.backup +346 -0
  31. package/src/studio/app/ping/page.tsx +204 -0
  32. package/src/studio/app/prompts/page.tsx +228 -0
  33. package/src/studio/app/resources/page.tsx +313 -0
  34. package/src/studio/components/EnlargeModal.tsx +116 -0
  35. package/src/studio/components/Sidebar.tsx +133 -0
  36. package/src/studio/components/ToolCard.tsx +108 -0
  37. package/src/studio/components/WidgetRenderer.tsx +99 -0
  38. package/src/studio/lib/api.ts +207 -0
  39. package/src/studio/lib/llm-service.ts +361 -0
  40. package/src/studio/lib/mcp-client.ts +168 -0
  41. package/src/studio/lib/store.ts +192 -0
  42. package/src/studio/lib/theme-provider.tsx +50 -0
  43. package/src/studio/lib/types.ts +107 -0
  44. package/src/studio/lib/widget-loader.ts +90 -0
  45. package/src/studio/middleware.ts +27 -0
  46. package/src/studio/next.config.js +16 -0
  47. package/src/studio/package-lock.json +2696 -0
  48. package/src/studio/package.json +34 -0
  49. package/src/studio/postcss.config.mjs +10 -0
  50. package/src/studio/tailwind.config.ts +67 -0
  51. package/src/studio/tsconfig.json +42 -0
package/CHANGELOG.md CHANGED
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.0.3] - 2025-10-27
11
+
12
+ ### Fixed
13
+ - **Critical**: Fixed Studio TypeScript path resolution in Next.js webpack
14
+ - Changed `moduleResolution` from `"bundler"` to `"node"` in `src/studio/tsconfig.json`
15
+ - Added `"baseUrl": "."` to enable proper `@/*` path mapping
16
+ - Resolves "Module not found: Can't resolve '@/lib/store'" error when running `nitrostack dev`
17
+
18
+ ## [1.0.2] - 2025-10-27
19
+
20
+ ### Fixed
21
+ - **Critical**: Fixed CLI `--version` command to read version dynamically from `package.json`
22
+ - Was showing hardcoded `1.0.0` instead of actual package version
23
+ - Now correctly displays the installed version
24
+ - **Critical**: Fixed `src/studio` not being included in npm package
25
+ - Studio files are now properly included (47 files)
26
+ - Users can now run `nitrostack dev` without Studio installation errors
27
+ - Changed `.npmignore` to exclude specific `/src/` subdirectories while keeping `/src/studio/`
28
+
29
+ ### Changed
30
+ - Updated `.npmignore` to specifically exclude `/src/auth/`, `/src/cli/`, `/src/core/`, etc. instead of blanket `/src/` exclusion
31
+ - CLI now uses `createRequire` to load `package.json` for dynamic version reading
32
+
10
33
  ## [1.0.1] - 2025-10-27
11
34
 
12
35
  ### Fixed
package/dist/cli/index.js CHANGED
@@ -1,15 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
+ import { createRequire } from 'module';
3
4
  import { initCommand } from './commands/init.js';
4
5
  import { devCommand } from './commands/dev.js';
5
6
  import { buildCommand } from './commands/build.js';
6
7
  import { startCommand } from './commands/start.js';
7
8
  import { generate } from './commands/generate.js';
9
+ const require = createRequire(import.meta.url);
10
+ const packageJson = require('../../package.json');
8
11
  const program = new Command();
9
12
  program
10
13
  .name('nitrostack')
11
14
  .description('NitroStack - Build MCP servers with ease')
12
- .version('1.0.0');
15
+ .version(packageJson.version);
13
16
  program
14
17
  .command('init')
15
18
  .description('Initialize a new NitroStack project')
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,YAAY,CAAC;KAClB,WAAW,CAAC,0CAA0C,CAAC;KACvD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,qCAAqC,CAAC;KAClD,QAAQ,CAAC,gBAAgB,EAAE,qBAAqB,CAAC;KACjD,MAAM,CAAC,uBAAuB,EAAE,+CAA+C,EAAE,YAAY,CAAC;KAC9F,MAAM,CAAC,WAAW,CAAC,CAAC;AAEvB,OAAO;KACJ,OAAO,CAAC,KAAK,CAAC;KACd,WAAW,CAAC,0CAA0C,CAAC;KACvD,MAAM,CAAC,eAAe,EAAE,iBAAiB,EAAE,MAAM,CAAC;KAClD,MAAM,CAAC,WAAW,EAAE,mCAAmC,CAAC;KACxD,MAAM,CAAC,UAAU,CAAC,CAAC;AAEtB,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,kCAAkC,CAAC;KAC/C,MAAM,CAAC,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,CAAC;KACrD,MAAM,CAAC,YAAY,CAAC,CAAC;AAExB,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,6BAA6B,CAAC;KAC1C,MAAM,CAAC,eAAe,EAAE,iBAAiB,EAAE,MAAM,CAAC;KAClD,MAAM,CAAC,YAAY,CAAC,CAAC;AAExB,OAAO;KACJ,OAAO,CAAC,UAAU,CAAC;KACnB,KAAK,CAAC,GAAG,CAAC;KACV,WAAW,CAAC,oCAAoC,CAAC;KACjD,QAAQ,CAAC,QAAQ,EAAE,6HAA6H,CAAC;KACjJ,QAAQ,CAAC,QAAQ,EAAE,8CAA8C,CAAC;KAClE,MAAM,CAAC,mBAAmB,EAAE,8CAA8C,CAAC;KAC3E,MAAM,CAAC,iBAAiB,EAAE,oCAAoC,CAAC;KAC/D,MAAM,CAAC,SAAS,EAAE,0BAA0B,CAAC;KAC7C,MAAM,CAAC,gBAAgB,EAAE,6CAA6C,CAAC;KACvE,MAAM,CAAC,QAAQ,CAAC,CAAC;AAEpB,OAAO,CAAC,KAAK,EAAE,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,WAAW,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;AAElD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,YAAY,CAAC;KAClB,WAAW,CAAC,0CAA0C,CAAC;KACvD,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;AAEhC,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,qCAAqC,CAAC;KAClD,QAAQ,CAAC,gBAAgB,EAAE,qBAAqB,CAAC;KACjD,MAAM,CAAC,uBAAuB,EAAE,+CAA+C,EAAE,YAAY,CAAC;KAC9F,MAAM,CAAC,WAAW,CAAC,CAAC;AAEvB,OAAO;KACJ,OAAO,CAAC,KAAK,CAAC;KACd,WAAW,CAAC,0CAA0C,CAAC;KACvD,MAAM,CAAC,eAAe,EAAE,iBAAiB,EAAE,MAAM,CAAC;KAClD,MAAM,CAAC,WAAW,EAAE,mCAAmC,CAAC;KACxD,MAAM,CAAC,UAAU,CAAC,CAAC;AAEtB,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,kCAAkC,CAAC;KAC/C,MAAM,CAAC,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,CAAC;KACrD,MAAM,CAAC,YAAY,CAAC,CAAC;AAExB,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,6BAA6B,CAAC;KAC1C,MAAM,CAAC,eAAe,EAAE,iBAAiB,EAAE,MAAM,CAAC;KAClD,MAAM,CAAC,YAAY,CAAC,CAAC;AAExB,OAAO;KACJ,OAAO,CAAC,UAAU,CAAC;KACnB,KAAK,CAAC,GAAG,CAAC;KACV,WAAW,CAAC,oCAAoC,CAAC;KACjD,QAAQ,CAAC,QAAQ,EAAE,6HAA6H,CAAC;KACjJ,QAAQ,CAAC,QAAQ,EAAE,8CAA8C,CAAC;KAClE,MAAM,CAAC,mBAAmB,EAAE,8CAA8C,CAAC;KAC3E,MAAM,CAAC,iBAAiB,EAAE,oCAAoC,CAAC;KAC/D,MAAM,CAAC,SAAS,EAAE,0BAA0B,CAAC;KAC7C,MAAM,CAAC,gBAAgB,EAAE,6CAA6C,CAAC;KACvE,MAAM,CAAC,QAAQ,CAAC,CAAC;AAEpB,OAAO,CAAC,KAAK,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nitrostack",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "NitroStack - Build powerful MCP servers with TypeScript",
5
5
  "type": "module",
6
6
  "main": "dist/core/index.js",
@@ -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('/.well-known/oauth-protected-resource', url).toString();
26
+ } else if (type === 'auth-server') {
27
+ // Fetch Authorization Server Metadata (RFC 8414)
28
+ metadataUrl = new URL('/.well-known/oauth-authorization-server', 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,123 @@
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
+ });
23
+
24
+ if (!provider || !messages || !apiKey) {
25
+ return NextResponse.json(
26
+ { error: 'Missing required fields' },
27
+ { status: 400 }
28
+ );
29
+ }
30
+
31
+ if (messages.length === 0) {
32
+ return NextResponse.json(
33
+ { error: 'Messages array is empty' },
34
+ { status: 400 }
35
+ );
36
+ }
37
+
38
+ // Get available tools from MCP server
39
+ const client = getMcpClient();
40
+ const toolsList = await client.listTools();
41
+ const tools = toolsList.tools?.map((tool: any) => ({
42
+ name: tool.name,
43
+ description: tool.description || '',
44
+ inputSchema: tool.inputSchema || {},
45
+ })) || [];
46
+
47
+ // Call LLM
48
+ const response = await llmService.chat(provider, messages, tools, apiKey);
49
+
50
+ // If LLM wants to call tools, execute them
51
+ if (response.toolCalls && response.toolCalls.length > 0) {
52
+ const toolResults: ChatMessage[] = [];
53
+
54
+ for (const toolCall of response.toolCalls) {
55
+ 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}`;
66
+ }
67
+
68
+ if (mcpApiKey) {
69
+ toolArgs._meta.apiKey = mcpApiKey;
70
+ toolArgs._meta['x-api-key'] = mcpApiKey;
71
+ }
72
+ }
73
+
74
+ // Execute tool via MCP client
75
+ const result = await client.executeTool(toolCall.name, toolArgs);
76
+
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);
85
+ }
86
+
87
+ // Store tool result with both ID and NAME (Gemini needs the name!)
88
+ toolResults.push({
89
+ role: 'tool',
90
+ content: toolContent,
91
+ toolCallId: toolCall.id,
92
+ toolName: toolCall.name, // Add tool name for Gemini function responses
93
+ });
94
+ } catch (error: any) {
95
+ toolResults.push({
96
+ role: 'tool',
97
+ content: JSON.stringify({ error: error.message }),
98
+ toolCallId: toolCall.id,
99
+ toolName: toolCall.name, // Add tool name for Gemini function responses
100
+ });
101
+ }
102
+ }
103
+
104
+ // Return response with tool results
105
+ return NextResponse.json({
106
+ message: response.message,
107
+ toolCalls: response.toolCalls,
108
+ toolResults,
109
+ finishReason: response.finishReason,
110
+ });
111
+ }
112
+
113
+ // No tool calls, just return the message
114
+ return NextResponse.json({
115
+ message: response.message,
116
+ finishReason: response.finishReason,
117
+ });
118
+ } catch (error: any) {
119
+ console.error('Chat error:', error);
120
+ return NextResponse.json({ error: error.message }, { status: 500 });
121
+ }
122
+ }
123
+
@@ -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
+
@@ -0,0 +1,85 @@
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() {
28
+ try {
29
+ const client = getMcpClient();
30
+
31
+ // Get MCP server config from environment
32
+ const command = process.env.MCP_COMMAND || 'node';
33
+ const argsString = process.env.MCP_ARGS || '';
34
+
35
+ // Parse MCP_ARGS - it can be a JSON array string or a single path
36
+ let args: string[] = [];
37
+ if (argsString) {
38
+ try {
39
+ // Try to parse as JSON array
40
+ args = JSON.parse(argsString);
41
+ } catch {
42
+ // If not JSON, treat as a single argument
43
+ args = [argsString];
44
+ }
45
+ }
46
+
47
+ if (!client.isConnected()) {
48
+ // Get project directory from the MCP server path
49
+ // If using wrapper, the actual server path is the second argument
50
+ const serverPath = args.length > 1 ? args[1] : args[0];
51
+ const projectPath = serverPath ? path.dirname(path.dirname(serverPath)) : process.cwd();
52
+
53
+ // Load environment variables from project's .env file
54
+ const projectEnv = loadEnvFile(projectPath);
55
+
56
+ console.log(`📂 Project path: ${projectPath}`);
57
+ console.log(`🔧 Loaded ${Object.keys(projectEnv).length} env vars from ${projectPath}/.env`);
58
+ console.log(`🔑 JWT_SECRET present: ${projectEnv.JWT_SECRET ? 'YES' : 'NO'}`);
59
+ console.log(`📝 Command: ${command} ${args.join(' ')}`);
60
+
61
+ await client.connect({
62
+ command,
63
+ args,
64
+ env: {
65
+ ...projectEnv,
66
+ NODE_ENV: 'development',
67
+ },
68
+ cwd: projectPath, // Set working directory to project root
69
+ });
70
+
71
+ console.log('✅ MCP client connected successfully');
72
+ }
73
+
74
+ return NextResponse.json({
75
+ success: true,
76
+ message: 'MCP client connected',
77
+ });
78
+ } catch (error: any) {
79
+ console.error('Failed to connect MCP client:', error);
80
+ return NextResponse.json(
81
+ { error: error.message },
82
+ { status: 500 }
83
+ );
84
+ }
85
+ }
@@ -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
+