nitrostack 1.0.55 → 1.0.56

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 (222) hide show
  1. package/dist/cli/commands/init.d.ts.map +1 -1
  2. package/dist/cli/commands/init.js +14 -9
  3. package/dist/cli/commands/init.js.map +1 -1
  4. package/dist/widgets/hooks/index.d.ts +7 -0
  5. package/dist/widgets/hooks/index.d.ts.map +1 -0
  6. package/dist/widgets/hooks/index.js +7 -0
  7. package/dist/widgets/hooks/index.js.map +1 -0
  8. package/dist/widgets/hooks/use-display-mode.d.ts +11 -0
  9. package/dist/widgets/hooks/use-display-mode.d.ts.map +1 -0
  10. package/dist/widgets/hooks/use-display-mode.js +13 -0
  11. package/dist/widgets/hooks/use-display-mode.js.map +1 -0
  12. package/dist/widgets/hooks/use-max-height.d.ts +10 -0
  13. package/dist/widgets/hooks/use-max-height.d.ts.map +1 -0
  14. package/dist/widgets/hooks/use-max-height.js +13 -0
  15. package/dist/widgets/hooks/use-max-height.js.map +1 -0
  16. package/dist/widgets/hooks/use-openai-global.d.ts +12 -0
  17. package/dist/widgets/hooks/use-openai-global.d.ts.map +1 -0
  18. package/dist/widgets/hooks/use-openai-global.js +32 -0
  19. package/dist/widgets/hooks/use-openai-global.js.map +1 -0
  20. package/dist/widgets/hooks/use-theme.d.ts +10 -0
  21. package/dist/widgets/hooks/use-theme.d.ts.map +1 -0
  22. package/dist/widgets/hooks/use-theme.js +12 -0
  23. package/dist/widgets/hooks/use-theme.js.map +1 -0
  24. package/dist/widgets/hooks/use-widget-state.d.ts +18 -0
  25. package/dist/widgets/hooks/use-widget-state.d.ts.map +1 -0
  26. package/dist/widgets/hooks/use-widget-state.js +27 -0
  27. package/dist/widgets/hooks/use-widget-state.js.map +1 -0
  28. package/dist/widgets/hooks/useWidgetSDK.d.ts +47 -0
  29. package/dist/widgets/hooks/useWidgetSDK.d.ts.map +1 -0
  30. package/dist/widgets/hooks/useWidgetSDK.js +67 -0
  31. package/dist/widgets/hooks/useWidgetSDK.js.map +1 -0
  32. package/dist/widgets/index.d.ts +7 -1
  33. package/dist/widgets/index.d.ts.map +1 -1
  34. package/dist/widgets/index.js +11 -1
  35. package/dist/widgets/index.js.map +1 -1
  36. package/dist/widgets/runtime/WidgetLayout.d.ts +32 -0
  37. package/dist/widgets/runtime/WidgetLayout.d.ts.map +1 -0
  38. package/dist/widgets/runtime/WidgetLayout.js +143 -0
  39. package/dist/widgets/runtime/WidgetLayout.js.map +1 -0
  40. package/dist/widgets/runtime/widget-polyfill.d.ts +1 -0
  41. package/dist/widgets/runtime/widget-polyfill.d.ts.map +1 -0
  42. package/dist/widgets/runtime/widget-polyfill.js +28 -0
  43. package/dist/widgets/runtime/widget-polyfill.js.map +1 -0
  44. package/dist/widgets/sdk.d.ts +109 -0
  45. package/dist/widgets/sdk.d.ts.map +1 -0
  46. package/dist/widgets/sdk.js +221 -0
  47. package/dist/widgets/sdk.js.map +1 -0
  48. package/dist/widgets/types.d.ts +89 -0
  49. package/dist/widgets/types.d.ts.map +1 -0
  50. package/dist/widgets/types.js +8 -0
  51. package/dist/widgets/types.js.map +1 -0
  52. package/dist/widgets/utils/media-queries.d.ts +34 -0
  53. package/dist/widgets/utils/media-queries.d.ts.map +1 -0
  54. package/dist/widgets/utils/media-queries.js +42 -0
  55. package/dist/widgets/utils/media-queries.js.map +1 -0
  56. package/package.json +1 -1
  57. package/src/studio/app/chat/page.tsx +274 -137
  58. package/src/studio/app/globals.css +140 -64
  59. package/src/studio/branding.md +807 -0
  60. package/src/studio/components/WidgetRenderer.tsx +222 -16
  61. package/src/studio/lib/llm-service.ts +39 -39
  62. package/templates/typescript-oauth/{env.example → .env.example} +4 -10
  63. package/templates/typescript-oauth/README.md +226 -306
  64. package/templates/typescript-oauth/package-lock.json +4253 -0
  65. package/templates/typescript-oauth/package.json +10 -5
  66. package/templates/typescript-oauth/src/app.module.ts +39 -36
  67. package/templates/typescript-oauth/src/guards/oauth.guard.ts +0 -1
  68. package/templates/typescript-oauth/src/index.ts +22 -30
  69. package/templates/typescript-oauth/src/modules/flights/booking.tools.ts +323 -0
  70. package/templates/typescript-oauth/src/modules/flights/flights.module.ts +14 -0
  71. package/templates/typescript-oauth/src/modules/flights/flights.prompts.ts +231 -0
  72. package/templates/typescript-oauth/src/modules/flights/flights.resources.ts +215 -0
  73. package/templates/typescript-oauth/src/modules/flights/flights.tools.ts +457 -0
  74. package/templates/typescript-oauth/src/services/duffel.service.ts +285 -0
  75. package/templates/typescript-oauth/src/widgets/app/airport-search/page.tsx +270 -0
  76. package/templates/typescript-oauth/src/widgets/app/flight-details/page.tsx +261 -0
  77. package/templates/typescript-oauth/src/widgets/app/flight-search-results/page.tsx +378 -0
  78. package/templates/typescript-oauth/src/widgets/app/globals.css +167 -0
  79. package/templates/typescript-oauth/src/widgets/app/layout.tsx +6 -2
  80. package/templates/typescript-oauth/src/widgets/app/order-cancellation/page.tsx +207 -0
  81. package/templates/typescript-oauth/src/widgets/app/order-summary/page.tsx +245 -0
  82. package/templates/typescript-oauth/src/widgets/app/payment-confirmation/page.tsx +152 -0
  83. package/templates/typescript-oauth/src/widgets/app/seat-selection/page.tsx +486 -0
  84. package/templates/typescript-oauth/src/widgets/next-env.d.ts +5 -0
  85. package/templates/typescript-oauth/src/widgets/package-lock.json +155 -126
  86. package/templates/typescript-oauth/src/widgets/widget-manifest.json +374 -27
  87. package/templates/typescript-pizzaz/IMPLEMENTATION.md +98 -0
  88. package/templates/typescript-pizzaz/README.md +233 -0
  89. package/templates/typescript-pizzaz/package.json +31 -0
  90. package/templates/typescript-pizzaz/src/app.module.ts +28 -0
  91. package/templates/typescript-pizzaz/src/index.ts +30 -0
  92. package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.data.ts +106 -0
  93. package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.module.ts +11 -0
  94. package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.service.ts +60 -0
  95. package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.tools.ts +197 -0
  96. package/templates/typescript-pizzaz/src/widgets/app/layout.tsx +18 -0
  97. package/templates/typescript-pizzaz/src/widgets/app/pizza-list/page.tsx +272 -0
  98. package/templates/typescript-pizzaz/src/widgets/app/pizza-map/page.tsx +216 -0
  99. package/templates/typescript-pizzaz/src/widgets/app/pizza-shop/page.tsx +374 -0
  100. package/templates/typescript-pizzaz/src/widgets/components/CompactShopCard.tsx +144 -0
  101. package/templates/typescript-pizzaz/src/widgets/components/PizzaCard.tsx +191 -0
  102. package/templates/typescript-pizzaz/src/widgets/package.json +30 -0
  103. package/templates/typescript-pizzaz/src/widgets/widget-manifest.json +253 -0
  104. package/templates/typescript-pizzaz/tsconfig.json +30 -0
  105. package/templates/typescript-starter/src/modules/calculator/calculator.resources.ts +0 -1
  106. package/templates/typescript-starter/src/widgets/app/calculator-result/page.tsx +102 -56
  107. package/templates/typescript-starter/src/widgets/app/layout.tsx +6 -2
  108. package/templates/typescript-auth/AI_AGENT_CLI_REFERENCE.md +0 -702
  109. package/templates/typescript-auth/AI_AGENT_SDK_REFERENCE.md +0 -1260
  110. package/templates/typescript-auth/README.md +0 -402
  111. package/templates/typescript-auth/env.example +0 -25
  112. package/templates/typescript-auth/package.json +0 -36
  113. package/templates/typescript-auth/src/app.module.ts +0 -103
  114. package/templates/typescript-auth/src/db/database.ts +0 -160
  115. package/templates/typescript-auth/src/db/seed.ts +0 -374
  116. package/templates/typescript-auth/src/db/setup.ts +0 -87
  117. package/templates/typescript-auth/src/events/analytics.service.ts +0 -52
  118. package/templates/typescript-auth/src/events/notification.service.ts +0 -40
  119. package/templates/typescript-auth/src/filters/global-exception.filter.ts +0 -28
  120. package/templates/typescript-auth/src/guards/README.md +0 -75
  121. package/templates/typescript-auth/src/guards/jwt.guard.ts +0 -105
  122. package/templates/typescript-auth/src/health/database.health.ts +0 -41
  123. package/templates/typescript-auth/src/index.ts +0 -29
  124. package/templates/typescript-auth/src/interceptors/transform.interceptor.ts +0 -24
  125. package/templates/typescript-auth/src/middleware/logging.middleware.ts +0 -42
  126. package/templates/typescript-auth/src/modules/addresses/addresses.module.ts +0 -16
  127. package/templates/typescript-auth/src/modules/addresses/addresses.prompts.ts +0 -114
  128. package/templates/typescript-auth/src/modules/addresses/addresses.resources.ts +0 -40
  129. package/templates/typescript-auth/src/modules/addresses/addresses.tools.ts +0 -284
  130. package/templates/typescript-auth/src/modules/auth/auth.module.ts +0 -16
  131. package/templates/typescript-auth/src/modules/auth/auth.prompts.ts +0 -147
  132. package/templates/typescript-auth/src/modules/auth/auth.resources.ts +0 -84
  133. package/templates/typescript-auth/src/modules/auth/auth.tools.ts +0 -139
  134. package/templates/typescript-auth/src/modules/cart/cart.module.ts +0 -16
  135. package/templates/typescript-auth/src/modules/cart/cart.prompts.ts +0 -95
  136. package/templates/typescript-auth/src/modules/cart/cart.resources.ts +0 -44
  137. package/templates/typescript-auth/src/modules/cart/cart.tools.ts +0 -277
  138. package/templates/typescript-auth/src/modules/orders/orders.module.ts +0 -16
  139. package/templates/typescript-auth/src/modules/orders/orders.prompts.ts +0 -88
  140. package/templates/typescript-auth/src/modules/orders/orders.resources.ts +0 -48
  141. package/templates/typescript-auth/src/modules/orders/orders.tools.ts +0 -303
  142. package/templates/typescript-auth/src/modules/products/products.module.ts +0 -16
  143. package/templates/typescript-auth/src/modules/products/products.prompts.ts +0 -146
  144. package/templates/typescript-auth/src/modules/products/products.resources.ts +0 -98
  145. package/templates/typescript-auth/src/modules/products/products.tools.ts +0 -266
  146. package/templates/typescript-auth/src/pipes/validation.pipe.ts +0 -42
  147. package/templates/typescript-auth/src/services/database.service.ts +0 -90
  148. package/templates/typescript-auth/src/widgets/app/add-to-cart/page.tsx +0 -122
  149. package/templates/typescript-auth/src/widgets/app/address-added/page.tsx +0 -116
  150. package/templates/typescript-auth/src/widgets/app/address-deleted/page.tsx +0 -105
  151. package/templates/typescript-auth/src/widgets/app/address-list/page.tsx +0 -139
  152. package/templates/typescript-auth/src/widgets/app/address-updated/page.tsx +0 -153
  153. package/templates/typescript-auth/src/widgets/app/cart-cleared/page.tsx +0 -86
  154. package/templates/typescript-auth/src/widgets/app/cart-updated/page.tsx +0 -116
  155. package/templates/typescript-auth/src/widgets/app/categories/page.tsx +0 -134
  156. package/templates/typescript-auth/src/widgets/app/layout.tsx +0 -21
  157. package/templates/typescript-auth/src/widgets/app/login-result/page.tsx +0 -129
  158. package/templates/typescript-auth/src/widgets/app/order-confirmation/page.tsx +0 -231
  159. package/templates/typescript-auth/src/widgets/app/order-details/page.tsx +0 -225
  160. package/templates/typescript-auth/src/widgets/app/order-history/page.tsx +0 -218
  161. package/templates/typescript-auth/src/widgets/app/product-card/page.tsx +0 -121
  162. package/templates/typescript-auth/src/widgets/app/products-grid/page.tsx +0 -198
  163. package/templates/typescript-auth/src/widgets/app/shopping-cart/page.tsx +0 -187
  164. package/templates/typescript-auth/src/widgets/app/whoami/page.tsx +0 -165
  165. package/templates/typescript-auth/src/widgets/next.config.js +0 -38
  166. package/templates/typescript-auth/src/widgets/package.json +0 -18
  167. package/templates/typescript-auth/src/widgets/styles/ecommerce.ts +0 -169
  168. package/templates/typescript-auth/src/widgets/tsconfig.json +0 -28
  169. package/templates/typescript-auth/src/widgets/types/tool-data.ts +0 -141
  170. package/templates/typescript-auth/src/widgets/widget-manifest.json +0 -464
  171. package/templates/typescript-auth/tsconfig.json +0 -27
  172. package/templates/typescript-auth-api-key/AI_AGENT_CLI_REFERENCE.md +0 -701
  173. package/templates/typescript-auth-api-key/AI_AGENT_SDK_REFERENCE.md +0 -1260
  174. package/templates/typescript-auth-api-key/README.md +0 -485
  175. package/templates/typescript-auth-api-key/env.example +0 -17
  176. package/templates/typescript-auth-api-key/package.json +0 -21
  177. package/templates/typescript-auth-api-key/src/app.module.ts +0 -38
  178. package/templates/typescript-auth-api-key/src/guards/apikey.guard.ts +0 -47
  179. package/templates/typescript-auth-api-key/src/guards/multi-auth.guard.ts +0 -157
  180. package/templates/typescript-auth-api-key/src/index.ts +0 -47
  181. package/templates/typescript-auth-api-key/src/modules/calculator/calculator.module.ts +0 -12
  182. package/templates/typescript-auth-api-key/src/modules/calculator/calculator.prompts.ts +0 -73
  183. package/templates/typescript-auth-api-key/src/modules/calculator/calculator.resources.ts +0 -60
  184. package/templates/typescript-auth-api-key/src/modules/calculator/calculator.tools.ts +0 -71
  185. package/templates/typescript-auth-api-key/src/modules/demo/demo.module.ts +0 -18
  186. package/templates/typescript-auth-api-key/src/modules/demo/demo.tools.ts +0 -155
  187. package/templates/typescript-auth-api-key/src/modules/demo/multi-auth.tools.ts +0 -123
  188. package/templates/typescript-auth-api-key/src/widgets/app/calculator-operations/page.tsx +0 -133
  189. package/templates/typescript-auth-api-key/src/widgets/app/calculator-result/page.tsx +0 -134
  190. package/templates/typescript-auth-api-key/src/widgets/app/layout.tsx +0 -14
  191. package/templates/typescript-auth-api-key/src/widgets/package.json +0 -24
  192. package/templates/typescript-auth-api-key/src/widgets/widget-manifest.json +0 -48
  193. package/templates/typescript-auth-api-key/tsconfig.json +0 -23
  194. package/templates/typescript-oauth/OAUTH_SETUP.md +0 -592
  195. package/templates/typescript-oauth/src/modules/demo/demo.module.ts +0 -16
  196. package/templates/typescript-oauth/src/modules/demo/demo.tools.ts +0 -190
  197. package/templates/typescript-oauth/src/widgets/app/calculator-operations/page.tsx +0 -133
  198. package/templates/typescript-oauth/src/widgets/app/calculator-result/page.tsx +0 -134
  199. package/templates/typescript-oauth/src/widgets/out/404.html +0 -1
  200. package/templates/typescript-oauth/src/widgets/out/_next/static/WU9THacVqL52RZbrZOLS1/_buildManifest.js +0 -1
  201. package/templates/typescript-oauth/src/widgets/out/_next/static/WU9THacVqL52RZbrZOLS1/_ssgManifest.js +0 -1
  202. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/117-eb57c7ef86f964a4.js +0 -2
  203. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/app/_not-found/page-dcb83ba3e4d0aafd.js +0 -1
  204. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/app/calculator-operations/page-b8913a740073ea8a.js +0 -1
  205. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/app/calculator-result/page-ddaaab2fce95dea2.js +0 -1
  206. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/app/layout-cbd3ebdc4ecc5247.js +0 -1
  207. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/fd9d1056-749e5812300142af.js +0 -1
  208. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/framework-f66176bb897dc684.js +0 -1
  209. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/main-76df43fcef3db344.js +0 -1
  210. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/main-app-f9c40224d04023c5.js +0 -1
  211. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/pages/_app-72b849fbd24ac258.js +0 -1
  212. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/pages/_error-7ba65e1336b92748.js +0 -1
  213. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
  214. package/templates/typescript-oauth/src/widgets/out/_next/static/chunks/webpack-100b9e646d9c912e.js +0 -1
  215. package/templates/typescript-oauth/src/widgets/out/calculator-operations.html +0 -1
  216. package/templates/typescript-oauth/src/widgets/out/calculator-operations.txt +0 -7
  217. package/templates/typescript-oauth/src/widgets/out/calculator-result.html +0 -1
  218. package/templates/typescript-oauth/src/widgets/out/calculator-result.txt +0 -7
  219. package/templates/typescript-starter/src/widgets/app/calculator-operations/page.tsx +0 -133
  220. /package/templates/{typescript-auth-api-key → typescript-oauth}/src/health/system.health.ts +0 -0
  221. /package/templates/{typescript-auth-api-key → typescript-pizzaz}/src/widgets/next.config.js +0 -0
  222. /package/templates/{typescript-auth-api-key → typescript-pizzaz}/src/widgets/tsconfig.json +0 -0
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "typescript-oauth",
2
+ "name": "nitrostack-typescript-oauth",
3
3
  "version": "1.0.0",
