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.
- package/cli-template/.run/kill-server.run.xml +1 -1
- package/cli-template/fa-mcp-sdk-spec.md +1197 -0
- package/cli-template/src/prompts/agent-prompt.ts +9 -0
- package/cli-template/src/prompts/custom-prompts.ts +2 -2
- package/dist/core/errors/errors.d.ts +0 -3
- package/dist/core/errors/errors.d.ts.map +1 -1
- package/dist/core/errors/errors.js +0 -5
- package/dist/core/errors/errors.js.map +1 -1
- package/dist/core/index.d.ts +4 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +5 -5
- package/dist/core/index.js.map +1 -1
- package/dist/core/token/token-auth.d.ts +0 -16
- package/dist/core/token/token-auth.d.ts.map +1 -1
- package/dist/core/token/token-auth.js +4 -4
- package/dist/core/token/token-auth.js.map +1 -1
- package/package.json +1 -1
|
@@ -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.
|