fa-mcp-sdk 0.2.111 → 0.2.116

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,1280 @@
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
+ ### Cache Management
390
+
391
+ #### `getCache(options?): CacheManager`
392
+
393
+ Get or create a global cache instance for your MCP server.
394
+
395
+ ```typescript
396
+ import { getCache, CacheManager } from 'fa-mcp-sdk';
397
+
398
+ // Create default cache instance
399
+ const cache = getCache();
400
+
401
+ // Create cache with custom options
402
+ const customCache = getCache({
403
+ ttlSeconds: 600, // Default TTL: 10 minutes
404
+ maxItems: 5000, // Max cached items
405
+ checkPeriod: 300, // Cleanup interval in seconds
406
+ verbose: true // Enable debug logging
407
+ });
408
+ ```
409
+
410
+ #### Cache Methods
411
+
412
+ The `CacheManager` provides the following methods:
413
+
414
+ | Method | Description | Example |
415
+ |--------|-------------|---------|
416
+ | `get<T>(key)` | Get value from cache | `const user = cache.get<User>('user:123');` |
417
+ | `set<T>(key, value, ttl?)` | Set value in cache | `cache.set('user:123', userData, 300);` |
418
+ | `has(key)` | Check if key exists | `if (cache.has('user:123')) { ... }` |
419
+ | `del(key)` | Delete key from cache | `cache.del('user:123');` |
420
+ | `take<T>(key)` | Get and delete (single use) | `const otp = cache.take<string>('otp:123');` |
421
+ | `mget<T>(keys[])` | Get multiple values | `const users = cache.mget(['user:1', 'user:2']);` |
422
+ | `mset(items[])` | Set multiple values | `cache.mset([{key: 'a', val: 1}, {key: 'b', val: 2}]);` |
423
+ | `getOrSet<T>(key, factory, ttl?)` | Get or compute value | `const data = await cache.getOrSet('key', () => fetchData());` |
424
+ | `keys()` | List all keys | `const allKeys = cache.keys();` |
425
+ | `flush()` | Clear all entries | `cache.flush();` |
426
+ | `ttl(key, seconds)` | Update key TTL | `cache.ttl('user:123', 600);` |
427
+ | `getTtl(key)` | Get remaining TTL | `const remaining = cache.getTtl('user:123');` |
428
+ | `getStats()` | Get cache statistics | `const stats = cache.getStats();` |
429
+ | `close()` | Close cache resources | `cache.close();` |
430
+
431
+ #### Usage Examples
432
+
433
+ ```typescript
434
+ import { getCache } from 'fa-mcp-sdk';
435
+
436
+ const cache = getCache();
437
+
438
+ // Basic caching
439
+ cache.set('user:123', { name: 'John', email: 'john@example.com' });
440
+ const user = cache.get<User>('user:123');
441
+
442
+ // Cache with TTL (time to live)
443
+ cache.set('session:abc', sessionData, 1800); // 30 minutes
444
+
445
+ // Single-use values (OTP, tokens)
446
+ cache.set('otp:user123', '123456', 300);
447
+ const otp = cache.take('otp:user123'); // Gets and deletes
448
+
449
+ // Get-or-set pattern
450
+ const expensiveData = await cache.getOrSet(
451
+ 'computation:key',
452
+ async () => {
453
+ // This function runs only on cache miss
454
+ return await performExpensiveOperation();
455
+ },
456
+ 3600 // Cache for 1 hour
457
+ );
458
+
459
+ // Batch operations
460
+ const userData = cache.mget(['user:1', 'user:2', 'user:3']);
461
+ cache.mset([
462
+ { key: 'user:1', val: user1Data },
463
+ { key: 'user:2', val: user2Data, ttl: 600 }
464
+ ]);
465
+
466
+ // Cache monitoring
467
+ const stats = cache.getStats();
468
+ console.log(`Hit rate: ${(stats.hitRate * 100).toFixed(1)}%`);
469
+ console.log(`Keys: ${stats.keys}, Memory: ${stats.vsize} bytes`);
470
+ ```
471
+
472
+ ### Database Integration
473
+
474
+ To disable the use of the database, you need to set appConfig.db.postgres.dbs.main.host to an empty value.
475
+ In this case, when the configuration is formed, appConfig.isMainDBUsed is set to false.
476
+
477
+
478
+ If you enable database support (`isMainDBUsed: true` in config):
479
+
480
+ ```typescript
481
+ import {
482
+ queryMAIN,
483
+ execMAIN,
484
+ oneRowMAIN,
485
+ getMainDBConnectionStatus
486
+ } from 'fa-mcp-sdk';
487
+
488
+ // Check database connection. If there is no connection, the application stops
489
+ await checkMainDB();
490
+
491
+ // queryMAIN - the main function of executing SQL queries to the main database
492
+
493
+ // Function Signature:
494
+ const queryMAIN = async <R extends QueryResultRow = any> (
495
+ arg: string | IQueryPgArgsCOptional,
496
+ sqlValues?: any[],
497
+ throwError = false,
498
+ ): Promise<QueryResult<R> | undefined> {...}
499
+
500
+ // Types used:
501
+ export interface IQueryPgArgs {
502
+ connectionId: string,
503
+ poolConfig?: PoolConfig & IDbOptionsPg,
504
+ client?: IPoolPg,
505
+ sqlText: string,
506
+ sqlValues?: any[],
507
+ throwError?: boolean,
508
+ prefix?: string,
509
+ registerTypesFunctions?: IRegisterTypeFn[],
510
+ }
511
+ export interface IQueryPgArgsCOptional extends Omit<IQueryPgArgs, 'connectionId'> {
512
+ connectionId?: string
513
+ }
514
+
515
+ // Examples of use
516
+ const users1 = await queryMAIN('SELECT * FROM users WHERE active = $1', [true]);
517
+ // Alternative use case
518
+ const users2 = await queryMAIN({ sqlText: 'SELECT * FROM users WHERE active = $1', sqlValues: [true] });
519
+
520
+
521
+ // execMAIN - execute SQL commands without returning result set
522
+ // Function Signature:
523
+ const execMAIN = async (
524
+ arg: string | IQueryPgArgsCOptional,
525
+ ): Promise<number | undefined> {...}
526
+
527
+ // Examples:
528
+ await execMAIN('INSERT INTO logs (message, created_at) VALUES ($1, $2)',
529
+ ['Server started', new Date()]);
530
+ await execMAIN({ sqlText: 'UPDATE users SET active = $1 WHERE id = $2', sqlValues: [false, userId] });
531
+
532
+ // queryRsMAIN - execute SQL and return rows array directly
533
+ // Function Signature:
534
+ const queryRsMAIN = async <R extends QueryResultRow = any> (
535
+ arg: string | IQueryPgArgsCOptional,
536
+ sqlValues?: any[],
537
+ throwError = false,
538
+ ): Promise<R[] | undefined> {...}
539
+
540
+ // Example:
541
+ const users = await queryRsMAIN<User>('SELECT * FROM users WHERE active = $1', [true]);
542
+
543
+ // oneRowMAIN - execute SQL and return single row
544
+ // Function Signature:
545
+ const oneRowMAIN = async <R extends QueryResultRow = any> (
546
+ arg: string | IQueryPgArgsCOptional,
547
+ sqlValues?: any[],
548
+ throwError = false,
549
+ ): Promise<R | undefined> {...}
550
+
551
+ // Example:
552
+ const user = await oneRowMAIN<User>('SELECT * FROM users WHERE id = $1', [userId]);
553
+
554
+ // getMainDBConnectionStatus - check database connection status
555
+ // Function Signature:
556
+ const getMainDBConnectionStatus = async (): Promise<string> {...}
557
+
558
+ // Possible return values: 'connected' | 'disconnected' | 'error' | 'db_not_used'
559
+ const status = await getMainDBConnectionStatus();
560
+
561
+ // checkMainDB - verify database connectivity (stops application if failed)
562
+ // Function Signature:
563
+ const checkMainDB = async (): Promise<void> {...}
564
+
565
+ // Example:
566
+ await checkMainDB(); // Throws or exits process if DB connection fails
567
+
568
+ // getInsertSqlMAIN - generate INSERT SQL statement
569
+ // Function Signature:
570
+ const getInsertSqlMAIN = async <U extends TDBRecord = TDBRecord> (arg: {
571
+ commonSchemaAndTable: string,
572
+ recordset: TRecordSet<U>,
573
+ excludeFromInsert?: string[],
574
+ addOutputInserted?: boolean,
575
+ isErrorOnConflict?: boolean,
576
+ keepSerialFields?: boolean,
577
+ }): Promise<string> {...}
578
+
579
+ // Example:
580
+ const insertSql = await getInsertSqlMAIN({
581
+ commonSchemaAndTable: 'public.users',
582
+ recordset: [{ name: 'John', email: 'john@example.com' }],
583
+ addOutputInserted: true
584
+ });
585
+
586
+ // getMergeSqlMAIN - generate UPSERT (INSERT...ON CONFLICT) SQL statement
587
+ // Function Signature:
588
+ const getMergeSqlMAIN = async <U extends TDBRecord = TDBRecord> (arg: {
589
+ commonSchemaAndTable: string,
590
+ recordset: TRecordSet<U>,
591
+ conflictFields?: string[],
592
+ omitFields?: string[],
593
+ updateFields?: string[],
594
+ fieldsExcludedFromUpdatePart?: string[],
595
+ noUpdateIfNull?: boolean,
596
+ mergeCorrection?: (_sql: string) => string,
597
+ returning?: string,
598
+ }): Promise<string> {...}
599
+
600
+ // Example:
601
+ const mergeSql = await getMergeSqlMAIN({
602
+ commonSchemaAndTable: 'public.users',
603
+ recordset: [{ id: 1, name: 'John Updated', email: 'john@example.com' }],
604
+ conflictFields: ['email'],
605
+ returning: '*'
606
+ });
607
+
608
+ // mergeByBatch - execute merge operations in batches
609
+ // Function Signature:
610
+ const mergeByBatch = async <U extends TDBRecord = TDBRecord> (arg: {
611
+ recordset: TRecordSet<U>,
612
+ getMergeSqlFn: Function
613
+ batchSize?: number
614
+ }): Promise<any[]> {...}
615
+
616
+ // Example:
617
+ const results = await mergeByBatch({
618
+ recordset: largeDataSet,
619
+ getMergeSqlFn: (batch) => getMergeSqlMAIN({
620
+ commonSchemaAndTable: 'public.users',
621
+ recordset: batch
622
+ }),
623
+ batchSize: 500
624
+ });
625
+ ```
626
+
627
+ ### Error Handling
628
+
629
+ #### Custom Error Classes
630
+
631
+ ```typescript
632
+ import { BaseMcpError, ToolExecutionError, ValidationError } from 'fa-mcp-sdk';
633
+
634
+ // Create custom error types
635
+ class MyCustomError extends BaseMcpError {
636
+ constructor(message: string) {
637
+ super(message, 'CUSTOM_ERROR');
638
+ }
639
+ }
640
+
641
+ // Use built-in error types
642
+ if (!validInput) {
643
+ throw new ValidationError('Input validation failed');
644
+ }
645
+
646
+ if (toolFailed) {
647
+ throw new ToolExecutionError('my_tool', 'Tool execution failed');
648
+ }
649
+ ```
650
+
651
+ #### Error Utilities
652
+
653
+ ```typescript
654
+ import {
655
+ createJsonRpcErrorResponse,
656
+ toError,
657
+ toStr,
658
+ addErrorMessage
659
+ } from 'fa-mcp-sdk';
660
+
661
+ // createJsonRpcErrorResponse - create JSON-RPC 2.0 error response
662
+ // Function Signature:
663
+ function createJsonRpcErrorResponse (
664
+ error: Error | BaseMcpError,
665
+ requestId?: string | number | null,
666
+ ): any {...}
667
+
668
+ // Example:
669
+ try {
670
+ // some operation
671
+ } catch (error) {
672
+ const jsonRpcError = createJsonRpcErrorResponse(error, 'request-123');
673
+ res.json(jsonRpcError);
674
+ }
675
+
676
+ // toError - safely convert any value to Error object
677
+ // Function Signature:
678
+ const toError = (err: any): Error {...}
679
+
680
+ // Examples:
681
+ const err1 = toError(new Error('Original error')); // Returns original Error
682
+ const err2 = toError('String error message'); // Returns new Error('String error message')
683
+ const err3 = toError({ message: 'Object error' }); // Returns new Error('[object Object]')
684
+
685
+ // toStr - safely convert error to string message
686
+ // Function Signature:
687
+ const toStr = (err: any): string {...}
688
+
689
+ // Examples:
690
+ const msg1 = toStr(new Error('Test error')); // Returns 'Test error'
691
+ const msg2 = toStr('String message'); // Returns 'String message'
692
+ const msg3 = toStr(null); // Returns 'Unknown error'
693
+
694
+ // addErrorMessage - add context to existing error message
695
+ // Function Signature:
696
+ const addErrorMessage = (err: any, msg: string): void {...}
697
+
698
+ // Example:
699
+ const originalError = new Error('Connection failed');
700
+ addErrorMessage(originalError, 'Database operation failed');
701
+ // originalError.message is now: 'Database operation failed. Connection failed'
702
+ ```
703
+
704
+ ### Authentication and Security
705
+
706
+ #### Token-based Authentication
707
+
708
+ ```typescript
709
+ import {
710
+ authByToken,
711
+ authTokenMW,
712
+ ICheckTokenResult,
713
+ checkToken,
714
+ generateToken
715
+ } from 'fa-mcp-sdk';
716
+
717
+ // Types used:
718
+ export interface ICheckTokenResult {
719
+ inTokenType?: TTokenType // 'permanent' | 'JWT'
720
+ payload?: ITokenPayload, // Token payload with user data
721
+ errorReason?: string, // Error message if validation failed
722
+ isTokenDecrypted?: boolean, // Whether token was successfully decrypted
723
+ }
724
+
725
+ export interface ITokenPayload {
726
+ user: string, // Username
727
+ expire: number, // Expiration timestamp
728
+ [key: string]: any, // Additional payload data
729
+ }
730
+
731
+ // checkToken - validate token and return detailed result
732
+ // Function Signature:
733
+ const checkToken = (arg: {
734
+ token: string,
735
+ expectedUser?: string,
736
+ expectedService?: string,
737
+ }): ICheckTokenResult {...}
738
+
739
+ // Example:
740
+ const tokenResult = checkToken({
741
+ token: 'user_provided_token',
742
+ expectedUser: 'john_doe',
743
+ expectedService: 'my-mcp-server'
744
+ });
745
+
746
+ if (!tokenResult.errorReason) {
747
+ console.log('Valid token for user:', tokenResult.payload?.user);
748
+ } else {
749
+ console.log('Auth failed:', tokenResult.errorReason);
750
+ }
751
+
752
+ // generateToken - create JWT token
753
+ // Function Signature:
754
+ const generateToken = (user: string, liveTimeSec: number, payload?: any): string {...}
755
+
756
+ // Example:
757
+ const token = generateToken('john_doe', 3600, { role: 'admin' }); // 1 hour token
758
+
759
+ // authByToken - Express route handler for token validation
760
+ // Function Signature:
761
+ const authByToken = (req: Request, res: Response): boolean {...}
762
+
763
+ // Example:
764
+ app.post('/api/secure', (req, res) => {
765
+ if (!authByToken(req, res)) {
766
+ return; // Response already sent with error
767
+ }
768
+ // User is authenticated, continue with request
769
+ res.json({ message: 'Access granted' });
770
+ });
771
+
772
+ // authTokenMW - Express middleware for token authentication
773
+ // Function Signature:
774
+ const authTokenMW = (req: Request, res: Response, next: NextFunction): void {...}
775
+
776
+ // Example:
777
+ import express from 'express';
778
+ const app = express();
779
+ app.use('/protected', authTokenMW); // Apply to all /protected/* routes
780
+
781
+ #### Token Generation
782
+
783
+ ```typescript
784
+ import { generateTokenApp } from 'fa-mcp-sdk';
785
+
786
+ // generateTokenApp - start token generation web application
787
+ // Function Signature:
788
+ async function generateTokenApp (...args: any[]): Promise<void> {...}
789
+
790
+ // Starts a web server for generating authentication tokens
791
+ // Uses NTLM authentication if configured
792
+ // Web interface available at configured host:port
793
+
794
+ // Example:
795
+ await generateTokenApp(); // Uses default configuration from appConfig
796
+
797
+ // Token generation app provides:
798
+ // - Web interface for token creation
799
+ // - NTLM domain authentication support
800
+ // - JWT token generation with configurable expiration
801
+ // - Integration with Active Directory (if configured)
802
+
803
+ // Configuration in config/default.yaml:
804
+ // webServer:
805
+ // auth:
806
+ // token:
807
+ // encryptKey: '***' # Symmetric key for token encryption
808
+ //
809
+ // Optional NTLM configuration:
810
+ // ntlm:
811
+ // domain: 'DOMAIN'
812
+ // domainController: 'dc.domain.com'
813
+ ```
814
+
815
+ ### Utility Functions
816
+
817
+ #### General Utilities
818
+
819
+ ```typescript
820
+ import {
821
+ trim,
822
+ isMainModule,
823
+ isNonEmptyObject,
824
+ isObject,
825
+ ppj,
826
+ encodeSvgForDataUri,
827
+ getAsset
828
+ } from 'fa-mcp-sdk';
829
+
830
+ // trim - safely trim string with null/undefined handling
831
+ // Function Signature:
832
+ const trim = (s: any): string {...}
833
+
834
+ // Examples:
835
+ const cleanText1 = trim(' hello '); // Returns 'hello'
836
+ const cleanText2 = trim(null); // Returns ''
837
+ const cleanText3 = trim(undefined); // Returns ''
838
+ const cleanText4 = trim(123); // Returns '123'
839
+
840
+ // isMainModule - check if current module is the main entry point
841
+ // Function Signature:
842
+ const isMainModule = (url: string): boolean {...}
843
+
844
+ // Example:
845
+ if (isMainModule(import.meta.url)) {
846
+ console.log('Running as main module');
847
+ startServer();
848
+ }
849
+
850
+ // isObject - check if value is an object (not null, not array)
851
+ // Function Signature:
852
+ const isObject = (o: any): boolean {...}
853
+
854
+ // Examples:
855
+ isObject({}); // Returns true
856
+ isObject({ key: 'value' }); // Returns true
857
+ isObject([]); // Returns false
858
+ isObject(null); // Returns false
859
+ isObject('string'); // Returns false
860
+
861
+ // isNonEmptyObject - check if value is non-empty object with defined values
862
+ // Function Signature:
863
+ const isNonEmptyObject = (o: any): boolean {...}
864
+
865
+ // Examples:
866
+ isNonEmptyObject({ key: 'value' }); // Returns true
867
+ isNonEmptyObject({}); // Returns false
868
+ isNonEmptyObject({ key: undefined }); // Returns false
869
+ isNonEmptyObject([]); // Returns false
870
+
871
+ // ppj - pretty-print JSON with 2-space indentation
872
+ // Function Signature:
873
+ const ppj = (v: any): string {...}
874
+
875
+ // Example:
876
+ const formatted = ppj({ user: 'john', age: 30 });
877
+ // Returns:
878
+ // {
879
+ // "user": "john",
880
+ // "age": 30
881
+ // }
882
+
883
+ // encodeSvgForDataUri - encode SVG content for use in data URI
884
+ // Function Signature:
885
+ const encodeSvgForDataUri = (svg: string): string {...}
886
+
887
+ // Example:
888
+ const svgContent = '<svg xmlns="http://www.w3.org/2000/svg"><circle r="10"/></svg>';
889
+ const encoded = encodeSvgForDataUri(svgContent);
890
+ const dataUri = `data:image/svg+xml,${encoded}`;
891
+
892
+ // getAsset - get asset file content from src/asset folder
893
+ // Function Signature:
894
+ const getAsset = (relPathFromAssetRoot: string): string | undefined {...}
895
+
896
+ // Example:
897
+ const logoContent = getAsset('logo.svg'); // Reads from src/asset/logo.svg
898
+ const iconContent = getAsset('icons/star.svg'); // Reads from src/asset/icons/star.svg
899
+ ```
900
+
901
+ #### Network Utilities
902
+
903
+ ```typescript
904
+ import { isPortAvailable, checkPortAvailability } from 'fa-mcp-sdk';
905
+
906
+ // isPortAvailable - check if port is available for binding
907
+ // Function Signature:
908
+ function isPortAvailable (port: number, host: string = '0.0.0.0'): Promise<boolean> {...}
909
+
910
+ // Examples:
911
+ const available1 = await isPortAvailable(3000); // Check on all interfaces
912
+ const available2 = await isPortAvailable(3000, 'localhost'); // Check on localhost
913
+ const available3 = await isPortAvailable(8080, '192.168.1.10'); // Check on specific IP
914
+
915
+ if (available1) {
916
+ console.log('Port 3000 is available');
917
+ } else {
918
+ console.log('Port 3000 is occupied');
919
+ }
920
+
921
+ // checkPortAvailability - check port with error handling
922
+ // Function Signature:
923
+ async function checkPortAvailability (
924
+ port: number,
925
+ host: string = '0.0.0.0',
926
+ exitOnError: boolean = true
927
+ ): Promise<void> {...}
928
+
929
+ // Examples:
930
+ try {
931
+ // Throws error if port is busy
932
+ await checkPortAvailability(3000, 'localhost', true);
933
+ console.log('Port is available, can start server');
934
+ } catch (error) {
935
+ console.log('Port is busy:', error.message);
936
+ }
937
+
938
+ // Don't exit process on busy port
939
+ try {
940
+ await checkPortAvailability(3000, 'localhost', false);
941
+ console.log('Port is available');
942
+ } catch (error) {
943
+ console.log('Port is occupied, will use different port');
944
+ // Continue execution instead of exiting
945
+ }
946
+ ```
947
+
948
+ #### Tool Result Formatting
949
+
950
+ ```typescript
951
+ import { formatToolResult, getJsonFromResult } from 'fa-mcp-sdk';
952
+
953
+ // formatToolResult - format tool execution results based on configuration
954
+ // Function Signature:
955
+ function formatToolResult (json: any): any {...}
956
+
957
+ // Behavior depends on appConfig.mcp.toolAnswerAs setting:
958
+ // - 'structuredContent': Returns { structuredContent: json }
959
+ // - 'text': Returns { content: [{ type: 'text', text: JSON.stringify(json, null, 2) }] }
960
+
961
+ // Examples:
962
+ const result = {
963
+ message: 'Operation completed',
964
+ data: { count: 42, items: ['a', 'b'] },
965
+ timestamp: new Date().toISOString(),
966
+ };
967
+
968
+ const formattedResult = formatToolResult(result);
969
+
970
+ // If toolAnswerAs = 'structuredContent':
971
+ // {
972
+ // structuredContent: {
973
+ // message: 'Operation completed',
974
+ // data: { count: 42, items: ['a', 'b'] },
975
+ // timestamp: '2025-01-01T12:00:00.000Z'
976
+ // }
977
+ // }
978
+
979
+ // If toolAnswerAs = 'text':
980
+ // {
981
+ // content: [{
982
+ // type: 'text',
983
+ // text: '{\n "message": "Operation completed",\n "data": {\n "count": 42,\n "items": ["a", "b"]\n },\n "timestamp": "2025-01-01T12:00:00.000Z"\n}'
984
+ // }]
985
+ // }
986
+
987
+ // getJsonFromResult - extract original JSON from formatted result
988
+ // Function Signature:
989
+ const getJsonFromResult = <T = any> (result: any): T {...}
990
+
991
+ // Examples:
992
+ const originalData1 = getJsonFromResult<MyDataType>(formattedResult);
993
+
994
+ // Works with both response formats:
995
+ const structuredResponse = { structuredContent: { user: 'john', age: 30 } };
996
+ const textResponse = {
997
+ content: [{ type: 'text', text: '{"user":"john","age":30}' }]
998
+ };
999
+
1000
+ const data1 = getJsonFromResult(structuredResponse); // { user: 'john', age: 30 }
1001
+ const data2 = getJsonFromResult(textResponse); // { user: 'john', age: 30 }
1002
+ ```
1003
+
1004
+ ### Logging
1005
+
1006
+ ```typescript
1007
+ import { logger, fileLogger } from 'fa-mcp-sdk';
1008
+
1009
+ // Console logging
1010
+ logger.info('Server started successfully');
1011
+ logger.warn('Warning message');
1012
+ logger.error('Error occurred', error);
1013
+
1014
+ // File logging (if configured)
1015
+ fileLogger.info('This goes to file');
1016
+
1017
+ // Ensure file logs are written before shutdown
1018
+ await fileLogger.asyncFinish();
1019
+ ```
1020
+
1021
+ ### Event System
1022
+
1023
+ ```typescript
1024
+ import { eventEmitter } from 'fa-mcp-sdk';
1025
+
1026
+ // Listen for events
1027
+ eventEmitter.on('server:started', (data) => {
1028
+ console.log('Server started with config:', data);
1029
+ });
1030
+
1031
+ // Emit custom events
1032
+ eventEmitter.emit('custom:event', { data: 'example' });
1033
+ ```
1034
+
1035
+ ### Testing Your MCP Server
1036
+
1037
+ #### Test Structure
1038
+
1039
+ Create tests in your `tests/` directory:
1040
+
1041
+ **`tests/utils.ts`** - Test utilities:
1042
+ ```typescript
1043
+ import { ITestResult, logResultToFile, formatResultAsMarkdown } from 'fa-mcp-sdk';
1044
+
1045
+ export interface ITestResult {
1046
+ fullId: string;
1047
+ toolName: string;
1048
+ description: string;
1049
+ parameters: unknown | null;
1050
+ timestamp: string;
1051
+ duration: number;
1052
+ status: 'pending' | 'passed' | 'failed' | 'skipped' | 'expected_failure';
1053
+ response: unknown | null;
1054
+ error: string | null;
1055
+ }
1056
+
1057
+ // Log test results
1058
+ await logResultToFile(testResult);
1059
+
1060
+ // Format as markdown
1061
+ const markdown = formatResultAsMarkdown(testResult);
1062
+ ```
1063
+
1064
+ #### Test Clients
1065
+
1066
+ Use the provided test clients to test your MCP server:
1067
+
1068
+ **STDIO Transport Testing:**
1069
+ ```typescript
1070
+ import { McpStdioClient } from 'fa-mcp-sdk';
1071
+ import { spawn } from 'child_process';
1072
+
1073
+ const proc = spawn('node', ['dist/start.js', 'stdio'], {
1074
+ stdio: ['pipe', 'pipe', 'pipe'],
1075
+ env: { ...process.env, NODE_ENV: 'test' },
1076
+ });
1077
+
1078
+ const client = new McpStdioClient(proc);
1079
+
1080
+ // Test tools
1081
+ const result = await client.callTool('my_custom_tool', { query: 'test' });
1082
+ console.log(result);
1083
+
1084
+ // Test prompts
1085
+ const prompt = await client.getPrompt('agent_brief');
1086
+ console.log(prompt);
1087
+ ```
1088
+
1089
+ **HTTP Transport Testing:**
1090
+ ```typescript
1091
+ import { McpHttpClient } from 'fa-mcp-sdk';
1092
+
1093
+ const client = new McpHttpClient('http://localhost:3000');
1094
+
1095
+ // Test with authentication headers
1096
+ const result = await client.callTool('my_custom_tool', { query: 'test' }, {
1097
+ 'Authorization': 'Bearer your-jwt-token'
1098
+ });
1099
+ ```
1100
+
1101
+ **SSE Transport Testing:**
1102
+ ```typescript
1103
+ import { McpSseClient } from 'fa-mcp-sdk';
1104
+
1105
+ const client = new McpSseClient('http://localhost:3000');
1106
+ const result = await client.callTool('my_custom_tool', { query: 'test' });
1107
+ ```
1108
+
1109
+ #### Test Categories and Recommendations
1110
+
1111
+ 1. **Prompt Tests**:
1112
+ - Test that prompts are listed correctly
1113
+ - Test prompt content retrieval
1114
+ - Test dynamic prompt generation
1115
+
1116
+ 2. **Resource Tests**:
1117
+ - Test resource listing
1118
+ - Test resource content reading
1119
+ - Test dynamic resource generation
1120
+
1121
+ 3. **Tool Tests**:
1122
+ - Test tool listing
1123
+ - Test tool execution with valid parameters
1124
+ - Test error handling for invalid parameters
1125
+ - Test tool response formatting
1126
+
1127
+ 4. **Transport Tests**:
1128
+ - Test all transport types your server supports
1129
+ - Test authentication (if enabled)
1130
+ - Test error responses
1131
+
1132
+ Example test implementation:
1133
+ ```typescript
1134
+ // tests/mcp/test-tools.js
1135
+ async function testMyCustomTool(client) {
1136
+ const name = 'Test my_custom_tool execution';
1137
+ try {
1138
+ const result = await client.callTool('my_custom_tool', { query: 'test input' });
1139
+ const success = result?.response?.includes('Processed');
1140
+ return success ?
1141
+ { name, passed: true, details: result } :
1142
+ { name, passed: false, details: result };
1143
+ } catch (error) {
1144
+ return { name, passed: false, details: { error: error.message } };
1145
+ }
1146
+ }
1147
+ ```
1148
+
1149
+ ### Consul Integration
1150
+
1151
+ If using Consul for service discovery:
1152
+
1153
+ ```typescript
1154
+ import {
1155
+ getConsulAPI,
1156
+ accessPointUpdater,
1157
+ deregisterServiceFromConsul
1158
+ } from 'fa-mcp-sdk';
1159
+
1160
+ // getConsulAPI - get configured Consul client instance
1161
+ // Function Signature:
1162
+ const getConsulAPI = async (): Promise<any> {...}
1163
+
1164
+ // Returns Consul API client configured from appConfig.consul settings
1165
+ // Example:
1166
+ const consulApi = await getConsulAPI();
1167
+ const services = await consulApi.catalog.service.list();
1168
+ console.log('Available services:', services);
1169
+
1170
+ // deregisterServiceFromConsul - remove service registration from Consul
1171
+ // Function Signature:
1172
+ const deregisterServiceFromConsul = async (): Promise<void> {...}
1173
+
1174
+ // Note: This function reads serviceId from command line arguments (process.argv)
1175
+ // Usage in command line context:
1176
+ // node script.js <serviceId> [agentHost] [agentPort]
1177
+
1178
+ // Example programmatic usage:
1179
+ await deregisterServiceFromConsul();
1180
+
1181
+ // accessPointUpdater - manage access point lifecycle
1182
+ // Object with start/stop methods:
1183
+ const accessPointUpdater = {
1184
+ start(): void; // Start automatic access point updates
1185
+ stop(): void; // Stop automatic access point updates
1186
+ }
1187
+
1188
+ // Examples:
1189
+ accessPointUpdater.start(); // Automatically starts if appConfig.accessPoints configured
1190
+ accessPointUpdater.stop(); // Stop updates (called automatically on shutdown)
1191
+
1192
+ // Access point configuration in config/default.yaml:
1193
+ // accessPoints:
1194
+ // myService:
1195
+ // title: 'My remote service'
1196
+ // host: <host>
1197
+ // port: 9999
1198
+ // token: '***'
1199
+ // noConsul: true
1200
+ // consulServiceName: <consulServiceName>
1201
+ ```
1202
+
1203
+ ### Graceful Shutdown
1204
+
1205
+ ```typescript
1206
+ import { gracefulShutdown } from 'fa-mcp-sdk';
1207
+
1208
+ // gracefulShutdown - perform graceful application shutdown
1209
+ // Function Signature:
1210
+ async function gracefulShutdown (signal: string, exitCode: number = 0): Promise<void> {...}
1211
+
1212
+ // Automatically handles:
1213
+ // - Stopping Consul service registration
1214
+ // - Closing database connections
1215
+ // - Flushing file logs
1216
+ // - Stopping access point updater
1217
+ // - Process exit with specified code
1218
+
1219
+ // Examples:
1220
+ // Manual shutdown
1221
+ process.on('SIGUSR2', () => {
1222
+ gracefulShutdown('SIGUSR2', 0);
1223
+ });
1224
+
1225
+ // Emergency shutdown
1226
+ process.on('uncaughtException', (error) => {
1227
+ console.error('Uncaught exception:', error);
1228
+ gracefulShutdown('UNCAUGHT_EXCEPTION', 1);
1229
+ });
1230
+
1231
+ // Note: SDK automatically registers SIGINT and SIGTERM handlers
1232
+ // in initMcpServer(), so manual registration is only needed for custom signals
1233
+ ```
1234
+
1235
+ ### Transport Types
1236
+
1237
+ #### STDIO Transport
1238
+ - Use for CLI tools and local development
1239
+ - Configure with `mcp.transportType: "stdio"`
1240
+ - Lightweight, no HTTP overhead
1241
+
1242
+ #### HTTP Transport
1243
+ - Use for web-based integrations
1244
+ - Configure with `mcp.transportType: "http"`
1245
+ - Supports REST API, authentication, Swagger docs
1246
+ - Requires `webServer` configuration
1247
+
1248
+ #### Server-Sent Events (SSE)
1249
+ - Real-time streaming over HTTP
1250
+ - Good for long-running operations
1251
+ - Maintains persistent connections
1252
+
1253
+ ### Best Practices
1254
+
1255
+ #### Project Organization
1256
+ 1. **Keep tools focused** - One responsibility per tool
1257
+ 2. **Use TypeScript** - Leverage type safety throughout
1258
+ 3. **Organize by feature** - Group related functionality
1259
+ 4. **Configure environments** - Use separate configs for dev/prod
1260
+
1261
+ #### Tool Development
1262
+ 1. **Validate inputs** - Always check required parameters
1263
+ 2. **Use formatToolResult()** - Consistent response formatting
1264
+ 3. **Handle errors gracefully** - Use appropriate error classes
1265
+ 4. **Log operations** - Use the provided logger
1266
+
1267
+ #### Testing
1268
+ 1. **Test all transports** - Ensure compatibility
1269
+ 2. **Include error cases** - Test failure scenarios
1270
+ 3. **Use provided clients** - Leverage built-in test utilities
1271
+ 4. **Document test cases** - Clear, descriptive test names
1272
+
1273
+ #### Security
1274
+ 1. **Environment variables** - Never hardcode secrets
1275
+ 2. **Authentication** - Enable for production HTTP servers
1276
+ 3. **Input validation** - Validate all user inputs
1277
+ 4. **Error messages** - Don't leak sensitive information
1278
+
1279
+ This documentation provides everything needed to build, test, and deploy your own
1280
+ MCP server using the FA-MCP-SDK framework.