4
4
  "private": true,
5
5
  "type": "module",
@@ -11,11 +11,16 @@
11
11
  "widget": "npm --prefix src/widgets"
12
12
  },
13
13
  "dependencies": {
14
- "dotenv": "^16.4.5",
15
14
  "nitrostack": "^1",
16
- "zod": "^3.23.8"
15
+ "zod": "^3.22.4",
16
+ "dotenv": "^16.3.1",
17
+ "@duffel/api": "^4.21.0",
18
+ "axios": "^1.7.9",
19
+ "date-fns": "^4.1.0"
17
20
  },
18
21
  "devDependencies": {
19
22
  "typescript": "^5.3.3"
20
- }
21
- }
23
+ },
24
+ "description": "NitroStack OAuth 2.1 template with flight booking - demonstrates authentication, API integration, and modern widget development",
25
+ "author": ""
26
+ }
@@ -1,49 +1,52 @@
1
- import { McpApp, Module, OAuthModule } from 'nitrostack';
2
- import { DemoModule } from './modules/demo/demo.module.js';
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';
3
4
 
4
5
  /**
5
- * App Module - Root module for the OAuth 2.1 MCP Server
6
+ * Root Application Module
6
7
  *
7
- * This module demonstrates OAuth 2.1 authentication according to:
8
- * - MCP Specification: https://modelcontextprotocol.io/specification/draft/basic/authorization
9
- * - OpenAI Apps SDK Requirements: https://developers.openai.com/apps-sdk/build/auth
8
+ * This is the main module that bootstraps the MCP server.
9
+ * It registers all feature modules and health checks.
10
10
  *
11
- * OAuth 2.1 Standards Compliance:
12
- * - OAuth 2.1 (draft-ietf-oauth-v2-1-13)
13
- * - RFC 9728 - Protected Resource Metadata
14
- * - RFC 8414 - Authorization Server Metadata
15
- * - RFC 7591 - Dynamic Client Registration
16
- * - RFC 8707 - Resource Indicators (Token Audience Binding)
17
- * - RFC 7636 - PKCE
18
- * - RFC 7662 - Token Introspection
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
19
20
  */
