nitrostack 1.0.0 → 1.0.2

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 (164) hide show
  1. package/CHANGELOG.md +30 -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 +41 -0
  52. package/templates/typescript-auth/.env.example +23 -0
  53. package/templates/typescript-auth/src/app.module.ts +103 -0
  54. package/templates/typescript-auth/src/db/database.ts +163 -0
  55. package/templates/typescript-auth/src/db/seed.ts +374 -0
  56. package/templates/typescript-auth/src/db/setup.ts +87 -0
  57. package/templates/typescript-auth/src/events/analytics.service.ts +52 -0
  58. package/templates/typescript-auth/src/events/notification.service.ts +40 -0
  59. package/templates/typescript-auth/src/filters/global-exception.filter.ts +28 -0
  60. package/templates/typescript-auth/src/guards/README.md +75 -0
  61. package/templates/typescript-auth/src/guards/jwt.guard.ts +105 -0
  62. package/templates/typescript-auth/src/health/database.health.ts +41 -0
  63. package/templates/typescript-auth/src/index.ts +26 -0
  64. package/templates/typescript-auth/src/interceptors/transform.interceptor.ts +24 -0
  65. package/templates/typescript-auth/src/middleware/logging.middleware.ts +42 -0
  66. package/templates/typescript-auth/src/modules/addresses/addresses.module.ts +16 -0
  67. package/templates/typescript-auth/src/modules/addresses/addresses.prompts.ts +114 -0
  68. package/templates/typescript-auth/src/modules/addresses/addresses.resources.ts +40 -0
  69. package/templates/typescript-auth/src/modules/addresses/addresses.tools.ts +241 -0
  70. package/templates/typescript-auth/src/modules/auth/auth.module.ts +16 -0
  71. package/templates/typescript-auth/src/modules/auth/auth.prompts.ts +147 -0
  72. package/templates/typescript-auth/src/modules/auth/auth.resources.ts +84 -0
  73. package/templates/typescript-auth/src/modules/auth/auth.tools.ts +139 -0
  74. package/templates/typescript-auth/src/modules/cart/cart.module.ts +16 -0
  75. package/templates/typescript-auth/src/modules/cart/cart.prompts.ts +95 -0
  76. package/templates/typescript-auth/src/modules/cart/cart.resources.ts +44 -0
  77. package/templates/typescript-auth/src/modules/cart/cart.tools.ts +281 -0
  78. package/templates/typescript-auth/src/modules/orders/orders.module.ts +16 -0
  79. package/templates/typescript-auth/src/modules/orders/orders.prompts.ts +88 -0
  80. package/templates/typescript-auth/src/modules/orders/orders.resources.ts +48 -0
  81. package/templates/typescript-auth/src/modules/orders/orders.tools.ts +281 -0
  82. package/templates/typescript-auth/src/modules/products/products.module.ts +16 -0
  83. package/templates/typescript-auth/src/modules/products/products.prompts.ts +146 -0
  84. package/templates/typescript-auth/src/modules/products/products.resources.ts +98 -0
  85. package/templates/typescript-auth/src/modules/products/products.tools.ts +266 -0
  86. package/templates/typescript-auth/src/pipes/validation.pipe.ts +42 -0
  87. package/templates/typescript-auth/src/services/database.service.ts +90 -0
  88. package/templates/typescript-auth/src/widgets/app/add-to-cart/page.tsx +122 -0
  89. package/templates/typescript-auth/src/widgets/app/address-added/page.tsx +116 -0
  90. package/templates/typescript-auth/src/widgets/app/address-deleted/page.tsx +105 -0
  91. package/templates/typescript-auth/src/widgets/app/address-list/page.tsx +139 -0
  92. package/templates/typescript-auth/src/widgets/app/address-updated/page.tsx +153 -0
  93. package/templates/typescript-auth/src/widgets/app/cart-cleared/page.tsx +86 -0
  94. package/templates/typescript-auth/src/widgets/app/cart-updated/page.tsx +116 -0
  95. package/templates/typescript-auth/src/widgets/app/categories/page.tsx +134 -0
  96. package/templates/typescript-auth/src/widgets/app/layout.tsx +21 -0
  97. package/templates/typescript-auth/src/widgets/app/login-result/page.tsx +129 -0
  98. package/templates/typescript-auth/src/widgets/app/order-confirmation/page.tsx +206 -0
  99. package/templates/typescript-auth/src/widgets/app/order-details/page.tsx +225 -0
  100. package/templates/typescript-auth/src/widgets/app/order-history/page.tsx +218 -0
  101. package/templates/typescript-auth/src/widgets/app/product-card/page.tsx +121 -0
  102. package/templates/typescript-auth/src/widgets/app/products-grid/page.tsx +173 -0
  103. package/templates/typescript-auth/src/widgets/app/shopping-cart/page.tsx +187 -0
  104. package/templates/typescript-auth/src/widgets/app/whoami/page.tsx +165 -0
  105. package/templates/typescript-auth/src/widgets/next.config.js +38 -0
  106. package/templates/typescript-auth/src/widgets/package.json +18 -0
  107. package/templates/typescript-auth/src/widgets/styles/ecommerce.ts +169 -0
  108. package/templates/typescript-auth/src/widgets/tsconfig.json +28 -0
  109. package/templates/typescript-auth/src/widgets/types/tool-data.ts +141 -0
  110. package/templates/typescript-auth/src/widgets/widget-manifest.json +464 -0
  111. package/templates/typescript-auth/tsconfig.json +27 -0
  112. package/templates/typescript-auth-api-key/.env +15 -0
  113. package/templates/typescript-auth-api-key/.env.example +4 -0
  114. package/templates/typescript-auth-api-key/src/app.module.ts +38 -0
  115. package/templates/typescript-auth-api-key/src/guards/apikey.guard.ts +47 -0
  116. package/templates/typescript-auth-api-key/src/guards/multi-auth.guard.ts +157 -0
  117. package/templates/typescript-auth-api-key/src/health/system.health.ts +55 -0
  118. package/templates/typescript-auth-api-key/src/index.ts +47 -0
  119. package/templates/typescript-auth-api-key/src/modules/calculator/calculator.module.ts +12 -0
  120. package/templates/typescript-auth-api-key/src/modules/calculator/calculator.prompts.ts +73 -0
  121. package/templates/typescript-auth-api-key/src/modules/calculator/calculator.resources.ts +60 -0
  122. package/templates/typescript-auth-api-key/src/modules/calculator/calculator.tools.ts +71 -0
  123. package/templates/typescript-auth-api-key/src/modules/demo/demo.module.ts +18 -0
  124. package/templates/typescript-auth-api-key/src/modules/demo/demo.tools.ts +155 -0
  125. package/templates/typescript-auth-api-key/src/modules/demo/multi-auth.tools.ts +123 -0
  126. package/templates/typescript-auth-api-key/src/widgets/app/calculator-operations/page.tsx +133 -0
  127. package/templates/typescript-auth-api-key/src/widgets/app/calculator-result/page.tsx +134 -0
  128. package/templates/typescript-auth-api-key/src/widgets/app/layout.tsx +14 -0
  129. package/templates/typescript-auth-api-key/src/widgets/next.config.js +37 -0
  130. package/templates/typescript-auth-api-key/src/widgets/package.json +24 -0
  131. package/templates/typescript-auth-api-key/src/widgets/tsconfig.json +28 -0
  132. package/templates/typescript-auth-api-key/src/widgets/widget-manifest.json +48 -0
  133. package/templates/typescript-auth-api-key/tsconfig.json +23 -0
  134. package/templates/typescript-oauth/.env.example +91 -0
  135. package/templates/typescript-oauth/src/app.module.ts +89 -0
  136. package/templates/typescript-oauth/src/guards/oauth.guard.ts +127 -0
  137. package/templates/typescript-oauth/src/index.ts +74 -0
  138. package/templates/typescript-oauth/src/modules/demo/demo.module.ts +16 -0
  139. package/templates/typescript-oauth/src/modules/demo/demo.tools.ts +190 -0
  140. package/templates/typescript-oauth/src/widgets/app/calculator-operations/page.tsx +133 -0
  141. package/templates/typescript-oauth/src/widgets/app/calculator-result/page.tsx +134 -0
  142. package/templates/typescript-oauth/src/widgets/app/layout.tsx +14 -0
  143. package/templates/typescript-oauth/src/widgets/next.config.js +37 -0
  144. package/templates/typescript-oauth/src/widgets/package.json +24 -0
  145. package/templates/typescript-oauth/src/widgets/tsconfig.json +28 -0
  146. package/templates/typescript-oauth/src/widgets/widget-manifest.json +48 -0
  147. package/templates/typescript-oauth/tsconfig.json +23 -0
  148. package/templates/typescript-starter/.env.example +4 -0
  149. package/templates/typescript-starter/src/app.module.ts +34 -0
  150. package/templates/typescript-starter/src/health/system.health.ts +55 -0
  151. package/templates/typescript-starter/src/index.ts +27 -0
  152. package/templates/typescript-starter/src/modules/calculator/calculator.module.ts +12 -0
  153. package/templates/typescript-starter/src/modules/calculator/calculator.prompts.ts +73 -0
  154. package/templates/typescript-starter/src/modules/calculator/calculator.resources.ts +60 -0
  155. package/templates/typescript-starter/src/modules/calculator/calculator.tools.ts +71 -0
  156. package/templates/typescript-starter/src/widgets/app/calculator-operations/page.tsx +133 -0
  157. package/templates/typescript-starter/src/widgets/app/calculator-result/page.tsx +134 -0
  158. package/templates/typescript-starter/src/widgets/app/layout.tsx +14 -0
  159. package/templates/typescript-starter/src/widgets/next.config.js +37 -0
  160. package/templates/typescript-starter/src/widgets/package.json +24 -0
  161. package/templates/typescript-starter/src/widgets/tsconfig.json +28 -0
  162. package/templates/typescript-starter/src/widgets/widget-manifest.json +48 -0
  163. package/templates/typescript-starter/tsconfig.json +23 -0
  164. package/LICENSE_URLS_UPDATE_COMPLETE.md +0 -388
