fa-mcp-sdk 0.2.144 → 0.2.174

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 (153) hide show
  1. package/README.md +1 -1
  2. package/bin/fa-mcp.js +66 -54
  3. package/cli-template/.env.example +2 -2
  4. package/cli-template/README.md +2 -2
  5. package/cli-template/fa-mcp-sdk-spec.md +122 -41
  6. package/cli-template/package.json +3 -3
  7. package/cli-template/r/TEST HTTP.xml +9 -0
  8. package/cli-template/{run/TEST SSE.run.xml → r/TEST SSE.xml } +2 -2
  9. package/cli-template/{run/TEST STDIO.run.xml → r/TEST STDIO.xml } +2 -2
  10. package/cli-template/r/generate-token.xml +14 -0
  11. package/cli-template/{run/kill-server.run.xml → r/kill-server.xml} +2 -2
  12. package/cli-template/{run/kill-token-gen-server.xml → r/remove-nul.xml} +4 -5
  13. package/{cli-template/config → config}/_local.yaml +28 -14
  14. package/{cli-template/config → config}/custom-environment-variables.yaml +3 -0
  15. package/{cli-template/config → config}/default.yaml +50 -10
  16. package/{cli-template/config → config}/development.yaml +4 -4
  17. package/config/local.yaml +81 -0
  18. package/{cli-template/config → config}/production.yaml +4 -4
  19. package/dist/core/_types_/active-directory-config.d.ts +3 -0
  20. package/dist/core/_types_/active-directory-config.d.ts.map +1 -1
  21. package/dist/core/_types_/config.d.ts +5 -1
  22. package/dist/core/_types_/config.d.ts.map +1 -1
  23. package/dist/core/_types_/types.d.ts +5 -1
  24. package/dist/core/_types_/types.d.ts.map +1 -1
  25. package/dist/core/ad/group-checker.d.ts +13 -0
  26. package/dist/core/ad/group-checker.d.ts.map +1 -0
  27. package/dist/core/ad/group-checker.js +86 -0
  28. package/dist/core/ad/group-checker.js.map +1 -0
  29. package/dist/core/auth/admin-auth.d.ts +16 -0
  30. package/dist/core/auth/admin-auth.d.ts.map +1 -0
  31. package/dist/core/auth/admin-auth.js +159 -0
  32. package/dist/core/auth/admin-auth.js.map +1 -0
  33. package/dist/core/auth/basic.d.ts +6 -0
  34. package/dist/core/auth/basic.d.ts.map +1 -0
  35. package/dist/core/auth/basic.js +26 -0
  36. package/dist/core/auth/basic.js.map +1 -0
  37. package/dist/core/auth/{jwt-validation.d.ts → jwt.d.ts} +4 -3
  38. package/dist/core/auth/jwt.d.ts.map +1 -0
  39. package/dist/core/auth/{jwt-validation.js → jwt.js} +9 -19
  40. package/dist/core/auth/jwt.js.map +1 -0
  41. package/dist/core/auth/middleware.d.ts.map +1 -1
  42. package/dist/core/auth/middleware.js +3 -3
  43. package/dist/core/auth/middleware.js.map +1 -1
  44. package/dist/core/auth/multi-auth.d.ts +14 -6
  45. package/dist/core/auth/multi-auth.d.ts.map +1 -1
  46. package/dist/core/auth/multi-auth.js +151 -141
  47. package/dist/core/auth/multi-auth.js.map +1 -1
  48. package/dist/core/auth/permanent.d.ts +6 -0
  49. package/dist/core/auth/permanent.d.ts.map +1 -0
  50. package/dist/core/auth/permanent.js +15 -0
  51. package/dist/core/auth/permanent.js.map +1 -0
  52. package/dist/core/auth/token-generator/ntlm/ntlm-domain-config.d.ts +1 -1
  53. package/dist/core/auth/token-generator/ntlm/ntlm-domain-config.d.ts.map +1 -1
  54. package/dist/core/auth/token-generator/ntlm/ntlm-domain-config.js +8 -10
  55. package/dist/core/auth/token-generator/ntlm/ntlm-domain-config.js.map +1 -1
  56. package/dist/core/auth/token-generator/ntlm/ntlm-integration.d.ts.map +1 -1
  57. package/dist/core/auth/token-generator/ntlm/ntlm-integration.js +9 -2
  58. package/dist/core/auth/token-generator/ntlm/ntlm-integration.js.map +1 -1
  59. package/dist/core/auth/token-generator/server.d.ts.map +1 -1
  60. package/dist/core/auth/token-generator/server.js +59 -25
  61. package/dist/core/auth/token-generator/server.js.map +1 -1
  62. package/dist/core/auth/types.d.ts +4 -3
  63. package/dist/core/auth/types.d.ts.map +1 -1
  64. package/dist/core/bootstrap/startup-info.d.ts.map +1 -1
  65. package/dist/core/bootstrap/startup-info.js +19 -0
  66. package/dist/core/bootstrap/startup-info.js.map +1 -1
  67. package/dist/core/consul/access-points-updater.js +1 -1
  68. package/dist/core/consul/access-points-updater.js.map +1 -1
  69. package/dist/core/consul/get-consul-api.d.ts +1 -1
  70. package/dist/core/consul/get-consul-api.d.ts.map +1 -1
  71. package/dist/core/consul/get-consul-api.js +1 -1
  72. package/dist/core/consul/get-consul-api.js.map +1 -1
  73. package/dist/core/consul/register.d.ts +1 -1
  74. package/dist/core/consul/register.d.ts.map +1 -1
  75. package/dist/core/index.d.ts +3 -1
  76. package/dist/core/index.d.ts.map +1 -1
  77. package/dist/core/index.js +3 -1
  78. package/dist/core/index.js.map +1 -1
  79. package/dist/core/init-mcp-server.d.ts.map +1 -1
  80. package/dist/core/init-mcp-server.js +1 -1
  81. package/dist/core/init-mcp-server.js.map +1 -1
  82. package/dist/core/utils/testing/McpSseClient.js.map +1 -1
  83. package/dist/core/web/admin-router.d.ts +10 -0
  84. package/dist/core/web/admin-router.d.ts.map +1 -0
  85. package/dist/core/web/admin-router.js +227 -0
  86. package/dist/core/web/admin-router.js.map +1 -0
  87. package/dist/core/web/favicon-svg.d.ts +1 -1
  88. package/dist/core/web/favicon-svg.d.ts.map +1 -1
  89. package/dist/core/web/favicon-svg.js +21 -3
  90. package/dist/core/web/favicon-svg.js.map +1 -1
  91. package/dist/core/web/home-api.d.ts +7 -0
  92. package/dist/core/web/home-api.d.ts.map +1 -0
  93. package/dist/core/web/home-api.js +93 -0
  94. package/dist/core/web/home-api.js.map +1 -0
  95. package/dist/core/web/server-http.d.ts +1 -0
  96. package/dist/core/web/server-http.d.ts.map +1 -1
  97. package/dist/core/web/server-http.js +60 -25
  98. package/dist/core/web/server-http.js.map +1 -1
  99. package/dist/core/web/static/home/index.html +206 -0
  100. package/dist/core/web/static/home/script.js +636 -0
  101. package/dist/core/web/{about-page/css.js → static/styles.css} +435 -105
  102. package/dist/core/web/static/token-gen/index.html +82 -0
  103. package/dist/core/web/static/token-gen/jwt-icon.svg +3 -0
  104. package/dist/core/web/static/token-gen/logout.svg +4 -0
  105. package/dist/core/web/static/token-gen/script.js +365 -0
  106. package/dist/core/web/static/token-gen/user.svg +4 -0
  107. package/dist/core/web/svg-icons.d.ts +7 -0
  108. package/dist/core/web/svg-icons.d.ts.map +1 -0
  109. package/dist/core/web/svg-icons.js +78 -0
  110. package/dist/core/web/svg-icons.js.map +1 -0
  111. package/package.json +7 -3
  112. package/scripts/copy-static.js +31 -0
  113. package/src/template/_examples/multi-auth-examples.ts +14 -47
  114. package/src/template/_types_/custom-config.ts +83 -0
  115. package/src/template/asset/logo.svg +4 -0
  116. package/src/template/start.ts +3 -3
  117. package/src/template/tools/handle-tool-call.ts +2 -1
  118. package/src/tests/mcp/test-http.js +10 -2
  119. package/src/tests/mcp/test-sse.js +10 -2
  120. package/src/tests/mcp/test-stdio.js +1 -2
  121. package/cli-template/run/TEST HTTP.run.xml +0 -5
  122. package/cli-template/run/TEST search.run.xml +0 -11
  123. package/cli-template/run/remove-nul.js.run.xml +0 -5
  124. package/dist/core/auth/jwt-validation.d.ts.map +0 -1
  125. package/dist/core/auth/jwt-validation.js.map +0 -1
  126. package/dist/core/auth/token-generator/html.d.ts +0 -9
  127. package/dist/core/auth/token-generator/html.d.ts.map +0 -1
  128. package/dist/core/auth/token-generator/html.js +0 -862
  129. package/dist/core/auth/token-generator/html.js.map +0 -1
  130. package/dist/core/web/about-page/css.d.ts +0 -2
  131. package/dist/core/web/about-page/css.d.ts.map +0 -1
  132. package/dist/core/web/about-page/css.js.map +0 -1
  133. package/dist/core/web/about-page/render.d.ts +0 -2
  134. package/dist/core/web/about-page/render.d.ts.map +0 -1
  135. package/dist/core/web/about-page/render.js +0 -773
  136. package/dist/core/web/about-page/render.js.map +0 -1
  137. /package/cli-template/{run/== START ==.run.xml → r/== START ==.xml} +0 -0
  138. /package/cli-template/{run/cb.run.xml → r/cb.xml} +0 -0
  139. /package/cli-template/{run/ci.run.xml → r/ci.xml} +0 -0
  140. /package/cli-template/{run/lint.run.xml → r/lint.xml} +0 -0
  141. /package/cli-template/{run/lint_fix.run.xml → r/lint_fix.xml} +0 -0
  142. /package/cli-template/{run/reinstall.run.xml → r/reinstall.xml} +0 -0
  143. /package/{cli-template/config → config}/test.yaml +0 -0
  144. /package/{src/template/asset/favicon.svg → dist/core/web/static/logo.svg} +0 -0
  145. /package/{cli-template/scripts → scripts}/kill-port.js +0 -0
  146. /package/{cli-template/scripts → scripts}/npm/patch_node_modules.js +0 -0
  147. /package/{cli-template/scripts → scripts}/npm/run.js +0 -0
  148. /package/{cli-template/scripts → scripts}/npm/yarn-ci.ps1 +0 -0
  149. /package/{cli-template/scripts → scripts}/npm/yarn-ci.sh +0 -0
  150. /package/{cli-template/scripts → scripts}/npm/yarn-reinstall.ps1 +0 -0
  151. /package/{cli-template/scripts → scripts}/npm/yarn-reinstall.sh +0 -0
  152. /package/{cli-template/scripts → scripts}/pre-commit +0 -0
  153. /package/{cli-template/scripts → scripts}/remove-nul.js +0 -0
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Admin panel router - Token Generator & Validator
3
+ * Endpoints for JWT token generation and validation
4
+ */
5
+ import { Router } from 'express';
6
+ import chalk from 'chalk';
7
+ import { dirname, join } from 'path';
8
+ import { fileURLToPath } from 'url';
9
+ import { appConfig } from '../bootstrap/init-config.js';
10
+ import { checkJwtToken, generateToken } from '../auth/jwt.js';
11
+ import { isNTLMEnabled } from '../auth/token-generator/ntlm/ntlm-domain-config.js';
12
+ import { getSessionStats } from '../auth/token-generator/ntlm/ntlm-session-storage.js';
13
+ import { getLoginPageHTML } from '../auth/token-generator/ntlm/ntlm-templates.js';
14
+ import { createAdminAuthMW } from '../auth/admin-auth.js';
15
+ import { logger as lgr } from '../logger.js';
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = dirname(__filename);
18
+ // Path to static files (relative to compiled JS location in dist/)
19
+ const staticPath = join(__dirname, 'static/token-gen');
20
+ const logger = lgr.getSubLogger({ name: chalk.bgCyan('admin-router') });
21
+ const timeToSeconds = {
22
+ minutes: 60,
23
+ hours: 60 * 60,
24
+ days: 60 * 60 * 24,
25
+ months: 60 * 60 * 24 * 30,
26
+ years: 60 * 60 * 24 * 365,
27
+ };
28
+ const { adminAuth } = appConfig.webServer || {};
29
+ const adminAuthType = adminAuth?.enabled === true ? adminAuth.type : undefined;
30
+ const ntlmEnabled = adminAuthType === 'ntlm' && isNTLMEnabled;
31
+ /**
32
+ * Creates admin router with all token generation endpoints
33
+ */
34
+ export function createAdminRouter() {
35
+ const router = Router();
36
+ // Apply admin authentication middleware to all admin routes
37
+ const adminAuthMW = createAdminAuthMW();
38
+ router.use(adminAuthMW);
39
+ // Note: Static files (CSS, JS) are served globally at /static/token-gen/ by server-http.ts
40
+ // Main admin page - Token Generator UI
41
+ router.get('/', (req, res) => {
42
+ const username = req.ntlm?.username || 'Unknown';
43
+ const domain = req.ntlm?.domain || 'Unknown';
44
+ const isAuthenticated = req.ntlm?.isAuthenticated || false;
45
+ logger.info(`Admin page accessed by: ${domain}\\${username} (Authenticated: ${isAuthenticated})`);
46
+ // Serve static index.html
47
+ res.sendFile(join(staticPath, 'index.html'));
48
+ });
49
+ // Login page (for NTLM)
50
+ router.get('/login', (req, res) => {
51
+ res.send(getLoginPageHTML(req.ntlm?.username || ''));
52
+ });
53
+ // Logout endpoint
54
+ router.get('/logout', (req, res) => {
55
+ logger.info(`Logout requested by: ${req.ntlm?.domain || 'Unknown'}\\${req.ntlm?.username || 'Unknown'}`);
56
+ if (adminAuthType === 'ntlm') {
57
+ // NTLM logout - send 401 to trigger browser auth prompt
58
+ res.setHeader('WWW-Authenticate', 'NTLM');
59
+ res.setHeader('Clear-Site-Data', '"cookies", "storage"');
60
+ return res.status(401).send('Authentication required - please login again');
61
+ }
62
+ // For other auth types, just clear the session indication
63
+ res.setHeader('Clear-Site-Data', '"cookies", "storage"');
64
+ return res.status(401).json({
65
+ success: true,
66
+ message: 'Logged out successfully',
67
+ });
68
+ });
69
+ // Debug endpoint for session stats (development only)
70
+ router.get('/debug/sessions', (req, res) => {
71
+ if (process.env.NODE_ENV === 'production') {
72
+ return res.status(404).json({ error: 'Not available in production' });
73
+ }
74
+ const stats = getSessionStats();
75
+ return res.json({
76
+ message: 'Admin Panel Session Statistics',
77
+ timestamp: new Date().toISOString(),
78
+ ...stats,
79
+ });
80
+ });
81
+ // API: Generate token
82
+ router.post('/api/generate-token', (req, res) => {
83
+ try {
84
+ const username = req.ntlm?.username || 'Unknown';
85
+ const domain = req.ntlm?.domain || 'Unknown';
86
+ const authenticatedUser = `${domain}\\${username}`;
87
+ const { user, timeValue, timeUnit, payload } = req.body;
88
+ if (!user || !timeValue || !timeUnit) {
89
+ logger.info(`Token generation failed (missing parameters) by: ${authenticatedUser}`);
90
+ return res.json({
91
+ success: false,
92
+ error: 'Need to fill in the user and token lifetime',
93
+ });
94
+ }
95
+ const multiplier = timeToSeconds[timeUnit];
96
+ if (!multiplier) {
97
+ logger.info(`Token generation failed (invalid time unit) by: ${authenticatedUser}`);
98
+ return res.json({
99
+ success: false,
100
+ error: 'Invalid Time Unit',
101
+ });
102
+ }
103
+ const liveTimeSec = timeValue * multiplier;
104
+ const token = generateToken(user, liveTimeSec, payload || {});
105
+ logger.info(`Generated token for user: ${user}, duration: ${timeValue} ${timeUnit}, requested by: ${authenticatedUser}`);
106
+ return res.json({
107
+ success: true,
108
+ token: token,
109
+ });
110
+ }
111
+ catch (error) {
112
+ const username = req.ntlm?.username || 'Unknown';
113
+ const domain = req.ntlm?.domain || 'Unknown';
114
+ logger.error(`Error generating token for ${domain}\\${username}:`, error);
115
+ return res.json({
116
+ success: false,
117
+ error: error.message,
118
+ });
119
+ }
120
+ });
121
+ // API: Validate token
122
+ router.post('/api/validate-token', (req, res) => {
123
+ try {
124
+ const username = req.ntlm?.username || 'Unknown';
125
+ const domain = req.ntlm?.domain || 'Unknown';
126
+ const authenticatedUser = `${domain}\\${username}`;
127
+ const { token } = req.body;
128
+ if (!token) {
129
+ logger.info(`Token validation failed (no token provided) by: ${authenticatedUser}`);
130
+ return res.json({
131
+ success: false,
132
+ error: 'Token Not Transferred',
133
+ });
134
+ }
135
+ const result = checkJwtToken({ token });
136
+ if (result.errorReason) {
137
+ logger.info(`Token validation failed (${result.errorReason}) by: ${authenticatedUser}`);
138
+ return res.json({
139
+ success: false,
140
+ error: result.errorReason,
141
+ });
142
+ }
143
+ logger.info(`Token validated successfully for user: ${result.payload?.user}, requested by: ${authenticatedUser}`);
144
+ return res.json({
145
+ success: true,
146
+ payload: result.payload,
147
+ });
148
+ }
149
+ catch (error) {
150
+ const username = req.ntlm?.username || 'Unknown';
151
+ const domain = req.ntlm?.domain || 'Unknown';
152
+ logger.error(`Error validating token for ${domain}\\${username}:`, error);
153
+ return res.json({
154
+ success: false,
155
+ error: error.message,
156
+ });
157
+ }
158
+ });
159
+ // API: Service info
160
+ router.get('/api/service-info', (req, res) => {
161
+ try {
162
+ const username = req.ntlm?.username || 'Unknown';
163
+ const domain = req.ntlm?.domain || 'Unknown';
164
+ const isAuthenticated = req.ntlm?.isAuthenticated || false;
165
+ logger.info(`Service info requested by: ${domain}\\${username}`);
166
+ res.json({
167
+ success: true,
168
+ serviceName: appConfig.name,
169
+ primaryColor: appConfig.uiColor.primary,
170
+ authenticatedUser: `${domain}\\${username}`,
171
+ isAuthenticated,
172
+ authType: adminAuthType,
173
+ ntlmEnabled,
174
+ timestamp: new Date().toISOString(),
175
+ });
176
+ }
177
+ catch (error) {
178
+ const username = req.ntlm?.username || 'Unknown';
179
+ const domain = req.ntlm?.domain || 'Unknown';
180
+ logger.error(`Error getting service info for ${domain}\\${username}:`, error);
181
+ res.json({
182
+ success: false,
183
+ error: error.message,
184
+ serviceName: appConfig.name,
185
+ authType: adminAuthType,
186
+ ntlmEnabled,
187
+ });
188
+ }
189
+ });
190
+ // API: Auth status
191
+ router.get('/api/auth-status', (req, res) => {
192
+ try {
193
+ const username = req.ntlm?.username || 'Unknown';
194
+ const domain = req.ntlm?.domain || 'Unknown';
195
+ const isAuthenticated = req.ntlm?.isAuthenticated || false;
196
+ // Determine if logout is available (only for basic and ntlm)
197
+ const canLogout = isAuthenticated && (adminAuthType === 'basic' || adminAuthType === 'ntlm');
198
+ // Format user display based on auth type
199
+ let userDisplay = null;
200
+ if (isAuthenticated) {
201
+ if (adminAuthType === 'ntlm') {
202
+ userDisplay = `${domain}\\${username}`;
203
+ }
204
+ else {
205
+ userDisplay = username;
206
+ }
207
+ }
208
+ res.json({
209
+ success: true,
210
+ authType: adminAuthType || null,
211
+ isAuthenticated,
212
+ user: userDisplay,
213
+ canLogout,
214
+ timestamp: new Date().toISOString(),
215
+ });
216
+ }
217
+ catch (error) {
218
+ res.json({
219
+ success: false,
220
+ error: error.message,
221
+ authType: adminAuthType || null,
222
+ });
223
+ }
224
+ });
225
+ return router;
226
+ }
227
+ //# sourceMappingURL=admin-router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-router.js","sourceRoot":"","sources":["../../../src/core/web/admin-router.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { RequestHandler } from 'express';
2
- export declare const getFaviconSvg: () => string;
2
+ export declare const getLogoSvg: () => string;
3
3
  /**
4
4
  * Serves the favicon located by the given `path`.
5
5
  */