20
21
  @McpApp({
21
22
  module: AppModule,
22
23
  server: {
23
- name: 'OAuth 2.1 MCP Server',
24
- version: '1.0.0',
24
+ name: 'airline-ticketing-server',
25
+ version: '1.0.0'
25
26
  },
26
27
  logging: {
27
- level: 'info',
28
- },
28
+ level: 'info'
29
+ }
29
30
  })
30
31
  @Module({
31
32
  name: 'app',
32
- description: 'Root application module with OAuth 2.1 authentication',
33
+ description: 'Airline ticketing MCP server with OAuth 2.1 authentication and Duffel integration',
33
34
  imports: [
35
+ ConfigModule.forRoot(),
36
+
34
37
  // Enable OAuth 2.1 authentication
35
38
  OAuthModule.forRoot({
36
39
  // Resource URI - YOUR MCP server's public URL
37
40
  // This is used for token audience binding (RFC 8707)
38
41
  // CRITICAL: Tokens must be issued specifically for this URI
39
- resourceUri: process.env.RESOURCE_URI || 'https://mcp.example.com',
40
-
42
+ resourceUri: process.env.RESOURCE_URI || 'https://mcplocal',
43
+
41
44
  // Authorization Server(s) - The OAuth provider URL(s)
42
45
  // Supports multiple auth servers for federation scenarios
43
46
  authorizationServers: [
44
- process.env.AUTH_SERVER_URL || 'https://auth.example.com',
47
+ process.env.AUTH_SERVER_URL || 'https://dev-5dt0utuk31h13tjm.us.auth0.com',
45
48
  ],
46
-
49
+
47
50
  // Supported scopes for this MCP server
48
51
  // Define what permissions your server supports
49
52
  scopesSupported: [
@@ -51,39 +54,39 @@ import { DemoModule } from './modules/demo/demo.module.js';
51
54
  'write', // Write/modify resources
52
55
  'admin', // Administrative operations
53
56
  ],
54
-
57
+
55
58
  // Token Introspection (RFC 7662) - For opaque tokens
56
59
  // If your OAuth provider issues opaque tokens (not JWTs),
57
60
  // you MUST configure introspection to validate them
58
61
  tokenIntrospectionEndpoint: process.env.INTROSPECTION_ENDPOINT,
59
62
  tokenIntrospectionClientId: process.env.INTROSPECTION_CLIENT_ID,
60
63
  tokenIntrospectionClientSecret: process.env.INTROSPECTION_CLIENT_SECRET,
61
-
64
+
62
65
  // Expected audience (defaults to resourceUri if not provided)
63
66
  // MUST match the audience claim in tokens (RFC 8707)
64
67
  audience: process.env.TOKEN_AUDIENCE,
65
-
68
+
66
69
  // Expected issuer (optional but recommended)
67
70
  // If provided, tokens must be from this issuer
68
71
  issuer: process.env.TOKEN_ISSUER,
69
-
72
+
70
73
  // Custom validation (optional)
71
74
  // Add any additional validation logic beyond spec requirements
72
75
  customValidation: async (tokenPayload) => {
73
76
  // Example: Check if user is active in your database
74
77
  // const user = await db.users.findOne({ id: tokenPayload.sub });
75
78
  // return user?.active === true;
76
-
77
- // For demo, accept all valid tokens
79
+
80
+ // For now, accept all valid tokens
78
81
  return true;
79
82
  },
80
83
  }),
81
-
82
- // Feature modules
83
- DemoModule,
84
+
85
+ FlightsModule
84
86
  ],
85
- controllers: [],
86
- providers: [],
87
+ providers: [
88
+ // Health Checks
89
+ SystemHealthCheck,
90
+ ]
87
91
  })