@@ -0,0 +1,89 @@
1
+ import { McpApp, Module, OAuthModule } from 'nitrostack';
2
+ import { DemoModule } from './modules/demo/demo.module.js';
3
+
4
+ /**
5
+ * App Module - Root module for the OAuth 2.1 MCP Server
6
+ *
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
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
19
+ */
20
+ @McpApp({
21
+ module: AppModule,
22
+ server: {
23
+ name: 'OAuth 2.1 MCP Server',
24
+ version: '1.0.0',
25
+ },
26
+ logging: {
27
+ level: 'info',
28
+ },
29
+ })
30
+ @Module({
31
+ name: 'app',
32
+ description: 'Root application module with OAuth 2.1 authentication',
33
+ imports: [
34
+ // Enable OAuth 2.1 authentication
35
+ OAuthModule.forRoot({
36
+ // Resource URI - YOUR MCP server's public URL
37
+ // This is used for token audience binding (RFC 8707)
38
+ // CRITICAL: Tokens must be issued specifically for this URI
39
+ resourceUri: process.env.RESOURCE_URI || 'https://mcp.example.com',
40
+
41
+ // Authorization Server(s) - The OAuth provider URL(s)
42
+ // Supports multiple auth servers for federation scenarios
43
+ authorizationServers: [
44
+ process.env.AUTH_SERVER_URL || 'https://auth.example.com',
45
+ ],
46
+
47
+ // Supported scopes for this MCP server
48
+ // Define what permissions your server supports
49
+ scopesSupported: [
50
+ 'read', // Read access to resources
51
+ 'write', // Write/modify resources
52
+ 'admin', // Administrative operations
53
+ ],
54
+
55
+ // Token Introspection (RFC 7662) - For opaque tokens
56
+ // If your OAuth provider issues opaque tokens (not JWTs),
57
+ // you MUST configure introspection to validate them
58
+ tokenIntrospectionEndpoint: process.env.INTROSPECTION_ENDPOINT,
59
+ tokenIntrospectionClientId: process.env.INTROSPECTION_CLIENT_ID,
60
+ tokenIntrospectionClientSecret: process.env.INTROSPECTION_CLIENT_SECRET,
61
+
62
+ // Expected audience (defaults to resourceUri if not provided)
63
+ // MUST match the audience claim in tokens (RFC 8707)
64
+ audience: process.env.TOKEN_AUDIENCE,
65
+
66
+ // Expected issuer (optional but recommended)
67
+ // If provided, tokens must be from this issuer
68
+ issuer: process.env.TOKEN_ISSUER,
69
+
70
+ // Custom validation (optional)
71
+ // Add any additional validation logic beyond spec requirements
72
+ customValidation: async (tokenPayload) => {
73
+ // Example: Check if user is active in your database
74
+ // const user = await db.users.findOne({ id: tokenPayload.sub });
75
+ // return user?.active === true;
76
+
77
+ // For demo, accept all valid tokens
78
+ return true;
79
+ },
80
+ }),
81
+
82
+ // Feature modules
83
+ DemoModule,
84
+ ],
85
+ controllers: [],
86
+ providers: [],
87
+ })
88
+ export class AppModule {}
89
+
@@ -0,0 +1,127 @@
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
+
127
+
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+ import { McpApplicationFactory } from 'nitrostack';
3
+ import { AppModule } from './app.module.js';
4
+
5
+ /**
6
+ * OAuth 2.1 MCP Server
7
+ *
8
+ * Demonstrates OAuth 2.1 authentication for Model Context Protocol servers.
9
+ *
10
+ * Compliant with:
11
+ * - MCP Specification: https://modelcontextprotocol.io/specification/draft/basic/authorization
12
+ * - OpenAI Apps SDK: https://developers.openai.com/apps-sdk/build/auth
13
+ *
14
+ * Standards:
15
+ * - OAuth 2.1 (draft-ietf-oauth-v2-1-13)
16
+ * - RFC 9728 - Protected Resource Metadata
17
+ * - RFC 8414 - Authorization Server Metadata
18
+ * - RFC 7591 - Dynamic Client Registration
19
+ * - RFC 8707 - Resource Indicators (Token Audience Binding)
20
+ * - RFC 7636 - PKCE
21
+ * - RFC 7662 - Token Introspection
22
+ *
23
+ * Compatible with:
24
+ * - Auth0
25
+ * - Okta
26
+ * - Keycloak
27
+ * - Any RFC-compliant OAuth 2.1 provider
28
+ */
29
+
30
+ async function bootstrap() {
31
+ try {
32
+ console.error('🔐 Starting OAuth 2.1 MCP Server...\n');
33
+
34
+ // Validate required environment variables
35
+ const requiredEnvVars = ['RESOURCE_URI', 'AUTH_SERVER_URL'];
36
+ const missing = requiredEnvVars.filter(v => !process.env[v]);
37
+
38
+ if (missing.length > 0) {
39
+ console.error('❌ Missing required environment variables:');
40
+ missing.forEach(v => console.error(` - ${v}`));
41
+ console.error('\n💡 Copy .env.example to .env and configure your OAuth provider');
42
+ console.error(' See OAUTH_SETUP.md for provider-specific setup guides\n');
43
+ process.exit(1);
44
+ }
45
+
46
+ // Create the MCP application
47
+ const app = await McpApplicationFactory.create(AppModule);
48
+
49
+ console.error('✅ OAuth 2.1 Module configured');
50
+ console.error(` Resource URI: ${process.env.RESOURCE_URI}`);
51
+ console.error(` Auth Server: ${process.env.AUTH_SERVER_URL}`);
52
+ console.error(` Scopes: read, write, admin`);
53
+ console.error(` Audience: ${process.env.TOKEN_AUDIENCE || process.env.RESOURCE_URI}\n`);
54
+
55
+ // Start the MCP server with HTTP transport (auto-configured by OAuthModule)
56
+ const transportType = (app as any)._transportType || 'stdio';
57
+ const transportOptions = (app as any)._transportOptions;
58
+
59
+ await app.start(transportType, transportOptions);
60
+
61
+ console.error('🚀 Server started successfully!');
62
+ console.error(' Open NitroStack Studio to test OAuth 2.1 flow');
63
+ console.error(' Configure OAuth in Studio → Auth → OAuth 2.1 tab\n');
64
+
65
+ } catch (error) {
66
+ console.error('❌ Failed to start server:', error);
67
+ console.error('\n💡 Check your OAuth configuration in .env');
68
+ console.error(' See OAUTH_SETUP.md for troubleshooting\n');
69
+ process.exit(1);
70
+ }
71
+ }
72
+
73
+ bootstrap();
74
+
@@ -0,0 +1,16 @@
1
+ import { Module } from 'nitrostack';
2
+ import { DemoTools } from './demo.tools.js';
3
+
4
+ /**
5
+ * Demo Module
6
+ *
7
+ * Contains example tools demonstrating OAuth 2.1 authentication
8
+ */
9
+ @Module({
10
+ name: 'demo',
11
+ description: 'Demo module with OAuth 2.1 authentication examples',
12
+ controllers: [DemoTools],
13
+ })
14
+ export class DemoModule {}
15
+
16
+
@@ -0,0 +1,190 @@
1
+ import { Injectable, ToolDecorator as Tool, UseGuards, z, ExecutionContext } from 'nitrostack';
2
+ import { OAuthGuard, createScopeGuard } from '../../guards/oauth.guard.js';
3
+
4
+ /**
5
+ * Demo Tools Module
6
+ *
7
+ * Demonstrates OAuth 2.1 authentication with:
8
+ * 1. Public tools (no authentication)
9
+ * 2. Protected tools (OAuth token required)
10
+ * 3. Scoped tools (specific permissions required)
11
+ */
12
+
13
+ @Injectable()
14
+ export class DemoTools {
15
+
16
+ /**
17
+ * PUBLIC TOOL - No authentication required
18
+ * Anyone can call this tool without OAuth
19
+ */
20
+ @Tool({
21
+ name: 'get_server_info',
22
+ description: 'Get public server information (no authentication required)',
23
+ inputSchema: z.object({}),
24
+ })
25
+ async getServerInfo() {
26
+ return {
27
+ name: 'OAuth 2.1 MCP Server',
28
+ version: '1.0.0',
29
+ description: 'Demonstrates OAuth 2.1 authentication for MCP',
30
+ authentication: {
31
+ type: 'OAuth 2.1',
32
+ compliant: [
33
+ 'OAuth 2.1 (draft-ietf-oauth-v2-1-13)',
34
+ 'RFC 9728 - Protected Resource Metadata',
35
+ 'RFC 8707 - Resource Indicators (Token Audience Binding)',
36
+ 'RFC 7636 - PKCE',
37
+ ],
38
+ compatibleWith: ['OpenAI Apps SDK', 'Any RFC-compliant OAuth provider'],
39
+ },
40
+ availableTools: {
41
+ public: ['get_server_info'],
42
+ protected: ['get_user_profile', 'list_resources', 'create_resource'],
43
+ },
44
+ timestamp: new Date().toISOString(),
45
+ };
46
+ }
47
+
48
+ /**
49
+ * PROTECTED TOOL - OAuth token required
50
+ * Basic authentication, no specific scopes needed
51
+ */
52
+ @Tool({
53
+ name: 'get_user_profile',
54
+ description: 'Get authenticated user profile (requires OAuth token)',
55
+ inputSchema: z.object({}),
56
+ })
57
+ @UseGuards(OAuthGuard)
58
+ async getUserProfile(args: {}, context?: ExecutionContext) {
59
+ const userId = context?.auth?.subject;
60
+ const scopes = context?.auth?.scopes || [];
61
+ const clientId = context?.auth?.clientId;
62
+
63
+ return {
64
+ message: 'User profile retrieved successfully',
65
+ user: {
66
+ id: userId,
67
+ authenticatedVia: 'OAuth 2.1',
68
+ scopes: scopes,
69
+ clientId: clientId,
70
+ },
71
+ timestamp: new Date().toISOString(),
72
+ note: 'This is a demo. In production, you would fetch real user data from your database.',
73
+ };
74
+ }
75
+
76
+ /**
77
+ * SCOPED TOOL - Requires 'read' scope
78
+ * Demonstrates fine-grained access control
79
+ */
80
+ @Tool({
81
+ name: 'list_resources',
82
+ description: 'List available resources (requires OAuth token with "read" scope)',
83
+ inputSchema: z.object({
84
+ category: z.enum(['documents', 'images', 'videos', 'all']).default('all').describe('Resource category'),
85
+ limit: z.number().min(1).max(100).default(10).describe('Maximum number of resources to return'),
86
+ }),
87
+ })
88
+ @UseGuards(OAuthGuard, createScopeGuard(['read']))
89
+ async listResources(
90
+ args: { category: string; limit: number },
91
+ context?: ExecutionContext
92
+ ) {
93
+ const userId = context?.auth?.subject;
94
+
95
+ // Generate demo resources
96
+ const resources = Array.from({ length: args.limit }, (_, i) => ({
97
+ id: `resource_${i + 1}`,
98
+ name: `${args.category === 'all' ? 'Sample' : args.category} Resource ${i + 1}`,
99
+ type: args.category === 'all' ? ['document', 'image', 'video'][i % 3] : args.category,
100
+ owner: userId,
101
+ createdAt: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000).toISOString(),
102
+ }));
103
+
104
+ return {
105
+ message: 'Resources listed successfully',
106
+ category: args.category,
107
+ count: resources.length,
108
+ resources: resources,
109
+ requiredScope: 'read',
110
+ authenticatedUser: userId,
111
+ timestamp: new Date().toISOString(),
112
+ };
113
+ }
114
+
115
+ /**
116
+ * SCOPED TOOL - Requires 'write' scope
117
+ * Demonstrates write operations with OAuth
118
+ */
119
+ @Tool({
120
+ name: 'create_resource',
121
+ description: 'Create a new resource (requires OAuth token with "write" scope)',
122
+ inputSchema: z.object({
123
+ name: z.string().describe('Resource name'),
124
+ type: z.enum(['document', 'image', 'video']).describe('Resource type'),
125
+ content: z.string().optional().describe('Resource content or description'),
126
+ }),
127
+ })
128
+ @UseGuards(OAuthGuard, createScopeGuard(['write']))
129
+ async createResource(
130
+ args: { name: string; type: string; content?: string },
131
+ context?: ExecutionContext
132
+ ) {
133
+ const userId = context?.auth?.subject;
134
+ const resourceId = `resource_${Date.now()}_${Math.random().toString(36).substring(7)}`;
135
+
136
+ return {
137
+ message: 'Resource created successfully',
138
+ resource: {
139
+ id: resourceId,
140
+ name: args.name,
141
+ type: args.type,
142
+ content: args.content || 'No content provided',
143
+ owner: userId,
144
+ createdAt: new Date().toISOString(),
145
+ },
146
+ requiredScope: 'write',
147
+ authenticatedUser: userId,
148
+ note: 'This is a demo. In production, this would persist to your database.',
149
+ timestamp: new Date().toISOString(),
150
+ };
151
+ }
152
+
153
+ /**
154
+ * MULTI-SCOPE TOOL - Requires both 'read' and 'admin' scopes
155
+ * Demonstrates multiple scope requirements
156
+ */
157
+ @Tool({
158
+ name: 'admin_statistics',
159
+ description: 'Get administrative statistics (requires OAuth token with "read" and "admin" scopes)',
160
+ inputSchema: z.object({
161
+ timeRange: z.enum(['24h', '7d', '30d', '90d']).default('7d').describe('Time range for statistics'),
162
+ }),
163
+ })
164
+ @UseGuards(OAuthGuard, createScopeGuard(['read', 'admin']))
165
+ async getAdminStatistics(
166
+ args: { timeRange: string },
167
+ context?: ExecutionContext
168
+ ) {
169
+ const userId = context?.auth?.subject;
170
+ const scopes = context?.auth?.scopes || [];
171
+
172
+ return {
173
+ message: 'Admin statistics retrieved successfully',
174
+ timeRange: args.timeRange,
175
+ statistics: {
176
+ totalUsers: Math.floor(Math.random() * 10000),
177
+ totalResources: Math.floor(Math.random() * 50000),
178
+ apiCalls: Math.floor(Math.random() * 1000000),
179
+ activeTokens: Math.floor(Math.random() * 5000),
180
+ },
181
+ requiredScopes: ['read', 'admin'],
182
+ authenticatedUser: userId,
183
+ userScopes: scopes,
184
+ timestamp: new Date().toISOString(),
185
+ note: 'This tool requires elevated permissions. Only users with admin scope can access this data.',
186
+ };
187
+ }
188
+ }
189
+
190
+
@@ -0,0 +1,133 @@
1
+ 'use client';
2
+
3
+ import { withToolData } from 'nitrostack/widgets';
4
+
5
+ /**
6
+ * Widget Metadata (stored in widget-manifest.json)
7
+ *
8
+ * This widget lists all available calculator operations.
9
+ *
10
+ * Example includes all four operations: add, subtract, multiply, divide
11
+ *
12
+ * Frontend developers: Edit widget-manifest.json to update examples
13
+ */
14
+
15
+ interface Operation {
16
+ name: string;
17
+ symbol: string;
18
+ description: string;
19
+ example: string;
20
+ }
21
+
22
+ interface OperationsData {
23
+ operations: Operation[];
24
+ }
25
+
26
+ function CalculatorOperations({ data }: { data: OperationsData }) {
27
+ const getOperationColor = (name: string) => {
28
+ const colors: Record<string, string> = {
29
+ add: '#10b981',
30
+ subtract: '#f59e0b',
31
+ multiply: '#3b82f6',
32
+ divide: '#8b5cf6'
33
+ };
34
+ return colors[name] || '#6b7280';
35
+ };
36
+
37
+ return (
38
+ <div style={{
39
+ padding: '24px',
40
+ background: 'linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)',
41
+ borderRadius: '16px',
42
+ maxWidth: '500px'
43
+ }}>
44
+ <h3 style={{
45
+ margin: '0 0 20px 0',
46
+ fontSize: '24px',
47
+ color: '#1f2937',
48
+ display: 'flex',
49
+ alignItems: 'center',
50
+ gap: '12px'
51
+ }}>
52
+ <span style={{ fontSize: '32px' }}>🔢</span>
53
+ Calculator Operations
54
+ </h3>
55
+
56
+ <div style={{ display: 'grid', gap: '12px' }}>
57
+ {data.operations.map((op) => (
58
+ <div
59
+ key={op.name}
60
+ style={{
61
+ background: 'white',
62
+ borderRadius: '12px',
63
+ padding: '16px',
64
+ boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
65
+ borderLeft: `4px solid ${getOperationColor(op.name)}`
66
+ }}
67
+ >
68
+ <div style={{
69
+ display: 'flex',
70
+ alignItems: 'center',
71
+ gap: '12px',
72
+ marginBottom: '8px'
73
+ }}>
74
+ <div style={{
75
+ width: '40px',
76
+ height: '40px',
77
+ borderRadius: '10px',
78
+ background: getOperationColor(op.name),
79
+ display: 'flex',
80
+ alignItems: 'center',
81
+ justifyContent: 'center',
82
+ color: 'white',
83
+ fontSize: '24px',
84
+ fontWeight: 'bold'
85
+ }}>
86
+ {op.symbol}
87
+ </div>
88
+ <div>
89
+ <div style={{
90
+ fontSize: '16px',
91
+ fontWeight: 'bold',
92
+ color: '#1f2937',
93
+ textTransform: 'capitalize'
94
+ }}>
95
+ {op.name}
96
+ </div>
97
+ <div style={{
98
+ fontSize: '14px',
99
+ color: '#6b7280'
100
+ }}>
101
+ {op.description}
102
+ </div>
103
+ </div>
104
+ </div>
105
+ <div style={{
106
+ marginTop: '12px',
107
+ padding: '8px 12px',
108
+ background: '#f9fafb',
109
+ borderRadius: '8px',
110
+ fontFamily: 'monospace',
111
+ fontSize: '14px',
112
+ color: '#374151'
113
+ }}>
114
+ {op.example}
115
+ </div>
116
+ </div>
117
+ ))}
118
+ </div>
119
+
120
+ <div style={{
121
+ marginTop: '16px',
122
+ textAlign: 'center',
123
+ fontSize: '12px',
124
+ color: '#6b7280'
125
+ }}>
126
+ ✨ Use the 'calculate' tool to perform these operations
127
+ </div>
128
+ </div>
129
+ );
130
+ }
131
+
132
+ export default withToolData(CalculatorOperations);
133
+