fa-mcp-sdk 0.4.76 → 0.4.77

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 (198) hide show
  1. package/README.md +319 -314
  2. package/bin/fa-mcp.js +85 -68
  3. package/cli-template/.claude/agents/javascript-pro.md +276 -276
  4. package/cli-template/.claude/settings.json +50 -50
  5. package/cli-template/.claude/skills/upgrade-guide/SKILL.md +2 -1
  6. package/cli-template/.oxfmtrc.json +41 -0
  7. package/cli-template/.oxlintrc.json +120 -0
  8. package/cli-template/CLAUDE.md +358 -355
  9. package/cli-template/FA-MCP-SDK-DOC/00-FA-MCP-SDK-index.md +132 -132
  10. package/cli-template/FA-MCP-SDK-DOC/01-getting-started.md +146 -146
  11. package/cli-template/FA-MCP-SDK-DOC/02-1-tools-and-api.md +431 -431
  12. package/cli-template/FA-MCP-SDK-DOC/02-2-prompts-and-resources.md +201 -201
  13. package/cli-template/FA-MCP-SDK-DOC/03-configuration.md +384 -384
  14. package/cli-template/FA-MCP-SDK-DOC/04-authentication.md +412 -412
  15. package/cli-template/FA-MCP-SDK-DOC/05-ad-authorization.md +196 -196
  16. package/cli-template/FA-MCP-SDK-DOC/06-utilities.md +163 -163
  17. package/cli-template/FA-MCP-SDK-DOC/07-testing-and-operations.md +127 -127
  18. package/cli-template/jest.config.js +27 -30
  19. package/cli-template/package.json +10 -5
  20. package/cli-template/prompt-example-new-MCP.md +101 -101
  21. package/cli-template/readme-docs/SKILLS.md +1 -1
  22. package/cli-template/tsconfig.json +58 -58
  23. package/cli-template/update.cjs +41 -38
  24. package/config/custom-environment-variables.yaml +63 -63
  25. package/config/development.yaml +4 -4
  26. package/config/production.yaml +4 -4
  27. package/config/test.yaml +26 -26
  28. package/dist/core/_types_/TNtlm.d.ts.map +1 -1
  29. package/dist/core/_types_/active-directory-config.d.ts.map +1 -1
  30. package/dist/core/_types_/config.d.ts.map +1 -1
  31. package/dist/core/_types_/types.d.ts.map +1 -1
  32. package/dist/core/ad/group-checker.d.ts.map +1 -1
  33. package/dist/core/ad/group-checker.js.map +1 -1
  34. package/dist/core/agent-tester/agent-tester-router.d.ts.map +1 -1
  35. package/dist/core/agent-tester/agent-tester-router.js +6 -6
  36. package/dist/core/agent-tester/agent-tester-router.js.map +1 -1
  37. package/dist/core/agent-tester/check-llm.d.ts.map +1 -1
  38. package/dist/core/agent-tester/check-llm.js.map +1 -1
  39. package/dist/core/agent-tester/services/SummaryMemory.d.ts.map +1 -1
  40. package/dist/core/agent-tester/services/SummaryMemory.js +3 -9
  41. package/dist/core/agent-tester/services/SummaryMemory.js.map +1 -1
  42. package/dist/core/agent-tester/services/TesterAgentService.d.ts.map +1 -1
  43. package/dist/core/agent-tester/services/TesterAgentService.js +25 -27
  44. package/dist/core/agent-tester/services/TesterAgentService.js.map +1 -1
  45. package/dist/core/agent-tester/services/TesterMcpClientService.d.ts.map +1 -1
  46. package/dist/core/agent-tester/services/TesterMcpClientService.js +26 -25
  47. package/dist/core/agent-tester/services/TesterMcpClientService.js.map +1 -1
  48. package/dist/core/auth/admin-auth.d.ts.map +1 -1
  49. package/dist/core/auth/admin-auth.js +5 -5
  50. package/dist/core/auth/admin-auth.js.map +1 -1
  51. package/dist/core/auth/agent-tester-auth.d.ts.map +1 -1
  52. package/dist/core/auth/agent-tester-auth.js +1 -6
  53. package/dist/core/auth/agent-tester-auth.js.map +1 -1
  54. package/dist/core/auth/basic.d.ts.map +1 -1
  55. package/dist/core/auth/basic.js.map +1 -1
  56. package/dist/core/auth/ip-check.d.ts.map +1 -1
  57. package/dist/core/auth/ip-check.js +1 -1
  58. package/dist/core/auth/ip-check.js.map +1 -1
  59. package/dist/core/auth/jwt.d.ts.map +1 -1
  60. package/dist/core/auth/jwt.js +1 -1
  61. package/dist/core/auth/jwt.js.map +1 -1
  62. package/dist/core/auth/middleware.d.ts.map +1 -1
  63. package/dist/core/auth/middleware.js +9 -6
  64. package/dist/core/auth/middleware.js.map +1 -1
  65. package/dist/core/auth/multi-auth.d.ts.map +1 -1
  66. package/dist/core/auth/multi-auth.js +6 -6
  67. package/dist/core/auth/multi-auth.js.map +1 -1
  68. package/dist/core/auth/revocation.d.ts.map +1 -1
  69. package/dist/core/auth/revocation.js +2 -6
  70. package/dist/core/auth/revocation.js.map +1 -1
  71. package/dist/core/auth/token-generator/ntlm/ntlm-auth-options.d.ts.map +1 -1
  72. package/dist/core/auth/token-generator/ntlm/ntlm-auth-options.js +2 -2
  73. package/dist/core/auth/token-generator/ntlm/ntlm-auth-options.js.map +1 -1
  74. package/dist/core/auth/token-generator/ntlm/ntlm-domain-config.js +1 -1
  75. package/dist/core/auth/token-generator/ntlm/ntlm-domain-config.js.map +1 -1
  76. package/dist/core/auth/token-generator/ntlm/ntlm-integration.d.ts.map +1 -1
  77. package/dist/core/auth/token-generator/ntlm/ntlm-integration.js +4 -2
  78. package/dist/core/auth/token-generator/ntlm/ntlm-integration.js.map +1 -1
  79. package/dist/core/auth/token-generator/server.d.ts.map +1 -1
  80. package/dist/core/auth/token-generator/server.js.map +1 -1
  81. package/dist/core/bootstrap/init-config.d.ts.map +1 -1
  82. package/dist/core/bootstrap/init-config.js +2 -2
  83. package/dist/core/bootstrap/init-config.js.map +1 -1
  84. package/dist/core/bootstrap/startup-info.d.ts.map +1 -1
  85. package/dist/core/bootstrap/startup-info.js +3 -7
  86. package/dist/core/bootstrap/startup-info.js.map +1 -1
  87. package/dist/core/cache/cache.d.ts.map +1 -1
  88. package/dist/core/cache/cache.js +2 -2
  89. package/dist/core/cache/cache.js.map +1 -1
  90. package/dist/core/consul/deregister.d.ts.map +1 -1
  91. package/dist/core/consul/deregister.js.map +1 -1
  92. package/dist/core/consul/get-consul-api.d.ts.map +1 -1
  93. package/dist/core/consul/get-consul-api.js +1 -2
  94. package/dist/core/consul/get-consul-api.js.map +1 -1
  95. package/dist/core/db/pg-db.d.ts.map +1 -1
  96. package/dist/core/db/pg-db.js +3 -3
  97. package/dist/core/db/pg-db.js.map +1 -1
  98. package/dist/core/debug.d.ts.map +1 -1
  99. package/dist/core/debug.js.map +1 -1
  100. package/dist/core/errors/BaseMcpError.d.ts.map +1 -1
  101. package/dist/core/errors/BaseMcpError.js.map +1 -1
  102. package/dist/core/errors/ValidationError.d.ts.map +1 -1
  103. package/dist/core/errors/ValidationError.js.map +1 -1
  104. package/dist/core/errors/errors.d.ts.map +1 -1
  105. package/dist/core/errors/errors.js +1 -1
  106. package/dist/core/errors/errors.js.map +1 -1
  107. package/dist/core/index.d.ts +6 -6
  108. package/dist/core/index.d.ts.map +1 -1
  109. package/dist/core/index.js +5 -5
  110. package/dist/core/index.js.map +1 -1
  111. package/dist/core/init-mcp-server.d.ts.map +1 -1
  112. package/dist/core/init-mcp-server.js.map +1 -1
  113. package/dist/core/logger.d.ts.map +1 -1
  114. package/dist/core/logger.js +1 -1
  115. package/dist/core/logger.js.map +1 -1
  116. package/dist/core/mcp/create-mcp-server.d.ts.map +1 -1
  117. package/dist/core/mcp/create-mcp-server.js +1 -1
  118. package/dist/core/mcp/create-mcp-server.js.map +1 -1
  119. package/dist/core/mcp/prompts.d.ts.map +1 -1
  120. package/dist/core/mcp/prompts.js.map +1 -1
  121. package/dist/core/mcp/readme-assembler.d.ts.map +1 -1
  122. package/dist/core/mcp/readme-assembler.js +3 -1
  123. package/dist/core/mcp/readme-assembler.js.map +1 -1
  124. package/dist/core/mcp/resources.d.ts.map +1 -1
  125. package/dist/core/mcp/resources.js.map +1 -1
  126. package/dist/core/mcp/server-stdio.d.ts.map +1 -1
  127. package/dist/core/utils/formatToolResult.d.ts.map +1 -1
  128. package/dist/core/utils/formatToolResult.js.map +1 -1
  129. package/dist/core/utils/port-checker.d.ts.map +1 -1
  130. package/dist/core/utils/port-checker.js.map +1 -1
  131. package/dist/core/utils/rate-limit.d.ts.map +1 -1
  132. package/dist/core/utils/rate-limit.js +2 -8
  133. package/dist/core/utils/rate-limit.js.map +1 -1
  134. package/dist/core/utils/testing/BaseMcpClient.d.ts.map +1 -1
  135. package/dist/core/utils/testing/BaseMcpClient.js.map +1 -1
  136. package/dist/core/utils/testing/McpHttpClient.d.ts.map +1 -1
  137. package/dist/core/utils/testing/McpHttpClient.js +2 -2
  138. package/dist/core/utils/testing/McpHttpClient.js.map +1 -1
  139. package/dist/core/utils/testing/McpSseClient.d.ts.map +1 -1
  140. package/dist/core/utils/testing/McpSseClient.js +3 -8
  141. package/dist/core/utils/testing/McpSseClient.js.map +1 -1
  142. package/dist/core/utils/testing/McpStdioClient.d.ts.map +1 -1
  143. package/dist/core/utils/testing/McpStdioClient.js.map +1 -1
  144. package/dist/core/utils/testing/McpStreamableHttpClient.d.ts.map +1 -1
  145. package/dist/core/utils/testing/McpStreamableHttpClient.js +7 -8
  146. package/dist/core/utils/testing/McpStreamableHttpClient.js.map +1 -1
  147. package/dist/core/utils/utils.d.ts.map +1 -1
  148. package/dist/core/utils/utils.js +3 -5
  149. package/dist/core/utils/utils.js.map +1 -1
  150. package/dist/core/web/admin-router.d.ts.map +1 -1
  151. package/dist/core/web/admin-router.js +3 -3
  152. package/dist/core/web/admin-router.js.map +1 -1
  153. package/dist/core/web/cors.d.ts.map +1 -1
  154. package/dist/core/web/cors.js.map +1 -1
  155. package/dist/core/web/favicon-svg.d.ts.map +1 -1
  156. package/dist/core/web/favicon-svg.js +1 -5
  157. package/dist/core/web/favicon-svg.js.map +1 -1
  158. package/dist/core/web/home-api.d.ts.map +1 -1
  159. package/dist/core/web/home-api.js +7 -8
  160. package/dist/core/web/home-api.js.map +1 -1
  161. package/dist/core/web/openapi.d.ts.map +1 -1
  162. package/dist/core/web/openapi.js +1 -3
  163. package/dist/core/web/openapi.js.map +1 -1
  164. package/dist/core/web/server-http.d.ts.map +1 -1
  165. package/dist/core/web/server-http.js +4 -4
  166. package/dist/core/web/server-http.js.map +1 -1
  167. package/dist/core/web/static/agent-tester/index.html +323 -323
  168. package/dist/core/web/static/agent-tester/script.js +311 -200
  169. package/dist/core/web/static/agent-tester/styles.css +1840 -1840
  170. package/dist/core/web/static/home/index.html +220 -220
  171. package/dist/core/web/static/home/script.js +72 -43
  172. package/dist/core/web/static/styles.css +927 -927
  173. package/dist/core/web/static/token-gen/index.html +136 -136
  174. package/dist/core/web/static/token-gen/script.js +58 -56
  175. package/dist/core/web/svg-icons.d.ts.map +1 -1
  176. package/dist/core/web/svg-icons.js +1 -5
  177. package/dist/core/web/svg-icons.js.map +1 -1
  178. package/package.json +10 -5
  179. package/{cli-template/.claude/hooks/eslint-fix.cjs → scripts/cc-hook-oxlint-oxfmt-fix.cjs} +109 -100
  180. package/scripts/generate-jwt.js +5 -9
  181. package/scripts/kill-port.js +5 -2
  182. package/scripts/npm/run.js +1 -2
  183. package/scripts/remove-nul.js +1 -1
  184. package/scripts/update-sdk.js +36 -14
  185. package/src/template/api/router.ts +3 -3
  186. package/src/template/prompts/agent-brief.ts +0 -1
  187. package/src/template/start.ts +3 -8
  188. package/src/template/tools/handle-tool-call.ts +3 -3
  189. package/src/template/tools/tools.ts +3 -7
  190. package/src/tests/jest-simple-reporter.js +1 -1
  191. package/src/tests/mcp/sse/mcp-sse-client-handling.md +111 -111
  192. package/src/tests/mcp/sse/test-sse-npm-package.js +2 -3
  193. package/src/tests/mcp/test-cases.js +6 -7
  194. package/src/tests/mcp/test-http.js +2 -2
  195. package/src/tests/mcp/test-sse.js +9 -7
  196. package/src/tests/mcp/test-stdio.js +12 -8
  197. package/src/tests/utils.ts +4 -3
  198. package/cli-template/eslint.config.js +0 -27
