fa-mcp-sdk 0.2.182 → 0.2.192
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/.claude/agents/fa-mcp-sdk.md +158 -0
- package/cli-template/FA-MCP-SDK-DOC/00-FA-MCP-SDK-index.md +216 -0
- package/cli-template/FA-MCP-SDK-DOC/01-getting-started.md +209 -0
- package/cli-template/FA-MCP-SDK-DOC/02-tools-and-api.md +321 -0
- package/cli-template/FA-MCP-SDK-DOC/03-configuration.md +415 -0
- package/cli-template/FA-MCP-SDK-DOC/04-authentication.md +544 -0
- package/cli-template/FA-MCP-SDK-DOC/05-ad-authorization.md +476 -0
- package/cli-template/FA-MCP-SDK-DOC/06-utilities.md +394 -0
- package/cli-template/FA-MCP-SDK-DOC/07-testing-and-operations.md +171 -0
- package/dist/core/_types_/types.d.ts +0 -5
- package/dist/core/_types_/types.d.ts.map +1 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/web/home-api.js +1 -1
- package/dist/core/web/home-api.js.map +1 -1
- package/dist/core/web/openapi.d.ts +64 -0
- package/dist/core/web/openapi.d.ts.map +1 -0
- package/dist/core/web/openapi.js +235 -0
- package/dist/core/web/openapi.js.map +1 -0
- package/dist/core/web/server-http.d.ts.map +1 -1
- package/dist/core/web/server-http.js +11 -9
- package/dist/core/web/server-http.js.map +1 -1
- package/dist/core/web/static/home/index.html +4 -2
- package/dist/core/web/static/home/script.js +2 -2
- package/package.json +9 -12
- package/src/template/api/router.ts +66 -4
- package/src/template/start.ts +0 -5
- package/cli-template/FA-MCP-SDK.md +0 -2540
- package/src/template/api/swagger.ts +0 -167
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
# Tools and REST API Development
|
|
2
|
+
|
|
3
|
+
## Tool Development
|
|
4
|
+
|
|
5
|
+
### Tool Definition in `src/tools/tools.ts`
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
+
import { IToolInputSchema } from 'fa-mcp-sdk';
|
|
10
|
+
|
|
11
|
+
export const tools: Tool[] = [
|
|
12
|
+
{
|
|
13
|
+
name: 'my_custom_tool',
|
|
14
|
+
description: 'Description of what this tool does',
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
query: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: 'Input query or text',
|
|
21
|
+
},
|
|
22
|
+
options: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
description: 'Optional configuration',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
required: ['query'],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Tool Handler in `src/tools/handle-tool-call.ts`
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { formatToolResult, ToolExecutionError, logger } from 'fa-mcp-sdk';
|
|
37
|
+
|
|
38
|
+
export const handleToolCall = async (params: { name: string, arguments?: any, headers?: Record<string, string> }): Promise<any> => {
|
|
39
|
+
const { name, arguments: args, headers } = params;
|
|
40
|
+
|
|
41
|
+
logger.info(`Tool called: ${name}`);
|
|
42
|
+
|
|
43
|
+
// Access normalized HTTP headers (all header names are lowercase)
|
|
44
|
+
if (headers) {
|
|
45
|
+
const authHeader = headers.authorization;
|
|
46
|
+
const userAgent = headers['user-agent'];
|
|
47
|
+
const customHeader = headers['x-custom-header'];
|
|
48
|
+
logger.info(`Headers available: authorization=${!!authHeader}, user-agent=${userAgent}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
switch (name) {
|
|
53
|
+
case 'my_custom_tool':
|
|
54
|
+
return await handleMyCustomTool(args);
|
|
55
|
+
|
|
56
|
+
default:
|
|
57
|
+
throw new ToolExecutionError(name, `Unknown tool: ${name}`);
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
logger.error(`Tool execution failed for ${name}:`, error);
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
async function handleMyCustomTool(args: any): Promise<string> {
|
|
66
|
+
const { query, options } = args || {};
|
|
67
|
+
|
|
68
|
+
if (!query) {
|
|
69
|
+
throw new ToolExecutionError('my_custom_tool', 'Query parameter is required');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Your tool logic here
|
|
73
|
+
const result = {
|
|
74
|
+
message: `Processed: ${query}`,
|
|
75
|
+
timestamp: new Date().toISOString(),
|
|
76
|
+
options: options || {},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return formatToolResult(result);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### HTTP Headers in Tool Handler
|
|
84
|
+
|
|
85
|
+
The FA-MCP-SDK automatically passes normalized HTTP headers to your `toolHandler` function, enabling context-aware tool execution based on client information.
|
|
86
|
+
|
|
87
|
+
**Key Features:**
|
|
88
|
+
- All headers are automatically normalized to lowercase
|
|
89
|
+
- Available in both HTTP and SSE transports (SSE provides empty headers object)
|
|
90
|
+
- Headers are sanitized and only string values are passed
|
|
91
|
+
- Array header values are joined with `', '` separator
|
|
92
|
+
|
|
93
|
+
**Example Usage:**
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
export const handleToolCall = async (params: {
|
|
97
|
+
name: string,
|
|
98
|
+
arguments?: any,
|
|
99
|
+
headers?: Record<string, string>
|
|
100
|
+
}): Promise<any> => {
|
|
101
|
+
const { name, arguments: args, headers } = params;
|
|
102
|
+
|
|
103
|
+
// Access client information via headers
|
|
104
|
+
if (headers) {
|
|
105
|
+
const authHeader = headers.authorization; // Lowercase normalized
|
|
106
|
+
const userAgent = headers['user-agent']; // Browser/client info
|
|
107
|
+
const clientIP = headers['x-real-ip'] || headers['x-forwarded-for']; // Proxy headers
|
|
108
|
+
const customData = headers['x-custom-header']; // Custom headers
|
|
109
|
+
|
|
110
|
+
logger.info(`Tool ${name} called by ${userAgent} from IP ${clientIP}`);
|
|
111
|
+
|
|
112
|
+
// Conditional logic based on client
|
|
113
|
+
if (userAgent?.includes('mobile')) {
|
|
114
|
+
return await handleMobileRequest(args);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Custom authorization beyond standard auth
|
|
118
|
+
if (customData === 'admin-mode' && authHeader) {
|
|
119
|
+
return await handleAdminRequest(args);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Regular tool logic
|
|
124
|
+
switch (name) {
|
|
125
|
+
case 'get_user_data':
|
|
126
|
+
// Use headers for audit logging
|
|
127
|
+
return await getUserData(args, {
|
|
128
|
+
clientIP: headers?.['x-real-ip'],
|
|
129
|
+
userAgent: headers?.['user-agent']
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Header Normalization Details:**
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
// Original headers from client:
|
|
139
|
+
{
|
|
140
|
+
'Authorization': 'Bearer token123',
|
|
141
|
+
'X-Custom-Header': 'value',
|
|
142
|
+
'USER-AGENT': 'MyClient/1.0'
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Normalized headers passed to toolHandler:
|
|
146
|
+
{
|
|
147
|
+
'authorization': 'Bearer token123',
|
|
148
|
+
'x-custom-header': 'value',
|
|
149
|
+
'user-agent': 'MyClient/1.0'
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Transport Differences:**
|
|
154
|
+
|
|
155
|
+
- **HTTP Transport**: Full headers available from Express request object
|
|
156
|
+
- **SSE Transport**: Headers preserved from initial SSE connection establishment (GET /sse request)
|
|
157
|
+
|
|
158
|
+
**Common Use Cases:**
|
|
159
|
+
- Client identification and analytics
|
|
160
|
+
- Custom authorization checks beyond standard authentication
|
|
161
|
+
- Request routing based on client capabilities
|
|
162
|
+
- Audit logging with client context
|
|
163
|
+
- Rate limiting per client type
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## REST API Endpoints
|
|
168
|
+
|
|
169
|
+
The SDK supports custom REST API endpoints alongside MCP tools. Define your endpoints in `src/api/router.ts` using [tsoa](https://tsoa-community.github.io/docs/) decorators for automatic OpenAPI/Swagger documentation generation.
|
|
170
|
+
|
|
171
|
+
### OpenAPI Generation
|
|
172
|
+
|
|
173
|
+
**Swagger is generated automatically** when the server starts if `swagger/openapi.yaml` doesn't exist. The specification is built from tsoa-decorated controllers in your `src/api/` directory.
|
|
174
|
+
|
|
175
|
+
- **Swagger UI**: Available at `/docs`
|
|
176
|
+
- **OpenAPI spec**: Available at `/api/openapi.json` and `/api/openapi.yaml`
|
|
177
|
+
|
|
178
|
+
To regenerate the spec, simply delete `swagger/openapi.yaml` and restart the server.
|
|
179
|
+
|
|
180
|
+
### Basic Controller Example
|
|
181
|
+
|
|
182
|
+
**`src/api/router.ts`:**
|
|
183
|
+
```typescript
|
|
184
|
+
import { Router } from 'express';
|
|
185
|
+
import { Route, Get, Post, Body, Tags, Query } from 'tsoa';
|
|
186
|
+
import { logger } from 'fa-mcp-sdk';
|
|
187
|
+
|
|
188
|
+
export const apiRouter: Router = Router();
|
|
189
|
+
|
|
190
|
+
// Response interfaces for tsoa (used in OpenAPI schema generation)
|
|
191
|
+
export interface UserResponse {
|
|
192
|
+
id: string;
|
|
193
|
+
name: string;
|
|
194
|
+
email: string;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export interface CreateUserRequest {
|
|
198
|
+
name: string;
|
|
199
|
+
email: string;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* User Management Controller
|
|
204
|
+
* All methods in this class will be under /api prefix
|
|
205
|
+
*/
|
|
206
|
+
@Route('api')
|
|
207
|
+
export class UserController {
|
|
208
|
+
/**
|
|
209
|
+
* Get user by ID
|
|
210
|
+
* @param userId The user's unique identifier
|
|
211
|
+
*/
|
|
212
|
+
@Get('users/{userId}')
|
|
213
|
+
@Tags('Users')
|
|
214
|
+
public async getUser(userId: string): Promise<UserResponse> {
|
|
215
|
+
logger.info(`Getting user: ${userId}`);
|
|
216
|
+
return {
|
|
217
|
+
id: userId,
|
|
218
|
+
name: 'John Doe',
|
|
219
|
+
email: 'john@example.com',
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Create a new user
|
|
225
|
+
*/
|
|
226
|
+
@Post('users')
|
|
227
|
+
@Tags('Users')
|
|
228
|
+
public async createUser(
|
|
229
|
+
@Body() body: CreateUserRequest
|
|
230
|
+
): Promise<UserResponse> {
|
|
231
|
+
logger.info(`Creating user: ${body.name}`);
|
|
232
|
+
return {
|
|
233
|
+
id: 'new-user-id',
|
|
234
|
+
name: body.name,
|
|
235
|
+
email: body.email,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Search users by query
|
|
241
|
+
*/
|
|
242
|
+
@Get('users')
|
|
243
|
+
@Tags('Users')
|
|
244
|
+
public async searchUsers(
|
|
245
|
+
@Query() query?: string,
|
|
246
|
+
@Query() limit?: number
|
|
247
|
+
): Promise<UserResponse[]> {
|
|
248
|
+
logger.info(`Searching users: ${query}, limit: ${limit}`);
|
|
249
|
+
return [];
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Tags Organization
|
|
255
|
+
|
|
256
|
+
Use `@Tags()` decorator to organize endpoints in Swagger UI:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
@Route('api')
|
|
260
|
+
export class MyController {
|
|
261
|
+
// This endpoint appears in "Users" section
|
|
262
|
+
@Get('users')
|
|
263
|
+
@Tags('Users')
|
|
264
|
+
public async listUsers(): Promise<User[]> { ... }
|
|
265
|
+
|
|
266
|
+
// This endpoint appears in "Admin" section
|
|
267
|
+
@Get('admin/stats')
|
|
268
|
+
@Tags('Admin')
|
|
269
|
+
public async getStats(): Promise<Stats> { ... }
|
|
270
|
+
|
|
271
|
+
// This endpoint appears in BOTH sections
|
|
272
|
+
@Get('admin/users')
|
|
273
|
+
@Tags('Admin', 'Users')
|
|
274
|
+
public async adminListUsers(): Promise<User[]> { ... }
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**Important**: Apply `@Tags()` to individual methods, not the class. Class-level `@Tags()` applies to ALL methods, which may cause unintended grouping.
|
|
279
|
+
|
|
280
|
+
### Common tsoa Decorators
|
|
281
|
+
|
|
282
|
+
| Decorator | Usage | Example |
|
|
283
|
+
|-----------|-------|---------|
|
|
284
|
+
| `@Route('prefix')` | Set route prefix | `@Route('api')` |
|
|
285
|
+
| `@Get('path')` | GET endpoint | `@Get('users/{id}')` |
|
|
286
|
+
| `@Post('path')` | POST endpoint | `@Post('users')` |
|
|
287
|
+
| `@Put('path')` | PUT endpoint | `@Put('users/{id}')` |
|
|
288
|
+
| `@Delete('path')` | DELETE endpoint | `@Delete('users/{id}')` |
|
|
289
|
+
| `@Tags('name')` | Swagger section | `@Tags('Users')` |
|
|
290
|
+
| `@Body()` | Request body | `@Body() data: CreateRequest` |
|
|
291
|
+
| `@Query()` | Query parameter | `@Query() search?: string` |
|
|
292
|
+
| `@Path()` | Path parameter | `@Path() id: string` |
|
|
293
|
+
| `@Header()` | Header value | `@Header('x-api-key') apiKey: string` |
|
|
294
|
+
|
|
295
|
+
### Manual Express Routes
|
|
296
|
+
|
|
297
|
+
For endpoints not requiring OpenAPI documentation, use standard Express routing:
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
import { Router } from 'express';
|
|
301
|
+
import { createAuthMW } from 'fa-mcp-sdk';
|
|
302
|
+
|
|
303
|
+
export const apiRouter: Router = Router();
|
|
304
|
+
|
|
305
|
+
const authMW = createAuthMW();
|
|
306
|
+
|
|
307
|
+
// Manual route with authentication
|
|
308
|
+
apiRouter.get('/internal/status', authMW, (req, res) => {
|
|
309
|
+
res.json({ status: 'ok', timestamp: Date.now() });
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// tsoa controllers are still processed for OpenAPI generation
|
|
313
|
+
@Route('api')
|
|
314
|
+
export class PublicController {
|
|
315
|
+
@Get('health')
|
|
316
|
+
@Tags('Server')
|
|
317
|
+
public async health(): Promise<{ status: string }> {
|
|
318
|
+
return { status: 'healthy' };
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
```
|