fa-mcp-sdk 0.2.249 → 0.2.258
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.
- package/cli-template/FA-MCP-SDK-DOC/00-FA-MCP-SDK-index.md +45 -161
- package/cli-template/FA-MCP-SDK-DOC/01-getting-started.md +71 -226
- package/cli-template/FA-MCP-SDK-DOC/02-1-tools-and-api.md +80 -360
- package/cli-template/FA-MCP-SDK-DOC/02-2-prompts-and-resources.md +191 -342
- package/cli-template/FA-MCP-SDK-DOC/03-configuration.md +141 -279
- package/cli-template/FA-MCP-SDK-DOC/04-authentication.md +73 -522
- package/cli-template/FA-MCP-SDK-DOC/05-ad-authorization.md +68 -419
- package/cli-template/FA-MCP-SDK-DOC/06-utilities.md +64 -447
- package/cli-template/FA-MCP-SDK-DOC/07-testing-and-operations.md +39 -196
- package/cli-template/package.json +2 -1
- package/config/local.yaml +1 -1
- package/dist/core/_types_/types.d.ts +36 -10
- package/dist/core/_types_/types.d.ts.map +1 -1
- package/dist/core/auth/admin-auth.js +1 -1
- package/dist/core/auth/admin-auth.js.map +1 -1
- package/dist/core/auth/middleware.js +8 -8
- package/dist/core/auth/middleware.js.map +1 -1
- package/dist/core/auth/multi-auth.d.ts.map +1 -1
- package/dist/core/auth/multi-auth.js +15 -14
- package/dist/core/auth/multi-auth.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/mcp/create-mcp-server.js +8 -9
- package/dist/core/mcp/create-mcp-server.js.map +1 -1
- package/dist/core/mcp/prompts.d.ts +10 -5
- package/dist/core/mcp/prompts.d.ts.map +1 -1
- package/dist/core/mcp/prompts.js +17 -15
- package/dist/core/mcp/prompts.js.map +1 -1
- package/dist/core/mcp/resources.d.ts +4 -4
- package/dist/core/mcp/resources.d.ts.map +1 -1
- package/dist/core/mcp/resources.js +21 -20
- package/dist/core/mcp/resources.js.map +1 -1
- package/dist/core/utils/utils.d.ts +2 -1
- package/dist/core/utils/utils.d.ts.map +1 -1
- package/dist/core/utils/utils.js +2 -2
- package/dist/core/utils/utils.js.map +1 -1
- package/dist/core/web/home-api.d.ts.map +1 -1
- package/dist/core/web/home-api.js +4 -3
- package/dist/core/web/home-api.js.map +1 -1
- package/dist/core/web/server-http.d.ts.map +1 -1
- package/dist/core/web/server-http.js +36 -21
- package/dist/core/web/server-http.js.map +1 -1
- package/package.json +1 -1
- package/scripts/update-doc.js +21 -0
- package/src/template/start.ts +1 -1
|
@@ -1,528 +1,177 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AD Group Authorization
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
group membership. These examples assume JWT token authentication (`jwtToken`) is configured,
|
|
5
|
-
and the user information is extracted from the JWT payload.
|
|
3
|
+
Authorization by AD group membership. Assumes JWT auth is configured.
|
|
6
4
|
|
|
7
5
|
## AD Configuration Types
|
|
8
6
|
|
|
9
|
-
### `IADConfig`
|
|
10
|
-
|
|
11
|
-
Main Active Directory configuration interface for group membership checks and NTLM authentication.
|
|
12
|
-
|
|
13
7
|
```typescript
|
|
14
|
-
import { IADConfig } from 'fa-mcp-sdk';
|
|
15
|
-
|
|
16
|
-
// Type Definition:
|
|
17
8
|
interface IADConfig {
|
|
18
9
|
ad: {
|
|
19
|
-
domains: {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
tlsOptions?: ConnectionOptions; // TLS options for LDAPS connections
|
|
24
|
-
strategy?: EAuthStrategy; // NTLM authentication strategy
|
|
25
|
-
groupCacheTtlMs?: number; // Cache TTL for group checks (default: 10 minutes)
|
|
26
|
-
dnCacheTtlMs?: number; // Cache TTL for DN lookups (default: 24 hours)
|
|
10
|
+
domains: { [domainName: string]: IDcConfig };
|
|
11
|
+
tlsOptions?: ConnectionOptions;
|
|
12
|
+
groupCacheTtlMs?: number; // Default: 10 min
|
|
13
|
+
dnCacheTtlMs?: number; // Default: 24 hours
|
|
27
14
|
};
|
|
28
15
|
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### `IDcConfig`
|
|
32
16
|
|
|
33
|
-
Domain Controller configuration for individual AD domains.
|
|
34
|
-
|
|
35
|
-
```typescript
|
|
36
|
-
import { IDcConfig } from 'fa-mcp-sdk';
|
|
37
|
-
|
|
38
|
-
// Type Definition:
|
|
39
17
|
interface IDcConfig {
|
|
40
|
-
controllers: string[];
|
|
41
|
-
username: string;
|
|
42
|
-
password: string;
|
|
43
|
-
baseDn?: string;
|
|
44
|
-
default?: boolean;
|
|
45
|
-
|
|
46
|
-
// Internal fields (assigned during config processing):
|
|
47
|
-
name?: string; // Domain name
|
|
48
|
-
hostReSource?: string; // RegExp source for hostname matching
|
|
49
|
-
hostRe?: RegExp; // Compiled hostname matcher
|
|
18
|
+
controllers: string[]; // ['ldap://dc1.corp.com']
|
|
19
|
+
username: string;
|
|
20
|
+
password: string;
|
|
21
|
+
baseDn?: string; // Auto-derived if omitted
|
|
22
|
+
default?: boolean;
|
|
50
23
|
}
|
|
51
24
|
```
|
|
52
25
|
|
|
53
|
-
**Configuration Example (`config/default.yaml`):**
|
|
54
|
-
|
|
55
26
|
```yaml
|
|
27
|
+
# config/default.yaml
|
|
56
28
|
ad:
|
|
57
|
-
groupCacheTtlMs: 600000
|
|
58
|
-
dnCacheTtlMs: 86400000 # 24 hours
|
|
29
|
+
groupCacheTtlMs: 600000
|
|
59
30
|
domains:
|
|
60
31
|
CORP:
|
|
61
32
|
default: true
|
|
62
|
-
controllers:
|
|
63
|
-
- 'ldap://dc1.corp.com'
|
|
64
|
-
- 'ldap://dc2.corp.com'
|
|
33
|
+
controllers: ['ldap://dc1.corp.com']
|
|
65
34
|
username: 'svc_mcp@corp.com'
|
|
66
|
-
password: '${AD_SERVICE_PASSWORD}'
|
|
67
|
-
baseDn: 'DC=corp,DC=com' # Optional, auto-derived if omitted
|
|
68
|
-
PARTNER:
|
|
69
|
-
controllers:
|
|
70
|
-
- 'ldap://dc1.partner.local'
|
|
71
|
-
username: 'svc_reader@partner.local'
|
|
72
|
-
password: '${AD_PARTNER_PASSWORD}'
|
|
35
|
+
password: '${AD_SERVICE_PASSWORD}'
|
|
73
36
|
```
|
|
74
37
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
## Configuration for AD Group Authorization
|
|
38
|
+
## Custom Config Extension
|
|
78
39
|
|
|
79
|
-
First, extend your configuration to include the required AD group:
|
|
80
|
-
|
|
81
|
-
**`src/_types_/custom-config.ts`:**
|
|
82
40
|
```typescript
|
|
41
|
+
// src/_types_/custom-config.ts
|
|
83
42
|
import { AppConfig } from 'fa-mcp-sdk';
|
|
84
43
|
|
|
85
|
-
|
|
86
|
-
groupAccess: {
|
|
87
|
-
/** AD group required for access */
|
|
88
|
-
requiredGroup: string;
|
|
89
|
-
/** Bypass group check for debugging (default: false) */
|
|
90
|
-
bypassGroupCheck?: boolean;
|
|
91
|
-
/** Cache TTL in seconds (default: 300) */
|
|
92
|
-
cacheTtlSeconds?: number;
|
|
93
|
-
};
|
|
44
|
+
interface IGroupAccessConfig {
|
|
45
|
+
groupAccess: { requiredGroup: string; bypassGroupCheck?: boolean };
|
|
94
46
|
}
|
|
95
|
-
|
|
96
47
|
export interface CustomAppConfig extends AppConfig, IGroupAccessConfig {}
|
|
97
48
|
```
|
|
98
49
|
|
|
99
|
-
**`config/default.yaml`:**
|
|
100
50
|
```yaml
|
|
51
|
+
# config/default.yaml
|
|
101
52
|
groupAccess:
|
|
102
53
|
requiredGroup: "DOMAIN\\MCP-Users"
|
|
103
54
|
bypassGroupCheck: false
|
|
104
|
-
cacheTtlSeconds: 300
|
|
105
55
|
```
|
|
106
56
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
## Example 1: HTTP Server Level Access Restriction
|
|
57
|
+
## Example 1: HTTP Level Restriction
|
|
110
58
|
|
|
111
|
-
|
|
112
|
-
If the user is not in the required group, a 403 Forbidden error is returned before any
|
|
113
|
-
MCP request processing.
|
|
59
|
+
Block unauthorized users at HTTP level (403 before MCP processing):
|
|
114
60
|
|
|
115
|
-
**`src/start.ts`:**
|
|
116
61
|
```typescript
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
initMcpServer,
|
|
120
|
-
McpServerData,
|
|
121
|
-
CustomAuthValidator,
|
|
122
|
-
AuthResult,
|
|
123
|
-
initADGroupChecker,
|
|
124
|
-
} from 'fa-mcp-sdk';
|
|
125
|
-
// Note: JWT validation is handled automatically by the SDK's multi-auth system.
|
|
126
|
-
// The customAuthValidator receives parsed JWT payload when jwtToken auth is enabled.
|
|
127
|
-
import { tools } from './tools/tools.js';
|
|
128
|
-
import { handleToolCall } from './tools/handle-tool-call.js';
|
|
129
|
-
import { AGENT_BRIEF } from './prompts/agent-brief.js';
|
|
130
|
-
import { AGENT_PROMPT } from './prompts/agent-prompt.js';
|
|
62
|
+
// src/start.ts
|
|
63
|
+
import { appConfig, initMcpServer, CustomAuthValidator, initADGroupChecker } from 'fa-mcp-sdk';
|
|
131
64
|
import { CustomAppConfig } from './_types_/custom-config.js';
|
|
132
65
|
|
|
133
|
-
// Get typed config
|
|
134
66
|
const config = appConfig as CustomAppConfig;
|
|
135
|
-
|
|
136
|
-
// Initialize AD group checker
|
|
137
67
|
const { isUserInGroup } = initADGroupChecker();
|
|
138
68
|
|
|
139
|
-
|
|
140
|
-
* Custom authentication validator with AD group membership check
|
|
141
|
-
* This validator is called AFTER the SDK's standard JWT validation.
|
|
142
|
-
* The req object contains parsed authInfo from the standard auth flow.
|
|
143
|
-
* Returns 403 Forbidden if user is not in the required AD group
|
|
144
|
-
*/
|
|
145
|
-
const customAuthValidator: CustomAuthValidator = async (req): Promise<AuthResult> => {
|
|
146
|
-
// Extract user info from request (populated by standard auth)
|
|
69
|
+
const customAuthValidator: CustomAuthValidator = async (req) => {
|
|
147
70
|
const authInfo = (req as any).authInfo;
|
|
71
|
+
if (!authInfo?.username) return { success: false, error: 'User info unavailable' };
|
|
148
72
|
|
|
149
|
-
// If standard auth failed or no user info, let standard auth handle it
|
|
150
|
-
if (!authInfo?.username) {
|
|
151
|
-
return { success: false, error: 'User information not available' };
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const username = authInfo.username;
|
|
155
|
-
|
|
156
|
-
// Bypass group check if configured (for debugging)
|
|
157
73
|
if (config.groupAccess.bypassGroupCheck) {
|
|
158
|
-
return {
|
|
159
|
-
success: true,
|
|
160
|
-
authType: authInfo.authType,
|
|
161
|
-
username,
|
|
162
|
-
payload: authInfo.payload,
|
|
163
|
-
};
|
|
74
|
+
return { success: true, authType: authInfo.authType, username: authInfo.username };
|
|
164
75
|
}
|
|
165
76
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const isInGroup = await isUserInGroup(username, requiredGroup);
|
|
170
|
-
|
|
171
|
-
if (!isInGroup) {
|
|
172
|
-
return {
|
|
173
|
-
success: false,
|
|
174
|
-
error: `Forbidden: User '${username}' is not a member of group '${requiredGroup}'`,
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return {
|
|
179
|
-
success: true,
|
|
180
|
-
authType: authInfo.authType,
|
|
181
|
-
username,
|
|
182
|
-
payload: authInfo.payload,
|
|
183
|
-
};
|
|
184
|
-
} catch (error) {
|
|
185
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
186
|
-
return {
|
|
187
|
-
success: false,
|
|
188
|
-
error: `AD group check failed: ${errorMessage}`,
|
|
189
|
-
};
|
|
77
|
+
const isInGroup = await isUserInGroup(authInfo.username, config.groupAccess.requiredGroup);
|
|
78
|
+
if (!isInGroup) {
|
|
79
|
+
return { success: false, error: `Forbidden: Not in group '${config.groupAccess.requiredGroup}'` };
|
|
190
80
|
}
|
|
191
|
-
};
|
|
192
81
|
|
|
193
|
-
|
|
194
|
-
const serverData: McpServerData = {
|
|
195
|
-
tools,
|
|
196
|
-
toolHandler: handleToolCall,
|
|
197
|
-
agentBrief: AGENT_BRIEF,
|
|
198
|
-
agentPrompt: AGENT_PROMPT,
|
|
199
|
-
|
|
200
|
-
// Enable custom authentication with AD group check
|
|
201
|
-
customAuthValidator,
|
|
202
|
-
|
|
203
|
-
// ... other configuration
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
await initMcpServer(serverData);
|
|
82
|
+
return { success: true, authType: authInfo.authType, username: authInfo.username };
|
|
207
83
|
};
|
|
208
84
|
|
|
209
|
-
|
|
85
|
+
await initMcpServer({ ..., customAuthValidator });
|
|
210
86
|
```
|
|
211
87
|
|
|
212
|
-
|
|
213
|
-
response before any MCP processing occurs.
|
|
214
|
-
|
|
215
|
-
---
|
|
88
|
+
## Example 2: All Tools Restriction
|
|
216
89
|
|
|
217
|
-
|
|
90
|
+
Check in `toolHandler` (MCP error response):
|
|
218
91
|
|
|
219
|
-
This example restricts access to all MCP tools by checking AD group membership in the
|
|
220
|
-
`toolHandler` function. If the user is not in the required group, the tool call returns
|
|
221
|
-
an MCP error with "Forbidden" message.
|
|
222
|
-
|
|
223
|
-
**`src/tools/handle-tool-call.ts`:**
|
|
224
92
|
```typescript
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
ToolExecutionError,
|
|
228
|
-
logger,
|
|
229
|
-
appConfig,
|
|
230
|
-
initADGroupChecker,
|
|
231
|
-
IToolHandlerParams,
|
|
232
|
-
} from 'fa-mcp-sdk';
|
|
93
|
+
// src/tools/handle-tool-call.ts
|
|
94
|
+
import { ToolExecutionError, appConfig, initADGroupChecker, IToolHandlerParams } from 'fa-mcp-sdk';
|
|
233
95
|
import { CustomAppConfig } from '../_types_/custom-config.js';
|
|
234
96
|
|
|
235
|
-
// Get typed config
|
|
236
97
|
const config = appConfig as CustomAppConfig;
|
|
237
|
-
|
|
238
|
-
// Initialize AD group checker
|
|
239
98
|
const { isUserInGroup } = initADGroupChecker();
|
|
240
99
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
async function checkToolAccess(payload: IToolHandlerParams['payload']): Promise<void> {
|
|
245
|
-
// Skip check if bypass is enabled
|
|
246
|
-
if (config.groupAccess.bypassGroupCheck) {
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
100
|
+
async function checkToolAccess(payload: IToolHandlerParams['payload']) {
|
|
101
|
+
if (config.groupAccess.bypassGroupCheck) return;
|
|
102
|
+
if (!payload?.user) throw new ToolExecutionError('auth', 'User info unavailable');
|
|
249
103
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const username = payload.user;
|
|
255
|
-
const requiredGroup = config.groupAccess.requiredGroup;
|
|
256
|
-
|
|
257
|
-
try {
|
|
258
|
-
const isInGroup = await isUserInGroup(username, requiredGroup);
|
|
259
|
-
|
|
260
|
-
if (!isInGroup) {
|
|
261
|
-
throw new ToolExecutionError(
|
|
262
|
-
'authorization',
|
|
263
|
-
`Forbidden: User '${username}' is not authorized to use MCP tools. ` +
|
|
264
|
-
`Required group: '${requiredGroup}'`
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
} catch (error) {
|
|
268
|
-
if (error instanceof ToolExecutionError) {
|
|
269
|
-
throw error;
|
|
270
|
-
}
|
|
271
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
272
|
-
throw new ToolExecutionError('authorization', `Forbidden: AD group check failed - ${errorMessage}`);
|
|
104
|
+
const isInGroup = await isUserInGroup(payload.user, config.groupAccess.requiredGroup);
|
|
105
|
+
if (!isInGroup) {
|
|
106
|
+
throw new ToolExecutionError('auth', `Forbidden: User not in '${config.groupAccess.requiredGroup}'`);
|
|
273
107
|
}
|
|
274
108
|
}
|
|
275
109
|
|
|
276
|
-
export const handleToolCall = async (params: IToolHandlerParams)
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
logger.info(`Tool called: ${name} by user: ${payload?.user || 'unknown'}`);
|
|
280
|
-
|
|
281
|
-
// Check AD group membership for ALL tools
|
|
282
|
-
await checkToolAccess(payload);
|
|
283
|
-
|
|
284
|
-
try {
|
|
285
|
-
switch (name) {
|
|
286
|
-
case 'my_tool':
|
|
287
|
-
return await handleMyTool(args);
|
|
288
|
-
case 'another_tool':
|
|
289
|
-
return await handleAnotherTool(args);
|
|
290
|
-
default:
|
|
291
|
-
throw new ToolExecutionError(name, `Unknown tool: ${name}`);
|
|
292
|
-
}
|
|
293
|
-
} catch (error) {
|
|
294
|
-
logger.error(`Tool execution failed for ${name}:`, error);
|
|
295
|
-
throw error;
|
|
296
|
-
}
|
|
110
|
+
export const handleToolCall = async (params: IToolHandlerParams) => {
|
|
111
|
+
await checkToolAccess(params.payload); // Check ALL tools
|
|
112
|
+
// ... tool switch logic
|
|
297
113
|
};
|
|
298
|
-
|
|
299
|
-
async function handleMyTool(args: any): Promise<any> {
|
|
300
|
-
// Tool implementation
|
|
301
|
-
return formatToolResult({ message: 'Tool executed successfully', args });
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
async function handleAnotherTool(args: any): Promise<any> {
|
|
305
|
-
// Tool implementation
|
|
306
|
-
return formatToolResult({ message: 'Another tool executed', args });
|
|
307
|
-
}
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
**Result**: If the user is not in the required AD group, any tool call returns an MCP error:
|
|
311
|
-
```json
|
|
312
|
-
{
|
|
313
|
-
"jsonrpc": "2.0",
|
|
314
|
-
"error": {
|
|
315
|
-
"code": -32603,
|
|
316
|
-
"message": "Forbidden: User 'john.doe' is not authorized to use MCP tools. Required group: 'DOMAIN\\MCP-Users'"
|
|
317
|
-
},
|
|
318
|
-
"id": 1
|
|
319
|
-
}
|
|
320
114
|
```
|
|
321
115
|
|
|
322
|
-
|
|
116
|
+
## Example 3: Per-Tool Restriction
|
|
323
117
|
|
|
324
|
-
|
|
118
|
+
Different groups for different tools:
|
|
325
119
|
|
|
326
|
-
This example restricts access to specific MCP tools based on AD group membership.
|
|
327
|
-
Different tools can require different AD groups.
|
|
328
|
-
|
|
329
|
-
**`src/_types_/custom-config.ts`:**
|
|
330
120
|
```typescript
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
export interface IToolGroupAccessConfig {
|
|
121
|
+
// src/_types_/custom-config.ts
|
|
122
|
+
interface IToolGroupAccessConfig {
|
|
334
123
|
toolGroupAccess: {
|
|
335
|
-
/** Default group required for tools without specific configuration */
|
|
336
124
|
defaultGroup?: string;
|
|
337
|
-
|
|
338
|
-
tools: Record<string, {
|
|
339
|
-
/** AD group required for this tool */
|
|
340
|
-
requiredGroup: string;
|
|
341
|
-
/** Allow access without group check (default: false) */
|
|
342
|
-
public?: boolean;
|
|
343
|
-
}>;
|
|
344
|
-
/** Bypass all group checks (for debugging) */
|
|
125
|
+
tools: Record<string, { requiredGroup?: string; public?: boolean }>;
|
|
345
126
|
bypassGroupCheck?: boolean;
|
|
346
127
|
};
|
|
347
128
|
}
|
|
348
|
-
|
|
349
|
-
export interface CustomAppConfig extends AppConfig, IToolGroupAccessConfig {}
|
|
350
129
|
```
|
|
351
130
|
|
|
352
|
-
**`config/default.yaml`:**
|
|
353
131
|
```yaml
|
|
132
|
+
# config/default.yaml
|
|
354
133
|
toolGroupAccess:
|
|
355
134
|
defaultGroup: "DOMAIN\\MCP-Users"
|
|
356
135
|
bypassGroupCheck: false
|
|
357
136
|
tools:
|
|
358
137
|
get_public_data:
|
|
359
|
-
public: true
|
|
138
|
+
public: true
|
|
360
139
|
get_user_data:
|
|
361
140
|
requiredGroup: "DOMAIN\\MCP-Users"
|
|
362
|
-
modify_data:
|
|
363
|
-
requiredGroup: "DOMAIN\\MCP-DataModifiers"
|
|
364
141
|
admin_operation:
|
|
365
142
|
requiredGroup: "DOMAIN\\MCP-Admins"
|
|
366
143
|
```
|
|
367
144
|
|
|
368
|
-
**`src/tools/handle-tool-call.ts`:**
|
|
369
145
|
```typescript
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
ToolExecutionError,
|
|
373
|
-
logger,
|
|
374
|
-
appConfig,
|
|
375
|
-
initADGroupChecker,
|
|
376
|
-
IToolHandlerParams,
|
|
377
|
-
} from 'fa-mcp-sdk';
|
|
378
|
-
import { CustomAppConfig } from '../_types_/custom-config.js';
|
|
379
|
-
|
|
380
|
-
// Get typed config
|
|
381
|
-
const config = appConfig as CustomAppConfig;
|
|
382
|
-
|
|
383
|
-
// Initialize AD group checker
|
|
384
|
-
const { isUserInGroup } = initADGroupChecker();
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Check if user has access to a specific tool based on AD group membership
|
|
388
|
-
*/
|
|
389
|
-
async function checkToolAccess(
|
|
390
|
-
toolName: string,
|
|
391
|
-
payload: IToolHandlerParams['payload']
|
|
392
|
-
): Promise<void> {
|
|
146
|
+
// src/tools/handle-tool-call.ts
|
|
147
|
+
async function checkToolAccess(toolName: string, payload: IToolHandlerParams['payload']) {
|
|
393
148
|
const toolAccess = config.toolGroupAccess;
|
|
394
|
-
|
|
395
|
-
// Skip check if bypass is enabled
|
|
396
|
-
if (toolAccess.bypassGroupCheck) {
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
149
|
+
if (toolAccess.bypassGroupCheck) return;
|
|
399
150
|
|
|
400
151
|
const toolConfig = toolAccess.tools[toolName];
|
|
152
|
+
if (toolConfig?.public) return;
|
|
401
153
|
|
|
402
|
-
|
|
403
|
-
if (toolConfig?.public) {
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// Check user availability
|
|
408
|
-
if (!payload?.user) {
|
|
409
|
-
throw new ToolExecutionError(
|
|
410
|
-
toolName,
|
|
411
|
-
`Forbidden: User information not available for tool '${toolName}'`
|
|
412
|
-
);
|
|
413
|
-
}
|
|
154
|
+
if (!payload?.user) throw new ToolExecutionError(toolName, 'User info unavailable');
|
|
414
155
|
|
|
415
|
-
const username = payload.user;
|
|
416
|
-
|
|
417
|
-
// Determine required group: tool-specific or default
|
|
418
156
|
const requiredGroup = toolConfig?.requiredGroup || toolAccess.defaultGroup;
|
|
157
|
+
if (!requiredGroup) return;
|
|
419
158
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
try {
|
|
426
|
-
const isInGroup = await isUserInGroup(username, requiredGroup);
|
|
427
|
-
|
|
428
|
-
if (!isInGroup) {
|
|
429
|
-
throw new ToolExecutionError(
|
|
430
|
-
toolName,
|
|
431
|
-
`Forbidden: User '${username}' is not authorized to use tool '${toolName}'. ` +
|
|
432
|
-
`Required group: '${requiredGroup}'`
|
|
433
|
-
);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
logger.info(`User '${username}' authorized for tool '${toolName}' via group '${requiredGroup}'`);
|
|
437
|
-
} catch (error) {
|
|
438
|
-
if (error instanceof ToolExecutionError) {
|
|
439
|
-
throw error;
|
|
440
|
-
}
|
|
441
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
442
|
-
throw new ToolExecutionError(
|
|
443
|
-
toolName,
|
|
444
|
-
`Forbidden: AD group check failed for tool '${toolName}' - ${errorMessage}`
|
|
445
|
-
);
|
|
159
|
+
const isInGroup = await isUserInGroup(payload.user, requiredGroup);
|
|
160
|
+
if (!isInGroup) {
|
|
161
|
+
throw new ToolExecutionError(toolName, `Forbidden: User not in '${requiredGroup}'`);
|
|
446
162
|
}
|
|
447
163
|
}
|
|
448
164
|
|
|
449
|
-
export const handleToolCall = async (params: IToolHandlerParams)
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
logger.info(`Tool called: ${name} by user: ${payload?.user || 'unknown'}`);
|
|
453
|
-
|
|
454
|
-
// Check AD group membership for the specific tool
|
|
455
|
-
await checkToolAccess(name, payload);
|
|
456
|
-
|
|
457
|
-
try {
|
|
458
|
-
switch (name) {
|
|
459
|
-
case 'get_public_data':
|
|
460
|
-
// Public tool - no group check was performed
|
|
461
|
-
return await handleGetPublicData(args);
|
|
462
|
-
|
|
463
|
-
case 'get_user_data':
|
|
464
|
-
// Requires MCP-Users group
|
|
465
|
-
return await handleGetUserData(args);
|
|
466
|
-
|
|
467
|
-
case 'modify_data':
|
|
468
|
-
// Requires MCP-DataModifiers group
|
|
469
|
-
return await handleModifyData(args);
|
|
470
|
-
|
|
471
|
-
case 'admin_operation':
|
|
472
|
-
// Requires MCP-Admins group
|
|
473
|
-
return await handleAdminOperation(args);
|
|
474
|
-
|
|
475
|
-
default:
|
|
476
|
-
// Unknown tools use defaultGroup if configured
|
|
477
|
-
throw new ToolExecutionError(name, `Unknown tool: ${name}`);
|
|
478
|
-
}
|
|
479
|
-
} catch (error) {
|
|
480
|
-
logger.error(`Tool execution failed for ${name}:`, error);
|
|
481
|
-
throw error;
|
|
482
|
-
}
|
|
165
|
+
export const handleToolCall = async (params: IToolHandlerParams) => {
|
|
166
|
+
await checkToolAccess(params.name, params.payload);
|
|
167
|
+
// ... tool switch logic
|
|
483
168
|
};
|
|
484
|
-
|
|
485
|
-
async function handleGetPublicData(args: any): Promise<any> {
|
|
486
|
-
return formatToolResult({ message: 'Public data retrieved', data: { public: true } });
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
async function handleGetUserData(args: any): Promise<any> {
|
|
490
|
-
return formatToolResult({ message: 'User data retrieved', data: args });
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
async function handleModifyData(args: any): Promise<any> {
|
|
494
|
-
return formatToolResult({ message: 'Data modified', modified: args });
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
async function handleAdminOperation(args: any): Promise<any> {
|
|
498
|
-
return formatToolResult({ message: 'Admin operation completed', operation: args });
|
|
499
|
-
}
|
|
500
169
|
```
|
|
501
170
|
|
|
502
|
-
|
|
503
|
-
- `get_public_data` - accessible to everyone (public)
|
|
504
|
-
- `get_user_data` - requires `DOMAIN\MCP-Users` group
|
|
505
|
-
- `modify_data` - requires `DOMAIN\MCP-DataModifiers` group
|
|
506
|
-
- `admin_operation` - requires `DOMAIN\MCP-Admins` group
|
|
507
|
-
|
|
508
|
-
If a user tries to call a tool without being in the required group:
|
|
509
|
-
```json
|
|
510
|
-
{
|
|
511
|
-
"jsonrpc": "2.0",
|
|
512
|
-
"error": {
|
|
513
|
-
"code": -32603,
|
|
514
|
-
"message": "Forbidden: User 'john.doe' is not authorized to use tool 'admin_operation'. Required group: 'DOMAIN\\MCP-Admins'"
|
|
515
|
-
},
|
|
516
|
-
"id": 1
|
|
517
|
-
}
|
|
518
|
-
```
|
|
519
|
-
|
|
520
|
-
---
|
|
521
|
-
|
|
522
|
-
## Summary: Authorization Levels
|
|
171
|
+
## Authorization Levels Summary
|
|
523
172
|
|
|
524
173
|
| Level | Location | Error Type | Use Case |
|
|
525
174
|
|-------|----------|------------|----------|
|
|
526
|
-
| HTTP Server | `customAuthValidator` | HTTP 403
|
|
527
|
-
| All Tools | `toolHandler` (global
|
|
528
|
-
|
|
|
175
|
+
| HTTP Server | `customAuthValidator` | HTTP 403 | Block completely |
|
|
176
|
+
| All Tools | `toolHandler` (global) | MCP Error | Allow HTTP, restrict tools |
|
|
177
|
+
| Per Tool | `toolHandler` (per-tool) | MCP Error | Fine-grained permissions |
|