fa-mcp-sdk 0.2.111 → 0.2.114

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.
@@ -0,0 +1,1197 @@
1
+ # FA-MCP-SDK API Documentation
2
+
3
+ ## Overview
4
+
5
+ The FA-MCP-SDK is a comprehensive TypeScript framework for building Model Context
6
+ Protocol (MCP) servers. This documentation covers how to use the SDK
7
+ to create your own MCP server project.
8
+
9
+ ## Getting Started
10
+
11
+ ### Installation
12
+
13
+ ```bash
14
+ npm install fa-mcp-sdk
15
+ ```
16
+
17
+ ### Project Structure
18
+
19
+ When creating a new MCP server, your project structure should follow this pattern:
20
+
21
+ ```
22
+ my-mcp-server/
23
+ ├── config/ # Environment configurations
24
+ │ ├── default.yaml # Base configuration
25
+ │ ├── development.yaml # Development settings
26
+ │ ├── production.yaml # Production settings
27
+ │ └── test.yaml # Test environment
28
+ ├── src/ # Source code
29
+ │ ├── _types_/ # TypeScript type definitions
30
+ │ ├── api/ # REST API routes (HTTP transport)
31
+ │ │ ├── router.ts # Express router
32
+ │ │ └── swagger.ts # API documentation
33
+ │ ├── prompts/ # Agent prompts
34
+ │ │ ├── agent-brief.ts # Agent brief
35
+ │ │ ├── agent-prompt.ts # Main agent prompt
36
+ │ │ └── custom-prompts.ts # Custom prompts
37
+ │ ├── tools/ # MCP tool implementations
38
+ │ │ ├── handle-tool-call.ts # Tool execution handler
39
+ │ │ └── tools.ts # Tool definitions
40
+ │ ├── custom-resources.ts # Custom MCP resources
41
+ │ └── start.ts # Application entry point
42
+ ├── tests/ # Test suites
43
+ │ ├── mcp/ # MCP protocol tests
44
+ │ └── utils.ts # Test utilities
45
+ ├── .env # Environment variables
46
+ ├── package.json # NPM package configuration
47
+ └── tsconfig.json # TypeScript configuration
48
+ ```
49
+
50
+ ## Core API Reference
51
+
52
+ ### Main Initialization Function
53
+
54
+ #### `initMcpServer(data: McpServerData): Promise<void>`
55
+
56
+ The primary function for starting your MCP server.
57
+
58
+ **Example Usage in `src/start.ts`:**
59
+
60
+ ```typescript
61
+ import { initMcpServer, McpServerData } from 'fa-mcp-sdk';
62
+ import { tools } from './tools/tools.js';
63
+ import { handleToolCall } from './tools/handle-tool-call.js';
64
+ import { AGENT_BRIEF } from './prompts/agent-brief.js';
65
+ import { AGENT_PROMPT } from './prompts/agent-prompt.js';
66
+
67
+ const serverData: McpServerData = {
68
+ tools,
69
+ toolHandler: handleToolCall,
70
+ agentBrief: AGENT_BRIEF,
71
+ agentPrompt: AGENT_PROMPT,
72
+ // ... other configuration
73
+ };
74
+
75
+ await initMcpServer(serverData);
76
+ ```
77
+
78
+ ### Core Types and Interfaces
79
+
80
+ #### `McpServerData`
81
+
82
+ Main configuration interface for your MCP server.
83
+
84
+ ```typescript
85
+ interface McpServerData {
86
+ // MCP Core Components
87
+ tools: Tool[]; // Your tool definitions
88
+ toolHandler: (params: { name: string; arguments?: any }) => Promise<any>; // Tool execution function
89
+
90
+ // Agent Configuration
91
+ agentBrief: string; // Brief description of your agent
92
+ agentPrompt: string; // System prompt for your agent
93
+ customPrompts?: IPromptData[]; // Additional custom prompts
94
+
95
+ // Resources
96
+ requiredHttpHeaders?: IRequiredHttpHeader[] | null; // HTTP headers for authentication
97
+ customResources?: IResourceData[] | null; // Custom resource definitions
98
+
99
+ // HTTP Server Components (for HTTP transport)
100
+ httpComponents?: {
101
+ apiRouter?: Router | null; // Express router for additional endpoints
102
+ endpointsOn404?: IEndpointsOn404; // Custom 404 handling
103
+ swagger?: ISwaggerData | null; // OpenAPI/Swagger configuration
104
+ };
105
+
106
+ // UI Assets
107
+ assets?: {
108
+ favicon?: string; // SVG content for favicon
109
+ maintainerHtml?: string; // Support contact HTML snippet
110
+ };
111
+
112
+ // Consul Integration
113
+ getConsulUIAddress?: (serviceId: string) => string; // Function to generate Consul UI URLs
114
+ }
115
+ ```
116
+
117
+ #### `IPromptData`
118
+
119
+ Configuration for custom prompts in `src/prompts/custom-prompts.ts`.
120
+
121
+ ```typescript
122
+ interface IPromptData {
123
+ name: string; // Unique prompt identifier
124
+ description: string; // Human-readable description
125
+ arguments: []; // Expected arguments (currently empty array)
126
+ content: IPromptContent; // Static string or dynamic function
127
+ requireAuth?: boolean; // Whether authentication is required
128
+ }
129
+
130
+ type IPromptContent = string | TPromptContentFunction;
131
+ type TPromptContentFunction = (request: IGetPromptRequest) => string | Promise<string>;
132
+ ```
133
+
134
+ Example `src/prompts/custom-prompts.ts`:
135
+ ```typescript
136
+ import { IPromptData } from 'fa-mcp-sdk';
137
+
138
+ export const customPrompts: IPromptData[] = [
139
+ {
140
+ name: 'custom_prompt',
141
+ description: 'A custom prompt for specific tasks',
142
+ arguments: [],
143
+ content: (request) => {
144
+ const { sample } = request.params.arguments || {};
145
+ return `Custom prompt content with parameter: ${sample}`;
146
+ },
147
+ },
148
+ ];
149
+ ```
150
+
151
+ #### `IResourceData`
152
+
153
+ Configuration for custom resources in `src/custom-resources.ts`.
154
+
155
+ ```typescript
156
+ interface IResourceData {
157
+ uri: string; // Unique resource URI (e.g., "custom-resource://data1")
158
+ name: string; // Resource name
159
+ title?: string; // Optional display title
160
+ description: string; // Human-readable description
161
+ mimeType: string; // MIME type (e.g., "text/plain", "application/json")
162
+ content: IResourceContent; // Static content or dynamic function
163
+ requireAuth?: boolean; // Whether authentication is required
164
+ }
165
+ ```
166
+
167
+ Example `src/custom-resources.ts`:
168
+ ```typescript
169
+ import { IResourceData } from 'fa-mcp-sdk';
170
+
171
+ export const customResources: IResourceData[] = [
172
+ {
173
+ uri: 'custom-resource://resource1',
174
+ name: 'resource1',
175
+ description: 'Example resource with dynamic content',
176
+ mimeType: 'text/plain',
177
+ content: (uri) => {
178
+ return `Dynamic content for ${uri} at ${new Date().toISOString()}`;
179
+ },
180
+ },
181
+ ];
182
+ ```
183
+
184
+ ### Tool Development
185
+
186
+ #### Tool Definition in `src/tools/tools.ts`
187
+
188
+ ```typescript
189
+ import { Tool } from '@modelcontextprotocol/sdk/types.js';
190
+ import { IToolInputSchema } from 'fa-mcp-sdk';
191
+
192
+ export const tools: Tool[] = [
193
+ {
194
+ name: 'my_custom_tool',
195
+ description: 'Description of what this tool does',
196
+ inputSchema: {
197
+ type: 'object',
198
+ properties: {
199
+ query: {
200
+ type: 'string',
201
+ description: 'Input query or text',
202
+ },
203
+ options: {
204
+ type: 'object',
205
+ description: 'Optional configuration',
206
+ },
207
+ },
208
+ required: ['query'],
209
+ },
210
+ },
211
+ ];
212
+ ```
213
+
214
+ #### Tool Handler in `src/tools/handle-tool-call.ts`
215
+
216
+ ```typescript
217
+ import { formatToolResult, ToolExecutionError, logger } from 'fa-mcp-sdk';
218
+
219
+ export const handleToolCall = async (params: { name: string, arguments?: any }): Promise<any> => {
220
+ const { name, arguments: args } = params;
221
+
222
+ logger.info(`Tool called: ${name}`);
223
+
224
+ try {
225
+ switch (name) {
226
+ case 'my_custom_tool':
227
+ return await handleMyCustomTool(args);
228
+
229
+ default:
230
+ throw new ToolExecutionError(name, `Unknown tool: ${name}`);
231
+ }
232
+ } catch (error) {
233
+ logger.error(`Tool execution failed for ${name}:`, error);
234
+ throw error;
235
+ }
236
+ };
237
+
238
+ async function handleMyCustomTool(args: any): Promise<string> {
239
+ const { query, options } = args || {};
240
+
241
+ if (!query) {
242
+ throw new ToolExecutionError('my_custom_tool', 'Query parameter is required');
243
+ }
244
+
245
+ // Your tool logic here
246
+ const result = {
247
+ message: `Processed: ${query}`,
248
+ timestamp: new Date().toISOString(),
249
+ options: options || {},
250
+ };
251
+
252
+ return formatToolResult(result);
253
+ }
254
+ ```
255
+
256
+ ### Configuration Management
257
+
258
+ #### Using `appConfig`
259
+
260
+ Access configuration in your code:
261
+
262
+ ```typescript
263
+ import { appConfig } from 'fa-mcp-sdk';
264
+
265
+ // Access configuration values
266
+ const serverPort = appConfig.webServer.port;
267
+ const dbEnabled = appConfig.isMainDBUsed;
268
+ const transport = appConfig.mcp.transportType; // 'stdio' | 'http'
269
+ ```
270
+
271
+ #### Configuration Files
272
+
273
+ **`config/default.yaml`** - Base configuration:
274
+ ```yaml
275
+ accessPoints:
276
+ myService:
277
+ title: 'My remote service'
278
+ host: <host>
279
+ port: 9999
280
+ token: '***'
281
+ noConsul: true # Use if the service developers do not provide registration in consul
282
+ consulServiceName: <consulServiceName>
283
+
284
+ consul:
285
+ check:
286
+ interval: '10s'
287
+ timeout: '5s'
288
+ deregistercriticalserviceafter: '3m'
289
+ agent:
290
+ # Credentials for getting information about services in the DEV DC
291
+ dev:
292
+ dc: '{{consul.agent.dev.dc}}'
293
+ host: '{{consul.agent.dev.host}}'
294
+ port: 443
295
+ secure: true
296
+ # Token for getting information about DEV services
297
+ token: '***'
298
+ # Credentials for getting information about services in the PROD DC
299
+ prd:
300
+ dc: '{{consul.agent.prd.dc}}'
301
+ host: '{{consul.agent.prd.host}}'
302
+ port: 443
303
+ secure: true
304
+ # Token for obtaining information about PROD services
305
+ token: '***'
306
+ # Credentials for registering the service with Consul
307
+ reg:
308
+ # The host of the consul agent where the service will be registered. If not specified, the server on which the service is running is used
309
+ host: null
310
+ port: 8500
311
+ secure: false
312
+ # Token for registering the service in the consul agent
313
+ token: '***'
314
+ service:
315
+ enable: {{consul.service.enable}} # true - Allows registration of the service with the consul
316
+ name: <name> # <name> will be replaced by <package.json>.name at initialization
317
+ instance: '{{SERVICE_INSTANCE}}' # This value will be specified as a suffix in the id of the service
318
+ version: <version> # <version> will be replaced by <package.json>.version at initialization
319
+ description: <description> # <description> will be replaced by <package.json>.description at initialization
320
+ tags: [] # If null or empty array - Will be pulled up from package.keywords at initialization
321
+ meta:
322
+ # "About" page link template
323
+ who: 'http://{address}:{port}/'
324
+ envCode: # Used to generate the service ID
325
+ prod: {{consul.envCode.prod}} # Production environment code
326
+ dev: {{consul.envCode.dev}} # Development environment code
327
+
328
+ db:
329
+ postgres:
330
+ dbs:
331
+ main:
332
+ label: 'My Database'
333
+ host: '' # To exclude the use of the database, you need to set host = ''
334
+ port: 5432
335
+ database: <database>
336
+ user: <user>
337
+ password: <password>
338
+ usedExtensions: []
339
+
340
+ logger:
341
+ level: info
342
+ useFileLogger: {{logger.useFileLogger}} # To use or not to use logging to a file
343
+ # Absolute path to the folder where logs will be written. Default <proj_root>/../logs
344
+ dir: '{{logger.dir}}'
345
+
346
+ mcp:
347
+ transportType: http # stdio | http
348
+ # Response format configuration.
349
+ # - structuredContent - default - the response in result.structuredContent returns JSON
350
+ # - text - in the response, serialized JSON is returned in result.content[0].text
351
+ toolAnswerAs: text # text | structuredContent
352
+ rateLimit:
353
+ maxRequests: 100
354
+ windowMs: 60000 # 1 minute
355
+
356
+ swagger:
357
+ servers: # An array of servers that will be added to swagger docs
358
+ # - url: http://localhost:9020
359
+ # description: "Development server (localhost)"
360
+ # - url: http://0.0.0.0:9020
361
+ # description: "Development server (all interfaces)"
362
+ # - url: http://<prod_server_host_or_ip>:{{port}}
363
+ # description: "PROD server"
364
+ - url: https://{{mcp.domain}}
365
+ description: "PROD server"
366
+
367
+ uiColor:
368
+ # Font color of the header and a number of interface elements on the ABOUT page
369
+ primary: '#0f65dc'
370
+
371
+ webServer:
372
+ host: '0.0.0.0'
373
+ port: {{port}}
374
+ # array of hosts that CORS skips
375
+ originHosts: ['localhost', '0.0.0.0']
376
+ auth:
377
+ enabled: false # Enables/disables token authorization
378
+ # An array of fixed tokens that pass to the MCP (use only for MCPs with green data or for development)
379
+ permanentServerTokens: []
380
+ token:
381
+ # Symmetric encryption key to generate a token for this MCP
382
+ encryptKey: '***'
383
+ # If webServer.auth.enabled and the parameter true, the service name and the service specified in the token will be checked
384
+ checkMCPName: true
385
+ ```
386
+
387
+ **`config/local.yaml`** - local overrides. Usually contains secrets.
388
+
389
+ ### Database Integration
390
+
391
+ To disable the use of the database, you need to set appConfig.db.postgres.dbs.main.host to an empty value.
392
+ In this case, when the configuration is formed, appConfig.isMainDBUsed is set to false.
393
+
394
+
395
+ If you enable database support (`isMainDBUsed: true` in config):
396
+
397
+ ```typescript
398
+ import {
399
+ queryMAIN,
400
+ execMAIN,
401
+ oneRowMAIN,
402
+ getMainDBConnectionStatus
403
+ } from 'fa-mcp-sdk';
404
+
405
+ // Check database connection. If there is no connection, the application stops
406
+ await checkMainDB();
407
+
408
+ // queryMAIN - the main function of executing SQL queries to the main database
409
+
410
+ // Function Signature:
411
+ const queryMAIN = async <R extends QueryResultRow = any> (
412
+ arg: string | IQueryPgArgsCOptional,
413
+ sqlValues?: any[],
414
+ throwError = false,
415
+ ): Promise<QueryResult<R> | undefined> {...}
416
+
417
+ // Types used:
418
+ export interface IQueryPgArgs {
419
+ connectionId: string,
420
+ poolConfig?: PoolConfig & IDbOptionsPg,
421
+ client?: IPoolPg,
422
+ sqlText: string,
423
+ sqlValues?: any[],
424
+ throwError?: boolean,
425
+ prefix?: string,
426
+ registerTypesFunctions?: IRegisterTypeFn[],
427
+ }
428
+ export interface IQueryPgArgsCOptional extends Omit<IQueryPgArgs, 'connectionId'> {
429
+ connectionId?: string
430
+ }
431
+
432
+ // Examples of use
433
+ const users1 = await queryMAIN('SELECT * FROM users WHERE active = $1', [true]);
434
+ // Alternative use case
435
+ const users2 = await queryMAIN({ sqlText: 'SELECT * FROM users WHERE active = $1', sqlValues: [true] });
436
+
437
+
438
+ // execMAIN - execute SQL commands without returning result set
439
+ // Function Signature:
440
+ const execMAIN = async (
441
+ arg: string | IQueryPgArgsCOptional,
442
+ ): Promise<number | undefined> {...}
443
+
444
+ // Examples:
445
+ await execMAIN('INSERT INTO logs (message, created_at) VALUES ($1, $2)',
446
+ ['Server started', new Date()]);
447
+ await execMAIN({ sqlText: 'UPDATE users SET active = $1 WHERE id = $2', sqlValues: [false, userId] });
448
+
449
+ // queryRsMAIN - execute SQL and return rows array directly
450
+ // Function Signature:
451
+ const queryRsMAIN = async <R extends QueryResultRow = any> (
452
+ arg: string | IQueryPgArgsCOptional,
453
+ sqlValues?: any[],
454
+ throwError = false,
455
+ ): Promise<R[] | undefined> {...}
456
+
457
+ // Example:
458
+ const users = await queryRsMAIN<User>('SELECT * FROM users WHERE active = $1', [true]);
459
+
460
+ // oneRowMAIN - execute SQL and return single row
461
+ // Function Signature:
462
+ const oneRowMAIN = async <R extends QueryResultRow = any> (
463
+ arg: string | IQueryPgArgsCOptional,
464
+ sqlValues?: any[],
465
+ throwError = false,
466
+ ): Promise<R | undefined> {...}
467
+
468
+ // Example:
469
+ const user = await oneRowMAIN<User>('SELECT * FROM users WHERE id = $1', [userId]);
470
+
471
+ // getMainDBConnectionStatus - check database connection status
472
+ // Function Signature:
473
+ const getMainDBConnectionStatus = async (): Promise<string> {...}
474
+
475
+ // Possible return values: 'connected' | 'disconnected' | 'error' | 'db_not_used'
476
+ const status = await getMainDBConnectionStatus();
477
+
478
+ // checkMainDB - verify database connectivity (stops application if failed)
479
+ // Function Signature:
480
+ const checkMainDB = async (): Promise<void> {...}
481
+
482
+ // Example:
483
+ await checkMainDB(); // Throws or exits process if DB connection fails
484
+
485
+ // getInsertSqlMAIN - generate INSERT SQL statement
486
+ // Function Signature:
487
+ const getInsertSqlMAIN = async <U extends TDBRecord = TDBRecord> (arg: {
488
+ commonSchemaAndTable: string,
489
+ recordset: TRecordSet<U>,
490
+ excludeFromInsert?: string[],
491
+ addOutputInserted?: boolean,
492
+ isErrorOnConflict?: boolean,
493
+ keepSerialFields?: boolean,
494
+ }): Promise<string> {...}
495
+
496
+ // Example:
497
+ const insertSql = await getInsertSqlMAIN({
498
+ commonSchemaAndTable: 'public.users',
499
+ recordset: [{ name: 'John', email: 'john@example.com' }],
500
+ addOutputInserted: true
501
+ });
502
+
503
+ // getMergeSqlMAIN - generate UPSERT (INSERT...ON CONFLICT) SQL statement
504
+ // Function Signature:
505
+ const getMergeSqlMAIN = async <U extends TDBRecord = TDBRecord> (arg: {
506
+ commonSchemaAndTable: string,
507
+ recordset: TRecordSet<U>,
508
+ conflictFields?: string[],
509
+ omitFields?: string[],
510
+ updateFields?: string[],
511
+ fieldsExcludedFromUpdatePart?: string[],
512
+ noUpdateIfNull?: boolean,
513
+ mergeCorrection?: (_sql: string) => string,
514
+ returning?: string,
515
+ }): Promise<string> {...}
516
+
517
+ // Example:
518
+ const mergeSql = await getMergeSqlMAIN({
519
+ commonSchemaAndTable: 'public.users',
520
+ recordset: [{ id: 1, name: 'John Updated', email: 'john@example.com' }],
521
+ conflictFields: ['email'],
522
+ returning: '*'
523
+ });
524
+
525
+ // mergeByBatch - execute merge operations in batches
526
+ // Function Signature:
527
+ const mergeByBatch = async <U extends TDBRecord = TDBRecord> (arg: {
528
+ recordset: TRecordSet<U>,
529
+ getMergeSqlFn: Function
530
+ batchSize?: number
531
+ }): Promise<any[]> {...}
532
+
533
+ // Example:
534
+ const results = await mergeByBatch({
535
+ recordset: largeDataSet,
536
+ getMergeSqlFn: (batch) => getMergeSqlMAIN({
537
+ commonSchemaAndTable: 'public.users',
538
+ recordset: batch
539
+ }),
540
+ batchSize: 500
541
+ });
542
+ ```
543
+
544
+ ### Error Handling
545
+
546
+ #### Custom Error Classes
547
+
548
+ ```typescript
549
+ import { BaseMcpError, ToolExecutionError, ValidationError } from 'fa-mcp-sdk';
550
+
551
+ // Create custom error types
552
+ class MyCustomError extends BaseMcpError {
553
+ constructor(message: string) {
554
+ super(message, 'CUSTOM_ERROR');
555
+ }
556
+ }
557
+
558
+ // Use built-in error types
559
+ if (!validInput) {
560
+ throw new ValidationError('Input validation failed');
561
+ }
562
+
563
+ if (toolFailed) {
564
+ throw new ToolExecutionError('my_tool', 'Tool execution failed');
565
+ }
566
+ ```
567
+
568
+ #### Error Utilities
569
+
570
+ ```typescript
571
+ import {
572
+ createJsonRpcErrorResponse,
573
+ toError,
574
+ toStr,
575
+ addErrorMessage
576
+ } from 'fa-mcp-sdk';
577
+
578
+ // createJsonRpcErrorResponse - create JSON-RPC 2.0 error response
579
+ // Function Signature:
580
+ function createJsonRpcErrorResponse (
581
+ error: Error | BaseMcpError,
582
+ requestId?: string | number | null,
583
+ ): any {...}
584
+
585
+ // Example:
586
+ try {
587
+ // some operation
588
+ } catch (error) {
589
+ const jsonRpcError = createJsonRpcErrorResponse(error, 'request-123');
590
+ res.json(jsonRpcError);
591
+ }
592
+
593
+ // toError - safely convert any value to Error object
594
+ // Function Signature:
595
+ const toError = (err: any): Error {...}
596
+
597
+ // Examples:
598
+ const err1 = toError(new Error('Original error')); // Returns original Error
599
+ const err2 = toError('String error message'); // Returns new Error('String error message')
600
+ const err3 = toError({ message: 'Object error' }); // Returns new Error('[object Object]')
601
+
602
+ // toStr - safely convert error to string message
603
+ // Function Signature:
604
+ const toStr = (err: any): string {...}
605
+
606
+ // Examples:
607
+ const msg1 = toStr(new Error('Test error')); // Returns 'Test error'
608
+ const msg2 = toStr('String message'); // Returns 'String message'
609
+ const msg3 = toStr(null); // Returns 'Unknown error'
610
+
611
+ // addErrorMessage - add context to existing error message
612
+ // Function Signature:
613
+ const addErrorMessage = (err: any, msg: string): void {...}
614
+
615
+ // Example:
616
+ const originalError = new Error('Connection failed');
617
+ addErrorMessage(originalError, 'Database operation failed');
618
+ // originalError.message is now: 'Database operation failed. Connection failed'
619
+ ```
620
+
621
+ ### Authentication and Security
622
+
623
+ #### Token-based Authentication
624
+
625
+ ```typescript
626
+ import {
627
+ authByToken,
628
+ authTokenMW,
629
+ ICheckTokenResult,
630
+ checkToken,
631
+ generateToken
632
+ } from 'fa-mcp-sdk';
633
+
634
+ // Types used:
635
+ export interface ICheckTokenResult {
636
+ inTokenType?: TTokenType // 'permanent' | 'JWT'
637
+ payload?: ITokenPayload, // Token payload with user data
638
+ errorReason?: string, // Error message if validation failed
639
+ isTokenDecrypted?: boolean, // Whether token was successfully decrypted
640
+ }
641
+
642
+ export interface ITokenPayload {
643
+ user: string, // Username
644
+ expire: number, // Expiration timestamp
645
+ [key: string]: any, // Additional payload data
646
+ }
647
+
648
+ // checkToken - validate token and return detailed result
649
+ // Function Signature:
650
+ const checkToken = (arg: {
651
+ token: string,
652
+ expectedUser?: string,
653
+ expectedService?: string,
654
+ }): ICheckTokenResult {...}
655
+
656
+ // Example:
657
+ const tokenResult = checkToken({
658
+ token: 'user_provided_token',
659
+ expectedUser: 'john_doe',
660
+ expectedService: 'my-mcp-server'
661
+ });
662
+
663
+ if (!tokenResult.errorReason) {
664
+ console.log('Valid token for user:', tokenResult.payload?.user);
665
+ } else {
666
+ console.log('Auth failed:', tokenResult.errorReason);
667
+ }
668
+
669
+ // generateToken - create JWT token
670
+ // Function Signature:
671
+ const generateToken = (user: string, liveTimeSec: number, payload?: any): string {...}
672
+
673
+ // Example:
674
+ const token = generateToken('john_doe', 3600, { role: 'admin' }); // 1 hour token
675
+
676
+ // authByToken - Express route handler for token validation
677
+ // Function Signature:
678
+ const authByToken = (req: Request, res: Response): boolean {...}
679
+
680
+ // Example:
681
+ app.post('/api/secure', (req, res) => {
682
+ if (!authByToken(req, res)) {
683
+ return; // Response already sent with error
684
+ }
685
+ // User is authenticated, continue with request
686
+ res.json({ message: 'Access granted' });
687
+ });
688
+
689
+ // authTokenMW - Express middleware for token authentication
690
+ // Function Signature:
691
+ const authTokenMW = (req: Request, res: Response, next: NextFunction): void {...}
692
+
693
+ // Example:
694
+ import express from 'express';
695
+ const app = express();
696
+ app.use('/protected', authTokenMW); // Apply to all /protected/* routes
697
+
698
+ #### Token Generation
699
+
700
+ ```typescript
701
+ import { generateTokenApp } from 'fa-mcp-sdk';
702
+
703
+ // generateTokenApp - start token generation web application
704
+ // Function Signature:
705
+ async function generateTokenApp (...args: any[]): Promise<void> {...}
706
+
707
+ // Starts a web server for generating authentication tokens
708
+ // Uses NTLM authentication if configured
709
+ // Web interface available at configured host:port
710
+
711
+ // Example:
712
+ await generateTokenApp(); // Uses default configuration from appConfig
713
+
714
+ // Token generation app provides:
715
+ // - Web interface for token creation
716
+ // - NTLM domain authentication support
717
+ // - JWT token generation with configurable expiration
718
+ // - Integration with Active Directory (if configured)
719
+
720
+ // Configuration in config/default.yaml:
721
+ // webServer:
722
+ // auth:
723
+ // token:
724
+ // encryptKey: '***' # Symmetric key for token encryption
725
+ //
726
+ // Optional NTLM configuration:
727
+ // ntlm:
728
+ // domain: 'DOMAIN'
729
+ // domainController: 'dc.domain.com'
730
+ ```
731
+
732
+ ### Utility Functions
733
+
734
+ #### General Utilities
735
+
736
+ ```typescript
737
+ import {
738
+ trim,
739
+ isMainModule,
740
+ isNonEmptyObject,
741
+ isObject,
742
+ ppj,
743
+ encodeSvgForDataUri,
744
+ getAsset
745
+ } from 'fa-mcp-sdk';
746
+
747
+ // trim - safely trim string with null/undefined handling
748
+ // Function Signature:
749
+ const trim = (s: any): string {...}
750
+
751
+ // Examples:
752
+ const cleanText1 = trim(' hello '); // Returns 'hello'
753
+ const cleanText2 = trim(null); // Returns ''
754
+ const cleanText3 = trim(undefined); // Returns ''
755
+ const cleanText4 = trim(123); // Returns '123'
756
+
757
+ // isMainModule - check if current module is the main entry point
758
+ // Function Signature:
759
+ const isMainModule = (url: string): boolean {...}
760
+
761
+ // Example:
762
+ if (isMainModule(import.meta.url)) {
763
+ console.log('Running as main module');
764
+ startServer();
765
+ }
766
+
767
+ // isObject - check if value is an object (not null, not array)
768
+ // Function Signature:
769
+ const isObject = (o: any): boolean {...}
770
+
771
+ // Examples:
772
+ isObject({}); // Returns true
773
+ isObject({ key: 'value' }); // Returns true
774
+ isObject([]); // Returns false
775
+ isObject(null); // Returns false
776
+ isObject('string'); // Returns false
777
+
778
+ // isNonEmptyObject - check if value is non-empty object with defined values
779
+ // Function Signature:
780
+ const isNonEmptyObject = (o: any): boolean {...}
781
+
782
+ // Examples:
783
+ isNonEmptyObject({ key: 'value' }); // Returns true
784
+ isNonEmptyObject({}); // Returns false
785
+ isNonEmptyObject({ key: undefined }); // Returns false
786
+ isNonEmptyObject([]); // Returns false
787
+
788
+ // ppj - pretty-print JSON with 2-space indentation
789
+ // Function Signature:
790
+ const ppj = (v: any): string {...}
791
+
792
+ // Example:
793
+ const formatted = ppj({ user: 'john', age: 30 });
794
+ // Returns:
795
+ // {
796
+ // "user": "john",
797
+ // "age": 30
798
+ // }
799
+
800
+ // encodeSvgForDataUri - encode SVG content for use in data URI
801
+ // Function Signature:
802
+ const encodeSvgForDataUri = (svg: string): string {...}
803
+
804
+ // Example:
805
+ const svgContent = '<svg xmlns="http://www.w3.org/2000/svg"><circle r="10"/></svg>';
806
+ const encoded = encodeSvgForDataUri(svgContent);
807
+ const dataUri = `data:image/svg+xml,${encoded}`;
808
+
809
+ // getAsset - get asset file content from src/asset folder
810
+ // Function Signature:
811
+ const getAsset = (relPathFromAssetRoot: string): string | undefined {...}
812
+
813
+ // Example:
814
+ const logoContent = getAsset('logo.svg'); // Reads from src/asset/logo.svg
815
+ const iconContent = getAsset('icons/star.svg'); // Reads from src/asset/icons/star.svg
816
+ ```
817
+
818
+ #### Network Utilities
819
+
820
+ ```typescript
821
+ import { isPortAvailable, checkPortAvailability } from 'fa-mcp-sdk';
822
+
823
+ // isPortAvailable - check if port is available for binding
824
+ // Function Signature:
825
+ function isPortAvailable (port: number, host: string = '0.0.0.0'): Promise<boolean> {...}
826
+
827
+ // Examples:
828
+ const available1 = await isPortAvailable(3000); // Check on all interfaces
829
+ const available2 = await isPortAvailable(3000, 'localhost'); // Check on localhost
830
+ const available3 = await isPortAvailable(8080, '192.168.1.10'); // Check on specific IP
831
+
832
+ if (available1) {
833
+ console.log('Port 3000 is available');
834
+ } else {
835
+ console.log('Port 3000 is occupied');
836
+ }
837
+
838
+ // checkPortAvailability - check port with error handling
839
+ // Function Signature:
840
+ async function checkPortAvailability (
841
+ port: number,
842
+ host: string = '0.0.0.0',
843
+ exitOnError: boolean = true
844
+ ): Promise<void> {...}
845
+
846
+ // Examples:
847
+ try {
848
+ // Throws error if port is busy
849
+ await checkPortAvailability(3000, 'localhost', true);
850
+ console.log('Port is available, can start server');
851
+ } catch (error) {
852
+ console.log('Port is busy:', error.message);
853
+ }
854
+
855
+ // Don't exit process on busy port
856
+ try {
857
+ await checkPortAvailability(3000, 'localhost', false);
858
+ console.log('Port is available');
859
+ } catch (error) {
860
+ console.log('Port is occupied, will use different port');
861
+ // Continue execution instead of exiting
862
+ }
863
+ ```
864
+
865
+ #### Tool Result Formatting
866
+
867
+ ```typescript
868
+ import { formatToolResult, getJsonFromResult } from 'fa-mcp-sdk';
869
+
870
+ // formatToolResult - format tool execution results based on configuration
871
+ // Function Signature:
872
+ function formatToolResult (json: any): any {...}
873
+
874
+ // Behavior depends on appConfig.mcp.toolAnswerAs setting:
875
+ // - 'structuredContent': Returns { structuredContent: json }
876
+ // - 'text': Returns { content: [{ type: 'text', text: JSON.stringify(json, null, 2) }] }
877
+
878
+ // Examples:
879
+ const result = {
880
+ message: 'Operation completed',
881
+ data: { count: 42, items: ['a', 'b'] },
882
+ timestamp: new Date().toISOString(),
883
+ };
884
+
885
+ const formattedResult = formatToolResult(result);
886
+
887
+ // If toolAnswerAs = 'structuredContent':
888
+ // {
889
+ // structuredContent: {
890
+ // message: 'Operation completed',
891
+ // data: { count: 42, items: ['a', 'b'] },
892
+ // timestamp: '2025-01-01T12:00:00.000Z'
893
+ // }
894
+ // }
895
+
896
+ // If toolAnswerAs = 'text':
897
+ // {
898
+ // content: [{
899
+ // type: 'text',
900
+ // text: '{\n "message": "Operation completed",\n "data": {\n "count": 42,\n "items": ["a", "b"]\n },\n "timestamp": "2025-01-01T12:00:00.000Z"\n}'
901
+ // }]
902
+ // }
903
+
904
+ // getJsonFromResult - extract original JSON from formatted result
905
+ // Function Signature:
906
+ const getJsonFromResult = <T = any> (result: any): T {...}
907
+
908
+ // Examples:
909
+ const originalData1 = getJsonFromResult<MyDataType>(formattedResult);
910
+
911
+ // Works with both response formats:
912
+ const structuredResponse = { structuredContent: { user: 'john', age: 30 } };
913
+ const textResponse = {
914
+ content: [{ type: 'text', text: '{"user":"john","age":30}' }]
915
+ };
916
+
917
+ const data1 = getJsonFromResult(structuredResponse); // { user: 'john', age: 30 }
918
+ const data2 = getJsonFromResult(textResponse); // { user: 'john', age: 30 }
919
+ ```
920
+
921
+ ### Logging
922
+
923
+ ```typescript
924
+ import { logger, fileLogger } from 'fa-mcp-sdk';
925
+
926
+ // Console logging
927
+ logger.info('Server started successfully');
928
+ logger.warn('Warning message');
929
+ logger.error('Error occurred', error);
930
+
931
+ // File logging (if configured)
932
+ fileLogger.info('This goes to file');
933
+
934
+ // Ensure file logs are written before shutdown
935
+ await fileLogger.asyncFinish();
936
+ ```
937
+
938
+ ### Event System
939
+
940
+ ```typescript
941
+ import { eventEmitter } from 'fa-mcp-sdk';
942
+
943
+ // Listen for events
944
+ eventEmitter.on('server:started', (data) => {
945
+ console.log('Server started with config:', data);
946
+ });
947
+
948
+ // Emit custom events
949
+ eventEmitter.emit('custom:event', { data: 'example' });
950
+ ```
951
+
952
+ ### Testing Your MCP Server
953
+
954
+ #### Test Structure
955
+
956
+ Create tests in your `tests/` directory:
957
+
958
+ **`tests/utils.ts`** - Test utilities:
959
+ ```typescript
960
+ import { ITestResult, logResultToFile, formatResultAsMarkdown } from 'fa-mcp-sdk';
961
+
962
+ export interface ITestResult {
963
+ fullId: string;
964
+ toolName: string;
965
+ description: string;
966
+ parameters: unknown | null;
967
+ timestamp: string;
968
+ duration: number;
969
+ status: 'pending' | 'passed' | 'failed' | 'skipped' | 'expected_failure';
970
+ response: unknown | null;
971
+ error: string | null;
972
+ }
973
+
974
+ // Log test results
975
+ await logResultToFile(testResult);
976
+
977
+ // Format as markdown
978
+ const markdown = formatResultAsMarkdown(testResult);
979
+ ```
980
+
981
+ #### Test Clients
982
+
983
+ Use the provided test clients to test your MCP server:
984
+
985
+ **STDIO Transport Testing:**
986
+ ```typescript
987
+ import { McpStdioClient } from 'fa-mcp-sdk';
988
+ import { spawn } from 'child_process';
989
+
990
+ const proc = spawn('node', ['dist/start.js', 'stdio'], {
991
+ stdio: ['pipe', 'pipe', 'pipe'],
992
+ env: { ...process.env, NODE_ENV: 'test' },
993
+ });
994
+
995
+ const client = new McpStdioClient(proc);
996
+
997
+ // Test tools
998
+ const result = await client.callTool('my_custom_tool', { query: 'test' });
999
+ console.log(result);
1000
+
1001
+ // Test prompts
1002
+ const prompt = await client.getPrompt('agent_brief');
1003
+ console.log(prompt);
1004
+ ```
1005
+
1006
+ **HTTP Transport Testing:**
1007
+ ```typescript
1008
+ import { McpHttpClient } from 'fa-mcp-sdk';
1009
+
1010
+ const client = new McpHttpClient('http://localhost:3000');
1011
+
1012
+ // Test with authentication headers
1013
+ const result = await client.callTool('my_custom_tool', { query: 'test' }, {
1014
+ 'Authorization': 'Bearer your-jwt-token'
1015
+ });
1016
+ ```
1017
+
1018
+ **SSE Transport Testing:**
1019
+ ```typescript
1020
+ import { McpSseClient } from 'fa-mcp-sdk';
1021
+
1022
+ const client = new McpSseClient('http://localhost:3000');
1023
+ const result = await client.callTool('my_custom_tool', { query: 'test' });
1024
+ ```
1025
+
1026
+ #### Test Categories and Recommendations
1027
+
1028
+ 1. **Prompt Tests**:
1029
+ - Test that prompts are listed correctly
1030
+ - Test prompt content retrieval
1031
+ - Test dynamic prompt generation
1032
+
1033
+ 2. **Resource Tests**:
1034
+ - Test resource listing
1035
+ - Test resource content reading
1036
+ - Test dynamic resource generation
1037
+
1038
+ 3. **Tool Tests**:
1039
+ - Test tool listing
1040
+ - Test tool execution with valid parameters
1041
+ - Test error handling for invalid parameters
1042
+ - Test tool response formatting
1043
+
1044
+ 4. **Transport Tests**:
1045
+ - Test all transport types your server supports
1046
+ - Test authentication (if enabled)
1047
+ - Test error responses
1048
+
1049
+ Example test implementation:
1050
+ ```typescript
1051
+ // tests/mcp/test-tools.js
1052
+ async function testMyCustomTool(client) {
1053
+ const name = 'Test my_custom_tool execution';
1054
+ try {
1055
+ const result = await client.callTool('my_custom_tool', { query: 'test input' });
1056
+ const success = result?.response?.includes('Processed');
1057
+ return success ?
1058
+ { name, passed: true, details: result } :
1059
+ { name, passed: false, details: result };
1060
+ } catch (error) {
1061
+ return { name, passed: false, details: { error: error.message } };
1062
+ }
1063
+ }
1064
+ ```
1065
+
1066
+ ### Consul Integration
1067
+
1068
+ If using Consul for service discovery:
1069
+
1070
+ ```typescript
1071
+ import {
1072
+ getConsulAPI,
1073
+ accessPointUpdater,
1074
+ deregisterServiceFromConsul
1075
+ } from 'fa-mcp-sdk';
1076
+
1077
+ // getConsulAPI - get configured Consul client instance
1078
+ // Function Signature:
1079
+ const getConsulAPI = async (): Promise<any> {...}
1080
+
1081
+ // Returns Consul API client configured from appConfig.consul settings
1082
+ // Example:
1083
+ const consulApi = await getConsulAPI();
1084
+ const services = await consulApi.catalog.service.list();
1085
+ console.log('Available services:', services);
1086
+
1087
+ // deregisterServiceFromConsul - remove service registration from Consul
1088
+ // Function Signature:
1089
+ const deregisterServiceFromConsul = async (): Promise<void> {...}
1090
+
1091
+ // Note: This function reads serviceId from command line arguments (process.argv)
1092
+ // Usage in command line context:
1093
+ // node script.js <serviceId> [agentHost] [agentPort]
1094
+
1095
+ // Example programmatic usage:
1096
+ await deregisterServiceFromConsul();
1097
+
1098
+ // accessPointUpdater - manage access point lifecycle
1099
+ // Object with start/stop methods:
1100
+ const accessPointUpdater = {
1101
+ start(): void; // Start automatic access point updates
1102
+ stop(): void; // Stop automatic access point updates
1103
+ }
1104
+
1105
+ // Examples:
1106
+ accessPointUpdater.start(); // Automatically starts if appConfig.accessPoints configured
1107
+ accessPointUpdater.stop(); // Stop updates (called automatically on shutdown)
1108
+
1109
+ // Access point configuration in config/default.yaml:
1110
+ // accessPoints:
1111
+ // myService:
1112
+ // title: 'My remote service'
1113
+ // host: <host>
1114
+ // port: 9999
1115
+ // token: '***'
1116
+ // noConsul: true
1117
+ // consulServiceName: <consulServiceName>
1118
+ ```
1119
+
1120
+ ### Graceful Shutdown
1121
+
1122
+ ```typescript
1123
+ import { gracefulShutdown } from 'fa-mcp-sdk';
1124
+
1125
+ // gracefulShutdown - perform graceful application shutdown
1126
+ // Function Signature:
1127
+ async function gracefulShutdown (signal: string, exitCode: number = 0): Promise<void> {...}
1128
+
1129
+ // Automatically handles:
1130
+ // - Stopping Consul service registration
1131
+ // - Closing database connections
1132
+ // - Flushing file logs
1133
+ // - Stopping access point updater
1134
+ // - Process exit with specified code
1135
+
1136
+ // Examples:
1137
+ // Manual shutdown
1138
+ process.on('SIGUSR2', () => {
1139
+ gracefulShutdown('SIGUSR2', 0);
1140
+ });
1141
+
1142
+ // Emergency shutdown
1143
+ process.on('uncaughtException', (error) => {
1144
+ console.error('Uncaught exception:', error);
1145
+ gracefulShutdown('UNCAUGHT_EXCEPTION', 1);
1146
+ });
1147
+
1148
+ // Note: SDK automatically registers SIGINT and SIGTERM handlers
1149
+ // in initMcpServer(), so manual registration is only needed for custom signals
1150
+ ```
1151
+
1152
+ ### Transport Types
1153
+
1154
+ #### STDIO Transport
1155
+ - Use for CLI tools and local development
1156
+ - Configure with `mcp.transportType: "stdio"`
1157
+ - Lightweight, no HTTP overhead
1158
+
1159
+ #### HTTP Transport
1160
+ - Use for web-based integrations
1161
+ - Configure with `mcp.transportType: "http"`
1162
+ - Supports REST API, authentication, Swagger docs
1163
+ - Requires `webServer` configuration
1164
+
1165
+ #### Server-Sent Events (SSE)
1166
+ - Real-time streaming over HTTP
1167
+ - Good for long-running operations
1168
+ - Maintains persistent connections
1169
+
1170
+ ### Best Practices
1171
+
1172
+ #### Project Organization
1173
+ 1. **Keep tools focused** - One responsibility per tool
1174
+ 2. **Use TypeScript** - Leverage type safety throughout
1175
+ 3. **Organize by feature** - Group related functionality
1176
+ 4. **Configure environments** - Use separate configs for dev/prod
1177
+
1178
+ #### Tool Development
1179
+ 1. **Validate inputs** - Always check required parameters
1180
+ 2. **Use formatToolResult()** - Consistent response formatting
1181
+ 3. **Handle errors gracefully** - Use appropriate error classes
1182
+ 4. **Log operations** - Use the provided logger
1183
+
1184
+ #### Testing
1185
+ 1. **Test all transports** - Ensure compatibility
1186
+ 2. **Include error cases** - Test failure scenarios
1187
+ 3. **Use provided clients** - Leverage built-in test utilities
1188
+ 4. **Document test cases** - Clear, descriptive test names
1189
+
1190
+ #### Security
1191
+ 1. **Environment variables** - Never hardcode secrets
1192
+ 2. **Authentication** - Enable for production HTTP servers
1193
+ 3. **Input validation** - Validate all user inputs
1194
+ 4. **Error messages** - Don't leak sensitive information
1195
+
1196
+ This documentation provides everything needed to build, test, and deploy your own
1197
+ MCP server using the FA-MCP-SDK framework.