@nitrostack/cli 1.0.0
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 +131 -0
- package/dist/commands/build.d.ts +6 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +185 -0
- package/dist/commands/dev.d.ts +7 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +365 -0
- package/dist/commands/generate-types.d.ts +8 -0
- package/dist/commands/generate-types.d.ts.map +1 -0
- package/dist/commands/generate-types.js +219 -0
- package/dist/commands/generate.d.ts +12 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +375 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +324 -0
- package/dist/commands/install.d.ts +10 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +80 -0
- package/dist/commands/start.d.ts +6 -0
- package/dist/commands/start.d.ts.map +1 -0
- package/dist/commands/start.js +70 -0
- package/dist/commands/upgrade.d.ts +10 -0
- package/dist/commands/upgrade.d.ts.map +1 -0
- package/dist/commands/upgrade.js +214 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +94 -0
- package/dist/mcp-dev-wrapper.d.ts +15 -0
- package/dist/mcp-dev-wrapper.d.ts.map +1 -0
- package/dist/mcp-dev-wrapper.js +187 -0
- package/dist/ui/branding.d.ts +31 -0
- package/dist/ui/branding.d.ts.map +1 -0
- package/dist/ui/branding.js +136 -0
- package/package.json +69 -0
- package/templates/typescript-oauth/.env.example +27 -0
- package/templates/typescript-oauth/OAUTH_SETUP.md +592 -0
- package/templates/typescript-oauth/README.md +263 -0
- package/templates/typescript-oauth/package.json +29 -0
- package/templates/typescript-oauth/src/app.module.ts +92 -0
- package/templates/typescript-oauth/src/guards/oauth.guard.ts +126 -0
- package/templates/typescript-oauth/src/health/system.health.ts +55 -0
- package/templates/typescript-oauth/src/index.ts +63 -0
- package/templates/typescript-oauth/src/modules/flights/booking.tools.ts +323 -0
- package/templates/typescript-oauth/src/modules/flights/flights.module.ts +14 -0
- package/templates/typescript-oauth/src/modules/flights/flights.prompts.ts +228 -0
- package/templates/typescript-oauth/src/modules/flights/flights.resources.ts +215 -0
- package/templates/typescript-oauth/src/modules/flights/flights.tools.ts +457 -0
- package/templates/typescript-oauth/src/services/duffel.service.ts +285 -0
- package/templates/typescript-oauth/src/widgets/app/airport-search/page.tsx +270 -0
- package/templates/typescript-oauth/src/widgets/app/flight-details/page.tsx +261 -0
- package/templates/typescript-oauth/src/widgets/app/flight-search-results/page.tsx +378 -0
- package/templates/typescript-oauth/src/widgets/app/globals.css +167 -0
- package/templates/typescript-oauth/src/widgets/app/layout.tsx +18 -0
- package/templates/typescript-oauth/src/widgets/app/order-cancellation/page.tsx +207 -0
- package/templates/typescript-oauth/src/widgets/app/order-summary/page.tsx +245 -0
- package/templates/typescript-oauth/src/widgets/app/payment-confirmation/page.tsx +152 -0
- package/templates/typescript-oauth/src/widgets/app/seat-selection/page.tsx +486 -0
- package/templates/typescript-oauth/src/widgets/next-env.d.ts +5 -0
- package/templates/typescript-oauth/src/widgets/next.config.js +45 -0
- package/templates/typescript-oauth/src/widgets/package-lock.json +4493 -0
- package/templates/typescript-oauth/src/widgets/package.json +24 -0
- package/templates/typescript-oauth/src/widgets/tsconfig.json +28 -0
- package/templates/typescript-oauth/src/widgets/widget-manifest.json +395 -0
- package/templates/typescript-oauth/tsconfig.json +23 -0
- package/templates/typescript-pizzaz/README.md +252 -0
- package/templates/typescript-pizzaz/package.json +34 -0
- package/templates/typescript-pizzaz/src/app.module.ts +28 -0
- package/templates/typescript-pizzaz/src/index.ts +30 -0
- package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.data.ts +106 -0
- package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.module.ts +11 -0
- package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.service.ts +60 -0
- package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.tools.ts +197 -0
- package/templates/typescript-pizzaz/src/widgets/app/layout.tsx +18 -0
- package/templates/typescript-pizzaz/src/widgets/app/pizza-list/page.tsx +272 -0
- package/templates/typescript-pizzaz/src/widgets/app/pizza-map/page.tsx +216 -0
- package/templates/typescript-pizzaz/src/widgets/app/pizza-shop/page.tsx +374 -0
- package/templates/typescript-pizzaz/src/widgets/components/CompactShopCard.tsx +144 -0
- package/templates/typescript-pizzaz/src/widgets/components/PizzaCard.tsx +191 -0
- package/templates/typescript-pizzaz/src/widgets/next.config.js +45 -0
- package/templates/typescript-pizzaz/src/widgets/package.json +30 -0
- package/templates/typescript-pizzaz/src/widgets/tsconfig.json +28 -0
- package/templates/typescript-pizzaz/src/widgets/widget-manifest.json +253 -0
- package/templates/typescript-pizzaz/tsconfig.json +30 -0
- package/templates/typescript-starter/README.md +320 -0
- package/templates/typescript-starter/package.json +25 -0
- package/templates/typescript-starter/src/app.module.ts +34 -0
- package/templates/typescript-starter/src/health/system.health.ts +55 -0
- package/templates/typescript-starter/src/index.ts +29 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.module.ts +12 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.prompts.ts +73 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.resources.ts +59 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.tools.ts +166 -0
- package/templates/typescript-starter/src/widgets/app/calculator-result/page.tsx +180 -0
- package/templates/typescript-starter/src/widgets/app/layout.tsx +18 -0
- package/templates/typescript-starter/src/widgets/next.config.js +45 -0
- package/templates/typescript-starter/src/widgets/package.json +24 -0
- package/templates/typescript-starter/src/widgets/tsconfig.json +28 -0
- package/templates/typescript-starter/src/widgets/widget-manifest.json +48 -0
- package/templates/typescript-starter/tsconfig.json +23 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# āļø NitroStack Flight Booking
|
|
2
|
+
|
|
3
|
+
A production-ready NitroStack template showcasing real-time flight search and booking with **Duffel API** integration. Search flights, view details, select seats, and book - all through beautiful interactive widgets.
|
|
4
|
+
|
|
5
|
+
## ⨠Features
|
|
6
|
+
|
|
7
|
+
### š **Duffel API Integration**
|
|
8
|
+
- Real-time flight search across 300+ airlines
|
|
9
|
+
- Live pricing and availability
|
|
10
|
+
- Seat selection with cabin maps
|
|
11
|
+
- Booking and order management
|
|
12
|
+
|
|
13
|
+
### šØ **Interactive Widgets**
|
|
14
|
+
- **Airport Search** - Autocomplete airport selection
|
|
15
|
+
- **Flight Search Results** - Compare flights with prices
|
|
16
|
+
- **Flight Details** - Comprehensive flight information
|
|
17
|
+
- **Seat Selection** - Visual cabin map with seat picker
|
|
18
|
+
- **Order Summary** - Complete booking overview
|
|
19
|
+
- **Payment Confirmation** - Secure payment flow
|
|
20
|
+
|
|
21
|
+
### š ļø **MCP Tools**
|
|
22
|
+
- `search_airports` - Find airports by name/code
|
|
23
|
+
- `search_flights` - Search available flights
|
|
24
|
+
- `show_flight_details` - Detailed flight information
|
|
25
|
+
- `select_seats` - Interactive seat selection
|
|
26
|
+
- `create_booking` - Book flights
|
|
27
|
+
- `get_order` - Retrieve booking details
|
|
28
|
+
|
|
29
|
+
## š Quick Start
|
|
30
|
+
|
|
31
|
+
### Prerequisites
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Install NitroStack CLI globally
|
|
35
|
+
npm install -g nitrostack
|
|
36
|
+
|
|
37
|
+
# Or use npx
|
|
38
|
+
npx nitrostack --version
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 1. Create Your Project
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Create a new project
|
|
45
|
+
nitrostack init my-flight-app --template typescript-oauth
|
|
46
|
+
cd my-flight-app
|
|
47
|
+
|
|
48
|
+
# Install all dependencies (root + widgets)
|
|
49
|
+
nitrostack install
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 2. Get Your Duffel API Key (Free)
|
|
53
|
+
|
|
54
|
+
[Duffel](https://duffel.com/) provides a free API for flight search and booking:
|
|
55
|
+
|
|
56
|
+
1. **Sign up** at [duffel.com](https://duffel.com/) (click "Start now" - it's free!)
|
|
57
|
+
2. Create a free account (no credit card required)
|
|
58
|
+
3. Go to your **Dashboard** ā **Developers** ā **Access tokens**
|
|
59
|
+
4. Click **"Create token"** to generate your API key
|
|
60
|
+
5. Copy the token (starts with `duffel_test_` for sandbox)
|
|
61
|
+
|
|
62
|
+
> š” **Tip**: The test mode (`duffel_test_*` tokens) gives you access to realistic mock data for development. Production tokens (`duffel_live_*`) connect to real airlines.
|
|
63
|
+
|
|
64
|
+
### 3. Configure Environment
|
|
65
|
+
|
|
66
|
+
Copy the example environment file and add your Duffel API key:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
cp .env.example .env
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Edit `.env` and update:
|
|
73
|
+
|
|
74
|
+
```env
|
|
75
|
+
# Duffel API Configuration
|
|
76
|
+
DUFFEL_API_KEY=duffel_test_your_api_key_here
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 4. Run Development Server
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm run dev
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
This starts:
|
|
86
|
+
- **MCP Server** - Hot reloads on code changes
|
|
87
|
+
- **Studio** on http://localhost:3000 - Visual testing environment
|
|
88
|
+
- **Widget Dev Server** on http://localhost:3001 - Hot module replacement
|
|
89
|
+
|
|
90
|
+
### 5. Test in Studio
|
|
91
|
+
|
|
92
|
+
Try these prompts in Studio chat:
|
|
93
|
+
- "Search flights from London to New York for next week"
|
|
94
|
+
- "Show me flight details"
|
|
95
|
+
- "I want to select seats"
|
|
96
|
+
- "Show me flights from JFK to LAX"
|
|
97
|
+
|
|
98
|
+
## š Project Structure
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
typescript-oauth/
|
|
102
|
+
āāā src/
|
|
103
|
+
ā āāā index.ts # Main server entry
|
|
104
|
+
ā āāā app.module.ts # App module
|
|
105
|
+
ā āāā services/
|
|
106
|
+
ā ā āāā duffel.service.ts # Duffel API client
|
|
107
|
+
ā āāā modules/
|
|
108
|
+
ā āāā flights/
|
|
109
|
+
ā āāā flights.module.ts # Module definition
|
|
110
|
+
ā āāā flights.tools.ts # Search & display tools
|
|
111
|
+
ā āāā flights.prompts.ts # AI prompts
|
|
112
|
+
ā āāā flights.resources.ts # Static resources
|
|
113
|
+
ā āāā booking.tools.ts # Booking tools
|
|
114
|
+
ā āāā widgets/
|
|
115
|
+
ā āāā app/
|
|
116
|
+
ā ā āāā airport-search/ # Airport autocomplete
|
|
117
|
+
ā ā āāā flight-search-results/ # Results list
|
|
118
|
+
ā ā āāā flight-details/ # Flight info
|
|
119
|
+
ā ā āāā seat-selection/ # Seat picker
|
|
120
|
+
ā ā āāā order-summary/ # Booking summary
|
|
121
|
+
ā ā āāā payment-confirmation/ # Payment widget
|
|
122
|
+
āāā .env.example # Environment template
|
|
123
|
+
āāā OAUTH_SETUP.md # OAuth configuration guide
|
|
124
|
+
āāā package.json
|
|
125
|
+
āāā README.md
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## š§ Commands
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# Installation
|
|
132
|
+
npm install # Install all dependencies (root + widgets)
|
|
133
|
+
nitrostack install # Same as above
|
|
134
|
+
|
|
135
|
+
# Development
|
|
136
|
+
npm run dev # Start dev server with Studio
|
|
137
|
+
npm run build # Build TypeScript and widgets for production
|
|
138
|
+
npm start # Run production server
|
|
139
|
+
|
|
140
|
+
# Upgrade
|
|
141
|
+
npm run upgrade # Upgrade nitrostack to latest version
|
|
142
|
+
|
|
143
|
+
# Widget Management
|
|
144
|
+
npm run widget <command> # Run npm command in widgets directory
|
|
145
|
+
npm run widget add <pkg> # Add a widget dependency
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## š OAuth Setup (Optional)
|
|
149
|
+
|
|
150
|
+
This template includes OAuth 2.1 authentication support. If you want to protect your MCP server with authentication:
|
|
151
|
+
|
|
152
|
+
1. Read the detailed guide: **[OAUTH_SETUP.md](./OAUTH_SETUP.md)**
|
|
153
|
+
2. Configure Auth0 (or your OAuth provider)
|
|
154
|
+
3. Update your `.env` with OAuth credentials
|
|
155
|
+
|
|
156
|
+
> **Note**: OAuth is optional for local development. The template works without it using Duffel's test API.
|
|
157
|
+
|
|
158
|
+
## š Duffel API Features
|
|
159
|
+
|
|
160
|
+
### Test Mode vs Live Mode
|
|
161
|
+
|
|
162
|
+
| Feature | Test Mode (`duffel_test_*`) | Live Mode (`duffel_live_*`) |
|
|
163
|
+
|---------|----------------------------|----------------------------|
|
|
164
|
+
| API Access | ā
Full access | ā
Full access |
|
|
165
|
+
| Pricing | Mock data | Real prices |
|
|
166
|
+
| Bookings | Simulated | Real bookings |
|
|
167
|
+
| Credit Card | Not charged | Charged |
|
|
168
|
+
| Rate Limits | Generous | Production limits |
|
|
169
|
+
|
|
170
|
+
### Supported Airlines
|
|
171
|
+
|
|
172
|
+
Duffel provides access to 300+ airlines including:
|
|
173
|
+
- Major carriers (British Airways, Lufthansa, Emirates, etc.)
|
|
174
|
+
- Low-cost carriers (Ryanair, EasyJet, Spirit, etc.)
|
|
175
|
+
- Regional airlines
|
|
176
|
+
|
|
177
|
+
### API Capabilities
|
|
178
|
+
|
|
179
|
+
- **Search**: Real-time availability and pricing
|
|
180
|
+
- **Ancillaries**: Seats, bags, meals
|
|
181
|
+
- **Booking**: Create and manage orders
|
|
182
|
+
- **Changes**: Modify existing bookings
|
|
183
|
+
- **Cancellations**: Cancel and refund
|
|
184
|
+
|
|
185
|
+
## šØ Widget Features
|
|
186
|
+
|
|
187
|
+
### Airport Search Widget
|
|
188
|
+
- Autocomplete with IATA codes
|
|
189
|
+
- City and airport name search
|
|
190
|
+
- Recent searches
|
|
191
|
+
|
|
192
|
+
### Flight Search Results Widget
|
|
193
|
+
- Compare multiple flights
|
|
194
|
+
- Filter by stops, price, time
|
|
195
|
+
- Sort options
|
|
196
|
+
- Airline logos
|
|
197
|
+
|
|
198
|
+
### Flight Details Widget
|
|
199
|
+
- Complete itinerary
|
|
200
|
+
- Fare breakdown
|
|
201
|
+
- Baggage allowance
|
|
202
|
+
- Flight duration and stops
|
|
203
|
+
|
|
204
|
+
### Seat Selection Widget
|
|
205
|
+
- Visual cabin map
|
|
206
|
+
- Seat categories (standard, extra legroom, etc.)
|
|
207
|
+
- Price per seat
|
|
208
|
+
- Real-time availability
|
|
209
|
+
|
|
210
|
+
## š ļø Customization
|
|
211
|
+
|
|
212
|
+
### Adding New Airlines
|
|
213
|
+
|
|
214
|
+
Duffel handles airline integrations automatically. You don't need to configure individual airlines.
|
|
215
|
+
|
|
216
|
+
### Modifying Search Parameters
|
|
217
|
+
|
|
218
|
+
Edit `src/modules/flights/flights.tools.ts` to customize:
|
|
219
|
+
- Default passenger counts
|
|
220
|
+
- Cabin class options
|
|
221
|
+
- Date ranges
|
|
222
|
+
- Result limits
|
|
223
|
+
|
|
224
|
+
### Styling Widgets
|
|
225
|
+
|
|
226
|
+
Each widget in `src/widgets/app/` can be customized:
|
|
227
|
+
- Edit page.tsx for layout
|
|
228
|
+
- Add custom CSS
|
|
229
|
+
- Modify component styles
|
|
230
|
+
|
|
231
|
+
## š Resources
|
|
232
|
+
|
|
233
|
+
### Duffel Documentation
|
|
234
|
+
- [Duffel API Docs](https://duffel.com/docs/api)
|
|
235
|
+
- [Duffel SDK (Node.js)](https://github.com/duffel/duffel-api-javascript)
|
|
236
|
+
- [Duffel Postman Collection](https://duffel.com/docs/postman)
|
|
237
|
+
|
|
238
|
+
### NitroStack Documentation
|
|
239
|
+
- [NitroStack Docs](https://nitrostack.ai/docs)
|
|
240
|
+
- [Widget Development Guide](https://nitrostack.ai/docs/widgets)
|
|
241
|
+
|
|
242
|
+
## š” Tips
|
|
243
|
+
|
|
244
|
+
- **Use Test Mode**: Start with `duffel_test_*` tokens for development
|
|
245
|
+
- **Check Rate Limits**: Duffel has rate limits - cache searches when possible
|
|
246
|
+
- **Error Handling**: The template includes comprehensive error handling
|
|
247
|
+
- **Responsive Design**: Widgets are mobile-friendly out of the box
|
|
248
|
+
|
|
249
|
+
## š Next Steps
|
|
250
|
+
|
|
251
|
+
- Try the **Starter Template** - Learn NitroStack basics
|
|
252
|
+
- Try the **Pizza Shop Template** - Interactive maps with Mapbox
|
|
253
|
+
- Read the [Duffel Getting Started Guide](https://duffel.com/docs/guides/getting-started)
|
|
254
|
+
|
|
255
|
+
## License
|
|
256
|
+
|
|
257
|
+
MIT
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
**Built with ā¤ļø using NitroStack + Duffel API**
|
|
262
|
+
|
|
263
|
+
Need help? Check [Duffel Support](https://duffel.com/docs) or [NitroStack Docs](https://nitrostack.ai/docs)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nitrostack-flight-booking",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "NitroStack Flight Booking - Real-time flight search and booking with Duffel API integration",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "nitrostack-cli dev",
|
|
9
|
+
"build": "nitrostack-cli build",
|
|
10
|
+
"start": "npm run build && nitrostack-cli start",
|
|
11
|
+
"start:prod": "nitrostack-cli start",
|
|
12
|
+
"upgrade": "nitrostack-cli upgrade",
|
|
13
|
+
"install:all": "nitrostack-cli install",
|
|
14
|
+
"widget": "npm --prefix src/widgets"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"nitrostack": "^1",
|
|
18
|
+
"zod": "^3.22.4",
|
|
19
|
+
"dotenv": "^16.3.1",
|
|
20
|
+
"@duffel/api": "^4.21.0",
|
|
21
|
+
"axios": "^1.7.9",
|
|
22
|
+
"date-fns": "^4.1.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@nitrostack/cli": "^1",
|
|
26
|
+
"typescript": "^5.3.3"
|
|
27
|
+
},
|
|
28
|
+
"author": ""
|
|
29
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { McpApp, Module, ConfigModule, OAuthModule } from 'nitrostack';
|
|
2
|
+
import { FlightsModule } from './modules/flights/flights.module.js';
|
|
3
|
+
import { SystemHealthCheck } from './health/system.health.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Root Application Module
|
|
7
|
+
*
|
|
8
|
+
* This is the main module that bootstraps the MCP server.
|
|
9
|
+
* It registers all feature modules and health checks.
|
|
10
|
+
*
|
|
11
|
+
* OAuth 2.1 Authentication:
|
|
12
|
+
* - Configured with Auth0 as the authorization server
|
|
13
|
+
* - Supports read, write, and admin scopes
|
|
14
|
+
* - Validates tokens with audience binding (RFC 8707)
|
|
15
|
+
*
|
|
16
|
+
* Flight Booking System:
|
|
17
|
+
* - Powered by Duffel API
|
|
18
|
+
* - Professional flight search and booking capabilities
|
|
19
|
+
* - Comprehensive widgets for search results and flight details
|
|
20
|
+
*/
|
|
21
|
+
@McpApp({
|
|
22
|
+
module: AppModule,
|
|
23
|
+
server: {
|
|
24
|
+
name: 'airline-ticketing-server',
|
|
25
|
+
version: '1.0.0'
|
|
26
|
+
},
|
|
27
|
+
logging: {
|
|
28
|
+
level: 'info'
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
@Module({
|
|
32
|
+
name: 'app',
|
|
33
|
+
description: 'Airline ticketing MCP server with OAuth 2.1 authentication and Duffel integration',
|
|
34
|
+
imports: [
|
|
35
|
+
ConfigModule.forRoot(),
|
|
36
|
+
|
|
37
|
+
// Enable OAuth 2.1 authentication
|
|
38
|
+
OAuthModule.forRoot({
|
|
39
|
+
// Resource URI - YOUR MCP server's public URL
|
|
40
|
+
// This is used for token audience binding (RFC 8707)
|
|
41
|
+
// CRITICAL: Tokens must be issued specifically for this URI
|
|
42
|
+
resourceUri: process.env.RESOURCE_URI || 'https://mcplocal',
|
|
43
|
+
|
|
44
|
+
// Authorization Server(s) - The OAuth provider URL(s)
|
|
45
|
+
// Supports multiple auth servers for federation scenarios
|
|
46
|
+
authorizationServers: [
|
|
47
|
+
process.env.AUTH_SERVER_URL || 'https://dev-5dt0utuk31h13tjm.us.auth0.com',
|
|
48
|
+
],
|
|
49
|
+
|
|
50
|
+
// Supported scopes for this MCP server
|
|
51
|
+
// Define what permissions your server supports
|
|
52
|
+
scopesSupported: [
|
|
53
|
+
'read', // Read access to resources
|
|
54
|
+
'write', // Write/modify resources
|
|
55
|
+
'admin', // Administrative operations
|
|
56
|
+
],
|
|
57
|
+
|
|
58
|
+
// Token Introspection (RFC 7662) - For opaque tokens
|
|
59
|
+
// If your OAuth provider issues opaque tokens (not JWTs),
|
|
60
|
+
// you MUST configure introspection to validate them
|
|
61
|
+
tokenIntrospectionEndpoint: process.env.INTROSPECTION_ENDPOINT,
|
|
62
|
+
tokenIntrospectionClientId: process.env.INTROSPECTION_CLIENT_ID,
|
|
63
|
+
tokenIntrospectionClientSecret: process.env.INTROSPECTION_CLIENT_SECRET,
|
|
64
|
+
|
|
65
|
+
// Expected audience (defaults to resourceUri if not provided)
|
|
66
|
+
// MUST match the audience claim in tokens (RFC 8707)
|
|
67
|
+
audience: process.env.TOKEN_AUDIENCE,
|
|
68
|
+
|
|
69
|
+
// Expected issuer (optional but recommended)
|
|
70
|
+
// If provided, tokens must be from this issuer
|
|
71
|
+
issuer: process.env.TOKEN_ISSUER,
|
|
72
|
+
|
|
73
|
+
// Custom validation (optional)
|
|
74
|
+
// Add any additional validation logic beyond spec requirements
|
|
75
|
+
customValidation: async (tokenPayload) => {
|
|
76
|
+
// Example: Check if user is active in your database
|
|
77
|
+
// const user = await db.users.findOne({ id: tokenPayload.sub });
|
|
78
|
+
// return user?.active === true;
|
|
79
|
+
|
|
80
|
+
// For now, accept all valid tokens
|
|
81
|
+
return true;
|
|
82
|
+
},
|
|
83
|
+
}),
|
|
84
|
+
|
|
85
|
+
FlightsModule
|
|
86
|
+
],
|
|
87
|
+
providers: [
|
|
88
|
+
// Health Checks
|
|
89
|
+
SystemHealthCheck,
|
|
90
|
+
]
|
|
91
|
+
})
|
|
92
|
+
export class AppModule { }
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Guard, ExecutionContext, OAuthModule, OAuthTokenPayload } from 'nitrostack';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* OAuth Guard
|
|
5
|
+
*
|
|
6
|
+
* Validates OAuth 2.1 access tokens according to MCP specification.
|
|
7
|
+
*
|
|
8
|
+
* Performs:
|
|
9
|
+
* - Token validation (introspection or JWT validation)
|
|
10
|
+
* - Audience binding (RFC 8707) - CRITICAL for security
|
|
11
|
+
* - Scope validation
|
|
12
|
+
* - Expiration checking
|
|
13
|
+
*
|
|
14
|
+
* Compatible with:
|
|
15
|
+
* - OpenAI Apps SDK
|
|
16
|
+
* - Any RFC-compliant OAuth 2.1 provider
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* ```typescript
|
|
20
|
+
* @Tool({
|
|
21
|
+
* name: 'protected_resource',
|
|
22
|
+
* description: 'A protected tool'
|
|
23
|
+
* })
|
|
24
|
+
* @UseGuards(OAuthGuard)
|
|
25
|
+
* async protectedTool() {
|
|
26
|
+
* // Only accessible with valid OAuth token
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export class OAuthGuard implements Guard {
|
|
31
|
+
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
32
|
+
// Extract token from metadata (sent by Studio or OAuth client)
|
|
33
|
+
const authHeader = context.metadata?.authorization as string;
|
|
34
|
+
const metaToken = context.metadata?._oauth || context.metadata?.token;
|
|
35
|
+
|
|
36
|
+
let token: string | null = null;
|
|
37
|
+
|
|
38
|
+
// Try Bearer token format first
|
|
39
|
+
if (authHeader?.startsWith('Bearer ')) {
|
|
40
|
+
token = authHeader.substring(7);
|
|
41
|
+
} else if (metaToken) {
|
|
42
|
+
token = metaToken as string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!token) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
'OAuth token required. Please authenticate in Studio (Auth ā OAuth 2.1 tab) ' +
|
|
48
|
+
'or provide token in Authorization header: "Bearer <token>"'
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Validate token using OAuthModule
|
|
53
|
+
const result = await OAuthModule.validateToken(token);
|
|
54
|
+
|
|
55
|
+
if (!result.valid) {
|
|
56
|
+
throw new Error(`OAuth token validation failed: ${result.error}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Extract scopes from token payload
|
|
60
|
+
const payload = result.payload as OAuthTokenPayload;
|
|
61
|
+
const scopes = this.extractScopes(payload);
|
|
62
|
+
|
|
63
|
+
// Populate context.auth with OAuth token information
|
|
64
|
+
context.auth = {
|
|
65
|
+
subject: payload.sub,
|
|
66
|
+
scopes: scopes,
|
|
67
|
+
clientId: payload.client_id,
|
|
68
|
+
tokenPayload: payload,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Extract scopes from token payload
|
|
76
|
+
* Handles both "scope" (space-separated string) and "scopes" (array) formats
|
|
77
|
+
*/
|
|
78
|
+
private extractScopes(payload: OAuthTokenPayload): string[] {
|
|
79
|
+
if (payload.scopes && Array.isArray(payload.scopes)) {
|
|
80
|
+
return payload.scopes;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (payload.scope && typeof payload.scope === 'string') {
|
|
84
|
+
return payload.scope.split(' ').filter(s => s.length > 0);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Scope Guard
|
|
93
|
+
*
|
|
94
|
+
* Validates that the OAuth token has required scopes.
|
|
95
|
+
* Use this in addition to OAuthGuard for fine-grained access control.
|
|
96
|
+
*
|
|
97
|
+
* Usage:
|
|
98
|
+
* ```typescript
|
|
99
|
+
* @Tool({ name: 'admin_action' })
|
|
100
|
+
* @UseGuards(OAuthGuard, createScopeGuard(['admin', 'write']))
|
|
101
|
+
* async adminAction() {
|
|
102
|
+
* // Requires OAuth token with 'admin' AND 'write' scopes
|
|
103
|
+
* }
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export function createScopeGuard(requiredScopes: string[]): new () => Guard {
|
|
107
|
+
return class ScopeGuard implements Guard {
|
|
108
|
+
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
109
|
+
const userScopes = context.auth?.scopes || [];
|
|
110
|
+
|
|
111
|
+
const missingScopes = requiredScopes.filter(
|
|
112
|
+
scope => !userScopes.includes(scope)
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (missingScopes.length > 0) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Insufficient scope. Required: ${requiredScopes.join(', ')}. ` +
|
|
118
|
+
`Missing: ${missingScopes.join(', ')}`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { HealthCheck, HealthCheckInterface, HealthCheckResult } from 'nitrostack';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* System Health Check
|
|
5
|
+
*
|
|
6
|
+
* Monitors system resources and uptime
|
|
7
|
+
*/
|
|
8
|
+
@HealthCheck({
|
|
9
|
+
name: 'system',
|
|
10
|
+
description: 'System resource and uptime check',
|
|
11
|
+
interval: 30 // Check every 30 seconds
|
|
12
|
+
})
|
|
13
|
+
export class SystemHealthCheck implements HealthCheckInterface {
|
|
14
|
+
private startTime: number;
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
this.startTime = Date.now();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async check(): Promise<HealthCheckResult> {
|
|
21
|
+
try {
|
|
22
|
+
const memoryUsage = process.memoryUsage();
|
|
23
|
+
const uptime = Date.now() - this.startTime;
|
|
24
|
+
const uptimeSeconds = Math.floor(uptime / 1000);
|
|
25
|
+
|
|
26
|
+
// Convert memory to MB
|
|
27
|
+
const memoryUsedMB = Math.round(memoryUsage.heapUsed / 1024 / 1024);
|
|
28
|
+
const memoryTotalMB = Math.round(memoryUsage.heapTotal / 1024 / 1024);
|
|
29
|
+
|
|
30
|
+
// Consider unhealthy if memory usage is > 90%
|
|
31
|
+
const memoryPercent = (memoryUsage.heapUsed / memoryUsage.heapTotal) * 100;
|
|
32
|
+
const isHealthy = memoryPercent < 90;
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
status: isHealthy ? 'up' : 'degraded',
|
|
36
|
+
message: isHealthy
|
|
37
|
+
? 'System is healthy'
|
|
38
|
+
: 'High memory usage detected',
|
|
39
|
+
details: {
|
|
40
|
+
uptime: `${uptimeSeconds}s`,
|
|
41
|
+
memory: `${memoryUsedMB}MB / ${memoryTotalMB}MB (${Math.round(memoryPercent)}%)`,
|
|
42
|
+
pid: process.pid,
|
|
43
|
+
nodeVersion: process.version,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
} catch (error: any) {
|
|
47
|
+
return {
|
|
48
|
+
status: 'down',
|
|
49
|
+
message: 'System health check failed',
|
|
50
|
+
details: error.message,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Calculator MCP Server with OAuth 2.1 Authentication
|
|
4
|
+
*
|
|
5
|
+
* Main entry point for the MCP server.
|
|
6
|
+
* Uses the @McpApp decorator pattern for clean, NestJS-style architecture.
|
|
7
|
+
*
|
|
8
|
+
* OAuth 2.1 Compliance:
|
|
9
|
+
* - MCP Specification: https://modelcontextprotocol.io/specification/draft/basic/authorization
|
|
10
|
+
* - OpenAI Apps SDK: https://developers.openai.com/apps-sdk/build/auth
|
|
11
|
+
* - RFC 9728 - Protected Resource Metadata
|
|
12
|
+
* - RFC 8707 - Resource Indicators (Token Audience Binding)
|
|
13
|
+
*
|
|
14
|
+
* Transport Configuration:
|
|
15
|
+
* - Development (NODE_ENV=development): STDIO only
|
|
16
|
+
* - Production (NODE_ENV=production): Dual transport (STDIO + HTTP SSE)
|
|
17
|
+
* - With OAuth: Dual mode (STDIO + HTTP for metadata endpoints)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import 'dotenv/config';
|
|
21
|
+
import { McpApplicationFactory } from 'nitrostack';
|
|
22
|
+
import { AppModule } from './app.module.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Bootstrap the application
|
|
26
|
+
*/
|
|
27
|
+
async function bootstrap() {
|
|
28
|
+
try {
|
|
29
|
+
console.error('š Starting Calculator MCP Server with OAuth 2.1...\\n');
|
|
30
|
+
|
|
31
|
+
// Validate required environment variables for OAuth
|
|
32
|
+
const requiredEnvVars = ['RESOURCE_URI', 'AUTH_SERVER_URL'];
|
|
33
|
+
const missing = requiredEnvVars.filter(v => !process.env[v]);
|
|
34
|
+
|
|
35
|
+
if (missing.length > 0) {
|
|
36
|
+
console.error('ā Missing required OAuth environment variables:');
|
|
37
|
+
missing.forEach(v => console.error(` - ${v}`));
|
|
38
|
+
console.error('\\nš” Copy .env.example to .env and configure your OAuth provider');
|
|
39
|
+
console.error(' Or check the test-oauth/.env for reference\\n');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Create the MCP application
|
|
44
|
+
const server = await McpApplicationFactory.create(AppModule);
|
|
45
|
+
|
|
46
|
+
console.error('ā
OAuth 2.1 Module configured');
|
|
47
|
+
console.error(` Resource URI: ${process.env.RESOURCE_URI}`);
|
|
48
|
+
console.error(` Auth Server: ${process.env.AUTH_SERVER_URL}`);
|
|
49
|
+
console.error(` Scopes: read, write, admin`);
|
|
50
|
+
console.error(` Audience: ${process.env.TOKEN_AUDIENCE || process.env.RESOURCE_URI}\\n`);
|
|
51
|
+
|
|
52
|
+
// Start the server
|
|
53
|
+
await server.start();
|
|
54
|
+
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error('ā Failed to start server:', error);
|
|
57
|
+
console.error('\\nš” Check your OAuth configuration in .env\\n');
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Start the application
|
|
63
|
+
bootstrap();
|