88
- export class AppModule {}
89
-
92
+ export class AppModule { }
@@ -124,4 +124,3 @@ export function createScopeGuard(requiredScopes: string[]): new () => Guard {
124
124
  };
125
125
  }
126
126
 
127
-
@@ -1,71 +1,63 @@
1
1
  #!/usr/bin/env node
2
- import 'dotenv/config';
3
- import { McpApplicationFactory } from 'nitrostack';
4
- import { AppModule } from './app.module.js';
5
-
6
2
  /**
7
- * OAuth 2.1 MCP Server
3
+ * Calculator MCP Server with OAuth 2.1 Authentication
8
4
  *
9
- * Demonstrates OAuth 2.1 authentication for Model Context Protocol servers.
5
+ * Main entry point for the MCP server.
6
+ * Uses the @McpApp decorator pattern for clean, NestJS-style architecture.
10
7
  *
11
- * Compliant with:
8
+ * OAuth 2.1 Compliance:
12
9
  * - MCP Specification: https://modelcontextprotocol.io/specification/draft/basic/authorization
13
10
  * - OpenAI Apps SDK: https://developers.openai.com/apps-sdk/build/auth
14
- *
15
- * Standards:
16
- * - OAuth 2.1 (draft-ietf-oauth-v2-1-13)
17
11
  * - RFC 9728 - Protected Resource Metadata
18
- * - RFC 8414 - Authorization Server Metadata
19
- * - RFC 7591 - Dynamic Client Registration
20
12
  * - RFC 8707 - Resource Indicators (Token Audience Binding)
21
- * - RFC 7636 - PKCE
22
- * - RFC 7662 - Token Introspection
23
- *
24
- * Compatible with:
25
- * - Auth0
26
- * - Okta
27
- * - Keycloak
28
- * - Any RFC-compliant OAuth 2.1 provider
29
13
  *
30
14
  * Transport Configuration:
31
15
  * - Development (NODE_ENV=development): STDIO only
32
16
  * - Production (NODE_ENV=production): Dual transport (STDIO + HTTP SSE)
17
+ * - With OAuth: Dual mode (STDIO + HTTP for metadata endpoints)
33
18
  */