@@ -1 +1 @@
1
- {"version":3,"file":"favicon-svg.d.ts","sourceRoot":"","sources":["../../../src/core/web/favicon-svg.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmC,cAAc,EAAE,MAAM,SAAS,CAAC;AAgB1E,eAAO,MAAM,aAAa,QAAO,MAIhC,CAAC;AAGF;;GAEG;AACH,eAAO,MAAM,UAAU,QAAO,cAyB7B,CAAC"}
1
+ {"version":3,"file":"favicon-svg.d.ts","sourceRoot":"","sources":["../../../src/core/web/favicon-svg.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmC,cAAc,EAAE,MAAM,SAAS,CAAC;AAoC1E,eAAO,MAAM,UAAU,QAAO,MAI7B,CAAC;AAGF;;GAEG;AACH,eAAO,MAAM,UAAU,QAAO,cAyB7B,CAAC"}
@@ -1,6 +1,24 @@
1
1
  import crypto from 'crypto';
2
+ import { readFileSync } from 'fs';
3
+ import { dirname, join } from 'path';
4
+ import { fileURLToPath } from 'url';
2
5
  import { config, getProjectData } from '../bootstrap/init-config.js';
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
3
8
  const ONE_YEAR_MS = 60 * 60 * 24 * 365 * 1000; // 1 year
9
+ // Default logo SVG (loaded from static/logo.svg)
10
+ let defaultLogoSvg = null;
11
+ const loadDefaultLogo = () => {
12
+ if (defaultLogoSvg === null) {
13
+ try {
14
+ defaultLogoSvg = readFileSync(join(__dirname, 'static/logo.svg'), 'utf-8');
15
+ }
16
+ catch {
17
+ defaultLogoSvg = '<svg><!-- No logo provided --></svg>';
18
+ }
19
+ }
20
+ return defaultLogoSvg;
21
+ };
4
22
  const etagS = (entity) => {
5
23
  // compute hash of entity
6
24
  const hash = crypto
@@ -10,9 +28,9 @@ const etagS = (entity) => {
10
28
  .substring(0, 27);
11
29
  return `"${Buffer.byteLength(entity, 'utf8').toString(16)}-${hash}"`;
12
30
  };
13
- export const getFaviconSvg = () => {
31
+ export const getLogoSvg = () => {
14
32
  const { assets } = getProjectData();
15
- let svg = assets?.favicon || '<svg><!-- No favicon provided --></svg>';
33
+ let svg = assets?.logoSvg || loadDefaultLogo();
16
34
  return svg.replace('fill="currentColor"', `fill="${config.uiColor.primary}"`);
17
35
  };
18
36
  /**
@@ -32,7 +50,7 @@ export const faviconSvg = () => {
32
50
  return;
33
51
  }
34
52
  // Lazy load SVG when needed
35
- const svg = getFaviconSvg();
53
+ const svg = getLogoSvg();
36
54
  res.setHeader('Cache-Control', `public, max-age=${Math.floor(ONE_YEAR_MS / 1000)}`);
37
55
  res.setHeader('ETag', etagS(svg));
38
56
  res.setHeader('Content-Length', svg.length);
@@ -1 +1 @@
1
- {"version":3,"file":"favicon-svg.js","sourceRoot":"","sources":["../../../src/core/web/favicon-svg.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAErE,MAAM,WAAW,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC,SAAS;AAExD,MAAM,KAAK,GAAG,CAAC,MAAc,EAAU,EAAE;IACvC,yBAAyB;IACzB,MAAM,IAAI,GAAG,MAAM;SAChB,UAAU,CAAC,MAAM,CAAC;SAClB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;SACtB,MAAM,CAAC,QAAQ,CAAC;SAChB,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACpB,OAAO,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,IAAI,GAAG,CAAC;AACvE,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,GAAW,EAAE;IACxC,MAAM,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;IACpC,IAAI,GAAG,GAAW,MAAM,EAAE,OAAO,IAAI,yCAAyC,CAAC;IAC/E,OAAO,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,SAAS,MAAM,CAAC,OAAO,CAAC,OAAO,GAAG,CAAC,CAAC;AAChF,CAAC,CAAC;AAGF;;GAEG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,GAAmB,EAAE;IAC7C,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACzD,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACrC,IAAI,EAAE,CAAC;YACP,OAAO;QACT,CAAC;QAED,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAClD,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;YACtD,GAAG,CAAC,SAAS,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC;YAC7C,GAAG,CAAC,SAAS,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC;YACrC,GAAG,CAAC,GAAG,EAAE,CAAC;YACV,OAAO;QACT,CAAC;QAED,4BAA4B;QAC5B,MAAM,GAAG,GAAG,aAAa,EAAE,CAAC;QAE5B,GAAG,CAAC,SAAS,CAAC,eAAe,EAAE,mBAAmB,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;QACpF,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QAClC,GAAG,CAAC,SAAS,CAAC,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5C,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,eAAe,CAAC,CAAC;QAC/C,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC;QACrB,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACxB,CAAC,CAAC;AACJ,CAAC,CAAC"}
1
+ {"version":3,"file":"favicon-svg.js","sourceRoot":"","sources":["../../../src/core/web/favicon-svg.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Home page API endpoint
3
+ * Returns all dynamic data needed for the home page
4
+ */
5
+ import { Request, Response } from 'express';
6
+ export declare function handleHomeInfo(_req: Request, res: Response): Promise<void>;
7
+ //# sourceMappingURL=home-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"home-api.d.ts","sourceRoot":"","sources":["../../../src/core/web/home-api.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAyB5C,wBAAsB,cAAc,CAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAqEjF"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Home page API endpoint
3
+ * Returns all dynamic data needed for the home page
4
+ */
5
+ import { getResourcesList } from '../mcp/resources.js';
6
+ import { getPromptsList } from '../mcp/prompts.js';
7
+ import { getMainDBConnectionStatus } from '../db/pg-db.js';
8
+ import { getLogoSvg } from './favicon-svg.js';
9
+ import { appConfig, getProjectData } from '../bootstrap/init-config.js';
10
+ const startTime = new Date();
11
+ const getUptime = () => {
12
+ const uptimeMs = Date.now() - startTime.getTime();
13
+ const uptimeSeconds = Math.floor(uptimeMs / 1000);
14
+ const hours = Math.floor(uptimeSeconds / 3600);
15
+ const minutes = Math.floor((uptimeSeconds % 3600) / 60);
16
+ const seconds = uptimeSeconds % 60;
17
+ if (hours > 0) {
18
+ return `${hours}h ${minutes}m ${seconds}s`;
19
+ }
20
+ else if (minutes > 0) {
21
+ return `${minutes}m ${seconds}s`;
22
+ }
23
+ else {
24
+ return `${seconds}s`;
25
+ }
26
+ };
27
+ export async function handleHomeInfo(_req, res) {
28
+ try {
29
+ const { version, repo } = appConfig;
30
+ const serviceTitle = appConfig.productName.replace(/MCP/i, '').replace(/\s{2,}/g, ' ').trim();
31
+ const logoSvg = getLogoSvg();
32
+ const { resources } = getResourcesList();
33
+ const { prompts } = getPromptsList();
34
+ const { tools, httpComponents } = global.__MCP_PROJECT_DATA__;
35
+ const { getConsulUIAddress = (_s) => '', assets } = getProjectData();
36
+ // Build footer HTML
37
+ const footerParts = [];
38
+ if (repo) {
39
+ footerParts.push(`<a href="${repo}" target="_blank" rel="noopener">GitHub Repository</a>`);
40
+ }
41
+ if (assets?.maintainerHtml) {
42
+ footerParts.push(assets.maintainerHtml);
43
+ }
44
+ // Database info
45
+ let db = null;
46
+ if (appConfig.isMainDBUsed) {
47
+ const dbStatus = await getMainDBConnectionStatus();
48
+ const { host, port, database } = appConfig.db.postgres.dbs.main;
49
+ db = {
50
+ connection: `${host}:${port}/${database}`,
51
+ status: dbStatus,
52
+ };
53
+ }
54
+ // Consul info
55
+ let consul = null;
56
+ if (appConfig.consul.service.enable) {
57
+ const { id } = appConfig.consul.service;
58
+ if (id) {
59
+ consul = {
60
+ id,
61
+ url: getConsulUIAddress(id),
62
+ };
63
+ }
64
+ }
65
+ const response = {
66
+ serviceTitle,
67
+ description: appConfig.description,
68
+ version,
69
+ uptime: getUptime(),
70
+ primaryColor: appConfig.uiColor.primary,
71
+ logoSvg,
72
+ toolsCount: tools.length,
73
+ resourcesCount: resources.length,
74
+ promptsCount: prompts.length,
75
+ tools,
76
+ resources,
77
+ prompts,
78
+ db,
79
+ swagger: !!httpComponents?.swagger,
80
+ consul,
81
+ repo,
82
+ footer: footerParts.join(' • '),
83
+ };
84
+ res.json(response);
85
+ }
86
+ catch (error) {
87
+ res.status(500).json({
88
+ error: 'Failed to get home info',
89
+ message: error.message,
90
+ });
91
+ }
92
+ }
93
+ //# sourceMappingURL=home-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"home-api.js","sourceRoot":"","sources":["../../../src/core/web/home-api.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ export declare const isAdminEnabled: boolean;
1
2
  /**
2
3
  * Start HTTP server with SSE transport
3
4
  */
@@ -1 +1 @@
1
- {"version":3,"file":"server-http.d.ts","sourceRoot":"","sources":["../../../src/core/web/server-http.ts"],"names":[],"mappings":"AAgEA;;GAEG;AACH,wBAAsB,eAAe,IAAK,OAAO,CAAC,IAAI,CAAC,CAgYtD"}
1
+ {"version":3,"file":"server-http.d.ts","sourceRoot":"","sources":["../../../src/core/web/server-http.ts"],"names":[],"mappings":"AAqCA,eAAO,MAAM,cAAc,SAA8B,CAAC;AAyC1D;;GAEG;AACH,wBAAsB,eAAe,IAAK,OAAO,CAAC,IAAI,CAAC,CA+ZtD"}
@@ -3,6 +3,8 @@ import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
3
3
  import express from 'express';
4
4
  import helmet from 'helmet';
5
5
  import { RateLimiterMemory } from 'rate-limiter-flexible';
6
+ import { dirname, join } from 'path';
7
+ import { fileURLToPath } from 'url';
6
8
  import { appConfig, getProjectData } from '../bootstrap/init-config.js';
7
9
  import { getResource, getResourcesList } from '../mcp/resources.js';
8
10
  import { createAuthMW } from '../auth/middleware.js';
@@ -15,10 +17,19 @@ import { applyCors } from './cors.js';
15
17
  import { faviconSvg } from './favicon-svg.js';
16
18
  import chalk from 'chalk';
17
19
  import { getPrompt, getPromptsList } from '../mcp/prompts.js';
18
- import { renderAboutPage } from './about-page/render.js';
20
+ import { handleHomeInfo } from './home-api.js';
19
21
  import { getMainDBConnectionStatus } from '../db/pg-db.js';
20
22
  import { normalizeHeaders } from '../utils/utils.js';
23
+ import { createAdminRouter } from './admin-router.js';
24
+ import { validateAdminAuthConfig } from '../auth/admin-auth.js';
25
+ import { createSvgRouter } from './svg-icons.js';
26
+ const __filename = fileURLToPath(import.meta.url);
27
+ const __dirname = dirname(__filename);
28
+ // Path to static files
29
+ const staticPath = join(__dirname, 'static');
21
30
  const logger = lgr.getSubLogger({ name: chalk.bgYellow('server-http') });
31
+ const { adminAuth } = appConfig.webServer || {};
32
+ export const isAdminEnabled = adminAuth?.enabled === true;
22
33
  /**
23
34
  * Handle rate limiting with consistent error response
24
35
  */
@@ -71,16 +82,15 @@ export async function startHttpServer() {
71
82
  app.use(express.urlencoded({ extended: true, limit: '10mb' }));
72
83
  applyCors(app);
73
84
  app.use(faviconSvg());
74
- // Root endpoint with About page
75
- app.get('/', async (req, res) => {
76
- try {
77
- const html = await renderAboutPage();
78
- res.type('html').send(html);
79
- }
80
- catch (error) {
81
- logger.error('Failed to render about page:', error);
82
- res.status(500).send('Error rendering about page');
83
- }
85
+ // Serve static files (CSS, JS, SVG)
86
+ app.use('/static', express.static(staticPath));
87
+ // SVG icons with color substitution
88
+ app.use('/svg', createSvgRouter());
89
+ // Home page API endpoint
90
+ app.get('/api/home-info', handleHomeInfo);
91
+ // Root endpoint - serve static Home page
92
+ app.get('/', (req, res) => {
93
+ res.sendFile(join(staticPath, 'home', 'index.html'));
84
94
  });
85
95
  // Health check endpoint
86
96
  app.get('/health', async (req, res) => {
@@ -112,21 +122,33 @@ export async function startHttpServer() {
112
122
  if (apiRouter) {
113
123
  app.use('/api', apiRouter);
114
124
  }
125
+ // Admin panel routes (Token Generator & Validator)
126
+ if (isAdminEnabled) {
127
+ const adminConfigError = validateAdminAuthConfig();
128
+ if (adminConfigError) {
129
+ logger.error(`Admin auth configuration error: ${adminConfigError}`);
130
+ throw new Error(`Admin auth configuration error: ${adminConfigError}`);
131
+ }
132
+ const adminRouter = createAdminRouter();
133
+ app.use('/admin', adminRouter);
134
+ logger.info('Admin panel mounted at /admin');
135
+ }
115
136
  // SSE endpoints for legacy MCP communication
116
- // Store SSE transports by session ID with transport, server, and preserved headers
137
+ // Store SSE transports by session ID with transport, server, preserved headers, and auth payload
117
138
  const sseTransports = new Map();
118
- // Create SSE server instance with preserved headers from connection establishment
119
- async function createSseServer(preservedHeaders) {
139
+ // Create SSE server instance with preserved headers and auth payload from connection establishment
140
+ async function createSseServer(preservedHeaders, authPayload) {
120
141
  const sseServer = createMcpServer();
121
- // Override the tool call handler to include rate limiting and preserved headers
142
+ // Override the tool call handler to include rate limiting, preserved headers and auth payload
122
143
  sseServer.setRequestHandler(CallToolRequestSchema, async (request) => {
123
144
  // Apply rate limiting for each SSE tool call
124
145
  const toolCallClientId = 'sse-tool-unknown';
125
146
  await handleRateLimit(rateLimiter, toolCallClientId, 'unknown', `SSE tool call | tool: ${request.params.name}`);
126
- // Execute the tool call with preserved headers from SSE connection establishment
147
+ // Execute the tool call with preserved headers and payload from SSE connection establishment
127
148
  const result = await toolHandler({
128
149
  ...request.params,
129
- headers: preservedHeaders // Use headers from when SSE connection was established
150
+ headers: preservedHeaders, // Use headers from when SSE connection was established
151
+ payload: authPayload // Use auth payload from when SSE connection was established
130
152
  });
131
153
  return {
132
154
  content: result.content,
@@ -144,15 +166,19 @@ export async function startHttpServer() {
144
166
  // Preserve normalized headers from SSE connection establishment
145
167
  const preservedHeaders = normalizeHeaders(req.headers);
146
168
  logger.debug('SSE connection headers preserved:', Object.keys(preservedHeaders));
169
+ // Extract auth payload from middleware (set by authMW)
170
+ const authInfo = req.authInfo;
171
+ const authPayload = authInfo?.payload;
147
172
  // Create SSE transport that will use the same endpoint for POST requests
148
173
  const transport = new SSEServerTransport('/sse', res);
149
- // Create a dedicated server instance with preserved headers for this SSE connection
150
- const sseServer = await createSseServer(preservedHeaders);
151
- // Store transport, server, and headers for cleanup and reference
174
+ // Create a dedicated server instance with preserved headers and auth payload for this SSE connection
175
+ const sseServer = await createSseServer(preservedHeaders, authPayload);
176
+ // Store transport, server, headers, and payload for cleanup and reference
152
177
  sseTransports.set(transport.sessionId, {
153
178
  transport,
154
179
  server: sseServer,
155
- headers: preservedHeaders
180
+ headers: preservedHeaders,
181
+ payload: authPayload
156
182
  });
157
183
  // Clean up transport and server on connection close
158
184
  res.on('close', () => {
@@ -275,9 +301,12 @@ export async function startHttpServer() {
275
301
  // Apply rate limiting for tool calls
276
302
  const toolCallClientId = `tool-${req.ip || 'unknown'}`;
277
303
  await handleRateLimit(rateLimiter, toolCallClientId, req.ip || 'unknown', `tool call | tool: ${params?.name || 'unknown'}`, res, id);
304
+ // Extract auth payload from middleware (set by authMW)
305
+ const mcpAuthPayload = req.authInfo?.payload;
278
306
  result = await toolHandler({
279
307
  ...params,
280
- headers: normalizeHeaders(req.headers)
308
+ headers: normalizeHeaders(req.headers),
309
+ payload: mcpAuthPayload
281
310
  });
282
311
  break;
283
312
  case 'prompts/list':
@@ -345,7 +374,7 @@ export async function startHttpServer() {
345
374
  // 404 handler for unknown routes
346
375
  app.use((req, res) => {
347
376
  const availableEndpoints = {
348
- about: 'GET /',
377
+ home: 'GET /',
349
378
  health: 'GET /health',
350
379
  sse: 'GET /sse, POST /sse',
351
380
  messages: 'POST /messages',
@@ -354,6 +383,9 @@ export async function startHttpServer() {
354
383
  if (swagger) {
355
384
  availableEndpoints.docs = 'GET /docs';
356
385
  }
386
+ if (isAdminEnabled) {
387
+ availableEndpoints.admin = 'GET /admin';
388
+ }
357
389
  Object.assign(availableEndpoints, {
358
390
  ...(httpComponents?.endpointsOn404 || {}),
359
391
  });
@@ -373,8 +405,11 @@ export async function startHttpServer() {
373
405
  // Start HTTP server
374
406
  const port = appConfig.webServer.port;
375
407
  app.listen(port, '0.0.0.0', () => {
376
- const msg = `${chalk.magenta(appConfig.productName)} started with ${chalk.blue('HTTP')} transport on port ${chalk.blue(port)}
377
- About page: http://localhost:${port}/`;
408
+ let msg = `${chalk.magenta(appConfig.productName)} started with ${chalk.blue('HTTP')} transport on port ${chalk.blue(port)}
409
+ Home page: http://localhost:${port}/`;
410
+ if (isAdminEnabled) {
411
+ msg += `\nAdmin panel: http://localhost:${port}/admin`;
412
+ }
378
413
  console.log(msg);
379
414
  });
380
415
  }