fa-mcp-sdk 0.2.125 → 0.2.132
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/bin/fa-mcp.js +46 -12
- package/cli-template/config/_local.yaml +29 -11
- package/cli-template/config/custom-environment-variables.yaml +0 -6
- package/cli-template/config/default.yaml +34 -14
- package/cli-template/fa-mcp-sdk-spec.md +396 -189
- package/dist/core/_types_/config.d.ts +4 -17
- package/dist/core/_types_/config.d.ts.map +1 -1
- package/dist/core/_types_/types.d.ts +12 -15
- package/dist/core/_types_/types.d.ts.map +1 -1
- package/dist/core/auth/middleware.d.ts +9 -37
- package/dist/core/auth/middleware.d.ts.map +1 -1
- package/dist/core/auth/middleware.js +31 -146
- package/dist/core/auth/middleware.js.map +1 -1
- package/dist/core/auth/multi-auth.d.ts +10 -14
- package/dist/core/auth/multi-auth.d.ts.map +1 -1
- package/dist/core/auth/multi-auth.js +133 -220
- package/dist/core/auth/multi-auth.js.map +1 -1
- package/dist/core/auth/types.d.ts +1 -7
- package/dist/core/auth/types.d.ts.map +1 -1
- package/dist/core/auth/types.js +1 -10
- package/dist/core/auth/types.js.map +1 -1
- package/dist/core/bootstrap/init-config.d.ts.map +1 -1
- package/dist/core/bootstrap/init-config.js +4 -0
- package/dist/core/bootstrap/init-config.js.map +1 -1
- package/dist/core/index.d.ts +6 -6
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +5 -4
- package/dist/core/index.js.map +1 -1
- package/dist/core/utils/utils.d.ts +6 -0
- package/dist/core/utils/utils.d.ts.map +1 -1
- package/dist/core/utils/utils.js +25 -0
- package/dist/core/utils/utils.js.map +1 -1
- package/dist/core/web/server-http.d.ts.map +1 -1
- package/dist/core/web/server-http.js +32 -18
- package/dist/core/web/server-http.js.map +1 -1
- package/package.json +1 -1
- package/cli-template/src/_examples/custom-basic-auth-example.ts +0 -252
- package/cli-template/src/_examples/multi-auth-examples.ts +0 -333
- package/cli-template/src/_types_/common.d.ts +0 -27
- package/cli-template/src/api/router.ts +0 -35
- package/cli-template/src/api/swagger.ts +0 -167
- package/cli-template/src/asset/favicon.svg +0 -3
- package/cli-template/src/custom-resources.ts +0 -12
- package/cli-template/src/prompts/agent-brief.ts +0 -8
- package/cli-template/src/prompts/agent-prompt.ts +0 -10
- package/cli-template/src/prompts/custom-prompts.ts +0 -12
- package/cli-template/src/start.ts +0 -71
- package/cli-template/src/tools/handle-tool-call.ts +0 -55
- package/cli-template/src/tools/tools.ts +0 -88
- package/cli-template/tests/jest-simple-reporter.js +0 -10
- package/cli-template/tests/mcp/sse/mcp-sse-client-handling.md +0 -111
- package/cli-template/tests/mcp/sse/test-sse-npm-package.js +0 -96
- package/cli-template/tests/mcp/test-cases.js +0 -143
- package/cli-template/tests/mcp/test-http.js +0 -63
- package/cli-template/tests/mcp/test-sse.js +0 -67
- package/cli-template/tests/mcp/test-stdio.js +0 -78
- package/cli-template/tests/utils.ts +0 -154
- package/cli-template/yarn.lock +0 -6375
|
@@ -58,16 +58,36 @@ The primary function for starting your MCP server.
|
|
|
58
58
|
**Example Usage in `src/start.ts`:**
|
|
59
59
|
|
|
60
60
|
```typescript
|
|
61
|
-
import { initMcpServer, McpServerData,
|
|
61
|
+
import { initMcpServer, McpServerData, CustomAuthValidator } from 'fa-mcp-sdk';
|
|
62
62
|
import { tools } from './tools/tools.js';
|
|
63
63
|
import { handleToolCall } from './tools/handle-tool-call.js';
|
|
64
64
|
import { AGENT_BRIEF } from './prompts/agent-brief.js';
|
|
65
65
|
import { AGENT_PROMPT } from './prompts/agent-prompt.js';
|
|
66
66
|
|
|
67
|
-
// Optional: Custom
|
|
68
|
-
const customAuthValidator:
|
|
69
|
-
// Your custom authentication logic here
|
|
70
|
-
|
|
67
|
+
// Optional: Custom Authentication validator (black box function)
|
|
68
|
+
const customAuthValidator: CustomAuthValidator = async (req): Promise<AuthResult> => {
|
|
69
|
+
// Your custom authentication logic here - full request object available
|
|
70
|
+
// Can access headers, IP, user-agent, etc.
|
|
71
|
+
const authHeader = req.headers.authorization;
|
|
72
|
+
const userID = req.headers['x-user-id'];
|
|
73
|
+
const clientIP = req.headers['x-real-ip'] || req.connection?.remoteAddress;
|
|
74
|
+
|
|
75
|
+
// Implement any authentication logic (database, LDAP, API, custom rules, etc.)
|
|
76
|
+
const isAuthenticated = await authenticateRequest(req);
|
|
77
|
+
|
|
78
|
+
if (isAuthenticated) {
|
|
79
|
+
return {
|
|
80
|
+
success: true,
|
|
81
|
+
authType: 'basic',
|
|
82
|
+
tokenType: 'custom',
|
|
83
|
+
username: userID || 'unknown',
|
|
84
|
+
};
|
|
85
|
+
} else {
|
|
86
|
+
return {
|
|
87
|
+
success: false,
|
|
88
|
+
error: 'Custom authentication failed',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
71
91
|
};
|
|
72
92
|
|
|
73
93
|
const serverData: McpServerData = {
|
|
@@ -76,8 +96,8 @@ const serverData: McpServerData = {
|
|
|
76
96
|
agentBrief: AGENT_BRIEF,
|
|
77
97
|
agentPrompt: AGENT_PROMPT,
|
|
78
98
|
|
|
79
|
-
// Optional: Provide custom
|
|
80
|
-
|
|
99
|
+
// Optional: Provide custom authentication function
|
|
100
|
+
customAuthValidator: customAuthValidator,
|
|
81
101
|
|
|
82
102
|
// ... other configuration
|
|
83
103
|
};
|
|
@@ -95,7 +115,7 @@ Main configuration interface for your MCP server.
|
|
|
95
115
|
interface McpServerData {
|
|
96
116
|
// MCP Core Components
|
|
97
117
|
tools: Tool[]; // Your tool definitions
|
|
98
|
-
toolHandler: (params: { name: string; arguments?: any }) => Promise<any>; // Tool execution function
|
|
118
|
+
toolHandler: (params: { name: string; arguments?: any; headers?: Record<string, string> }) => Promise<any>; // Tool execution function
|
|
99
119
|
|
|
100
120
|
// Agent Configuration
|
|
101
121
|
agentBrief: string; // Brief description of your agent
|
|
@@ -107,7 +127,7 @@ interface McpServerData {
|
|
|
107
127
|
customResources?: IResourceData[] | null; // Custom resource definitions
|
|
108
128
|
|
|
109
129
|
// Authentication
|
|
110
|
-
|
|
130
|
+
customAuthValidator?: CustomAuthValidator; // Custom authentication validator function
|
|
111
131
|
|
|
112
132
|
// HTTP Server Components (for HTTP transport)
|
|
113
133
|
httpComponents?: {
|
|
@@ -229,11 +249,19 @@ export const tools: Tool[] = [
|
|
|
229
249
|
```typescript
|
|
230
250
|
import { formatToolResult, ToolExecutionError, logger } from 'fa-mcp-sdk';
|
|
231
251
|
|
|
232
|
-
export const handleToolCall = async (params: { name: string, arguments?: any }): Promise<any> => {
|
|
233
|
-
const { name, arguments: args } = params;
|
|
252
|
+
export const handleToolCall = async (params: { name: string, arguments?: any, headers?: Record<string, string> }): Promise<any> => {
|
|
253
|
+
const { name, arguments: args, headers } = params;
|
|
234
254
|
|
|
235
255
|
logger.info(`Tool called: ${name}`);
|
|
236
256
|
|
|
257
|
+
// Access normalized HTTP headers (all header names are lowercase)
|
|
258
|
+
if (headers) {
|
|
259
|
+
const authHeader = headers.authorization;
|
|
260
|
+
const userAgent = headers['user-agent'];
|
|
261
|
+
const customHeader = headers['x-custom-header'];
|
|
262
|
+
logger.info(`Headers available: authorization=${!!authHeader}, user-agent=${userAgent}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
237
265
|
try {
|
|
238
266
|
switch (name) {
|
|
239
267
|
case 'my_custom_tool':
|
|
@@ -266,6 +294,88 @@ async function handleMyCustomTool(args: any): Promise<string> {
|
|
|
266
294
|
}
|
|
267
295
|
```
|
|
268
296
|
|
|
297
|
+
#### HTTP Headers in Tool Handler
|
|
298
|
+
|
|
299
|
+
The FA-MCP-SDK automatically passes normalized HTTP headers to your `toolHandler` function, enabling context-aware tool execution based on client information.
|
|
300
|
+
|
|
301
|
+
**Key Features:**
|
|
302
|
+
- All headers are automatically normalized to lowercase
|
|
303
|
+
- Available in both HTTP and SSE transports (SSE provides empty headers object)
|
|
304
|
+
- Headers are sanitized and only string values are passed
|
|
305
|
+
- Array header values are joined with `', '` separator
|
|
306
|
+
|
|
307
|
+
**Example Usage:**
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
export const handleToolCall = async (params: {
|
|
311
|
+
name: string,
|
|
312
|
+
arguments?: any,
|
|
313
|
+
headers?: Record<string, string>
|
|
314
|
+
}): Promise<any> => {
|
|
315
|
+
const { name, arguments: args, headers } = params;
|
|
316
|
+
|
|
317
|
+
// Access client information via headers
|
|
318
|
+
if (headers) {
|
|
319
|
+
const authHeader = headers.authorization; // Lowercase normalized
|
|
320
|
+
const userAgent = headers['user-agent']; // Browser/client info
|
|
321
|
+
const clientIP = headers['x-real-ip'] || headers['x-forwarded-for']; // Proxy headers
|
|
322
|
+
const customData = headers['x-custom-header']; // Custom headers
|
|
323
|
+
|
|
324
|
+
logger.info(`Tool ${name} called by ${userAgent} from IP ${clientIP}`);
|
|
325
|
+
|
|
326
|
+
// Conditional logic based on client
|
|
327
|
+
if (userAgent?.includes('mobile')) {
|
|
328
|
+
return await handleMobileRequest(args);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Custom authorization beyond standard auth
|
|
332
|
+
if (customData === 'admin-mode' && authHeader) {
|
|
333
|
+
return await handleAdminRequest(args);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Regular tool logic
|
|
338
|
+
switch (name) {
|
|
339
|
+
case 'get_user_data':
|
|
340
|
+
// Use headers for audit logging
|
|
341
|
+
return await getUserData(args, {
|
|
342
|
+
clientIP: headers?.['x-real-ip'],
|
|
343
|
+
userAgent: headers?.['user-agent']
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
**Header Normalization Details:**
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
// Original headers from client:
|
|
353
|
+
{
|
|
354
|
+
'Authorization': 'Bearer token123',
|
|
355
|
+
'X-Custom-Header': 'value',
|
|
356
|
+
'USER-AGENT': 'MyClient/1.0'
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Normalized headers passed to toolHandler:
|
|
360
|
+
{
|
|
361
|
+
'authorization': 'Bearer token123',
|
|
362
|
+
'x-custom-header': 'value',
|
|
363
|
+
'user-agent': 'MyClient/1.0'
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**Transport Differences:**
|
|
368
|
+
|
|
369
|
+
- **HTTP Transport**: Full headers available from Express request object
|
|
370
|
+
- **SSE Transport**: Headers preserved from initial SSE connection establishment (GET /sse request)
|
|
371
|
+
|
|
372
|
+
**Common Use Cases:**
|
|
373
|
+
- Client identification and analytics
|
|
374
|
+
- Custom authorization checks beyond standard authentication
|
|
375
|
+
- Request routing based on client capabilities
|
|
376
|
+
- Audit logging with client context
|
|
377
|
+
- Rate limiting per client type
|
|
378
|
+
|
|
269
379
|
### Configuration Management
|
|
270
380
|
|
|
271
381
|
#### Using `appConfig`
|
|
@@ -395,25 +505,45 @@ webServer:
|
|
|
395
505
|
port: {{port}}
|
|
396
506
|
# array of hosts that CORS skips
|
|
397
507
|
originHosts: ['localhost', '0.0.0.0']
|
|
508
|
+
# Authentication is configured here only when accessing the MCP server
|
|
509
|
+
# Authentication in services that enable tools, resources, and prompts
|
|
510
|
+
# is implemented more deeply. To do this, you need to use the information passed in HTTP headers
|
|
511
|
+
# You can also use a custom authorization function
|
|
398
512
|
auth:
|
|
399
|
-
enabled: false # Enables/disables
|
|
400
|
-
#
|
|
401
|
-
|
|
513
|
+
enabled: false # Enables/disables authorization
|
|
514
|
+
# ========================================================================
|
|
515
|
+
# PERMANENT SERVER TOKENS
|
|
516
|
+
# Static tokens for server-to-server communication
|
|
517
|
+
# CPU cost: O(1) - fastest authentication method
|
|
518
|
+
#
|
|
519
|
+
# To enable this authentication, you need to set auth.enabled = true
|
|
520
|
+
# and set one token of at least 20 characters in length
|
|
521
|
+
# ========================================================================
|
|
522
|
+
permanentServerTokens: [ ] # Add your server tokens here: ['token1', 'token2']
|
|
523
|
+
|
|
524
|
+
# ========================================================================
|
|
525
|
+
# JWT TOKEN WITH SYMMETRIC ENCRYPTION
|
|
526
|
+
# Custom JWT tokens with AES-256 encryption
|
|
527
|
+
# CPU cost: Medium - decryption + JSON parsing
|
|
528
|
+
#
|
|
529
|
+
# To enable this authentication, you need to set auth.enabled = true and set
|
|
530
|
+
# encryptKey to at least 20 characters
|
|
531
|
+
# ========================================================================
|
|
402
532
|
jwtToken:
|
|
403
|
-
# Symmetric encryption key to generate a token for this MCP
|
|
533
|
+
# Symmetric encryption key to generate a token for this MCP (minimum 8 chars)
|
|
404
534
|
encryptKey: '***'
|
|
405
535
|
# If webServer.auth.enabled and the parameter true, the service name and the service specified in the token will be checked
|
|
406
536
|
checkMCPName: true
|
|
407
|
-
|
|
408
|
-
#
|
|
409
|
-
#
|
|
410
|
-
#
|
|
411
|
-
#
|
|
412
|
-
#
|
|
413
|
-
#
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
537
|
+
|
|
538
|
+
# ========================================================================
|
|
539
|
+
# Basic Authentication - Base64 encoded username:password
|
|
540
|
+
# CPU cost: Medium - Base64 decoding + string comparison
|
|
541
|
+
# To enable this authentication, you need to set auth.enabled = true
|
|
542
|
+
# and set username and password to valid values
|
|
543
|
+
# ========================================================================
|
|
544
|
+
basic:
|
|
545
|
+
username: ''
|
|
546
|
+
password: '***'
|
|
417
547
|
```
|
|
418
548
|
|
|
419
549
|
**`config/local.yaml`** - local overrides. Usually contains secrets.
|
|
@@ -739,8 +869,6 @@ addErrorMessage(originalError, 'Database operation failed');
|
|
|
739
869
|
|
|
740
870
|
```typescript
|
|
741
871
|
import {
|
|
742
|
-
authByToken,
|
|
743
|
-
authTokenMW,
|
|
744
872
|
ICheckTokenResult,
|
|
745
873
|
checkToken,
|
|
746
874
|
generateToken
|
|
@@ -788,27 +916,20 @@ const generateToken = (user: string, liveTimeSec: number, payload?: any): string
|
|
|
788
916
|
// Example:
|
|
789
917
|
const token = generateToken('john_doe', 3600, { role: 'admin' }); // 1 hour token
|
|
790
918
|
|
|
791
|
-
// authByToken
|
|
792
|
-
//
|
|
793
|
-
const authByToken = (req: Request, res: Response): boolean {...}
|
|
919
|
+
// Deprecated: authByToken was replaced by createAuthMW universal middleware
|
|
920
|
+
// Use createAuthMW instead for all authentication scenarios:
|
|
794
921
|
|
|
795
|
-
// Example:
|
|
796
|
-
app.post('/api/secure', (req, res) => {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
922
|
+
// Example - Modern approach:
|
|
923
|
+
app.post('/api/secure', createAuthMW(), (req, res) => {
|
|
924
|
+
// User is authenticated, authInfo available on req
|
|
925
|
+
const authInfo = (req as any).authInfo;
|
|
926
|
+
res.json({
|
|
927
|
+
message: 'Access granted',
|
|
928
|
+
authType: authInfo?.authType,
|
|
929
|
+
username: authInfo?.username
|
|
930
|
+
});
|
|
802
931
|
});
|
|
803
932
|
|
|
804
|
-
// authTokenMW - Express middleware for token authentication
|
|
805
|
-
// Function Signature:
|
|
806
|
-
const authTokenMW = (req: Request, res: Response, next: NextFunction): void {...}
|
|
807
|
-
|
|
808
|
-
// Example:
|
|
809
|
-
import express from 'express';
|
|
810
|
-
const app = express();
|
|
811
|
-
app.use('/protected', authTokenMW); // Apply to all /protected/* routes
|
|
812
933
|
```
|
|
813
934
|
|
|
814
935
|
#### Token Generation
|
|
@@ -856,21 +977,20 @@ import {
|
|
|
856
977
|
AuthType,
|
|
857
978
|
AuthResult,
|
|
858
979
|
AuthDetectionResult,
|
|
859
|
-
|
|
980
|
+
CustomAuthValidator,
|
|
860
981
|
checkMultiAuth,
|
|
982
|
+
checkCombinedAuth,
|
|
861
983
|
detectAuthConfiguration,
|
|
862
984
|
logAuthConfiguration,
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
getAuthInfo,
|
|
866
|
-
getMultiAuthError
|
|
985
|
+
createAuthMW, // Universal authentication middleware
|
|
986
|
+
getMultiAuthError, // Programmatic authentication checking
|
|
867
987
|
} from 'fa-mcp-sdk';
|
|
868
988
|
|
|
869
989
|
// Authentication types in CPU priority order (low to high cost)
|
|
870
|
-
export type AuthType = 'permanentServerTokens' | '
|
|
990
|
+
export type AuthType = 'permanentServerTokens' | 'basic' | 'jwtToken';
|
|
871
991
|
|
|
872
|
-
// Custom
|
|
873
|
-
export type
|
|
992
|
+
// Custom Authentication validator function (black box - receives full request)
|
|
993
|
+
export type CustomAuthValidator = (req: any) => Promise<AuthResult> | AuthResult;
|
|
874
994
|
|
|
875
995
|
// Authentication result interface
|
|
876
996
|
export interface AuthResult {
|
|
@@ -879,7 +999,6 @@ export interface AuthResult {
|
|
|
879
999
|
authType?: AuthType;
|
|
880
1000
|
tokenType?: string;
|
|
881
1001
|
username?: string;
|
|
882
|
-
accessToken?: string;
|
|
883
1002
|
payload?: any;
|
|
884
1003
|
}
|
|
885
1004
|
|
|
@@ -894,16 +1013,12 @@ export interface AuthDetectionResult {
|
|
|
894
1013
|
##### Core Multi-Authentication Functions
|
|
895
1014
|
|
|
896
1015
|
```typescript
|
|
897
|
-
// checkMultiAuth - validate
|
|
1016
|
+
// checkMultiAuth - validate using all configured authentication methods
|
|
898
1017
|
// Function Signature:
|
|
899
|
-
async function checkMultiAuth(
|
|
900
|
-
token: string,
|
|
901
|
-
authConfig: AppConfig['webServer']['auth']
|
|
902
|
-
): Promise<AuthResult> {...}
|
|
1018
|
+
async function checkMultiAuth(req: Request): Promise<AuthResult> {...}
|
|
903
1019
|
|
|
904
1020
|
// Example:
|
|
905
|
-
const
|
|
906
|
-
const result = await checkMultiAuth('user_token', authConfig);
|
|
1021
|
+
const result = await checkMultiAuth(req);
|
|
907
1022
|
|
|
908
1023
|
if (result.success) {
|
|
909
1024
|
console.log(`Authenticated via ${result.authType} as ${result.username}`);
|
|
@@ -911,27 +1026,44 @@ if (result.success) {
|
|
|
911
1026
|
console.log('Authentication failed:', result.error);
|
|
912
1027
|
}
|
|
913
1028
|
|
|
1029
|
+
// checkCombinedAuth - validate using configured auth + custom validator
|
|
1030
|
+
// Function Signature:
|
|
1031
|
+
async function checkCombinedAuth( req: any ): Promise<AuthResult> {...}
|
|
1032
|
+
|
|
1033
|
+
// This is the enhanced function that:
|
|
1034
|
+
// 1. Runs standard MCP auth methods (if configured)
|
|
1035
|
+
// 2. Additionally runs custom validator (if configured)
|
|
1036
|
+
// 3. Can use custom validator as fallback if standard auth fails
|
|
1037
|
+
|
|
1038
|
+
// Example:
|
|
1039
|
+
const authResult = await checkCombinedAuth(req);
|
|
1040
|
+
|
|
1041
|
+
if (authResult.success) {
|
|
1042
|
+
console.log(`Authentication successful via ${authResult.authType}`);
|
|
1043
|
+
} else {
|
|
1044
|
+
console.log('Combined authentication failed:', authResult.error);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
914
1047
|
// detectAuthConfiguration - analyze auth configuration
|
|
915
1048
|
// Function Signature:
|
|
916
|
-
function detectAuthConfiguration(
|
|
1049
|
+
function detectAuthConfiguration(): AuthDetectionResult {...}
|
|
917
1050
|
|
|
918
1051
|
// Example:
|
|
919
|
-
const detection = detectAuthConfiguration(
|
|
1052
|
+
const detection = detectAuthConfiguration();
|
|
920
1053
|
console.log('Configured auth types:', detection.configured);
|
|
921
1054
|
console.log('Valid auth types:', detection.valid);
|
|
922
1055
|
console.log('Configuration errors:', detection.errors);
|
|
923
1056
|
|
|
924
1057
|
// logAuthConfiguration - log auth system status (debugging)
|
|
925
1058
|
// Function Signature:
|
|
926
|
-
function logAuthConfiguration(
|
|
1059
|
+
function logAuthConfiguration(): void {...}
|
|
927
1060
|
|
|
928
1061
|
// Example:
|
|
929
|
-
logAuthConfiguration(
|
|
1062
|
+
logAuthConfiguration();
|
|
930
1063
|
// Output:
|
|
931
1064
|
// Auth system configuration:
|
|
932
1065
|
// - enabled: true
|
|
933
|
-
// - configured types: permanentServerTokens, basic
|
|
934
|
-
// - valid types: permanentServerTokens, pat
|
|
1066
|
+
// - configured types: permanentServerTokens, basic
|
|
935
1067
|
```
|
|
936
1068
|
|
|
937
1069
|
##### Multi-Authentication Middleware
|
|
@@ -939,16 +1071,16 @@ logAuthConfiguration(appConfig.webServer.auth);
|
|
|
939
1071
|
```typescript
|
|
940
1072
|
import express from 'express';
|
|
941
1073
|
import {
|
|
942
|
-
|
|
943
|
-
createConfigurableAuthMiddleware,
|
|
1074
|
+
createAuthMW,
|
|
944
1075
|
getMultiAuthError,
|
|
945
|
-
getAuthInfo
|
|
946
1076
|
} from 'fa-mcp-sdk';
|
|
947
1077
|
|
|
948
|
-
//
|
|
949
|
-
// Automatically detects if multi-auth is needed based on configuration
|
|
1078
|
+
// Universal authentication middleware with flexible options
|
|
950
1079
|
const app = express();
|
|
951
|
-
|
|
1080
|
+
|
|
1081
|
+
// Basic usage - handles all authentication scenarios automatically
|
|
1082
|
+
const authMW = createAuthMW();
|
|
1083
|
+
app.use('/api', authMW);
|
|
952
1084
|
|
|
953
1085
|
app.get('/api/protected', (req, res) => {
|
|
954
1086
|
const authInfo = (req as any).authInfo;
|
|
@@ -960,24 +1092,34 @@ app.get('/api/protected', (req, res) => {
|
|
|
960
1092
|
});
|
|
961
1093
|
});
|
|
962
1094
|
|
|
963
|
-
//
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
logConfiguration?: boolean;
|
|
968
|
-
} = {}): (req: Request, res: Response, next: NextFunction) => void {...}
|
|
969
|
-
|
|
970
|
-
// Example:
|
|
971
|
-
const authMW = createConfigurableAuthMiddleware({
|
|
972
|
-
logConfiguration: true, // Log auth config on first request
|
|
973
|
-
forceMultiAuth: false // Auto-detect multi-auth need
|
|
1095
|
+
// Advanced usage with custom options
|
|
1096
|
+
const customAuthMW = createAuthMW({
|
|
1097
|
+
mcpPaths: ['/mcp', '/messages', '/sse', '/custom'], // Custom MCP paths
|
|
1098
|
+
logConfig: true, // Force logging
|
|
974
1099
|
});
|
|
1100
|
+
app.use('/custom-endpoints', customAuthMW);
|
|
975
1101
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1102
|
+
// createAuthMW - Universal authentication middleware
|
|
1103
|
+
// Function Signature:
|
|
1104
|
+
function createAuthMW(options?: {
|
|
1105
|
+
mcpPaths?: string[]; // Paths to check for public MCP requests (default: ['/mcp', '/messages', '/sse'])
|
|
1106
|
+
logConfig?: boolean; // Log auth configuration on first request (default: from LOG_AUTH_CONFIG env)
|
|
1107
|
+
}): (req: Request, res: Response, next: NextFunction) => Promise<void>
|
|
1108
|
+
|
|
1109
|
+
// Features:
|
|
1110
|
+
// ✅ Combines all authentication methods (standard + custom validator)
|
|
1111
|
+
// ✅ Supports public MCP resources/prompts (requireAuth: false)
|
|
1112
|
+
// ✅ Configurable MCP paths
|
|
1113
|
+
// ✅ CPU-optimized authentication order
|
|
1114
|
+
// ✅ Automatic auth method detection
|
|
1115
|
+
// ✅ Request context enrichment (req.authInfo)
|
|
1116
|
+
|
|
1117
|
+
// getMultiAuthError - Programmatic authentication checking
|
|
979
1118
|
// Function Signature:
|
|
980
|
-
async function getMultiAuthError(req: Request): Promise<{ code: number, message: string } | undefined>
|
|
1119
|
+
async function getMultiAuthError(req: Request): Promise<{ code: number, message: string } | undefined>
|
|
1120
|
+
|
|
1121
|
+
// Returns error object if authentication failed, undefined if successful
|
|
1122
|
+
// Uses checkCombinedAuth internally - supports all authentication methods
|
|
981
1123
|
|
|
982
1124
|
// Example - Custom middleware with different auth levels
|
|
983
1125
|
app.use('/api/custom', async (req, res, next) => {
|
|
@@ -1007,90 +1149,174 @@ app.use('/api/custom', async (req, res, next) => {
|
|
|
1007
1149
|
}
|
|
1008
1150
|
});
|
|
1009
1151
|
|
|
1010
|
-
// getAuthInfo - get current authentication configuration info
|
|
1011
|
-
// Function Signature:
|
|
1012
|
-
function getAuthInfo(): {
|
|
1013
|
-
enabled: boolean;
|
|
1014
|
-
configured: AuthType[];
|
|
1015
|
-
valid: AuthType[];
|
|
1016
|
-
errors: Record<string, string[]>;
|
|
1017
|
-
usingMultiAuth: boolean;
|
|
1018
|
-
} {...}
|
|
1019
|
-
|
|
1020
|
-
// Example:
|
|
1021
|
-
app.get('/auth/info', (req, res) => {
|
|
1022
|
-
const authInfo = getAuthInfo();
|
|
1023
|
-
res.json(authInfo);
|
|
1024
|
-
});
|
|
1025
1152
|
```
|
|
1026
1153
|
|
|
1027
|
-
##### Custom
|
|
1154
|
+
##### Custom Authentication
|
|
1028
1155
|
|
|
1029
|
-
You can provide custom
|
|
1156
|
+
You can provide custom authentication validation functions through the `McpServerData` interface. The custom validator receives the full Express request object, allowing for flexible authentication logic:
|
|
1030
1157
|
|
|
1031
1158
|
```typescript
|
|
1032
|
-
import { McpServerData,
|
|
1159
|
+
import { McpServerData, CustomAuthValidator } from 'fa-mcp-sdk';
|
|
1033
1160
|
|
|
1034
|
-
// Database-backed authentication
|
|
1035
|
-
const databaseAuthValidator:
|
|
1161
|
+
// Database-backed authentication with request context
|
|
1162
|
+
const databaseAuthValidator: CustomAuthValidator = async (req): Promise<AuthResult> => {
|
|
1036
1163
|
try {
|
|
1037
|
-
|
|
1038
|
-
|
|
1164
|
+
// Extract authentication data from various sources
|
|
1165
|
+
const authHeader = req.headers.authorization;
|
|
1166
|
+
const username = req.headers['x-username'];
|
|
1167
|
+
const apiKey = req.headers['x-api-key'];
|
|
1168
|
+
|
|
1169
|
+
if (authHeader?.startsWith('Basic ')) {
|
|
1170
|
+
const [user, pass] = Buffer.from(authHeader.slice(6), 'base64').toString().split(':');
|
|
1171
|
+
const dbUser = await getUserFromDatabase(user);
|
|
1172
|
+
|
|
1173
|
+
if (dbUser && await comparePassword(pass, dbUser.hashedPassword)) {
|
|
1174
|
+
return {
|
|
1175
|
+
success: true,
|
|
1176
|
+
authType: 'basic',
|
|
1177
|
+
tokenType: 'basic',
|
|
1178
|
+
username: dbUser.username,
|
|
1179
|
+
payload: { userId: dbUser.id, roles: dbUser.roles }
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1039
1183
|
|
|
1040
|
-
|
|
1184
|
+
if (apiKey && username) {
|
|
1185
|
+
const isValid = await validateUserApiKey(username, apiKey);
|
|
1186
|
+
if (isValid) {
|
|
1187
|
+
return {
|
|
1188
|
+
success: true,
|
|
1189
|
+
authType: 'basic',
|
|
1190
|
+
tokenType: 'apiKey',
|
|
1191
|
+
username: username,
|
|
1192
|
+
payload: { apiKey: apiKey.substring(0, 8) + '...' }
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
return { success: false, error: 'Invalid credentials' };
|
|
1041
1198
|
} catch (error) {
|
|
1042
1199
|
console.error('Database authentication error:', error);
|
|
1043
|
-
return false;
|
|
1200
|
+
return { success: false, error: 'Database authentication error' };
|
|
1044
1201
|
}
|
|
1045
1202
|
};
|
|
1046
1203
|
|
|
1047
|
-
//
|
|
1048
|
-
const
|
|
1204
|
+
// IP-based authentication with time restrictions
|
|
1205
|
+
const ipBasedAuthValidator: CustomAuthValidator = async (req): Promise<AuthResult> => {
|
|
1049
1206
|
try {
|
|
1050
|
-
const
|
|
1051
|
-
|
|
1207
|
+
const clientIP = req.headers['x-real-ip'] || req.connection?.remoteAddress;
|
|
1208
|
+
const userAgent = req.headers['user-agent'];
|
|
1209
|
+
|
|
1210
|
+
// Check IP whitelist
|
|
1211
|
+
if (!isIPAllowed(clientIP)) {
|
|
1212
|
+
return { success: false, error: `IP address ${clientIP} not allowed` };
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// Block bots and crawlers
|
|
1216
|
+
if (userAgent?.includes('bot') || userAgent?.includes('crawler')) {
|
|
1217
|
+
return { success: false, error: 'Bots and crawlers are not allowed' };
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// Time-based restrictions (business hours only)
|
|
1221
|
+
const hour = new Date().getHours();
|
|
1222
|
+
if (hour < 9 || hour > 17) {
|
|
1223
|
+
return { success: false, error: 'Access only allowed during business hours (9-17)' };
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
return {
|
|
1227
|
+
success: true,
|
|
1228
|
+
authType: 'basic',
|
|
1229
|
+
tokenType: 'ipBased',
|
|
1230
|
+
username: `ip-${clientIP}`,
|
|
1231
|
+
payload: { clientIP, userAgent, accessTime: new Date().toISOString() }
|
|
1232
|
+
};
|
|
1052
1233
|
} catch (error) {
|
|
1053
|
-
console.error('
|
|
1054
|
-
return false;
|
|
1234
|
+
console.error('IP authentication error:', error);
|
|
1235
|
+
return { success: false, error: 'IP authentication error' };
|
|
1055
1236
|
}
|
|
1056
1237
|
};
|
|
1057
1238
|
|
|
1058
|
-
// External
|
|
1059
|
-
const
|
|
1239
|
+
// External service authentication
|
|
1240
|
+
const externalServiceAuthValidator: CustomAuthValidator = async (req): Promise<AuthResult> => {
|
|
1060
1241
|
try {
|
|
1242
|
+
const token = req.headers.authorization?.replace('Bearer ', '');
|
|
1243
|
+
const clientId = req.headers['x-client-id'];
|
|
1244
|
+
|
|
1245
|
+
if (!token || !clientId) {
|
|
1246
|
+
return { success: false, error: 'Missing token or client ID' };
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1061
1249
|
const response = await fetch('https://auth.example.com/validate', {
|
|
1062
1250
|
method: 'POST',
|
|
1063
1251
|
headers: { 'Content-Type': 'application/json' },
|
|
1064
|
-
body: JSON.stringify({
|
|
1252
|
+
body: JSON.stringify({ token, clientId, ip: req.ip })
|
|
1065
1253
|
});
|
|
1066
1254
|
|
|
1067
|
-
if (!response.ok)
|
|
1255
|
+
if (!response.ok) {
|
|
1256
|
+
return { success: false, error: 'External service validation failed' };
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1068
1259
|
const result = await response.json();
|
|
1069
|
-
|
|
1260
|
+
if (result.valid === true) {
|
|
1261
|
+
return {
|
|
1262
|
+
success: true,
|
|
1263
|
+
authType: 'basic',
|
|
1264
|
+
tokenType: 'external',
|
|
1265
|
+
username: result.username || clientId,
|
|
1266
|
+
payload: {
|
|
1267
|
+
clientId,
|
|
1268
|
+
externalUserId: result.userId,
|
|
1269
|
+
scopes: result.scopes
|
|
1270
|
+
}
|
|
1271
|
+
};
|
|
1272
|
+
} else {
|
|
1273
|
+
return { success: false, error: result.error || 'Invalid token' };
|
|
1274
|
+
}
|
|
1070
1275
|
} catch (error) {
|
|
1071
|
-
console.error('
|
|
1072
|
-
return false;
|
|
1276
|
+
console.error('External service authentication error:', error);
|
|
1277
|
+
return { success: false, error: 'External service authentication error' };
|
|
1073
1278
|
}
|
|
1074
1279
|
};
|
|
1075
1280
|
|
|
1076
|
-
// Multi-factor authentication
|
|
1077
|
-
const mfaAuthValidator:
|
|
1281
|
+
// Multi-factor authentication with context
|
|
1282
|
+
const mfaAuthValidator: CustomAuthValidator = async (req): Promise<AuthResult> => {
|
|
1078
1283
|
try {
|
|
1079
|
-
|
|
1080
|
-
const
|
|
1081
|
-
|
|
1284
|
+
const authHeader = req.headers.authorization;
|
|
1285
|
+
const mfaToken = req.headers['x-mfa-token'];
|
|
1286
|
+
const userSession = req.headers['x-session-id'];
|
|
1287
|
+
|
|
1288
|
+
if (!authHeader?.startsWith('Basic ') || !mfaToken) {
|
|
1289
|
+
return { success: false, error: 'Missing basic auth or MFA token' };
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
const [username, password] = Buffer.from(authHeader.slice(6), 'base64').toString().split(':');
|
|
1082
1293
|
|
|
1083
1294
|
// Validate base credentials
|
|
1084
1295
|
const user = await getUserFromDatabase(username);
|
|
1085
|
-
if (!user || !(await comparePassword(
|
|
1086
|
-
return false;
|
|
1296
|
+
if (!user || !(await comparePassword(password, user.hashedPassword))) {
|
|
1297
|
+
return { success: false, error: 'Invalid username or password' };
|
|
1087
1298
|
}
|
|
1088
1299
|
|
|
1089
|
-
// Validate MFA token
|
|
1090
|
-
|
|
1300
|
+
// Validate MFA token and session
|
|
1301
|
+
const mfaValid = await validateMFAToken(username, mfaToken, userSession);
|
|
1302
|
+
if (mfaValid) {
|
|
1303
|
+
return {
|
|
1304
|
+
success: true,
|
|
1305
|
+
authType: 'basic',
|
|
1306
|
+
tokenType: 'mfa',
|
|
1307
|
+
username: username,
|
|
1308
|
+
payload: {
|
|
1309
|
+
userId: user.id,
|
|
1310
|
+
sessionId: userSession,
|
|
1311
|
+
mfaMethod: 'totp' // or whatever MFA method was used
|
|
1312
|
+
}
|
|
1313
|
+
};
|
|
1314
|
+
} else {
|
|
1315
|
+
return { success: false, error: 'Invalid MFA token or session' };
|
|
1316
|
+
}
|
|
1091
1317
|
} catch (error) {
|
|
1092
1318
|
console.error('MFA authentication error:', error);
|
|
1093
|
-
return false;
|
|
1319
|
+
return { success: false, error: 'MFA authentication error' };
|
|
1094
1320
|
}
|
|
1095
1321
|
};
|
|
1096
1322
|
|
|
@@ -1101,8 +1327,8 @@ const serverData: McpServerData = {
|
|
|
1101
1327
|
agentBrief: 'My MCP Server',
|
|
1102
1328
|
agentPrompt: 'Server with custom authentication',
|
|
1103
1329
|
|
|
1104
|
-
// Provide custom
|
|
1105
|
-
|
|
1330
|
+
// Provide custom authentication validator (black box function)
|
|
1331
|
+
customAuthValidator: databaseAuthValidator, // or ipBasedAuthValidator, externalServiceAuthValidator, mfaAuthValidator
|
|
1106
1332
|
|
|
1107
1333
|
// ... other configuration
|
|
1108
1334
|
};
|
|
@@ -1110,46 +1336,6 @@ const serverData: McpServerData = {
|
|
|
1110
1336
|
await initMcpServer(serverData);
|
|
1111
1337
|
```
|
|
1112
1338
|
|
|
1113
|
-
##### Authentication Configuration
|
|
1114
|
-
|
|
1115
|
-
Multi-authentication is configured in `config/default.yaml`:
|
|
1116
|
-
|
|
1117
|
-
```yaml
|
|
1118
|
-
webServer:
|
|
1119
|
-
auth:
|
|
1120
|
-
enabled: true
|
|
1121
|
-
|
|
1122
|
-
# Permanent server tokens (CPU priority: 1 - fastest)
|
|
1123
|
-
permanentServerTokens:
|
|
1124
|
-
- 'server-token-1'
|
|
1125
|
-
- 'server-token-2'
|
|
1126
|
-
|
|
1127
|
-
# Personal Access Tokens (CPU priority: 2)
|
|
1128
|
-
pat: 'ATATT3xFfGF0...'
|
|
1129
|
-
|
|
1130
|
-
# Basic Authentication (CPU priority: 3)
|
|
1131
|
-
basic:
|
|
1132
|
-
type: 'basic'
|
|
1133
|
-
username: 'admin'
|
|
1134
|
-
password: 'password'
|
|
1135
|
-
# Note: When using customBasicAuthValidator, username/password can be omitted
|
|
1136
|
-
|
|
1137
|
-
# JWT Tokens (CPU priority: 4)
|
|
1138
|
-
jwtToken:
|
|
1139
|
-
encryptKey: 'your-secret-key'
|
|
1140
|
-
checkMCPName: true
|
|
1141
|
-
|
|
1142
|
-
# OAuth2 (CPU priority: 5 - most expensive)
|
|
1143
|
-
oauth2:
|
|
1144
|
-
type: 'oauth2'
|
|
1145
|
-
clientId: 'your-client-id'
|
|
1146
|
-
clientSecret: 'your-client-secret'
|
|
1147
|
-
accessToken: 'your-access-token'
|
|
1148
|
-
refreshToken: 'your-refresh-token'
|
|
1149
|
-
redirectUri: 'https://example.com/callback'
|
|
1150
|
-
tokenEndpoint: 'https://auth.provider.com/token'
|
|
1151
|
-
```
|
|
1152
|
-
|
|
1153
1339
|
##### Usage Examples
|
|
1154
1340
|
|
|
1155
1341
|
```typescript
|
|
@@ -1161,7 +1347,7 @@ app.post('/test-token', async (req, res) => {
|
|
|
1161
1347
|
}
|
|
1162
1348
|
|
|
1163
1349
|
try {
|
|
1164
|
-
const result = await checkMultiAuth(
|
|
1350
|
+
const result = await checkMultiAuth(req);
|
|
1165
1351
|
res.json({
|
|
1166
1352
|
valid: result.success,
|
|
1167
1353
|
authType: result.authType,
|
|
@@ -1176,11 +1362,29 @@ app.post('/test-token', async (req, res) => {
|
|
|
1176
1362
|
});
|
|
1177
1363
|
|
|
1178
1364
|
// Different authentication requirements for different endpoints
|
|
1179
|
-
app.use('/rest',
|
|
1180
|
-
app.use('/graphql',
|
|
1181
|
-
app.use('/websocket',
|
|
1365
|
+
app.use('/rest', createAuthMW()); // Standard auth with all methods
|
|
1366
|
+
app.use('/graphql', createAuthMW({ logConfig: false })); // Silent auth
|
|
1367
|
+
app.use('/websocket', createAuthMW({ mcpPaths: [] })); // No public MCP paths
|
|
1182
1368
|
```
|
|
1183
1369
|
|
|
1370
|
+
**Authentication Logic Flow:**
|
|
1371
|
+
|
|
1372
|
+
The enhanced authentication system follows this logic:
|
|
1373
|
+
|
|
1374
|
+
1. **If configured auth methods exist** (permanentServerTokens, jwtToken, basic + auth.enabled = true):
|
|
1375
|
+
- Standard MCP authentication runs first
|
|
1376
|
+
- If successful AND custom validator exists → run custom validator additionally
|
|
1377
|
+
- Both must pass for authentication to succeed
|
|
1378
|
+
|
|
1379
|
+
2. **If no standard auth OR standard auth fails:**
|
|
1380
|
+
- Custom validator runs as fallback (if configured)
|
|
1381
|
+
- Can authenticate using custom logic alone
|
|
1382
|
+
|
|
1383
|
+
3. **Custom validator is completely independent:**
|
|
1384
|
+
- Receives full Express request object
|
|
1385
|
+
- Can implement any authentication/authorization logic
|
|
1386
|
+
- Works as black box as requested
|
|
1387
|
+
|
|
1184
1388
|
**Client Usage Examples:**
|
|
1185
1389
|
|
|
1186
1390
|
```bash
|
|
@@ -1193,14 +1397,17 @@ curl -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhb..." http://localhost:3000/m
|
|
|
1193
1397
|
# Using Basic Authentication
|
|
1194
1398
|
curl -H "Authorization: Basic $(echo -n 'admin:password' | base64)" http://localhost:3000/mcp
|
|
1195
1399
|
|
|
1196
|
-
# Using
|
|
1197
|
-
curl -H "
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
# Using
|
|
1203
|
-
curl -H "Authorization:
|
|
1400
|
+
# Using custom headers for custom validator
|
|
1401
|
+
curl -H "X-User-ID: john.doe" \
|
|
1402
|
+
-H "X-API-Key: custom-api-key-12345" \
|
|
1403
|
+
-H "X-Client-IP: 192.168.1.10" \
|
|
1404
|
+
http://localhost:3000/mcp
|
|
1405
|
+
|
|
1406
|
+
# Using custom authentication with context
|
|
1407
|
+
curl -H "Authorization: Bearer token123" \
|
|
1408
|
+
-H "X-MFA-Token: 123456" \
|
|
1409
|
+
-H "X-Session-ID: sess_abc123" \
|
|
1410
|
+
http://localhost:3000/mcp
|
|
1204
1411
|
```
|
|
1205
1412
|
|
|
1206
1413
|
The multi-authentication system automatically tries authentication methods in CPU-optimized order (fastest first) and returns on the first successful match, providing both performance and flexibility.
|