34
19
 
20
+ import 'dotenv/config';
21
+ import { McpApplicationFactory } from 'nitrostack';
22
+ import { AppModule } from './app.module.js';
23
+
24
+ /**
25
+ * Bootstrap the application
26
+ */
35
27
  async function bootstrap() {
36
28
  try {
37
- console.error('🔐 Starting OAuth 2.1 MCP Server...\n');
29
+ console.error('🔐 Starting Calculator MCP Server with OAuth 2.1...\\n');
38
30
 
39
- // Validate required environment variables
31
+ // Validate required environment variables for OAuth
40
32
  const requiredEnvVars = ['RESOURCE_URI', 'AUTH_SERVER_URL'];
41
33
  const missing = requiredEnvVars.filter(v => !process.env[v]);
42
34
 
43
35
  if (missing.length > 0) {
44
- console.error('❌ Missing required environment variables:');
36
+ console.error('❌ Missing required OAuth environment variables:');
45
37
  missing.forEach(v => console.error(` - ${v}`));
46
- console.error('\n💡 Copy .env.example to .env and configure your OAuth provider');
47
- console.error(' See OAUTH_SETUP.md for provider-specific setup guides\n');
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');
48
40
  process.exit(1);
49
41
  }
50
42
 
51
43
  // Create the MCP application
52
- const app = await McpApplicationFactory.create(AppModule);
44
+ const server = await McpApplicationFactory.create(AppModule);
53
45
 
54
46
  console.error('✅ OAuth 2.1 Module configured');
55
47
  console.error(` Resource URI: ${process.env.RESOURCE_URI}`);
56
48
  console.error(` Auth Server: ${process.env.AUTH_SERVER_URL}`);
57
49
  console.error(` Scopes: read, write, admin`);
58
- console.error(` Audience: ${process.env.TOKEN_AUDIENCE || process.env.RESOURCE_URI}\n`);
50
+ console.error(` Audience: ${process.env.TOKEN_AUDIENCE || process.env.RESOURCE_URI}\\n`);
59
51
 
60
52
  // Start the server
61
- await app.start();
53
+ await server.start();
62
54
 
63
55
  } catch (error) {
64
56
  console.error('❌ Failed to start server:', error);
65
- console.error('\n💡 Check your OAuth configuration in .env');
66
- console.error(' See OAUTH_SETUP.md for troubleshooting\n');
57
+ console.error('\\n💡 Check your OAuth configuration in .env\\n');
67
58
  process.exit(1);
68
59
  }
69
60
  }
