@frontmcp/adapters 0.3.1 → 0.4.1
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/LICENSE +201 -0
- package/README.md +4 -1
- package/package.json +7 -5
- package/src/openapi/README.md +753 -0
- package/src/openapi/__tests__/fixtures.d.ts +58 -0
- package/src/openapi/__tests__/fixtures.js +286 -0
- package/src/openapi/__tests__/fixtures.js.map +1 -0
- package/src/openapi/openapi.adapter.d.ts +6 -1
- package/src/openapi/openapi.adapter.js +73 -22
- package/src/openapi/openapi.adapter.js.map +1 -1
- package/src/openapi/openapi.security.d.ts +56 -0
- package/src/openapi/openapi.security.js +174 -0
- package/src/openapi/openapi.security.js.map +1 -0
- package/src/openapi/openapi.tool.d.ts +10 -3
- package/src/openapi/openapi.tool.js +50 -78
- package/src/openapi/openapi.tool.js.map +1 -1
- package/src/openapi/openapi.types.d.ts +75 -5
- package/src/openapi/openapi.types.js.map +1 -1
- package/src/openapi/openapi.utils.d.ts +35 -0
- package/src/openapi/openapi.utils.js +126 -0
- package/src/openapi/openapi.utils.js.map +1 -0
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
# OpenAPI Adapter for FrontMCP
|
|
2
|
+
|
|
3
|
+
Automatically generate MCP tools from any OpenAPI 3.0/3.1 specification. This adapter converts OpenAPI endpoints into
|
|
4
|
+
FrontMCP tools with full type safety, authentication support, and automatic request building.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
✅ **Universal Compatibility** - Works with any OpenAPI spec found on the internet
|
|
9
|
+
|
|
10
|
+
✅ **12+ Authentication Types** - Bearer, Basic, Digest, API Keys, mTLS, HMAC, AWS Signature V4, OAuth2, and more
|
|
11
|
+
|
|
12
|
+
✅ **Full Type Safety** - Automatic Zod schema generation from JSON Schema
|
|
13
|
+
|
|
14
|
+
✅ **Smart Request Building** - Automatic parameter mapping (path, query, header, body)
|
|
15
|
+
|
|
16
|
+
✅ **Security Resolution** - Framework-agnostic authentication from context
|
|
17
|
+
|
|
18
|
+
✅ **Custom Mappers** - Transform headers and body based on session data
|
|
19
|
+
|
|
20
|
+
✅ **Production Ready** - Comprehensive error handling and validation
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### From URL
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { OpenapiAdapter } from '@frontmcp/adapters';
|
|
28
|
+
|
|
29
|
+
const adapter = new OpenapiAdapter({
|
|
30
|
+
name: 'my-api',
|
|
31
|
+
url: 'https://api.example.com/openapi.json',
|
|
32
|
+
baseUrl: 'https://api.example.com',
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### From Local Spec
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { OpenapiAdapter } from '@frontmcp/adapters';
|
|
40
|
+
import spec from './openapi.json';
|
|
41
|
+
|
|
42
|
+
const adapter = new OpenapiAdapter({
|
|
43
|
+
name: 'my-api',
|
|
44
|
+
spec: spec,
|
|
45
|
+
baseUrl: 'https://api.example.com',
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Configuration
|
|
50
|
+
|
|
51
|
+
### Basic Options
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
const adapter = new OpenapiAdapter({
|
|
55
|
+
// Required
|
|
56
|
+
name: 'my-api', // Adapter name (used for tool prefixing)
|
|
57
|
+
baseUrl: 'https://api.example.com', // API base URL
|
|
58
|
+
|
|
59
|
+
// One of:
|
|
60
|
+
url: 'https://api.example.com/openapi.json', // OpenAPI spec URL
|
|
61
|
+
// OR
|
|
62
|
+
spec: openapiDocument, // OpenAPI spec object
|
|
63
|
+
|
|
64
|
+
// Optional
|
|
65
|
+
additionalHeaders: {
|
|
66
|
+
// Static headers for all requests
|
|
67
|
+
'User-Agent': 'FrontMCP/1.0',
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Load Options
|
|
73
|
+
|
|
74
|
+
Control how the OpenAPI spec is loaded:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
const adapter = new OpenapiAdapter({
|
|
78
|
+
name: 'my-api',
|
|
79
|
+
url: 'https://api.example.com/openapi.json',
|
|
80
|
+
baseUrl: 'https://api.example.com',
|
|
81
|
+
|
|
82
|
+
loadOptions: {
|
|
83
|
+
validate: true, // Validate OpenAPI spec (default: true)
|
|
84
|
+
dereference: true, // Resolve $refs for flat schemas (default: true)
|
|
85
|
+
headers: {
|
|
86
|
+
// Headers for fetching spec
|
|
87
|
+
Authorization: 'Bearer token',
|
|
88
|
+
},
|
|
89
|
+
timeout: 30000, // Request timeout in ms (default: 30000)
|
|
90
|
+
followRedirects: true, // Follow HTTP redirects (default: true)
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Generate Options
|
|
96
|
+
|
|
97
|
+
Control which tools are generated:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
const adapter = new OpenapiAdapter({
|
|
101
|
+
name: 'my-api',
|
|
102
|
+
url: 'https://api.example.com/openapi.json',
|
|
103
|
+
baseUrl: 'https://api.example.com',
|
|
104
|
+
|
|
105
|
+
generateOptions: {
|
|
106
|
+
// Filter operations
|
|
107
|
+
includeOperations: ['getUser', 'createUser'], // Only these operations
|
|
108
|
+
excludeOperations: ['deleteUser'], // Exclude these operations
|
|
109
|
+
includeDeprecated: false, // Include deprecated ops (default: false)
|
|
110
|
+
|
|
111
|
+
// Custom filter
|
|
112
|
+
filterFn: (operation) => {
|
|
113
|
+
return operation.tags?.includes('public');
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
// Response handling
|
|
117
|
+
preferredStatusCodes: [200, 201, 202, 204], // Preferred response codes
|
|
118
|
+
includeAllResponses: true, // Include all response schemas (default: true)
|
|
119
|
+
|
|
120
|
+
// Security (see Authentication section)
|
|
121
|
+
includeSecurityInInput: false, // Add auth to input schema (default: false)
|
|
122
|
+
|
|
123
|
+
// Naming strategy
|
|
124
|
+
namingStrategy: {
|
|
125
|
+
toolNameGenerator: (path, method, operationId) => {
|
|
126
|
+
return operationId || `${method}_${path.replace(/\//g, '_')}`;
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Authentication
|
|
134
|
+
|
|
135
|
+
### Automatic Bearer Token
|
|
136
|
+
|
|
137
|
+
If your OpenAPI spec uses Bearer authentication, the adapter automatically uses the JWT token from FrontMCP's auth
|
|
138
|
+
context:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// OpenAPI spec with Bearer auth
|
|
142
|
+
{
|
|
143
|
+
"components": {
|
|
144
|
+
"securitySchemes": {
|
|
145
|
+
"BearerAuth": { // This name doesn't matter!
|
|
146
|
+
"type": "http",
|
|
147
|
+
"scheme": "bearer"
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
"security": [{ "BearerAuth": [] }]
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Adapter (no config needed - uses ctx.authInfo.token automatically!)
|
|
155
|
+
const adapter = new OpenapiAdapter({
|
|
156
|
+
name: 'my-api',
|
|
157
|
+
url: 'https://api.example.com/openapi.json',
|
|
158
|
+
baseUrl: 'https://api.example.com',
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Works with ANY security scheme name** - Whether it's called "BearerAuth", "JWT", "Authorization", or anything else,
|
|
163
|
+
the adapter automatically detects Bearer tokens and uses `ctx.authInfo.token`!
|
|
164
|
+
|
|
165
|
+
### Custom Headers (API Keys)
|
|
166
|
+
|
|
167
|
+
Add static authentication headers:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
const adapter = new OpenapiAdapter({
|
|
171
|
+
name: 'my-api',
|
|
172
|
+
url: 'https://api.example.com/openapi.json',
|
|
173
|
+
baseUrl: 'https://api.example.com',
|
|
174
|
+
|
|
175
|
+
additionalHeaders: {
|
|
176
|
+
'X-API-Key': process.env.API_KEY,
|
|
177
|
+
'X-Client-ID': 'my-client-id',
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Dynamic Headers from Auth Context
|
|
183
|
+
|
|
184
|
+
Map user session data to headers:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
const adapter = new OpenapiAdapter({
|
|
188
|
+
name: 'my-api',
|
|
189
|
+
url: 'https://api.example.com/openapi.json',
|
|
190
|
+
baseUrl: 'https://api.example.com',
|
|
191
|
+
|
|
192
|
+
headersMapper: (authInfo, headers) => {
|
|
193
|
+
// Add tenant ID from session to header
|
|
194
|
+
const tenantId = authInfo.user?.tenantId;
|
|
195
|
+
if (tenantId) {
|
|
196
|
+
headers.set('X-Tenant-ID', tenantId);
|
|
197
|
+
}
|
|
198
|
+
return headers;
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Dynamic Body Mapping
|
|
204
|
+
|
|
205
|
+
Inject session data into request bodies:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
const adapter = new OpenapiAdapter({
|
|
209
|
+
name: 'my-api',
|
|
210
|
+
url: 'https://api.example.com/openapi.json',
|
|
211
|
+
baseUrl: 'https://api.example.com',
|
|
212
|
+
|
|
213
|
+
bodyMapper: (authInfo, body) => {
|
|
214
|
+
// Add user ID to all request bodies
|
|
215
|
+
return {
|
|
216
|
+
...body,
|
|
217
|
+
userId: authInfo.user?.id,
|
|
218
|
+
createdBy: authInfo.user?.email,
|
|
219
|
+
};
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Multiple Auth Providers
|
|
225
|
+
|
|
226
|
+
When you have multiple OAuth providers or different tools need different authentication, use one of these approaches:
|
|
227
|
+
|
|
228
|
+
#### Approach 1: Auth Provider Mapper (Recommended)
|
|
229
|
+
|
|
230
|
+
Map OpenAPI security scheme names to auth provider extractors:
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
const adapter = new OpenapiAdapter({
|
|
234
|
+
name: 'multi-api',
|
|
235
|
+
url: 'https://api.example.com/openapi.json',
|
|
236
|
+
baseUrl: 'https://api.example.com',
|
|
237
|
+
|
|
238
|
+
// Map security schemes to auth extractors
|
|
239
|
+
authProviderMapper: {
|
|
240
|
+
// GitHub OAuth security scheme
|
|
241
|
+
GitHubAuth: (authInfo) => authInfo.user?.githubToken,
|
|
242
|
+
|
|
243
|
+
// Google OAuth security scheme
|
|
244
|
+
GoogleAuth: (authInfo) => authInfo.user?.googleToken,
|
|
245
|
+
|
|
246
|
+
// API Key security scheme
|
|
247
|
+
ApiKeyAuth: (authInfo) => authInfo.user?.apiKey,
|
|
248
|
+
|
|
249
|
+
// Slack OAuth security scheme
|
|
250
|
+
SlackAuth: (authInfo) => authInfo.user?.slackToken,
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**How it works:**
|
|
256
|
+
|
|
257
|
+
- Each tool uses security schemes defined in your OpenAPI spec
|
|
258
|
+
- The adapter looks up the scheme name in `authProviderMapper`
|
|
259
|
+
- It calls the corresponding extractor to get the token from `authInfo.user`
|
|
260
|
+
- Different tools automatically use different tokens based on their security requirements
|
|
261
|
+
|
|
262
|
+
#### Approach 2: Custom Security Resolver
|
|
263
|
+
|
|
264
|
+
For complex logic, provide a custom resolver per tool:
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
const adapter = new OpenapiAdapter({
|
|
268
|
+
name: 'multi-api',
|
|
269
|
+
url: 'https://api.example.com/openapi.json',
|
|
270
|
+
baseUrl: 'https://api.example.com',
|
|
271
|
+
|
|
272
|
+
securityResolver: (tool, authInfo) => {
|
|
273
|
+
// Route by tool name prefix
|
|
274
|
+
if (tool.name.startsWith('github_')) {
|
|
275
|
+
return {
|
|
276
|
+
jwt: authInfo.user?.githubToken,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (tool.name.startsWith('google_')) {
|
|
281
|
+
return {
|
|
282
|
+
jwt: authInfo.user?.googleToken,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (tool.name.startsWith('stripe_')) {
|
|
287
|
+
return {
|
|
288
|
+
apiKey: authInfo.user?.stripeApiKey,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Default to main JWT
|
|
293
|
+
return {
|
|
294
|
+
jwt: authInfo.token,
|
|
295
|
+
};
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
#### Approach 3: Static Auth (Server-to-Server)
|
|
301
|
+
|
|
302
|
+
For server-to-server APIs with static credentials:
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
const adapter = new OpenapiAdapter({
|
|
306
|
+
name: 'internal-api',
|
|
307
|
+
url: 'https://internal.example.com/openapi.json',
|
|
308
|
+
baseUrl: 'https://internal.example.com',
|
|
309
|
+
|
|
310
|
+
// Use static auth instead of dynamic context
|
|
311
|
+
staticAuth: {
|
|
312
|
+
jwt: process.env.INTERNAL_API_JWT,
|
|
313
|
+
apiKey: process.env.INTERNAL_API_KEY,
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Auth Resolution Priority
|
|
319
|
+
|
|
320
|
+
The adapter resolves authentication in this order:
|
|
321
|
+
|
|
322
|
+
1. **Custom `securityResolver`** (highest priority) - Full control per tool
|
|
323
|
+
2. **`authProviderMapper`** - Map security schemes to auth providers
|
|
324
|
+
3. **`staticAuth`** - Static credentials
|
|
325
|
+
4. **Default** - Uses `ctx.authInfo.token` (lowest priority)
|
|
326
|
+
|
|
327
|
+
## Real-World Examples
|
|
328
|
+
|
|
329
|
+
### Multi-Tenant SaaS API
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
const adapter = new OpenapiAdapter({
|
|
333
|
+
name: 'saas-api',
|
|
334
|
+
url: 'https://api.saas.com/openapi.json',
|
|
335
|
+
baseUrl: 'https://api.saas.com',
|
|
336
|
+
|
|
337
|
+
// Inject tenant context
|
|
338
|
+
headersMapper: (authInfo, headers) => {
|
|
339
|
+
const tenantId = authInfo.user?.organizationId;
|
|
340
|
+
if (tenantId) {
|
|
341
|
+
headers.set('X-Organization-ID', tenantId);
|
|
342
|
+
}
|
|
343
|
+
return headers;
|
|
344
|
+
},
|
|
345
|
+
|
|
346
|
+
// Filter to only public endpoints
|
|
347
|
+
generateOptions: {
|
|
348
|
+
filterFn: (operation) => {
|
|
349
|
+
return !operation.tags?.includes('internal');
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### GitHub API
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
const adapter = new OpenapiAdapter({
|
|
359
|
+
name: 'github',
|
|
360
|
+
url: 'https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json',
|
|
361
|
+
baseUrl: 'https://api.github.com',
|
|
362
|
+
|
|
363
|
+
additionalHeaders: {
|
|
364
|
+
Accept: 'application/vnd.github+json',
|
|
365
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
headersMapper: (authInfo, headers) => {
|
|
369
|
+
const githubToken = authInfo.user?.githubToken;
|
|
370
|
+
if (githubToken) {
|
|
371
|
+
headers.set('Authorization', `Bearer ${githubToken}`);
|
|
372
|
+
}
|
|
373
|
+
return headers;
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Multi-Provider Integration Platform
|
|
379
|
+
|
|
380
|
+
When building a platform that integrates multiple third-party APIs:
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
// OpenAPI spec with multiple security schemes
|
|
384
|
+
{
|
|
385
|
+
"components": {
|
|
386
|
+
"securitySchemes": {
|
|
387
|
+
"GitHubOAuth": {
|
|
388
|
+
"type": "http",
|
|
389
|
+
"scheme": "bearer",
|
|
390
|
+
"description": "GitHub OAuth token"
|
|
391
|
+
},
|
|
392
|
+
"SlackOAuth": {
|
|
393
|
+
"type": "http",
|
|
394
|
+
"scheme": "bearer",
|
|
395
|
+
"description": "Slack OAuth token"
|
|
396
|
+
},
|
|
397
|
+
"StripeAuth": {
|
|
398
|
+
"type": "apiKey",
|
|
399
|
+
"in": "header",
|
|
400
|
+
"name": "Authorization",
|
|
401
|
+
"description": "Stripe secret key"
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
"paths": {
|
|
406
|
+
"/github/repos": {
|
|
407
|
+
"get": {
|
|
408
|
+
"security": [{ "GitHubOAuth": [] }]
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
"/slack/messages": {
|
|
412
|
+
"post": {
|
|
413
|
+
"security": [{ "SlackOAuth": [] }]
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
"/stripe/customers": {
|
|
417
|
+
"get": {
|
|
418
|
+
"security": [{ "StripeAuth": [] }]
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Adapter configuration
|
|
425
|
+
const adapter = new OpenapiAdapter({
|
|
426
|
+
name: 'integration-platform',
|
|
427
|
+
url: 'https://platform.example.com/openapi.json',
|
|
428
|
+
baseUrl: 'https://platform.example.com',
|
|
429
|
+
|
|
430
|
+
// Map each security scheme to the right auth provider
|
|
431
|
+
authProviderMapper: {
|
|
432
|
+
// GitHub tools use GitHub OAuth token
|
|
433
|
+
'GitHubOAuth': (authInfo) => authInfo.user?.integrations?.github?.token,
|
|
434
|
+
|
|
435
|
+
// Slack tools use Slack OAuth token
|
|
436
|
+
'SlackOAuth': (authInfo) => authInfo.user?.integrations?.slack?.token,
|
|
437
|
+
|
|
438
|
+
// Stripe tools use Stripe API key
|
|
439
|
+
'StripeAuth': (authInfo) => authInfo.user?.integrations?.stripe?.apiKey,
|
|
440
|
+
},
|
|
441
|
+
});
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
**Result:**
|
|
445
|
+
|
|
446
|
+
- `github_getRepos` tool → Uses GitHub token from `authInfo.user.integrations.github.token`
|
|
447
|
+
- `slack_postMessage` tool → Uses Slack token from `authInfo.user.integrations.slack.token`
|
|
448
|
+
- `stripe_getCustomers` tool → Uses Stripe key from `authInfo.user.integrations.stripe.apiKey`
|
|
449
|
+
- Each tool automatically gets the correct authentication!
|
|
450
|
+
|
|
451
|
+
## How It Works
|
|
452
|
+
|
|
453
|
+
### 1. Spec Loading & Validation
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
// Loads and validates OpenAPI spec
|
|
457
|
+
const generator = await OpenAPIToolGenerator.fromURL(url, {
|
|
458
|
+
dereference: true, // Resolves all $refs for flat schemas
|
|
459
|
+
validate: true, // Validates against OpenAPI spec
|
|
460
|
+
});
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### 2. Tool Generation
|
|
464
|
+
|
|
465
|
+
Each OpenAPI operation becomes a FrontMCP tool with full type safety:
|
|
466
|
+
|
|
467
|
+
```yaml
|
|
468
|
+
# OpenAPI Operation
|
|
469
|
+
paths:
|
|
470
|
+
/users/{id}:
|
|
471
|
+
get:
|
|
472
|
+
operationId: getUser
|
|
473
|
+
parameters:
|
|
474
|
+
- name: id
|
|
475
|
+
in: path
|
|
476
|
+
required: true
|
|
477
|
+
schema:
|
|
478
|
+
type: string
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
// Becomes FrontMCP Tool
|
|
483
|
+
{
|
|
484
|
+
name: 'getUser',
|
|
485
|
+
description: 'Get user by ID',
|
|
486
|
+
parameters: z.object({ id: z.string() }), // Auto-generated Zod schema
|
|
487
|
+
execute: async (args, ctx) => {
|
|
488
|
+
// Automatic request building with auth
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### 3. Request Building
|
|
494
|
+
|
|
495
|
+
The adapter automatically builds requests using the parameter mapper:
|
|
496
|
+
|
|
497
|
+
- **Path parameters** → URL path (`/users/{id}` → `/users/123`)
|
|
498
|
+
- **Query parameters** → Query string (`?page=1&limit=10`)
|
|
499
|
+
- **Header parameters** → HTTP headers
|
|
500
|
+
- **Body parameters** → Request body (JSON)
|
|
501
|
+
- **Security** → Authentication headers (resolved from context)
|
|
502
|
+
|
|
503
|
+
### 4. Authentication Resolution
|
|
504
|
+
|
|
505
|
+
Security is resolved automatically using the `SecurityResolver`:
|
|
506
|
+
|
|
507
|
+
```typescript
|
|
508
|
+
// 1. Extract security from OpenAPI spec
|
|
509
|
+
const security = await securityResolver.resolve(tool.mapper, {
|
|
510
|
+
jwt: ctx.authInfo.token, // From FrontMCP context
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
// 2. Apply to request
|
|
514
|
+
fetch(url, {
|
|
515
|
+
headers: {
|
|
516
|
+
...security.headers, // Authorization: Bearer xxx
|
|
517
|
+
...customHeaders,
|
|
518
|
+
},
|
|
519
|
+
});
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
## Supported Authentication Types
|
|
523
|
+
|
|
524
|
+
| Type | OpenAPI | Auto-Resolved From |
|
|
525
|
+
| ---------------- | ---------------- | -------------------- |
|
|
526
|
+
| Bearer Token | `http: bearer` | `ctx.authInfo.token` |
|
|
527
|
+
| Basic Auth | `http: basic` | Custom resolver |
|
|
528
|
+
| Digest Auth | `http: digest` | Custom resolver |
|
|
529
|
+
| API Key (Header) | `apiKey: header` | `additionalHeaders` |
|
|
530
|
+
| API Key (Query) | `apiKey: query` | `additionalHeaders` |
|
|
531
|
+
| OAuth2 | `oauth2` | `ctx.authInfo.token` |
|
|
532
|
+
| OpenID Connect | `openIdConnect` | `ctx.authInfo.token` |
|
|
533
|
+
| mTLS | `mutualTLS` | Custom resolver |
|
|
534
|
+
| HMAC Signature | Custom | Custom resolver |
|
|
535
|
+
| AWS Signature V4 | Custom | Custom resolver |
|
|
536
|
+
| Custom Headers | `apiKey` | `additionalHeaders` |
|
|
537
|
+
| Cookies | Context | Custom resolver |
|
|
538
|
+
|
|
539
|
+
See [SECURITY.md](../../../../mcp-from-openapi/SECURITY.md) for detailed authentication examples.
|
|
540
|
+
|
|
541
|
+
## Security Validation
|
|
542
|
+
|
|
543
|
+
The adapter automatically validates your security configuration on startup and provides helpful error messages if
|
|
544
|
+
authentication is not properly configured.
|
|
545
|
+
|
|
546
|
+
### Automatic Validation
|
|
547
|
+
|
|
548
|
+
When the adapter loads, it:
|
|
549
|
+
|
|
550
|
+
1. **Extracts all security schemes** from your OpenAPI spec
|
|
551
|
+
2. **Validates your auth configuration** matches the security requirements
|
|
552
|
+
3. **Calculates a security risk score** (low/medium/high)
|
|
553
|
+
4. **Fails early** with clear errors if auth mapping is missing
|
|
554
|
+
|
|
555
|
+
### Security Risk Scores
|
|
556
|
+
|
|
557
|
+
| Score | Configuration | Description |
|
|
558
|
+
| ------------- | ------------------------------------------ | --------------------------------------------- |
|
|
559
|
+
| **LOW** ✅ | `authProviderMapper` or `securityResolver` | Auth from context - Production ready |
|
|
560
|
+
| **MEDIUM** ⚠️ | `staticAuth` or default | Static credentials - Secure but less flexible |
|
|
561
|
+
| **HIGH** ❌ | `includeSecurityInInput: true` | User provides auth - High security risk |
|
|
562
|
+
|
|
563
|
+
### Example: Missing Auth Configuration
|
|
564
|
+
|
|
565
|
+
If you have an OpenAPI spec with security schemes but no auth configuration:
|
|
566
|
+
|
|
567
|
+
```typescript
|
|
568
|
+
// OpenAPI spec has security schemes
|
|
569
|
+
{
|
|
570
|
+
"components": {
|
|
571
|
+
"securitySchemes": {
|
|
572
|
+
"GitHubAuth": { "type": "http", "scheme": "bearer" },
|
|
573
|
+
"SlackAuth": { "type": "http", "scheme": "bearer" }
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// ❌ This will FAIL on startup
|
|
579
|
+
const adapter = new OpenapiAdapter({
|
|
580
|
+
name: 'my-api',
|
|
581
|
+
url: 'https://api.example.com/openapi.json',
|
|
582
|
+
baseUrl: 'https://api.example.com',
|
|
583
|
+
// No auth configuration provided!
|
|
584
|
+
});
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
**Error message you'll see:**
|
|
588
|
+
|
|
589
|
+
```
|
|
590
|
+
[OpenAPI Adapter: my-api] Invalid security configuration.
|
|
591
|
+
Missing auth provider mappings for security schemes: GitHubAuth, SlackAuth
|
|
592
|
+
|
|
593
|
+
Your OpenAPI spec requires these security schemes, but no auth configuration was provided.
|
|
594
|
+
|
|
595
|
+
Add one of the following to your adapter configuration:
|
|
596
|
+
|
|
597
|
+
1. authProviderMapper (recommended):
|
|
598
|
+
authProviderMapper: {
|
|
599
|
+
'GitHubAuth': (authInfo) => authInfo.user?.githubauthToken,
|
|
600
|
+
'SlackAuth': (authInfo) => authInfo.user?.slackauthToken,
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
2. securityResolver:
|
|
604
|
+
securityResolver: (tool, authInfo) => ({ jwt: authInfo.token })
|
|
605
|
+
|
|
606
|
+
3. staticAuth:
|
|
607
|
+
staticAuth: { jwt: process.env.API_TOKEN }
|
|
608
|
+
|
|
609
|
+
4. Include security in input (NOT recommended for production):
|
|
610
|
+
generateOptions: { includeSecurityInInput: true }
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
### Example: Valid Configuration
|
|
614
|
+
|
|
615
|
+
```typescript
|
|
616
|
+
// ✅ This will SUCCEED with security risk score: LOW
|
|
617
|
+
const adapter = new OpenapiAdapter({
|
|
618
|
+
name: 'my-api',
|
|
619
|
+
url: 'https://api.example.com/openapi.json',
|
|
620
|
+
baseUrl: 'https://api.example.com',
|
|
621
|
+
|
|
622
|
+
authProviderMapper: {
|
|
623
|
+
GitHubAuth: (authInfo) => authInfo.user?.githubToken,
|
|
624
|
+
SlackAuth: (authInfo) => authInfo.user?.slackToken,
|
|
625
|
+
},
|
|
626
|
+
});
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
**Console output:**
|
|
630
|
+
|
|
631
|
+
```
|
|
632
|
+
[OpenAPI Adapter: my-api] Security Analysis:
|
|
633
|
+
Security Risk Score: LOW
|
|
634
|
+
Valid Configuration: YES
|
|
635
|
+
|
|
636
|
+
Messages:
|
|
637
|
+
- INFO: Using authProviderMapper for auth resolution.
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
### Runtime Validation
|
|
641
|
+
|
|
642
|
+
Each tool execution also validates authentication:
|
|
643
|
+
|
|
644
|
+
```typescript
|
|
645
|
+
// If a tool requires GitHubAuth but no token is available
|
|
646
|
+
await tool.execute(
|
|
647
|
+
{
|
|
648
|
+
/* ... */
|
|
649
|
+
},
|
|
650
|
+
ctx,
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
// Error:
|
|
654
|
+
// Authentication required for tool 'github_getRepos' but no auth configuration found.
|
|
655
|
+
// Required security schemes: GitHubAuth
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
### Bypassing Validation (Not Recommended)
|
|
659
|
+
|
|
660
|
+
For testing or internal tools only:
|
|
661
|
+
|
|
662
|
+
```typescript
|
|
663
|
+
const adapter = new OpenapiAdapter({
|
|
664
|
+
name: 'my-api',
|
|
665
|
+
url: 'https://api.example.com/openapi.json',
|
|
666
|
+
baseUrl: 'https://api.example.com',
|
|
667
|
+
|
|
668
|
+
generateOptions: {
|
|
669
|
+
// ⚠️ HIGH SECURITY RISK: Users provide auth directly
|
|
670
|
+
includeSecurityInInput: true,
|
|
671
|
+
},
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
// Console output:
|
|
675
|
+
// [OpenAPI Adapter: my-api] Security Analysis:
|
|
676
|
+
// Security Risk Score: HIGH
|
|
677
|
+
// Valid Configuration: YES
|
|
678
|
+
//
|
|
679
|
+
// Messages:
|
|
680
|
+
// - SECURITY WARNING: includeSecurityInInput is enabled. Users will provide
|
|
681
|
+
// authentication directly in tool inputs. This increases security risk as
|
|
682
|
+
// credentials may be logged or exposed.
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
## Best Practices
|
|
686
|
+
|
|
687
|
+
### 1. Use Environment Variables
|
|
688
|
+
|
|
689
|
+
```typescript
|
|
690
|
+
const adapter = new OpenapiAdapter({
|
|
691
|
+
name: 'my-api',
|
|
692
|
+
url: process.env.OPENAPI_URL,
|
|
693
|
+
baseUrl: process.env.API_BASE_URL,
|
|
694
|
+
additionalHeaders: {
|
|
695
|
+
'X-API-Key': process.env.API_KEY,
|
|
696
|
+
},
|
|
697
|
+
});
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
### 2. Filter Endpoints
|
|
701
|
+
|
|
702
|
+
```typescript
|
|
703
|
+
generateOptions: {
|
|
704
|
+
filterFn: (operation) => {
|
|
705
|
+
return (
|
|
706
|
+
operation.tags?.includes('public') &&
|
|
707
|
+
!operation.deprecated
|
|
708
|
+
);
|
|
709
|
+
},
|
|
710
|
+
}
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
### 3. Handle Multi-Tenant
|
|
714
|
+
|
|
715
|
+
```typescript
|
|
716
|
+
headersMapper: (authInfo, headers) => {
|
|
717
|
+
const tenantId = authInfo.user?.organizationId;
|
|
718
|
+
if (!tenantId) {
|
|
719
|
+
throw new Error('Tenant ID required');
|
|
720
|
+
}
|
|
721
|
+
headers.set('X-Tenant-ID', tenantId);
|
|
722
|
+
return headers;
|
|
723
|
+
},
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
## Troubleshooting
|
|
727
|
+
|
|
728
|
+
### Tools not generated
|
|
729
|
+
|
|
730
|
+
- Check that your OpenAPI spec is valid (`validate: true`)
|
|
731
|
+
- Verify `filterFn` isn't excluding all operations
|
|
732
|
+
- Check console for validation errors
|
|
733
|
+
|
|
734
|
+
### Authentication not working
|
|
735
|
+
|
|
736
|
+
- Ensure security is defined in OpenAPI spec
|
|
737
|
+
- Verify `ctx.authInfo.token` is available
|
|
738
|
+
- Add `additionalHeaders` if needed
|
|
739
|
+
|
|
740
|
+
### Type errors
|
|
741
|
+
|
|
742
|
+
- Ensure `dereference: true` to resolve `$ref` objects
|
|
743
|
+
- Check that JSON schemas are valid
|
|
744
|
+
|
|
745
|
+
## Links
|
|
746
|
+
|
|
747
|
+
- [mcp-from-openapi](https://github.com/frontmcp/mcp-from-openapi) - Core OpenAPI to MCP converter
|
|
748
|
+
- [Security Guide](../../../../mcp-from-openapi/SECURITY.md) - Comprehensive authentication guide
|
|
749
|
+
- [FrontMCP SDK](../../../../sdk) - FrontMCP core SDK
|
|
750
|
+
|
|
751
|
+
## License
|
|
752
|
+
|
|
753
|
+
MIT
|