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,196 +1,196 @@
1
- # AD Group Authorization
2
-
3
- Authorization by AD group membership. Assumes JWT auth is configured.
4
-
5
- ## AD Configuration Types
6
-
7
- ```typescript
8
- interface IADConfig {
9
- ad: {
10
- domains: { [domainName: string]: IDcConfig };
11
- tlsOptions?: ConnectionOptions;
12
- groupCacheTtlMs?: number; // Default: 10 min
13
- dnCacheTtlMs?: number; // Default: 24 hours
14
- };
15
- }
16
-
17
- interface IDcConfig {
18
- controllers: string[]; // ['ldap://dc1.corp.com']
19
- username: string;
20
- password: string;
21
- baseDn?: string; // Auto-derived if omitted
22
- default?: boolean;
23
- }
24
- ```
25
-
26
- ```yaml
27
- # config/default.yaml
28
- ad:
29
- groupCacheTtlMs: 600000
30
- domains:
31
- CORP:
32
- default: true
33
- controllers: ['ldap://dc1.corp.com']
34
- username: 'svc_mcp@corp.com'
35
- password: '${AD_SERVICE_PASSWORD}'
36
- ```
37
-
38
- ## Custom Config Extension
39
-
40
- ```typescript
41
- // src/_types_/custom-config.ts
42
- import { AppConfig } from 'fa-mcp-sdk';
43
-
44
- interface IGroupAccessConfig {
45
- groupAccess: { requiredGroup: string; bypassGroupCheck?: boolean };
46
- }
47
- export interface CustomAppConfig extends AppConfig, IGroupAccessConfig {}
48
- ```
49
-
50
- ```yaml
51
- # config/default.yaml
52
- groupAccess:
53
- requiredGroup: "DOMAIN\\MCP-Users"
54
- bypassGroupCheck: false
55
- ```
56
-
57
- ## Example 1: HTTP Level Restriction
58
-
59
- Block unauthorized users at HTTP level (403 before MCP processing).
60
-
61
- > **Important:** `customAuthValidator` runs **before** standard auth and before `authInfo` is set on
62
- > the request. It cannot read `(req as any).authInfo` — that value is populated by the middleware
63
- > only after successful authentication. Use `httpComponents.apiRouter` to add a post-auth middleware
64
- > if you need to check group membership after the user has been authenticated.
65
-
66
- ```typescript
67
- // src/start.ts
68
- import { Router } from 'express';
69
- import { appConfig, initMcpServer, getMultiAuthError, initADGroupChecker } from 'fa-mcp-sdk';
70
- import { CustomAppConfig } from './_types_/custom-config.js';
71
-
72
- const config = appConfig as CustomAppConfig;
73
- const { isUserInGroup } = initADGroupChecker();
74
-
75
- // Post-auth AD group check: runs after standard auth has verified the token
76
- // and set authInfo on the request.
77
- const groupCheckRouter = Router();
78
- groupCheckRouter.use(async (req, res, next) => {
79
- // Verify standard auth first (sets authInfo on req)
80
- const authError = await getMultiAuthError(req);
81
- if (authError) return res.status(authError.code).send(authError.message);
82
-
83
- if (config.groupAccess.bypassGroupCheck) return next();
84
-
85
- const authInfo = (req as any).authInfo;
86
- const username = authInfo?.username || authInfo?.payload?.user;
87
- if (!username) return res.status(403).send('Forbidden: User info unavailable');
88
-
89
- const isInGroup = await isUserInGroup(username, config.groupAccess.requiredGroup);
90
- if (!isInGroup) return res.status(403).send(`Forbidden: Not in group '${config.groupAccess.requiredGroup}'`);
91
-
92
- next();
93
- });
94
-
95
- await initMcpServer({
96
- ...,
97
- httpComponents: { apiRouter: groupCheckRouter },
98
- });
99
- ```
100
-
101
- ## Example 2: All Tools Restriction
102
-
103
- Check in `toolHandler` (MCP error response):
104
-
105
- ```typescript
106
- // src/tools/handle-tool-call.ts
107
- import { ToolExecutionError, appConfig, initADGroupChecker, IToolHandlerParams } from 'fa-mcp-sdk';
108
- import { CustomAppConfig } from '../_types_/custom-config.js';
109
-
110
- const config = appConfig as CustomAppConfig;
111
- const { isUserInGroup } = initADGroupChecker();
112
-
113
- async function checkToolAccess(payload: IToolHandlerParams['payload']) {
114
- if (config.groupAccess.bypassGroupCheck) return;
115
- if (!payload?.user) throw new ToolExecutionError('auth', 'User info unavailable');
116
-
117
- const isInGroup = await isUserInGroup(payload.user, config.groupAccess.requiredGroup);
118
- if (!isInGroup) {
119
- throw new ToolExecutionError('auth', `Forbidden: User not in '${config.groupAccess.requiredGroup}'`);
120
- }
121
- }
122
-
123
- export const handleToolCall = async (params: IToolHandlerParams) => {
124
- await checkToolAccess(params.payload); // Check ALL tools
125
- // ... tool switch logic
126
- };
127
- ```
128
-
129
- ## Example 3: Per-Tool Restriction
130
-
131
- Different groups for different tools:
132
-
133
- ```typescript
134
- // src/_types_/custom-config.ts
135
- interface IToolGroupAccessConfig {
136
- toolGroupAccess: {
137
- defaultGroup?: string;
138
- tools: Record<string, { requiredGroup?: string; public?: boolean }>;
139
- bypassGroupCheck?: boolean;
140
- };
141
- }
142
- ```
143
-
144
- ```yaml
145
- # config/default.yaml
146
- toolGroupAccess:
147
- defaultGroup: "DOMAIN\\MCP-Users"
148
- bypassGroupCheck: false
149
- tools:
150
- get_public_data:
151
- public: true
152
- get_user_data:
153
- requiredGroup: "DOMAIN\\MCP-Users"
154
- admin_operation:
155
- requiredGroup: "DOMAIN\\MCP-Admins"
156
- ```
157
-
158
- ```typescript
159
- // src/tools/handle-tool-call.ts
160
- async function checkToolAccess(toolName: string, payload: IToolHandlerParams['payload']) {
161
- const toolAccess = config.toolGroupAccess;
162
- if (toolAccess.bypassGroupCheck) return;
163
-
164
- const toolConfig = toolAccess.tools[toolName];
165
- if (toolConfig?.public) return;
166
-
167
- if (!payload?.user) throw new ToolExecutionError(toolName, 'User info unavailable');
168
-
169
- const requiredGroup = toolConfig?.requiredGroup || toolAccess.defaultGroup;
170
- if (!requiredGroup) return;
171
-
172
- const isInGroup = await isUserInGroup(payload.user, requiredGroup);
173
- if (!isInGroup) {
174
- throw new ToolExecutionError(toolName, `Forbidden: User not in '${requiredGroup}'`);
175
- }
176
- }
177
-
178
- export const handleToolCall = async (params: IToolHandlerParams) => {
179
- await checkToolAccess(params.name, params.payload);
180
- // ... tool switch logic
181
- };
182
- ```
183
-
184
- ## Authorization Levels Summary
185
-
186
- | Level | Location | Error Type | Use Case |
187
- |-------|----------|------------|----------|
188
- | Pre-auth bypass | `customAuthValidator` | HTTP 401 | Allow alternative credentials (no `Authorization` header) |
189
- | HTTP Server (post-auth) | `httpComponents.apiRouter` + `getMultiAuthError` | HTTP 403 | Block completely after identity is known |
190
- | All Tools | `toolHandler` (global) | MCP Error | Allow HTTP, restrict tools |
191
- | Per Tool | `toolHandler` (per-tool) | MCP Error | Fine-grained permissions |
192
-
193
- > `customAuthValidator` is a **pre-auth** hook — it runs before standard auth and before `authInfo`
194
- > is available. Use it to allow alternative credentials, not to check group membership.
195
- > For group checks that require a verified username, use `httpComponents.apiRouter` (post-auth)
196
- > or `toolHandler` (per-call).
1
+ # AD Group Authorization
2
+
3
+ Authorization by AD group membership. Assumes JWT auth is configured.
4
+
5
+ ## AD Configuration Types
6
+
7
+ ```typescript
8
+ interface IADConfig {
9
+ ad: {
10
+ domains: { [domainName: string]: IDcConfig };
11
+ tlsOptions?: ConnectionOptions;
12
+ groupCacheTtlMs?: number; // Default: 10 min
13
+ dnCacheTtlMs?: number; // Default: 24 hours
14
+ };
15
+ }
16
+
17
+ interface IDcConfig {
18
+ controllers: string[]; // ['ldap://dc1.corp.com']
19
+ username: string;
20
+ password: string;
21
+ baseDn?: string; // Auto-derived if omitted
22
+ default?: boolean;
23
+ }
24
+ ```
25
+
26
+ ```yaml
27
+ # config/default.yaml
28
+ ad:
29
+ groupCacheTtlMs: 600000
30
+ domains:
31
+ CORP:
32
+ default: true
33
+ controllers: ['ldap://dc1.corp.com']
34
+ username: 'svc_mcp@corp.com'
35
+ password: '${AD_SERVICE_PASSWORD}'
36
+ ```
37
+
38
+ ## Custom Config Extension
39
+
40
+ ```typescript
41
+ // src/_types_/custom-config.ts
42
+ import { AppConfig } from 'fa-mcp-sdk';
43
+
44
+ interface IGroupAccessConfig {
45
+ groupAccess: { requiredGroup: string; bypassGroupCheck?: boolean };
46
+ }
47
+ export interface CustomAppConfig extends AppConfig, IGroupAccessConfig {}
48
+ ```
49
+
50
+ ```yaml
51
+ # config/default.yaml
52
+ groupAccess:
53
+ requiredGroup: "DOMAIN\\MCP-Users"
54
+ bypassGroupCheck: false
55
+ ```
56
+
57
+ ## Example 1: HTTP Level Restriction
58
+
59
+ Block unauthorized users at HTTP level (403 before MCP processing).
60
+
61
+ > **Important:** `customAuthValidator` runs **before** standard auth and before `authInfo` is set on
62
+ > the request. It cannot read `(req as any).authInfo` — that value is populated by the middleware
63
+ > only after successful authentication. Use `httpComponents.apiRouter` to add a post-auth middleware
64
+ > if you need to check group membership after the user has been authenticated.
65
+
66
+ ```typescript
67
+ // src/start.ts
68
+ import { Router } from 'express';
69
+ import { appConfig, initMcpServer, getMultiAuthError, initADGroupChecker } from 'fa-mcp-sdk';
70
+ import { CustomAppConfig } from './_types_/custom-config.js';
71
+
72
+ const config = appConfig as CustomAppConfig;
73
+ const { isUserInGroup } = initADGroupChecker();
74
+
75
+ // Post-auth AD group check: runs after standard auth has verified the token
76
+ // and set authInfo on the request.
77
+ const groupCheckRouter = Router();
78
+ groupCheckRouter.use(async (req, res, next) => {
79
+ // Verify standard auth first (sets authInfo on req)
80
+ const authError = await getMultiAuthError(req);
81
+ if (authError) return res.status(authError.code).send(authError.message);
82
+
83
+ if (config.groupAccess.bypassGroupCheck) return next();
84
+
85
+ const authInfo = (req as any).authInfo;
86
+ const username = authInfo?.username || authInfo?.payload?.user;
87
+ if (!username) return res.status(403).send('Forbidden: User info unavailable');
88
+
89
+ const isInGroup = await isUserInGroup(username, config.groupAccess.requiredGroup);
90
+ if (!isInGroup) return res.status(403).send(`Forbidden: Not in group '${config.groupAccess.requiredGroup}'`);
91
+
92
+ next();
93
+ });
94
+
95
+ await initMcpServer({
96
+ ...,
97
+ httpComponents: { apiRouter: groupCheckRouter },
98
+ });
99
+ ```
100
+
101
+ ## Example 2: All Tools Restriction
102
+
103
+ Check in `toolHandler` (MCP error response):
104
+
105
+ ```typescript
106
+ // src/tools/handle-tool-call.ts
107
+ import { ToolExecutionError, appConfig, initADGroupChecker, IToolHandlerParams } from 'fa-mcp-sdk';
108
+ import { CustomAppConfig } from '../_types_/custom-config.js';
109
+
110
+ const config = appConfig as CustomAppConfig;
111
+ const { isUserInGroup } = initADGroupChecker();
112
+
113
+ async function checkToolAccess(payload: IToolHandlerParams['payload']) {
114
+ if (config.groupAccess.bypassGroupCheck) return;
115
+ if (!payload?.user) throw new ToolExecutionError('auth', 'User info unavailable');
116
+
117
+ const isInGroup = await isUserInGroup(payload.user, config.groupAccess.requiredGroup);
118
+ if (!isInGroup) {
119
+ throw new ToolExecutionError('auth', `Forbidden: User not in '${config.groupAccess.requiredGroup}'`);
120
+ }
121
+ }
122
+
123
+ export const handleToolCall = async (params: IToolHandlerParams) => {
124
+ await checkToolAccess(params.payload); // Check ALL tools
125
+ // ... tool switch logic
126
+ };
127
+ ```
128
+
129
+ ## Example 3: Per-Tool Restriction
130
+
131
+ Different groups for different tools:
132
+
133
+ ```typescript
134
+ // src/_types_/custom-config.ts
135
+ interface IToolGroupAccessConfig {
136
+ toolGroupAccess: {
137
+ defaultGroup?: string;
138
+ tools: Record<string, { requiredGroup?: string; public?: boolean }>;
139
+ bypassGroupCheck?: boolean;
140
+ };
141
+ }
142
+ ```
143
+
144
+ ```yaml
145
+ # config/default.yaml
146
+ toolGroupAccess:
147
+ defaultGroup: "DOMAIN\\MCP-Users"
148
+ bypassGroupCheck: false
149
+ tools:
150
+ get_public_data:
151
+ public: true
152
+ get_user_data:
153
+ requiredGroup: "DOMAIN\\MCP-Users"
154
+ admin_operation:
155
+ requiredGroup: "DOMAIN\\MCP-Admins"
156
+ ```
157
+
158
+ ```typescript
159
+ // src/tools/handle-tool-call.ts
160
+ async function checkToolAccess(toolName: string, payload: IToolHandlerParams['payload']) {
161
+ const toolAccess = config.toolGroupAccess;
162
+ if (toolAccess.bypassGroupCheck) return;
163
+
164
+ const toolConfig = toolAccess.tools[toolName];
165
+ if (toolConfig?.public) return;
166
+
167
+ if (!payload?.user) throw new ToolExecutionError(toolName, 'User info unavailable');
168
+
169
+ const requiredGroup = toolConfig?.requiredGroup || toolAccess.defaultGroup;
170
+ if (!requiredGroup) return;
171
+
172
+ const isInGroup = await isUserInGroup(payload.user, requiredGroup);
173
+ if (!isInGroup) {
174
+ throw new ToolExecutionError(toolName, `Forbidden: User not in '${requiredGroup}'`);
175
+ }
176
+ }
177
+
178
+ export const handleToolCall = async (params: IToolHandlerParams) => {
179
+ await checkToolAccess(params.name, params.payload);
180
+ // ... tool switch logic
181
+ };
182
+ ```
183
+
184
+ ## Authorization Levels Summary
185
+
186
+ | Level | Location | Error Type | Use Case |
187
+ |-------|----------|------------|----------|
188
+ | Pre-auth bypass | `customAuthValidator` | HTTP 401 | Allow alternative credentials (no `Authorization` header) |
189
+ | HTTP Server (post-auth) | `httpComponents.apiRouter` + `getMultiAuthError` | HTTP 403 | Block completely after identity is known |
190
+ | All Tools | `toolHandler` (global) | MCP Error | Allow HTTP, restrict tools |
191
+ | Per Tool | `toolHandler` (per-tool) | MCP Error | Fine-grained permissions |
192
+
193
+ > `customAuthValidator` is a **pre-auth** hook — it runs before standard auth and before `authInfo`
194
+ > is available. Use it to allow alternative credentials, not to check group membership.
195
+ > For group checks that require a verified username, use `httpComponents.apiRouter` (post-auth)
196
+ > or `toolHandler` (per-call).