@@ -1,412 +1,412 @@
1
- # Authentication
2
-
3
- ## Auth Types
4
-
5
- ```typescript
6
- type TTokenType = 'permanent' | 'JWT';
7
-
8
- interface AuthResult {
9
- success: boolean;
10
- error?: string;
11
- authType?: 'permanentServerTokens' | 'jwtToken' | 'basic' | 'custom';
12
- username?: string;
13
- isTokenDecrypted?: boolean;
14
- payload?: any;
15
- }
16
- ```
17
-
18
- ## Token Operations
19
-
20
- ```typescript
21
- import { generateToken } from 'fa-mcp-sdk';
22
-
23
- // Generate JWT (liveTimeSec = seconds until expiry)
24
- const token = generateToken('john_doe', 3600, { role: 'admin' }); // 1 hour
25
- ```
26
-
27
- ## Test Authentication
28
-
29
- ```typescript
30
- import { getAuthHeadersForTests, McpHttpClient, appConfig } from 'fa-mcp-sdk';
31
-
32
- // Auto-generates auth headers based on config (permanent → basic → JWT priority)
33
- const headers = getAuthHeadersForTests();
34
-
35
- // Usage
36
- const response = await fetch(`http://localhost:${appConfig.webServer.port}/mcp`, {
37
- method: 'POST',
38
- headers: { 'Content-Type': 'application/json', ...headers },
39
- body: JSON.stringify({ jsonrpc: '2.0', method: 'tools/call', params: {...}, id: 1 })
40
- });
41
-
42
- // With test client
43
- const client = new McpHttpClient('http://localhost:3000');
44
- const result = await client.callTool('tool', args, getAuthHeadersForTests());
45
- ```
46
-
47
- ## Admin Panel Authentication
48
-
49
- The admin panel (`/admin`) supports 4 authentication types and can be configured with a single type or multiple types:
50
-
51
- ```yaml
52
- # config/default.yaml
53
- adminPanel:
54
- enabled: true
55
- # Single type (string)
56
- authType: 'basic'
57
- # Or multiple types (array) — login page shows tabs to choose
58
- authType: ['jwtToken', 'basic']
59
- # 'none' / null / empty array / not set — panel opens WITHOUT authentication
60
- # (convenience for local development; do NOT use in production).
61
- authType: 'none'
62
- ```
63
-
64
- **Supported types:** `permanentServerTokens`, `basic`, `jwtToken`, `ntlm`, `none` (= open access)
65
-
66
- When multiple types are configured (e.g. `['jwtToken', 'basic']`), the login page shows tabs:
67
- - **Token** tab — for `permanentServerTokens` and `jwtToken` authentication
68
- - **Login** tab — for `basic` (username/password) authentication
69
-
70
- For `permanentServerTokens`, `basic`, `jwtToken` — credentials are taken from `webServer.auth` section.
71
- For `ntlm` — uses AD configuration from `ad.domains` section.
72
-
73
- ### JWT Admin Requirement: `payload.allow === 'gen-token'`
74
-
75
- When `jwtToken` is used to authenticate into the admin panel (`/admin`), the decoded
76
- payload **must** contain `allow: 'gen-token'`. Any JWT without this claim is rejected
77
- with `401` even if it decrypts and is not expired. This prevents short-lived JWTs
78
- issued for other purposes (e.g. the Agent Tester page auto-fills a JWT into its
79
- `Authorization` header — TTL is configurable via `agentTester.tokenTTLSec`, default
80
- 30 min) from being replayed against `/admin` to mint arbitrary long-lived tokens.
81
-
82
- Generate an admin-capable JWT by including `allow=gen-token` in the payload:
83
-
84
- ```bash
85
- node scripts/generate-jwt.js -u admin -ttl 30d -p "allow=gen-token"
86
- ```
87
-
88
- `permanentServerTokens` and `basic` admin auth are unaffected — this check applies
89
- only to the `jwtToken` admin path.
90
-
91
- ## Token Generator Authorization
92
-
93
- Protect `/admin/` page with custom authorization:
94
-
95
- ```typescript
96
- import { TokenGenAuthHandler, initADGroupChecker } from 'fa-mcp-sdk';
97
-
98
- const { isUserInGroup } = initADGroupChecker();
99
-
100
- const tokenGenAuthHandler: TokenGenAuthHandler = async (input) => {
101
- // input: { user, domain?, payload?, authType }
102
- if (input.authType === 'ntlm') {
103
- const isAdmin = await isUserInGroup(input.user, 'TokenGeneratorAdmins');
104
- if (!isAdmin) return { success: false, error: `User not authorized` };
105
- }
106
- return { success: true, username: input.user };
107
- };
108
-
109
- const serverData: McpServerData = { ..., tokenGenAuthHandler };
110
- ```
111
-
112
- ## Multi-Authentication System
113
-
114
- ### createAuthMW()
115
-
116
- Universal middleware supporting all auth methods:
117
-
118
- ```typescript
119
- import { createAuthMW } from 'fa-mcp-sdk';
120
-
121
- const authMW = createAuthMW();
122
- app.use('/api', authMW);
123
-
124
- app.get('/api/protected', (req, res) => {
125
- const authInfo = (req as any).authInfo;
126
- res.json({ authType: authInfo?.authType, username: authInfo?.username });
127
- });
128
-
129
- // Advanced options
130
- const authMW = createAuthMW({
131
- mcpPaths: ['/mcp', '/messages', '/sse'], // Paths with public resource access
132
- logConfig: true, // Log config on first request
133
- });
134
- ```
135
-
136
- ### getMultiAuthError()
137
-
138
- Programmatic auth checking:
139
-
140
- ```typescript
141
- import { getMultiAuthError } from 'fa-mcp-sdk';
142
-
143
- const authError = await getMultiAuthError(req);
144
- if (authError) {
145
- return res.status(authError.code).send(authError.message);
146
- }
147
- ```
148
-
149
- ### Custom Authentication
150
-
151
- `customAuthValidator` runs **before** standard auth (`Authorization` header check).
152
-
153
- **Execution order:**
154
- 1. `customAuthValidator` is called first
155
- 2. If `success: true` → request is allowed, standard auth is **skipped**
156
- 3. If `success: false` → falls through to standard auth (`permanentServerTokens` / `basic` / `jwtToken`)
157
- 4. If standard auth also fails → 401
158
-
159
- This allows using service-specific credentials (e.g. `x-api-key`, `x-service-token`) as an alternative
160
- to the MCP `Authorization` header, without disabling standard auth entirely.
161
-
162
- ```typescript
163
- import { CustomAuthValidator, AuthResult } from 'fa-mcp-sdk';
164
-
165
- // Example: bypass MCP auth if service-specific header is present
166
- const customValidator: CustomAuthValidator = async (req): Promise<AuthResult> => {
167
- const apiKey = req.headers['x-api-key'];
168
- if (apiKey && await validateApiKey(apiKey)) {
169
- return { success: true, authType: 'custom', username: 'api-user' };
170
- }
171
- // Return false → falls through to standard Authorization header check
172
- return { success: false, error: 'No valid API key' };
173
- };
174
-
175
- const serverData: McpServerData = { ..., customAuthValidator: customValidator };
176
- ```
177
-
178
- **Example: allow requests with upstream service headers to bypass MCP auth**
179
-
180
- ```typescript
181
- // Clients that pass x-service-token OR x-username+x-password are allowed in
182
- // without an MCP Authorization token. Clients without these headers still
183
- // need a valid Authorization header (permanentToken / basic / JWT).
184
- const serviceHeadersValidator: CustomAuthValidator = (req) => {
185
- const h = req.headers as Record<string, string>;
186
- if (h['x-service-token'] || (h['x-username'] && h['x-password'])) {
187
- return { success: true, authType: 'custom' };
188
- }
189
- return { success: false, error: 'No service credentials and no MCP Authorization token' };
190
- };
191
- ```
192
-
193
- > **Note:** `customAuthValidator` receives a request with **normalized** (lowercased) header names.
194
- > `authInfo` is **not** set on `req` when the validator runs — it is set by the middleware only after
195
- > successful authentication completes.
196
-
197
- ## Agent Tester Authentication
198
-
199
- Protect the Agent Tester (`/agent-tester/*`) with `agentTester.useAuth`:
200
-
201
- ```yaml
202
- agentTester:
203
- useAuth: true # Require authentication for Agent Tester
204
- webServer:
205
- auth:
206
- enabled: true
207
- permanentServerTokens: ['my-secret-token']
208
- ```
209
-
210
- Or via ENV: `AGENT_TESTER_USE_AUTH=true`
211
-
212
- When `useAuth` is `true`, the full multi-auth middleware is applied to Agent Tester routes — the same authentication used for MCP endpoints (`permanentServerTokens` / `basic` / `jwtToken` / `custom`). Browser users see a login dialog; headless clients pass `Authorization` header directly.
213
-
214
- See [Agent Tester docs](08-agent-tester-and-headless-api.md#authentication-agenttesteruseauth) for details on the login flow, session management, and API endpoints.
215
-
216
- ## AD Group Checking
217
-
218
- ### Configuration
219
-
220
- ```yaml
221
- # config/local.yaml
222
- ad:
223
- domains:
224
- MYDOMAIN:
225
- default: true
226
- controllers: ['ldap://dc1.corp.com']
227
- username: 'svc_account@corp.com'
228
- password: '***'
229
- ```
230
-
231
- ### Usage
232
-
233
- ```typescript
234
- import { initADGroupChecker } from 'fa-mcp-sdk';
235
-
236
- const { isUserInGroup, groupChecker } = initADGroupChecker();
237
-
238
- const isAdmin = await isUserInGroup('john.doe', 'Admins');
239
- groupChecker.clearCache(); // Clear if needed
240
- ```
241
-
242
- ## JWT IP Restriction
243
-
244
- When `webServer.auth.jwtToken.isCheckIP` is `true`, JWT tokens can include an `ip` field in their payload to restrict which client IPs may use the token.
245
-
246
- ### Configuration
247
-
248
- ```yaml
249
- # config/default.yaml
250
- webServer:
251
- auth:
252
- jwtToken:
253
- isCheckIP: true # Enable IP checking
254
- ```
255
-
256
- ### Token Generation
257
-
258
- When generating a token (via admin UI or `generateToken()`), include the `ip` field in the payload:
259
-
260
- ```typescript
261
- const token = generateToken('john_doe', 3600, {
262
- service: 'my-mcp-server',
263
- ip: '192.168.1.100, 10.0.0.0/24',
264
- });
265
- ```
266
-
267
- The `ip` field is a string of IP addresses and/or CIDR masks, separated by commas, semicolons, or spaces.
268
-
269
- In the admin UI (`/admin`), there is a dedicated "Allowed IP addresses" field for entering these values.
270
-
271
- ### Behavior
272
-
273
- | `isCheckIP` | `payload.ip` | Client IP | Result |
274
- |-------------|-------------|-----------|--------|
275
- | `false` | any | any | IP not checked |
276
- | `true` | empty/missing | any | IP not checked (pass-through) |
277
- | `true` | `10.0.0.0/24` | `10.0.0.5` | Allowed |
278
- | `true` | `10.0.0.0/24` | `192.168.1.1` | Denied |
279
- | `true` | `192.168.1.1, 10.0.0.0/8` | `10.5.5.5` | Allowed (covered by /8) |
280
-
281
- Supported formats: IPv4, IPv6, CIDR notation (e.g., `10.0.0.0/24`, `fe80::/10`), IPv4-mapped IPv6 (`::ffff:192.168.1.1`).
282
-
283
- ## Client Examples
284
-
285
- ```bash
286
- # Permanent token
287
- curl -H "Authorization: Bearer server-token-1" http://localhost:3000/mcp
288
-
289
- # JWT
290
- curl -H "Authorization: Bearer eyJ..." http://localhost:3000/mcp
291
-
292
- # Basic Auth
293
- curl -H "Authorization: Basic $(echo -n 'admin:password' | base64)" http://localhost:3000/mcp
294
-
295
- # Custom headers
296
- curl -H "X-API-Key: custom-key" http://localhost:3000/mcp
297
- ```
298
-
299
- ## CLI Token Generator
300
-
301
- Generate JWT tokens from the command line without starting the server:
302
-
303
- ```bash
304
- node scripts/generate-jwt.js -u <username> -ttl <duration> [-s <service>] [-p <params>]
305
- ```
306
-
307
- | Option | ENV | Description |
308
- |--------|-----|-------------|
309
- | `-u`, `--username` | `JWT_PAYLOAD_USERNAME` | Username (required) |
310
- | `-ttl` | `JWT_TTL` | Token lifetime: `<N>s` \| `<N>m` \| `<N>d` \| `<N>y` (required) |
311
- | `-s`, `--service-name` | `JWT_PAYLOAD_SERVICE_NAME` | Service name (optional) |
312
- | `-p`, `--params` | `JWT_PAYLOAD_PARAMS` | Extra payload `key=value;key=value` (optional) |
313
-
314
- The `encryptKey` is read from config `webServer.auth.jwtToken.encryptKey` (via `config/local.yaml` or ENV `WS_TOKEN_ENCRYPT_KEY`).
315
-
316
- **Examples:**
317
-
318
- ```bash
319
- # 30-day token with service name
320
- node scripts/generate-jwt.js -u admin -ttl 30d -s my-mcp-server
321
-
322
- # 1-year token with extra payload fields
323
- node scripts/generate-jwt.js -u svc-account -ttl 1y -p "role=admin;team=backend"
324
-
325
- # Via environment variables
326
- JWT_PAYLOAD_USERNAME=admin JWT_TTL=8d node scripts/generate-jwt.js
327
- ```
328
-
329
- ## Claude Code Skill: `/gen-jwt`
330
-
331
- Interactive JWT token generation via Claude Code. Invoke with `/gen-jwt` or natural language (e.g. "сгенерируй токен для vpupkin на 1 год").
332
-
333
- The skill parses your request for `username`, `ttl`, `service`, `request` (ticket ID), `ip`, and extra key=value params. If required params (`username`, `ttl`) are missing, it asks interactively. Optional params (`request`, `ip`) are prompted once with an option to skip.
334
-
335
- Runs `node scripts/generate-jwt.js` under the hood.
336
-
337
- **Example:**
338
- ```
339
- /gen-jwt для vpupkin, по заявке REQ-12345, на 1 год, role=admin, IP 10.0.0.0/24
340
- ```
341
-
342
- Skill location: `.claude/skills/gen-jwt/SKILL.md`
343
-
344
- ## JWT Generation API
345
-
346
- HTTP endpoint for programmatic JWT token generation. Disabled by default.
347
-
348
- ### Configuration
349
-
350
- ```yaml
351
- # config/default.yaml
352
- webServer:
353
- genJwtApiEnable: true # Enable POST /gen-jwt endpoint
354
- auth:
355
- enabled: true # Auth must be enabled — endpoint requires valid credentials
356
- jwtToken:
357
- encryptKey: 'your-secret-key-here'
358
- ```
359
-
360
- Or via ENV: `WS_GEN_JWT_API_ENABLE=true`
361
-
362
- ### Usage
363
-
364
- ```bash
365
- # POST /gen-jwt with any configured auth method
366
- curl -X POST http://localhost:3000/gen-jwt \
367
- -H "Content-Type: application/json" \
368
- -u "admin:password" \
369
- -d '{
370
- "username": "testuser",
371
- "ttl": "30d",
372
- "service": "my-mcp-server",
373
- "params": "role=admin;team=backend"
374
- }'
375
- ```
376
-
377
- ### Request Body
378
-
379
- | Field | Type | Required | Description |
380
- |-------|------|----------|-------------|
381
- | `username` | string | yes | Username for the token |
382
- | `ttl` | string | yes | Token lifetime: `<N>s` \| `<N>m` \| `<N>d` \| `<N>y` |
383
- | `service` | string | no | Service name |
384
- | `params` | string \| object | no | Extra payload. String: `"key=value;key=value"`. Object: `{"key": "value"}` |
385
-
386
- ### Response
387
-
388
- ```json
389
- {
390
- "success": true,
391
- "token": "1718000000000.a1b2c3...",
392
- "user": "testuser",
393
- "expire": "2025-07-10T12:00:00.000Z",
394
- "ttlSeconds": 2592000
395
- }
396
- ```
397
-
398
- ## Token Generator App
399
-
400
- ```typescript
401
- import { generateTokenApp } from 'fa-mcp-sdk';
402
-
403
- generateTokenApp(); // Port 3030
404
- generateTokenApp(1234); // Custom port
405
- ```
406
-
407
- **Endpoints:**
408
- - `/` - Web UI
409
- - `/admin/api/generate-token` - POST: Generate token
410
- - `/admin/api/validate-token` - POST: Validate token
411
- - `/admin/api/service-info` - GET: Service info
412
- - `/admin/api/auth-status` - GET: Auth status
1
+ # Authentication
2
+
3
+ ## Auth Types
4
+
5
+ ```typescript
6
+ type TTokenType = 'permanent' | 'JWT';
7
+
8
+ interface AuthResult {
9
+ success: boolean;
10
+ error?: string;
11
+ authType?: 'permanentServerTokens' | 'jwtToken' | 'basic' | 'custom';
12
+ username?: string;
13
+ isTokenDecrypted?: boolean;
14
+ payload?: any;
15
+ }
16
+ ```
17
+
18
+ ## Token Operations
19
+
20
+ ```typescript
21
+ import { generateToken } from 'fa-mcp-sdk';
22
+
23
+ // Generate JWT (liveTimeSec = seconds until expiry)
24
+ const token = generateToken('john_doe', 3600, { role: 'admin' }); // 1 hour
25
+ ```
26
+
27
+ ## Test Authentication
28
+
29
+ ```typescript
30
+ import { getAuthHeadersForTests, McpHttpClient, appConfig } from 'fa-mcp-sdk';
31
+
32
+ // Auto-generates auth headers based on config (permanent → basic → JWT priority)
33
+ const headers = getAuthHeadersForTests();
34
+
35
+ // Usage
36
+ const response = await fetch(`http://localhost:${appConfig.webServer.port}/mcp`, {
37
+ method: 'POST',
38
+ headers: { 'Content-Type': 'application/json', ...headers },
39
+ body: JSON.stringify({ jsonrpc: '2.0', method: 'tools/call', params: {...}, id: 1 })
40
+ });
41
+
42
+ // With test client
43
+ const client = new McpHttpClient('http://localhost:3000');
44
+ const result = await client.callTool('tool', args, getAuthHeadersForTests());
45
+ ```
46
+
47
+ ## Admin Panel Authentication
48
+
49
+ The admin panel (`/admin`) supports 4 authentication types and can be configured with a single type or multiple types:
50
+
51
+ ```yaml
52
+ # config/default.yaml
53
+ adminPanel:
54
+ enabled: true
55
+ # Single type (string)
56
+ authType: 'basic'
57
+ # Or multiple types (array) — login page shows tabs to choose
58
+ authType: ['jwtToken', 'basic']
59
+ # 'none' / null / empty array / not set — panel opens WITHOUT authentication
60
+ # (convenience for local development; do NOT use in production).
61
+ authType: 'none'
62
+ ```
63
+
64
+ **Supported types:** `permanentServerTokens`, `basic`, `jwtToken`, `ntlm`, `none` (= open access)
65
+
66
+ When multiple types are configured (e.g. `['jwtToken', 'basic']`), the login page shows tabs:
67
+ - **Token** tab — for `permanentServerTokens` and `jwtToken` authentication
68
+ - **Login** tab — for `basic` (username/password) authentication
69
+
70
+ For `permanentServerTokens`, `basic`, `jwtToken` — credentials are taken from `webServer.auth` section.
71
+ For `ntlm` — uses AD configuration from `ad.domains` section.
72
+
73
+ ### JWT Admin Requirement: `payload.allow === 'gen-token'`
74
+
75
+ When `jwtToken` is used to authenticate into the admin panel (`/admin`), the decoded
76
+ payload **must** contain `allow: 'gen-token'`. Any JWT without this claim is rejected
77
+ with `401` even if it decrypts and is not expired. This prevents short-lived JWTs
78
+ issued for other purposes (e.g. the Agent Tester page auto-fills a JWT into its
79
+ `Authorization` header — TTL is configurable via `agentTester.tokenTTLSec`, default
80
+ 30 min) from being replayed against `/admin` to mint arbitrary long-lived tokens.
81
+
82
+ Generate an admin-capable JWT by including `allow=gen-token` in the payload:
83
+
84
+ ```bash
85
+ node scripts/generate-jwt.js -u admin -ttl 30d -p "allow=gen-token"
86
+ ```
87
+
88
+ `permanentServerTokens` and `basic` admin auth are unaffected — this check applies
89
+ only to the `jwtToken` admin path.
90
+
91
+ ## Token Generator Authorization
92
+
93
+ Protect `/admin/` page with custom authorization:
94
+
95
+ ```typescript
96
+ import { TokenGenAuthHandler, initADGroupChecker } from 'fa-mcp-sdk';
97
+
98
+ const { isUserInGroup } = initADGroupChecker();
99
+
100
+ const tokenGenAuthHandler: TokenGenAuthHandler = async (input) => {
101
+ // input: { user, domain?, payload?, authType }
102
+ if (input.authType === 'ntlm') {
103
+ const isAdmin = await isUserInGroup(input.user, 'TokenGeneratorAdmins');
104
+ if (!isAdmin) return { success: false, error: `User not authorized` };
105
+ }
106
+ return { success: true, username: input.user };
107
+ };
108
+
109
+ const serverData: McpServerData = { ..., tokenGenAuthHandler };
110
+ ```
111
+
112
+ ## Multi-Authentication System
113
+
114
+ ### createAuthMW()
115
+
116
+ Universal middleware supporting all auth methods:
117
+
118
+ ```typescript
119
+ import { createAuthMW } from 'fa-mcp-sdk';
120
+
121
+ const authMW = createAuthMW();
122
+ app.use('/api', authMW);
123
+
124
+ app.get('/api/protected', (req, res) => {
125
+ const authInfo = (req as any).authInfo;
126
+ res.json({ authType: authInfo?.authType, username: authInfo?.username });
127
+ });
128
+
129
+ // Advanced options
130
+ const authMW = createAuthMW({
131
+ mcpPaths: ['/mcp', '/messages', '/sse'], // Paths with public resource access
132
+ logConfig: true, // Log config on first request
133
+ });
134
+ ```
135
+
136
+ ### getMultiAuthError()
137
+
138
+ Programmatic auth checking:
139
+
140
+ ```typescript
141
+ import { getMultiAuthError } from 'fa-mcp-sdk';
142
+
143
+ const authError = await getMultiAuthError(req);
144
+ if (authError) {
145
+ return res.status(authError.code).send(authError.message);
146
+ }
147
+ ```
148
+
149
+ ### Custom Authentication
150
+
151
+ `customAuthValidator` runs **before** standard auth (`Authorization` header check).
152
+
153
+ **Execution order:**
154
+ 1. `customAuthValidator` is called first
155
+ 2. If `success: true` → request is allowed, standard auth is **skipped**
156
+ 3. If `success: false` → falls through to standard auth (`permanentServerTokens` / `basic` / `jwtToken`)
157
+ 4. If standard auth also fails → 401
158
+
159
+ This allows using service-specific credentials (e.g. `x-api-key`, `x-service-token`) as an alternative
160
+ to the MCP `Authorization` header, without disabling standard auth entirely.
161
+
162
+ ```typescript
163
+ import { CustomAuthValidator, AuthResult } from 'fa-mcp-sdk';
164
+
165
+ // Example: bypass MCP auth if service-specific header is present
166
+ const customValidator: CustomAuthValidator = async (req): Promise<AuthResult> => {
167
+ const apiKey = req.headers['x-api-key'];
168
+ if (apiKey && await validateApiKey(apiKey)) {
169
+ return { success: true, authType: 'custom', username: 'api-user' };
170
+ }
171
+ // Return false → falls through to standard Authorization header check
172
+ return { success: false, error: 'No valid API key' };
173
+ };
174
+
175
+ const serverData: McpServerData = { ..., customAuthValidator: customValidator };
176
+ ```
177
+
178
+ **Example: allow requests with upstream service headers to bypass MCP auth**
179
+
180
+ ```typescript
181
+ // Clients that pass x-service-token OR x-username+x-password are allowed in
182
+ // without an MCP Authorization token. Clients without these headers still
183
+ // need a valid Authorization header (permanentToken / basic / JWT).
184
+ const serviceHeadersValidator: CustomAuthValidator = (req) => {
185
+ const h = req.headers as Record<string, string>;
186
+ if (h['x-service-token'] || (h['x-username'] && h['x-password'])) {
187
+ return { success: true, authType: 'custom' };
188
+ }
189
+ return { success: false, error: 'No service credentials and no MCP Authorization token' };
190
+ };
191
+ ```
192
+
193
+ > **Note:** `customAuthValidator` receives a request with **normalized** (lowercased) header names.
194
+ > `authInfo` is **not** set on `req` when the validator runs — it is set by the middleware only after
195
+ > successful authentication completes.
196
+
197
+ ## Agent Tester Authentication
198
+
199
+ Protect the Agent Tester (`/agent-tester/*`) with `agentTester.useAuth`:
200
+
201
+ ```yaml
202
+ agentTester:
203
+ useAuth: true # Require authentication for Agent Tester
204
+ webServer:
205
+ auth:
206
+ enabled: true
207
+ permanentServerTokens: ['my-secret-token']
208
+ ```
209
+
210
+ Or via ENV: `AGENT_TESTER_USE_AUTH=true`
211
+
212
+ When `useAuth` is `true`, the full multi-auth middleware is applied to Agent Tester routes — the same authentication used for MCP endpoints (`permanentServerTokens` / `basic` / `jwtToken` / `custom`). Browser users see a login dialog; headless clients pass `Authorization` header directly.
213
+
214
+ See [Agent Tester docs](08-agent-tester-and-headless-api.md#authentication-agenttesteruseauth) for details on the login flow, session management, and API endpoints.
215
+
216
+ ## AD Group Checking
217
+
218
+ ### Configuration
219
+
220
+ ```yaml
221
+ # config/local.yaml
222
+ ad:
223
+ domains:
224
+ MYDOMAIN:
225
+ default: true
226
+ controllers: ['ldap://dc1.corp.com']
227
+ username: 'svc_account@corp.com'
228
+ password: '***'
229
+ ```
230
+
231
+ ### Usage
232
+
233
+ ```typescript
234
+ import { initADGroupChecker } from 'fa-mcp-sdk';
235
+
236
+ const { isUserInGroup, groupChecker } = initADGroupChecker();
237
+
238
+ const isAdmin = await isUserInGroup('john.doe', 'Admins');
239
+ groupChecker.clearCache(); // Clear if needed
240
+ ```
241
+
242
+ ## JWT IP Restriction
243
+
244
+ When `webServer.auth.jwtToken.isCheckIP` is `true`, JWT tokens can include an `ip` field in their payload to restrict which client IPs may use the token.
245
+
246
+ ### Configuration
247
+
248
+ ```yaml
249
+ # config/default.yaml
250
+ webServer:
251
+ auth:
252
+ jwtToken:
253
+ isCheckIP: true # Enable IP checking
254
+ ```
255
+
256
+ ### Token Generation
257
+
258
+ When generating a token (via admin UI or `generateToken()`), include the `ip` field in the payload:
259
+
260
+ ```typescript
261
+ const token = generateToken('john_doe', 3600, {
262
+ service: 'my-mcp-server',
263
+ ip: '192.168.1.100, 10.0.0.0/24',
264
+ });
265
+ ```
266
+
267
+ The `ip` field is a string of IP addresses and/or CIDR masks, separated by commas, semicolons, or spaces.
268
+
269
+ In the admin UI (`/admin`), there is a dedicated "Allowed IP addresses" field for entering these values.
270
+
271
+ ### Behavior
272
+
273
+ | `isCheckIP` | `payload.ip` | Client IP | Result |
274
+ |-------------|-------------|-----------|--------|
275
+ | `false` | any | any | IP not checked |
276
+ | `true` | empty/missing | any | IP not checked (pass-through) |
277
+ | `true` | `10.0.0.0/24` | `10.0.0.5` | Allowed |
278
+ | `true` | `10.0.0.0/24` | `192.168.1.1` | Denied |
279
+ | `true` | `192.168.1.1, 10.0.0.0/8` | `10.5.5.5` | Allowed (covered by /8) |
280
+
281
+ Supported formats: IPv4, IPv6, CIDR notation (e.g., `10.0.0.0/24`, `fe80::/10`), IPv4-mapped IPv6 (`::ffff:192.168.1.1`).
282
+
283
+ ## Client Examples
284
+
285
+ ```bash
286
+ # Permanent token
287
+ curl -H "Authorization: Bearer server-token-1" http://localhost:3000/mcp
288
+
289
+ # JWT
290
+ curl -H "Authorization: Bearer eyJ..." http://localhost:3000/mcp
291
+
292
+ # Basic Auth
293
+ curl -H "Authorization: Basic $(echo -n 'admin:password' | base64)" http://localhost:3000/mcp
294
+
295
+ # Custom headers
296
+ curl -H "X-API-Key: custom-key" http://localhost:3000/mcp
297
+ ```
298
+
299
+ ## CLI Token Generator
300
+
301
+ Generate JWT tokens from the command line without starting the server:
302
+
303
+ ```bash
304
+ node scripts/generate-jwt.js -u <username> -ttl <duration> [-s <service>] [-p <params>]
305
+ ```
306
+
307
+ | Option | ENV | Description |
308
+ |--------|-----|-------------|
309
+ | `-u`, `--username` | `JWT_PAYLOAD_USERNAME` | Username (required) |
310
+ | `-ttl` | `JWT_TTL` | Token lifetime: `<N>s` \| `<N>m` \| `<N>d` \| `<N>y` (required) |
311
+ | `-s`, `--service-name` | `JWT_PAYLOAD_SERVICE_NAME` | Service name (optional) |
312
+ | `-p`, `--params` | `JWT_PAYLOAD_PARAMS` | Extra payload `key=value;key=value` (optional) |
313
+
314
+ The `encryptKey` is read from config `webServer.auth.jwtToken.encryptKey` (via `config/local.yaml` or ENV `WS_TOKEN_ENCRYPT_KEY`).
315
+
316
+ **Examples:**
317
+
318
+ ```bash
319
+ # 30-day token with service name
320
+ node scripts/generate-jwt.js -u admin -ttl 30d -s my-mcp-server
321
+
322
+ # 1-year token with extra payload fields
323
+ node scripts/generate-jwt.js -u svc-account -ttl 1y -p "role=admin;team=backend"
324
+
325
+ # Via environment variables
326
+ JWT_PAYLOAD_USERNAME=admin JWT_TTL=8d node scripts/generate-jwt.js
327
+ ```
328
+
329
+ ## Claude Code Skill: `/gen-jwt`
330
+
331
+ Interactive JWT token generation via Claude Code. Invoke with `/gen-jwt` or natural language (e.g. "сгенерируй токен для vpupkin на 1 год").
332
+
333
+ The skill parses your request for `username`, `ttl`, `service`, `request` (ticket ID), `ip`, and extra key=value params. If required params (`username`, `ttl`) are missing, it asks interactively. Optional params (`request`, `ip`) are prompted once with an option to skip.
334
+
335
+ Runs `node scripts/generate-jwt.js` under the hood.
336
+
337
+ **Example:**
338
+ ```
339
+ /gen-jwt для vpupkin, по заявке REQ-12345, на 1 год, role=admin, IP 10.0.0.0/24
340
+ ```
341
+
342
+ Skill location: `.claude/skills/gen-jwt/SKILL.md`
343
+
344
+ ## JWT Generation API
345
+
346
+ HTTP endpoint for programmatic JWT token generation. Disabled by default.
347
+
348
+ ### Configuration
349
+
350
+ ```yaml
351
+ # config/default.yaml
352
+ webServer:
353
+ genJwtApiEnable: true # Enable POST /gen-jwt endpoint
354
+ auth:
355
+ enabled: true # Auth must be enabled — endpoint requires valid credentials
356
+ jwtToken:
357
+ encryptKey: 'your-secret-key-here'
358
+ ```
359
+
360
+ Or via ENV: `WS_GEN_JWT_API_ENABLE=true`
361
+
362
+ ### Usage
363
+
364
+ ```bash
365
+ # POST /gen-jwt with any configured auth method
366
+ curl -X POST http://localhost:3000/gen-jwt \
367
+ -H "Content-Type: application/json" \
368
+ -u "admin:password" \
369
+ -d '{
370
+ "username": "testuser",
371
+ "ttl": "30d",
372
+ "service": "my-mcp-server",
373
+ "params": "role=admin;team=backend"
374
+ }'
375
+ ```
376
+
377
+ ### Request Body
378
+
379
+ | Field | Type | Required | Description |
380
+ |-------|------|----------|-------------|
381
+ | `username` | string | yes | Username for the token |
382
+ | `ttl` | string | yes | Token lifetime: `<N>s` \| `<N>m` \| `<N>d` \| `<N>y` |
383
+ | `service` | string | no | Service name |
384
+ | `params` | string \| object | no | Extra payload. String: `"key=value;key=value"`. Object: `{"key": "value"}` |
385
+
386
+ ### Response
387
+
388
+ ```json
389
+ {
390
+ "success": true,
391
+ "token": "1718000000000.a1b2c3...",
392
+ "user": "testuser",
393
+ "expire": "2025-07-10T12:00:00.000Z",
394
+ "ttlSeconds": 2592000
395
+ }
396
+ ```
397
+
398
+ ## Token Generator App
399
+
400
+ ```typescript
401
+ import { generateTokenApp } from 'fa-mcp-sdk';
402
+
403
+ generateTokenApp(); // Port 3030
404
+ generateTokenApp(1234); // Custom port
405
+ ```
406
+
407
+ **Endpoints:**
408
+ - `/` - Web UI
409
+ - `/admin/api/generate-token` - POST: Generate token
410
+ - `/admin/api/validate-token` - POST: Validate token
411
+ - `/admin/api/service-info` - GET: Service info
412
+ - `/admin/api/auth-status` - GET: Auth status