70
61
 
62
+ // Start the application
71
63
  bootstrap();
@@ -0,0 +1,323 @@
1
+ import { ToolDecorator as Tool, Widget, ExecutionContext, z, UseGuards, Injectable } from 'nitrostack';
2
+ import { OAuthGuard } from '../../guards/oauth.guard.js';
3
+ import { DuffelService } from '../../services/duffel.service.js';
4
+
5
+ @Injectable()
6
+ export class BookingTools {
7
+ constructor(private duffelService: DuffelService) { }
8
+
9
+ @Tool({
10
+ name: 'create_order',
11
+ description: 'Create a flight order with hold (no payment required). IMPORTANT: Before calling this tool, you MUST collect passenger information from the user. Ask for: full name (first and last), title (Mr/Ms/Mrs/Miss/Dr), gender (M/F), date of birth (YYYY-MM-DD), email, and phone number with country code. The order will be held for later payment.',
12
+ inputSchema: z.object({
13
+ offerId: z.string().describe('The offer ID to book'),
14
+ passengers: z.string().describe('JSON string containing array of passenger objects. Each passenger must have: title (mr/ms/mrs/miss/dr), givenName (first name), familyName (last name), gender (M/F), bornOn (YYYY-MM-DD), email, phoneNumber. Example: \'[{"title":"mr","givenName":"John","familyName":"Doe","gender":"M","bornOn":"1990-01-15","email":"john@example.com","phoneNumber":"+1234567890"}]\'')
15
+ }),
16
+ examples: {
17
+ request: {
18
+ offerId: 'off_123456',
19
+ passengers: '[{"title":"mr","givenName":"John","familyName":"Doe","gender":"M","bornOn":"1990-01-15","email":"john.doe@example.com","phoneNumber":"+1234567890"}]'
20
+ },
21
+ response: {
22
+ orderId: 'ord_123456',
23
+ status: 'held',
24
+ totalAmount: '450.00',
25
+ totalCurrency: 'USD',
26
+ expiresAt: '2024-03-01T12:00:00Z',
27
+ passengers: [],
28
+ slices: [],
29
+ message: 'Order created and held successfully.'
30
+ }
31
+ }
32
+ })
33
+ @UseGuards(OAuthGuard)
34
+ @Widget('order-summary')
35
+ async createOrder(input: any, ctx: ExecutionContext) {
36
+ ctx.logger.info('Creating flight order (hold)', {
37
+ user: ctx.auth?.subject,
38
+ offerId: input.offerId
39
+ });
40
+
41
+ // Validate and parse passengers
42
+ let passengersArray;
43
+ try {
44
+ if (typeof input.passengers === 'string') {
45
+ // Try to parse the JSON string
46
+ // Handle both regular JSON and double-encoded JSON
47
+ let passengerStr = input.passengers;
48
+
49
+ // If the string starts with escaped quotes, it might be double-encoded
50
+ if (passengerStr.startsWith('\\"') || passengerStr.includes('\\"')) {
51
+ // Remove escape characters
52
+ passengerStr = passengerStr.replace(/\\"/g, '"').replace(/\\\\/g, '\\');
53
+ }
54
+
55
+ passengersArray = JSON.parse(passengerStr);
56
+ } else if (Array.isArray(input.passengers)) {
57
+ passengersArray = input.passengers;
58
+ } else {
59
+ throw new Error('Passengers must be a JSON string or array');
60
+ }
61
+ } catch (error: any) {
62
+ ctx.logger.error('Failed to parse passengers', {
63
+ input: input.passengers,
64
+ error: error.message
65
+ });
66
+ throw new Error(`Invalid passengers format: ${error.message}. Expected JSON string like '[{"title":"mr","givenName":"John","familyName":"Doe","gender":"M","bornOn":"1990-01-15","email":"john@example.com","phoneNumber":"+1234567890"}]'`);
67
+ }
68
+
69
+ if (!passengersArray || !Array.isArray(passengersArray) || passengersArray.length === 0) {
70
+ throw new Error('At least one passenger is required to create an order');
71
+ }
72
+
73
+ // Transform passengers to Duffel format
74
+ // Pass inline passenger data - Duffel will create passenger records automatically
75
+ const passengers = passengersArray.map((pax: any) => ({
76
+ title: pax.title,
77
+ given_name: pax.givenName,
78
+ family_name: pax.familyName,
79
+ gender: pax.gender,
80
+ born_on: pax.bornOn,
81
+ email: pax.email,
82
+ phone_number: pax.phoneNumber
83
+ }));
84
+
85
+ const orderParams: any = {
86
+ selectedOffers: [input.offerId],
87
+ passengers,
88
+ type: 'hold' // Always create hold orders
89
+ };
90
+
91
+ const order = await this.duffelService.createOrder(orderParams);
92
+
93
+ ctx.logger.info('Order created successfully', {
94
+ user: ctx.auth?.subject,
95
+ orderId: order.id,
96
+ status: 'held'
97
+ });
98
+
99
+ return {
100
+ orderId: order.id,
101
+ status: 'held',
102
+ totalAmount: order.total_amount,
103
+ totalCurrency: order.total_currency,
104
+ expiresAt: (order as any).expires_at,
105
+ bookingReference: order.booking_reference,
106
+ passengers: order.passengers.map((pax: any) => ({
107
+ id: pax.id,
108
+ name: `${pax.given_name} ${pax.family_name}`,
109
+ type: pax.type
110
+ })),
111
+ slices: order.slices.map((slice: any) => ({
112
+ origin: slice.origin.iata_code,
113
+ destination: slice.destination.iata_code,
114
+ departureTime: slice.segments[0].departing_at,
115
+ arrivalTime: slice.segments[slice.segments.length - 1].arriving_at
116
+ })),
117
+ message: 'Order created and held successfully.'
118
+ };
119
+ }
120
+
121
+
122
+
123
+ @Tool({
124
+ name: 'get_order_details',
125
+ description: 'Get detailed information about an order',
126
+ inputSchema: z.object({
127
+ orderId: z.string().describe('The order ID')
128
+ }),
129
+ examples: {
130
+ request: {
131
+ orderId: 'ord_123456'
132
+ },
133
+ response: {
134
+ orderId: 'ord_123456',
135
+ status: 'confirmed',
136
+ bookingReference: 'ABC123',
137
+ totalAmount: '450.00',
138
+ totalCurrency: 'USD',
139
+ passengers: [],
140
+ slices: []
141
+ }
142
+ }
143
+ })
144
+ @UseGuards(OAuthGuard)
145
+ @Widget('order-summary')
146
+ async getOrderDetails(input: any, ctx: ExecutionContext) {
147
+ ctx.logger.info('Getting order details', {
148
+ user: ctx.auth?.subject,
149
+ orderId: input.orderId
150
+ });
151
+
152
+ const order = await this.duffelService.getOrder(input.orderId);
153
+
154
+ return {
155
+ orderId: order.id,
156
+ status: (order as any).status || 'confirmed',
157
+ bookingReference: order.booking_reference,
158
+ totalAmount: order.total_amount,
159
+ totalCurrency: order.total_currency,
160
+ createdAt: order.created_at,
161
+ expiresAt: (order as any).expires_at,
162
+ passengers: order.passengers.map((pax: any) => ({
163
+ id: pax.id,
164
+ name: `${pax.given_name} ${pax.family_name}`,
165
+ type: pax.type,
166
+ email: pax.email,
167
+ phoneNumber: pax.phone_number
168
+ })),
169
+ slices: order.slices.map((slice: any) => ({
170
+ id: slice.id,
171
+ origin: {
172
+ code: slice.origin.iata_code,
173
+ name: slice.origin.name,
174
+ city: slice.origin.city_name
175
+ },
176
+ destination: {
177
+ code: slice.destination.iata_code,
178
+ name: slice.destination.name,
179
+ city: slice.destination.city_name
180
+ },
181
+ duration: slice.duration,
182
+ segments: slice.segments.map((seg: any) => ({
183
+ id: seg.id,
184
+ origin: seg.origin.iata_code,
185
+ destination: seg.destination.iata_code,
186
+ departingAt: seg.departing_at,
187
+ arrivingAt: seg.arriving_at,
188
+ airline: seg.marketing_carrier.name,
189
+ flightNumber: seg.marketing_carrier_flight_number,
190
+ aircraft: seg.aircraft?.name
191
+ }))
192
+ }))
193
+ };
194
+ }
195
+
196
+ @Tool({
197
+ name: 'get_seat_map',
198
+ description: 'Get available seats for a flight offer to allow seat selection',
199
+ inputSchema: z.object({
200
+ offerId: z.string().describe('The offer ID to get seats for')
201
+ }),
202
+ examples: {
203
+ request: {
204
+ offerId: 'off_123456'
205
+ },
206
+ response: {
207
+ offerId: 'off_123456',
208
+ cabins: [
209
+ {
210
+ cabinClass: 'economy',
211
+ rows: [
212
+ {
213
+ rowNumber: 10,
214
+ seats: [
215
+ {
216
+ id: 'seat_10a',
217
+ column: 'A',
218
+ available: true,
219
+ price: '25.00',
220
+ currency: 'USD',
221
+ type: 'window'
222
+ },
223
+ {
224
+ id: 'seat_10b',
225
+ column: 'B',
226
+ available: true,
227
+ price: '0',
228
+ currency: 'USD',
229
+ type: 'middle'
230
+ },
231
+ {
232
+ id: 'seat_10c',
233
+ column: 'C',
234
+ available: true,
235
+ price: '15.00',
236
+ currency: 'USD',
237
+ type: 'aisle'
238
+ }
239
+ ]
240
+ }
241
+ ]
242
+ }
243
+ ],
244
+ message: 'Select your preferred seats from the available options'
245
+ }
246
+ }
247
+ })
248
+ @UseGuards(OAuthGuard)
249
+ @Widget('seat-selection')
250
+ async getSeatMap(input: any, ctx: ExecutionContext) {
251
+ ctx.logger.info('Getting seat map', {
252
+ user: ctx.auth?.subject,
253
+ offerId: input.offerId
254
+ });
255
+
256
+ const seatMaps = await this.duffelService.getSeatsForOffer(input.offerId);
257
+
258
+ return {
259
+ offerId: input.offerId,
260
+ cabins: seatMaps.map((cabin: any) => ({
261
+ cabinClass: cabin.cabin_class,
262
+ rows: cabin.rows.map((row: any) => ({
263
+ rowNumber: row.row_number,
264
+ seats: row.sections.flatMap((section: any) =>
265
+ section.elements.filter((el: any) => el.type === 'seat').map((seat: any) => ({
266
+ id: seat.id,
267
+ column: seat.designator,
268
+ available: seat.available_services?.length > 0,
269
+ price: seat.available_services?.[0]?.total_amount,
270
+ currency: seat.available_services?.[0]?.total_currency,
271
+ type: seat.disclosures?.join(', ') || 'standard'
272
+ }))
273
+ )
274
+ }))
275
+ })),
276
+ message: 'Select your preferred seats from the available options'
277
+ };
278
+ }
279
+
280
+ @Tool({
281
+ name: 'cancel_order',
282
+ description: 'Cancel a flight order and request refund if applicable',
283
+ inputSchema: z.object({
284
+ orderId: z.string().describe('The order ID to cancel')
285
+ }),
286
+ examples: {
287
+ request: {
288
+ orderId: 'ord_123456'
289
+ },
290
+ response: {
291
+ orderId: 'ord_123456',
292
+ cancellationId: 'ocr_123456',
293
+ status: 'cancelled',
294
+ refundAmount: '450.00',
295
+ refundCurrency: 'USD',
296
+ confirmedAt: '2024-03-01T12:00:00Z',
297
+ message: 'Order cancelled. Refund of USD 450.00 will be processed.'
298
+ }
299
+ }
300
+ })
301
+ @UseGuards(OAuthGuard)
302
+ @Widget('order-cancellation')
303
+ async cancelOrder(input: any, ctx: ExecutionContext) {
304
+ ctx.logger.info('Cancelling order', {
305
+ user: ctx.auth?.subject,
306
+ orderId: input.orderId
307
+ });
308
+
309
+ const cancellation = await this.duffelService.cancelOrder(input.orderId);
310
+
311
+ return {
312
+ orderId: input.orderId,
313
+ cancellationId: cancellation.id,
314
+ status: 'cancelled',
315
+ refundAmount: cancellation.refund_amount,
316
+ refundCurrency: cancellation.refund_currency,
317
+ confirmedAt: cancellation.confirmed_at,
318
+ message: cancellation.refund_amount
319
+ ? `Order cancelled. Refund of ${cancellation.refund_currency} ${cancellation.refund_amount} will be processed.`
320
+ : 'Order cancelled. No refund available for this booking.'
321
+ };
322
+ }
323
+ }
@@ -0,0 +1,14 @@
1
+ import { Module } from 'nitrostack';
2
+ import { FlightTools } from './flights.tools.js';
3
+ import { BookingTools } from './booking.tools.js';
4
+ import { FlightPrompts } from './flights.prompts.js';
5
+ import { FlightResources } from './flights.resources.js';
6
+ import { DuffelService } from '../../services/duffel.service.js';
7
+
8
+ @Module({
9
+ name: 'flights',
10
+ description: 'Professional flight search and booking system powered by Duffel API',
11
+ controllers: [FlightTools, BookingTools, FlightPrompts, FlightResources],
12
+ providers: [DuffelService]
13
+ })
14
+ export class FlightsModule { }