@frontmcp/adapters 0.6.0 → 0.6.2
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/esm/index.mjs +1168 -0
- package/esm/openapi/index.mjs +1168 -0
- package/esm/package.json +63 -0
- package/index.js +1193 -0
- package/openapi/index.js +1192 -0
- package/package.json +30 -12
- package/src/index.js +0 -8
- package/src/index.js.map +0 -1
- package/src/openapi/README.md +0 -1215
- package/src/openapi/index.js +0 -8
- package/src/openapi/index.js.map +0 -1
- package/src/openapi/openapi.adapter.js +0 -434
- package/src/openapi/openapi.adapter.js.map +0 -1
- package/src/openapi/openapi.frontmcp-schema.js +0 -358
- package/src/openapi/openapi.frontmcp-schema.js.map +0 -1
- package/src/openapi/openapi.security.js +0 -242
- package/src/openapi/openapi.security.js.map +0 -1
- package/src/openapi/openapi.tool.js +0 -267
- package/src/openapi/openapi.tool.js.map +0 -1
- package/src/openapi/openapi.types.js +0 -29
- package/src/openapi/openapi.types.js.map +0 -1
- package/src/openapi/openapi.utils.js +0 -229
- package/src/openapi/openapi.utils.js.map +0 -1
- /package/{src/index.d.ts → index.d.ts} +0 -0
- /package/{src/openapi → openapi}/index.d.ts +0 -0
- /package/{src/openapi → openapi}/openapi.adapter.d.ts +0 -0
- /package/{src/openapi → openapi}/openapi.frontmcp-schema.d.ts +0 -0
- /package/{src/openapi → openapi}/openapi.security.d.ts +0 -0
- /package/{src/openapi → openapi}/openapi.tool.d.ts +0 -0
- /package/{src/openapi → openapi}/openapi.types.d.ts +0 -0
- /package/{src/openapi → openapi}/openapi.utils.d.ts +0 -0
package/src/openapi/README.md
DELETED
|
@@ -1,1215 +0,0 @@
|
|
|
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, validation, and security protections
|
|
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, // Accepts object, OpenAPIV3.Document, or OpenAPIV3_1.Document
|
|
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
|
-
- **Note:** Empty string tokens throw a descriptive error. Return `undefined` if no token is available.
|
|
262
|
-
|
|
263
|
-
#### Approach 2: Custom Security Resolver
|
|
264
|
-
|
|
265
|
-
For complex logic, provide a custom resolver per tool:
|
|
266
|
-
|
|
267
|
-
```typescript
|
|
268
|
-
const adapter = new OpenapiAdapter({
|
|
269
|
-
name: 'multi-api',
|
|
270
|
-
url: 'https://api.example.com/openapi.json',
|
|
271
|
-
baseUrl: 'https://api.example.com',
|
|
272
|
-
|
|
273
|
-
securityResolver: (tool, authInfo) => {
|
|
274
|
-
// Route by tool name prefix
|
|
275
|
-
if (tool.name.startsWith('github_')) {
|
|
276
|
-
return {
|
|
277
|
-
jwt: authInfo.user?.githubToken,
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (tool.name.startsWith('google_')) {
|
|
282
|
-
return {
|
|
283
|
-
jwt: authInfo.user?.googleToken,
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (tool.name.startsWith('stripe_')) {
|
|
288
|
-
return {
|
|
289
|
-
apiKey: authInfo.user?.stripeApiKey,
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Default to main JWT
|
|
294
|
-
return {
|
|
295
|
-
jwt: authInfo.token,
|
|
296
|
-
};
|
|
297
|
-
},
|
|
298
|
-
});
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
#### Approach 3: Static Auth (Server-to-Server)
|
|
302
|
-
|
|
303
|
-
For server-to-server APIs with static credentials:
|
|
304
|
-
|
|
305
|
-
```typescript
|
|
306
|
-
const adapter = new OpenapiAdapter({
|
|
307
|
-
name: 'internal-api',
|
|
308
|
-
url: 'https://internal.example.com/openapi.json',
|
|
309
|
-
baseUrl: 'https://internal.example.com',
|
|
310
|
-
|
|
311
|
-
// Use static auth instead of dynamic context
|
|
312
|
-
staticAuth: {
|
|
313
|
-
jwt: process.env.INTERNAL_API_JWT,
|
|
314
|
-
apiKey: process.env.INTERNAL_API_KEY,
|
|
315
|
-
},
|
|
316
|
-
});
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
#### Approach 4: Hybrid Authentication (Per-Scheme Control)
|
|
320
|
-
|
|
321
|
-
When you need some security schemes to be provided by the user in tool inputs while others are resolved from context (session/headers), use `securitySchemesInInput`:
|
|
322
|
-
|
|
323
|
-
```typescript
|
|
324
|
-
// OpenAPI spec with multiple security schemes
|
|
325
|
-
{
|
|
326
|
-
"components": {
|
|
327
|
-
"securitySchemes": {
|
|
328
|
-
"BearerAuth": {
|
|
329
|
-
"type": "http",
|
|
330
|
-
"scheme": "bearer",
|
|
331
|
-
"description": "User's OAuth token"
|
|
332
|
-
},
|
|
333
|
-
"ApiKeyAuth": {
|
|
334
|
-
"type": "apiKey",
|
|
335
|
-
"in": "header",
|
|
336
|
-
"name": "X-API-Key",
|
|
337
|
-
"description": "Server API key"
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
},
|
|
341
|
-
"paths": {
|
|
342
|
-
"/data": {
|
|
343
|
-
"get": {
|
|
344
|
-
"security": [
|
|
345
|
-
{ "BearerAuth": [], "ApiKeyAuth": [] }
|
|
346
|
-
]
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Adapter configuration
|
|
353
|
-
const adapter = new OpenapiAdapter({
|
|
354
|
-
name: 'hybrid-api',
|
|
355
|
-
url: 'https://api.example.com/openapi.json',
|
|
356
|
-
baseUrl: 'https://api.example.com',
|
|
357
|
-
|
|
358
|
-
// Only these schemes appear in tool input schema (user provides)
|
|
359
|
-
securitySchemesInInput: ['BearerAuth'],
|
|
360
|
-
|
|
361
|
-
// Other schemes (ApiKeyAuth) resolved from context
|
|
362
|
-
authProviderMapper: {
|
|
363
|
-
ApiKeyAuth: (authInfo) => authInfo.user?.apiKey,
|
|
364
|
-
},
|
|
365
|
-
});
|
|
366
|
-
```
|
|
367
|
-
|
|
368
|
-
**How it works:**
|
|
369
|
-
|
|
370
|
-
- `securitySchemesInInput: ['BearerAuth']` - Only `BearerAuth` appears in the tool's input schema
|
|
371
|
-
- User provides the Bearer token when calling the tool
|
|
372
|
-
- `ApiKeyAuth` is automatically resolved from `authProviderMapper` (not visible to user)
|
|
373
|
-
- This is useful when:
|
|
374
|
-
- Some credentials are user-specific (OAuth tokens) and must be provided per-call
|
|
375
|
-
- Other credentials are server-side secrets (API keys) managed by your application
|
|
376
|
-
|
|
377
|
-
**Use cases:**
|
|
378
|
-
|
|
379
|
-
1. **Multi-tenant with user OAuth**: Server API key for tenant access + user OAuth token for identity
|
|
380
|
-
2. **Third-party integrations**: Your API key for rate limiting + user's token for their data
|
|
381
|
-
3. **Hybrid auth flows**: Some endpoints need user tokens, others use service accounts
|
|
382
|
-
|
|
383
|
-
### Auth Resolution Priority
|
|
384
|
-
|
|
385
|
-
The adapter resolves authentication in this order:
|
|
386
|
-
|
|
387
|
-
1. **Custom `securityResolver`** (highest priority) - Full control per tool
|
|
388
|
-
2. **`authProviderMapper`** with `securitySchemesInInput` - Hybrid: some from input, some from context
|
|
389
|
-
3. **`authProviderMapper`** - Map security schemes to auth providers
|
|
390
|
-
4. **`staticAuth`** - Static credentials
|
|
391
|
-
5. **Default** - Uses `ctx.authInfo.token` (lowest priority)
|
|
392
|
-
|
|
393
|
-
**Note:** When using `securitySchemesInInput`, only the specified schemes appear in the tool's input schema. All other schemes must have mappings in `authProviderMapper` or will use the default resolution.
|
|
394
|
-
|
|
395
|
-
## Real-World Examples
|
|
396
|
-
|
|
397
|
-
### Multi-Tenant SaaS API
|
|
398
|
-
|
|
399
|
-
```typescript
|
|
400
|
-
const adapter = new OpenapiAdapter({
|
|
401
|
-
name: 'saas-api',
|
|
402
|
-
url: 'https://api.saas.com/openapi.json',
|
|
403
|
-
baseUrl: 'https://api.saas.com',
|
|
404
|
-
|
|
405
|
-
// Inject tenant context
|
|
406
|
-
headersMapper: (authInfo, headers) => {
|
|
407
|
-
const tenantId = authInfo.user?.organizationId;
|
|
408
|
-
if (tenantId) {
|
|
409
|
-
headers.set('X-Organization-ID', tenantId);
|
|
410
|
-
}
|
|
411
|
-
return headers;
|
|
412
|
-
},
|
|
413
|
-
|
|
414
|
-
// Filter to only public endpoints
|
|
415
|
-
generateOptions: {
|
|
416
|
-
filterFn: (operation) => {
|
|
417
|
-
return !operation.tags?.includes('internal');
|
|
418
|
-
},
|
|
419
|
-
},
|
|
420
|
-
});
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
### GitHub API
|
|
424
|
-
|
|
425
|
-
```typescript
|
|
426
|
-
const adapter = new OpenapiAdapter({
|
|
427
|
-
name: 'github',
|
|
428
|
-
url: 'https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json',
|
|
429
|
-
baseUrl: 'https://api.github.com',
|
|
430
|
-
|
|
431
|
-
additionalHeaders: {
|
|
432
|
-
Accept: 'application/vnd.github+json',
|
|
433
|
-
'X-GitHub-Api-Version': '2022-11-28',
|
|
434
|
-
},
|
|
435
|
-
|
|
436
|
-
headersMapper: (authInfo, headers) => {
|
|
437
|
-
const githubToken = authInfo.user?.githubToken;
|
|
438
|
-
if (githubToken) {
|
|
439
|
-
headers.set('Authorization', `Bearer ${githubToken}`);
|
|
440
|
-
}
|
|
441
|
-
return headers;
|
|
442
|
-
},
|
|
443
|
-
});
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
### Multi-Provider Integration Platform
|
|
447
|
-
|
|
448
|
-
When building a platform that integrates multiple third-party APIs:
|
|
449
|
-
|
|
450
|
-
```typescript
|
|
451
|
-
// OpenAPI spec with multiple security schemes
|
|
452
|
-
{
|
|
453
|
-
"components": {
|
|
454
|
-
"securitySchemes": {
|
|
455
|
-
"GitHubOAuth": {
|
|
456
|
-
"type": "http",
|
|
457
|
-
"scheme": "bearer",
|
|
458
|
-
"description": "GitHub OAuth token"
|
|
459
|
-
},
|
|
460
|
-
"SlackOAuth": {
|
|
461
|
-
"type": "http",
|
|
462
|
-
"scheme": "bearer",
|
|
463
|
-
"description": "Slack OAuth token"
|
|
464
|
-
},
|
|
465
|
-
"StripeAuth": {
|
|
466
|
-
"type": "apiKey",
|
|
467
|
-
"in": "header",
|
|
468
|
-
"name": "Authorization",
|
|
469
|
-
"description": "Stripe secret key"
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
},
|
|
473
|
-
"paths": {
|
|
474
|
-
"/github/repos": {
|
|
475
|
-
"get": {
|
|
476
|
-
"security": [{ "GitHubOAuth": [] }]
|
|
477
|
-
}
|
|
478
|
-
},
|
|
479
|
-
"/slack/messages": {
|
|
480
|
-
"post": {
|
|
481
|
-
"security": [{ "SlackOAuth": [] }]
|
|
482
|
-
}
|
|
483
|
-
},
|
|
484
|
-
"/stripe/customers": {
|
|
485
|
-
"get": {
|
|
486
|
-
"security": [{ "StripeAuth": [] }]
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// Adapter configuration
|
|
493
|
-
const adapter = new OpenapiAdapter({
|
|
494
|
-
name: 'integration-platform',
|
|
495
|
-
url: 'https://platform.example.com/openapi.json',
|
|
496
|
-
baseUrl: 'https://platform.example.com',
|
|
497
|
-
|
|
498
|
-
// Map each security scheme to the right auth provider
|
|
499
|
-
authProviderMapper: {
|
|
500
|
-
// GitHub tools use GitHub OAuth token
|
|
501
|
-
'GitHubOAuth': (authInfo) => authInfo.user?.integrations?.github?.token,
|
|
502
|
-
|
|
503
|
-
// Slack tools use Slack OAuth token
|
|
504
|
-
'SlackOAuth': (authInfo) => authInfo.user?.integrations?.slack?.token,
|
|
505
|
-
|
|
506
|
-
// Stripe tools use Stripe API key
|
|
507
|
-
'StripeAuth': (authInfo) => authInfo.user?.integrations?.stripe?.apiKey,
|
|
508
|
-
},
|
|
509
|
-
});
|
|
510
|
-
```
|
|
511
|
-
|
|
512
|
-
**Result:**
|
|
513
|
-
|
|
514
|
-
- `github_getRepos` tool → Uses GitHub token from `authInfo.user.integrations.github.token`
|
|
515
|
-
- `slack_postMessage` tool → Uses Slack token from `authInfo.user.integrations.slack.token`
|
|
516
|
-
- `stripe_getCustomers` tool → Uses Stripe key from `authInfo.user.integrations.stripe.apiKey`
|
|
517
|
-
- Each tool automatically gets the correct authentication!
|
|
518
|
-
|
|
519
|
-
## Input Schema Transforms
|
|
520
|
-
|
|
521
|
-
Hide inputs from the AI/users and inject values at request time. This is useful for tenant headers, correlation IDs, and
|
|
522
|
-
other server-side data that shouldn't be exposed to MCP clients.
|
|
523
|
-
|
|
524
|
-
### Basic Usage
|
|
525
|
-
|
|
526
|
-
```typescript
|
|
527
|
-
const adapter = new OpenapiAdapter({
|
|
528
|
-
name: 'my-api',
|
|
529
|
-
baseUrl: 'https://api.example.com',
|
|
530
|
-
spec: mySpec,
|
|
531
|
-
inputTransforms: {
|
|
532
|
-
// Global transforms applied to ALL tools
|
|
533
|
-
global: [
|
|
534
|
-
{ inputKey: 'X-Tenant-Id', inject: (ctx) => ctx.authInfo.user?.tenantId },
|
|
535
|
-
{ inputKey: 'X-Correlation-Id', inject: () => crypto.randomUUID() },
|
|
536
|
-
],
|
|
537
|
-
},
|
|
538
|
-
});
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
### Per-Tool Transforms
|
|
542
|
-
|
|
543
|
-
```typescript
|
|
544
|
-
const adapter = new OpenapiAdapter({
|
|
545
|
-
name: 'my-api',
|
|
546
|
-
baseUrl: 'https://api.example.com',
|
|
547
|
-
spec: mySpec,
|
|
548
|
-
inputTransforms: {
|
|
549
|
-
perTool: {
|
|
550
|
-
createUser: [{ inputKey: 'createdBy', inject: (ctx) => ctx.authInfo.user?.email }],
|
|
551
|
-
updateUser: [{ inputKey: 'modifiedBy', inject: (ctx) => ctx.authInfo.user?.email }],
|
|
552
|
-
},
|
|
553
|
-
},
|
|
554
|
-
});
|
|
555
|
-
```
|
|
556
|
-
|
|
557
|
-
### Dynamic Transforms with Generator
|
|
558
|
-
|
|
559
|
-
```typescript
|
|
560
|
-
const adapter = new OpenapiAdapter({
|
|
561
|
-
name: 'my-api',
|
|
562
|
-
baseUrl: 'https://api.example.com',
|
|
563
|
-
spec: mySpec,
|
|
564
|
-
inputTransforms: {
|
|
565
|
-
generator: (tool) => {
|
|
566
|
-
// Add correlation ID to all mutating operations
|
|
567
|
-
if (['post', 'put', 'patch', 'delete'].includes(tool.metadata.method)) {
|
|
568
|
-
return [{ inputKey: 'X-Request-Id', inject: () => crypto.randomUUID() }];
|
|
569
|
-
}
|
|
570
|
-
return [];
|
|
571
|
-
},
|
|
572
|
-
},
|
|
573
|
-
});
|
|
574
|
-
```
|
|
575
|
-
|
|
576
|
-
### Transform Context
|
|
577
|
-
|
|
578
|
-
The `inject` function receives a context object with:
|
|
579
|
-
|
|
580
|
-
- `authInfo` - Authentication info from the MCP session
|
|
581
|
-
- `env` - Environment variables (`process.env`)
|
|
582
|
-
- `tool` - The OpenAPI tool being executed (access metadata, name, etc.)
|
|
583
|
-
|
|
584
|
-
## Tool Transforms
|
|
585
|
-
|
|
586
|
-
Customize generated tools with annotations, tags, descriptions, and more. Tool transforms can be applied globally,
|
|
587
|
-
per-tool, or dynamically using a generator function.
|
|
588
|
-
|
|
589
|
-
### Basic Usage
|
|
590
|
-
|
|
591
|
-
```typescript
|
|
592
|
-
const adapter = new OpenapiAdapter({
|
|
593
|
-
name: 'my-api',
|
|
594
|
-
baseUrl: 'https://api.example.com',
|
|
595
|
-
spec: mySpec,
|
|
596
|
-
toolTransforms: {
|
|
597
|
-
// Global transforms applied to ALL tools
|
|
598
|
-
global: {
|
|
599
|
-
annotations: { openWorldHint: true },
|
|
600
|
-
},
|
|
601
|
-
},
|
|
602
|
-
});
|
|
603
|
-
```
|
|
604
|
-
|
|
605
|
-
### Per-Tool Transforms
|
|
606
|
-
|
|
607
|
-
```typescript
|
|
608
|
-
const adapter = new OpenapiAdapter({
|
|
609
|
-
name: 'my-api',
|
|
610
|
-
baseUrl: 'https://api.example.com',
|
|
611
|
-
spec: mySpec,
|
|
612
|
-
toolTransforms: {
|
|
613
|
-
perTool: {
|
|
614
|
-
createUser: {
|
|
615
|
-
annotations: { destructiveHint: false },
|
|
616
|
-
tags: ['user-management'],
|
|
617
|
-
},
|
|
618
|
-
deleteUser: {
|
|
619
|
-
annotations: { destructiveHint: true },
|
|
620
|
-
tags: ['user-management', 'dangerous'],
|
|
621
|
-
},
|
|
622
|
-
},
|
|
623
|
-
},
|
|
624
|
-
});
|
|
625
|
-
```
|
|
626
|
-
|
|
627
|
-
### Dynamic Transforms with Generator
|
|
628
|
-
|
|
629
|
-
```typescript
|
|
630
|
-
const adapter = new OpenapiAdapter({
|
|
631
|
-
name: 'my-api',
|
|
632
|
-
baseUrl: 'https://api.example.com',
|
|
633
|
-
spec: mySpec,
|
|
634
|
-
toolTransforms: {
|
|
635
|
-
generator: (tool) => {
|
|
636
|
-
// Auto-annotate based on HTTP method
|
|
637
|
-
if (tool.metadata.method === 'get') {
|
|
638
|
-
return { annotations: { readOnlyHint: true, destructiveHint: false } };
|
|
639
|
-
}
|
|
640
|
-
if (tool.metadata.method === 'delete') {
|
|
641
|
-
return { annotations: { destructiveHint: true } };
|
|
642
|
-
}
|
|
643
|
-
return undefined;
|
|
644
|
-
},
|
|
645
|
-
},
|
|
646
|
-
});
|
|
647
|
-
```
|
|
648
|
-
|
|
649
|
-
### Available Transform Properties
|
|
650
|
-
|
|
651
|
-
| Property | Type | Description |
|
|
652
|
-
| ------------------- | -------------------- | -------------------------------------------- |
|
|
653
|
-
| `name` | `string \| function` | Override or transform the tool name |
|
|
654
|
-
| `description` | `string \| function` | Override or transform the tool description |
|
|
655
|
-
| `annotations` | `ToolAnnotations` | MCP tool behavior hints |
|
|
656
|
-
| `tags` | `string[]` | Categorization tags |
|
|
657
|
-
| `examples` | `ToolExample[]` | Usage examples |
|
|
658
|
-
| `hideFromDiscovery` | `boolean` | Hide tool from listing (can still be called) |
|
|
659
|
-
| `ui` | `ToolUIConfig` | UI configuration for tool forms |
|
|
660
|
-
|
|
661
|
-
### Tool Annotations
|
|
662
|
-
|
|
663
|
-
```typescript
|
|
664
|
-
annotations: {
|
|
665
|
-
title: 'Human-readable title',
|
|
666
|
-
readOnlyHint: true, // Tool doesn't modify state
|
|
667
|
-
destructiveHint: false, // Tool doesn't delete data
|
|
668
|
-
idempotentHint: true, // Repeated calls have same effect
|
|
669
|
-
openWorldHint: true, // Tool interacts with external systems
|
|
670
|
-
}
|
|
671
|
-
```
|
|
672
|
-
|
|
673
|
-
## Description Mode
|
|
674
|
-
|
|
675
|
-
Control how tool descriptions are generated from OpenAPI operations:
|
|
676
|
-
|
|
677
|
-
```typescript
|
|
678
|
-
const adapter = new OpenapiAdapter({
|
|
679
|
-
name: 'my-api',
|
|
680
|
-
baseUrl: 'https://api.example.com',
|
|
681
|
-
spec: mySpec,
|
|
682
|
-
descriptionMode: 'combined', // Default: 'summaryOnly'
|
|
683
|
-
});
|
|
684
|
-
```
|
|
685
|
-
|
|
686
|
-
| Mode | Description |
|
|
687
|
-
| ------------------- | ------------------------------------------- |
|
|
688
|
-
| `'summaryOnly'` | Use only the OpenAPI summary (default) |
|
|
689
|
-
| `'descriptionOnly'` | Use only the OpenAPI description |
|
|
690
|
-
| `'combined'` | Summary followed by description |
|
|
691
|
-
| `'full'` | Summary, description, and operation details |
|
|
692
|
-
|
|
693
|
-
## x-frontmcp OpenAPI Extension
|
|
694
|
-
|
|
695
|
-
Configure tool behavior directly in your OpenAPI spec using the `x-frontmcp` extension. This allows API designers to
|
|
696
|
-
embed FrontMCP-specific configuration in the spec itself.
|
|
697
|
-
|
|
698
|
-
### Basic Example
|
|
699
|
-
|
|
700
|
-
```yaml
|
|
701
|
-
paths:
|
|
702
|
-
/users:
|
|
703
|
-
get:
|
|
704
|
-
operationId: listUsers
|
|
705
|
-
summary: List all users
|
|
706
|
-
x-frontmcp:
|
|
707
|
-
annotations:
|
|
708
|
-
readOnlyHint: true
|
|
709
|
-
idempotentHint: true
|
|
710
|
-
cache:
|
|
711
|
-
ttl: 300
|
|
712
|
-
tags:
|
|
713
|
-
- users
|
|
714
|
-
- public-api
|
|
715
|
-
```
|
|
716
|
-
|
|
717
|
-
### Full Extension Schema
|
|
718
|
-
|
|
719
|
-
```yaml
|
|
720
|
-
x-frontmcp:
|
|
721
|
-
# Tool annotations (AI behavior hints)
|
|
722
|
-
annotations:
|
|
723
|
-
title: 'Human-readable title'
|
|
724
|
-
readOnlyHint: true
|
|
725
|
-
destructiveHint: false
|
|
726
|
-
idempotentHint: true
|
|
727
|
-
openWorldHint: true
|
|
728
|
-
|
|
729
|
-
# Cache configuration
|
|
730
|
-
cache:
|
|
731
|
-
ttl: 300 # Time-to-live in seconds
|
|
732
|
-
slideWindow: true # Slide cache window on access
|
|
733
|
-
|
|
734
|
-
# CodeCall plugin configuration
|
|
735
|
-
codecall:
|
|
736
|
-
enabledInCodeCall: true # Allow use via CodeCall
|
|
737
|
-
visibleInListTools: true # Show in list_tools when CodeCall active
|
|
738
|
-
|
|
739
|
-
# Categorization
|
|
740
|
-
tags:
|
|
741
|
-
- users
|
|
742
|
-
- public-api
|
|
743
|
-
|
|
744
|
-
# Hide from tool listing (can still be called directly)
|
|
745
|
-
hideFromDiscovery: false
|
|
746
|
-
|
|
747
|
-
# Usage examples
|
|
748
|
-
examples:
|
|
749
|
-
- description: Get all users
|
|
750
|
-
input: {}
|
|
751
|
-
output: { users: [], total: 0 }
|
|
752
|
-
```
|
|
753
|
-
|
|
754
|
-
### Extension Properties
|
|
755
|
-
|
|
756
|
-
| Property | Type | Description |
|
|
757
|
-
| ------------------- | ---------- | --------------------------------------------------- |
|
|
758
|
-
| `annotations` | `object` | Tool behavior hints (readOnlyHint, etc.) |
|
|
759
|
-
| `cache` | `object` | Cache config: `ttl` (seconds), `slideWindow` |
|
|
760
|
-
| `codecall` | `object` | CodeCall: `enabledInCodeCall`, `visibleInListTools` |
|
|
761
|
-
| `tags` | `string[]` | Categorization tags |
|
|
762
|
-
| `hideFromDiscovery` | `boolean` | Hide from tool listing |
|
|
763
|
-
| `examples` | `array` | Usage examples with input/output |
|
|
764
|
-
|
|
765
|
-
### Priority: Spec vs Adapter
|
|
766
|
-
|
|
767
|
-
When both `x-frontmcp` (in the OpenAPI spec) and `toolTransforms` (in adapter config) are used:
|
|
768
|
-
|
|
769
|
-
1. `x-frontmcp` in OpenAPI spec is applied first (base layer)
|
|
770
|
-
2. `toolTransforms` in adapter config overrides/extends spec values
|
|
771
|
-
|
|
772
|
-
This allows API designers to set defaults in the spec, while adapter consumers can override as needed.
|
|
773
|
-
|
|
774
|
-
### Complete Example
|
|
775
|
-
|
|
776
|
-
```yaml
|
|
777
|
-
openapi: '3.0.0'
|
|
778
|
-
info:
|
|
779
|
-
title: User Management API
|
|
780
|
-
version: '1.0.0'
|
|
781
|
-
paths:
|
|
782
|
-
/users:
|
|
783
|
-
get:
|
|
784
|
-
operationId: listUsers
|
|
785
|
-
summary: List all users
|
|
786
|
-
description: Returns a paginated list of users with optional filtering
|
|
787
|
-
x-frontmcp:
|
|
788
|
-
annotations:
|
|
789
|
-
title: List Users
|
|
790
|
-
readOnlyHint: true
|
|
791
|
-
idempotentHint: true
|
|
792
|
-
cache:
|
|
793
|
-
ttl: 60
|
|
794
|
-
tags:
|
|
795
|
-
- users
|
|
796
|
-
- public-api
|
|
797
|
-
examples:
|
|
798
|
-
- description: List all users
|
|
799
|
-
input: { limit: 10 }
|
|
800
|
-
output: { users: [{ id: '1', name: 'John' }], total: 1 }
|
|
801
|
-
post:
|
|
802
|
-
operationId: createUser
|
|
803
|
-
summary: Create a new user
|
|
804
|
-
x-frontmcp:
|
|
805
|
-
annotations:
|
|
806
|
-
destructiveHint: false
|
|
807
|
-
idempotentHint: false
|
|
808
|
-
tags:
|
|
809
|
-
- users
|
|
810
|
-
- admin
|
|
811
|
-
delete:
|
|
812
|
-
operationId: deleteUser
|
|
813
|
-
summary: Delete a user
|
|
814
|
-
x-frontmcp:
|
|
815
|
-
annotations:
|
|
816
|
-
destructiveHint: true
|
|
817
|
-
tags:
|
|
818
|
-
- users
|
|
819
|
-
- admin
|
|
820
|
-
- dangerous
|
|
821
|
-
```
|
|
822
|
-
|
|
823
|
-
## Logger Integration
|
|
824
|
-
|
|
825
|
-
The adapter uses logging for diagnostics and security analysis. The logger is handled automatically:
|
|
826
|
-
|
|
827
|
-
### Within FrontMCP Apps (Recommended)
|
|
828
|
-
|
|
829
|
-
When using the adapter within a FrontMCP app, the SDK automatically injects the logger before `fetch()` is called:
|
|
830
|
-
|
|
831
|
-
```typescript
|
|
832
|
-
import { App } from '@frontmcp/sdk';
|
|
833
|
-
import { OpenapiAdapter } from '@frontmcp/adapters';
|
|
834
|
-
|
|
835
|
-
@App({
|
|
836
|
-
id: 'my-api',
|
|
837
|
-
adapters: [
|
|
838
|
-
OpenapiAdapter.init({
|
|
839
|
-
name: 'my-api',
|
|
840
|
-
baseUrl: 'https://api.example.com',
|
|
841
|
-
spec: mySpec,
|
|
842
|
-
// logger is automatically injected by the SDK
|
|
843
|
-
}),
|
|
844
|
-
],
|
|
845
|
-
})
|
|
846
|
-
export default class MyApiApp {}
|
|
847
|
-
```
|
|
848
|
-
|
|
849
|
-
### Standalone Usage
|
|
850
|
-
|
|
851
|
-
For standalone usage (outside FrontMCP apps), the adapter automatically creates a console-based logger:
|
|
852
|
-
|
|
853
|
-
```typescript
|
|
854
|
-
const adapter = new OpenapiAdapter({
|
|
855
|
-
name: 'my-api',
|
|
856
|
-
baseUrl: 'https://api.example.com',
|
|
857
|
-
spec: mySpec,
|
|
858
|
-
// Console logger is created automatically: [openapi:my-api] INFO: ...
|
|
859
|
-
});
|
|
860
|
-
```
|
|
861
|
-
|
|
862
|
-
### Custom Logger
|
|
863
|
-
|
|
864
|
-
You can provide a custom logger that implements `FrontMcpLogger`:
|
|
865
|
-
|
|
866
|
-
```typescript
|
|
867
|
-
const adapter = new OpenapiAdapter({
|
|
868
|
-
name: 'my-api',
|
|
869
|
-
baseUrl: 'https://api.example.com',
|
|
870
|
-
spec: mySpec,
|
|
871
|
-
logger: myCustomLogger, // Optional - uses console fallback if not provided
|
|
872
|
-
});
|
|
873
|
-
```
|
|
874
|
-
|
|
875
|
-
## How It Works
|
|
876
|
-
|
|
877
|
-
### 1. Spec Loading & Validation
|
|
878
|
-
|
|
879
|
-
```typescript
|
|
880
|
-
// Loads and validates OpenAPI spec
|
|
881
|
-
const generator = await OpenAPIToolGenerator.fromURL(url, {
|
|
882
|
-
dereference: true, // Resolves all $refs for flat schemas
|
|
883
|
-
validate: true, // Validates against OpenAPI spec
|
|
884
|
-
});
|
|
885
|
-
```
|
|
886
|
-
|
|
887
|
-
### 2. Tool Generation
|
|
888
|
-
|
|
889
|
-
Each OpenAPI operation becomes a FrontMCP tool with full type safety:
|
|
890
|
-
|
|
891
|
-
```yaml
|
|
892
|
-
# OpenAPI Operation
|
|
893
|
-
paths:
|
|
894
|
-
/users/{id}:
|
|
895
|
-
get:
|
|
896
|
-
operationId: getUser
|
|
897
|
-
parameters:
|
|
898
|
-
- name: id
|
|
899
|
-
in: path
|
|
900
|
-
required: true
|
|
901
|
-
schema:
|
|
902
|
-
type: string
|
|
903
|
-
```
|
|
904
|
-
|
|
905
|
-
```typescript
|
|
906
|
-
// Becomes FrontMCP Tool
|
|
907
|
-
{
|
|
908
|
-
name: 'getUser',
|
|
909
|
-
description: 'Get user by ID',
|
|
910
|
-
parameters: z.object({ id: z.string() }), // Auto-generated Zod schema
|
|
911
|
-
execute: async (args, ctx) => {
|
|
912
|
-
// Automatic request building with auth
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
```
|
|
916
|
-
|
|
917
|
-
### 3. Request Building
|
|
918
|
-
|
|
919
|
-
The adapter automatically builds requests using the parameter mapper:
|
|
920
|
-
|
|
921
|
-
- **Path parameters** → URL path (`/users/{id}` → `/users/123`)
|
|
922
|
-
- **Query parameters** → Query string (`?page=1&limit=10`)
|
|
923
|
-
- **Header parameters** → HTTP headers
|
|
924
|
-
- **Body parameters** → Request body (JSON)
|
|
925
|
-
- **Security** → Authentication headers (resolved from context)
|
|
926
|
-
|
|
927
|
-
### 4. Authentication Resolution
|
|
928
|
-
|
|
929
|
-
Security is resolved automatically using the `SecurityResolver`. Tokens are routed to the correct context field based on the security scheme type:
|
|
930
|
-
|
|
931
|
-
| Scheme Type | Context Field |
|
|
932
|
-
| -------------- | --------------------- |
|
|
933
|
-
| `http: bearer` | `context.jwt` |
|
|
934
|
-
| `apiKey` | `context.apiKey` |
|
|
935
|
-
| `http: basic` | `context.basic` |
|
|
936
|
-
| `oauth2` | `context.oauth2Token` |
|
|
937
|
-
|
|
938
|
-
```typescript
|
|
939
|
-
// 1. Extract security from OpenAPI spec
|
|
940
|
-
const security = await securityResolver.resolve(tool.mapper, {
|
|
941
|
-
jwt: ctx.authInfo.token, // From FrontMCP context
|
|
942
|
-
});
|
|
943
|
-
|
|
944
|
-
// 2. Apply to request
|
|
945
|
-
fetch(url, {
|
|
946
|
-
headers: {
|
|
947
|
-
...security.headers, // Authorization: Bearer xxx
|
|
948
|
-
...customHeaders,
|
|
949
|
-
},
|
|
950
|
-
});
|
|
951
|
-
```
|
|
952
|
-
|
|
953
|
-
## Security Protections
|
|
954
|
-
|
|
955
|
-
The adapter includes defense-in-depth security protections:
|
|
956
|
-
|
|
957
|
-
| Protection | Description |
|
|
958
|
-
| --------------------- | ------------------------------------------------------------------------------------- |
|
|
959
|
-
| SSRF Prevention | Validates server URLs, blocks dangerous protocols (`file://`, `javascript:`, `data:`) |
|
|
960
|
-
| Header Injection | Rejects control characters (`\r`, `\n`, `\x00`, `\f`, `\v`) in header values |
|
|
961
|
-
| Prototype Pollution | Blocks reserved JS keys (`__proto__`, `constructor`, `prototype`) in input transforms |
|
|
962
|
-
| Request Size Limits | Content-Length validation with integer overflow protection (`isFinite()` check) |
|
|
963
|
-
| Query Param Collision | Detects conflicts between security and user input parameters |
|
|
964
|
-
|
|
965
|
-
See [`openapi.executor.ts`](./openapi.executor.ts) for implementation details.
|
|
966
|
-
|
|
967
|
-
## Supported Authentication Types
|
|
968
|
-
|
|
969
|
-
| Type | OpenAPI | Auto-Resolved From |
|
|
970
|
-
| ---------------- | ---------------- | -------------------- |
|
|
971
|
-
| Bearer Token | `http: bearer` | `ctx.authInfo.token` |
|
|
972
|
-
| Basic Auth | `http: basic` | Custom resolver |
|
|
973
|
-
| Digest Auth | `http: digest` | Custom resolver |
|
|
974
|
-
| API Key (Header) | `apiKey: header` | `additionalHeaders` |
|
|
975
|
-
| API Key (Query) | `apiKey: query` | `additionalHeaders` |
|
|
976
|
-
| OAuth2 | `oauth2` | `ctx.authInfo.token` |
|
|
977
|
-
| OpenID Connect | `openIdConnect` | `ctx.authInfo.token` |
|
|
978
|
-
| mTLS | `mutualTLS` | Custom resolver |
|
|
979
|
-
| HMAC Signature | Custom | Custom resolver |
|
|
980
|
-
| AWS Signature V4 | Custom | Custom resolver |
|
|
981
|
-
| Custom Headers | `apiKey` | `additionalHeaders` |
|
|
982
|
-
| Cookies | Context | Custom resolver |
|
|
983
|
-
|
|
984
|
-
See [SECURITY.md](../../../../mcp-from-openapi/SECURITY.md) for detailed authentication examples.
|
|
985
|
-
|
|
986
|
-
## Security Validation
|
|
987
|
-
|
|
988
|
-
The adapter automatically validates your security configuration on startup and provides helpful error messages if
|
|
989
|
-
authentication is not properly configured.
|
|
990
|
-
|
|
991
|
-
### Automatic Validation
|
|
992
|
-
|
|
993
|
-
When the adapter loads, it:
|
|
994
|
-
|
|
995
|
-
1. **Extracts all security schemes** from your OpenAPI spec
|
|
996
|
-
2. **Validates your auth configuration** matches the security requirements
|
|
997
|
-
3. **Calculates a security risk score** (low/medium/high)
|
|
998
|
-
4. **Fails early** with clear errors if auth mapping is missing
|
|
999
|
-
|
|
1000
|
-
### Security Risk Scores
|
|
1001
|
-
|
|
1002
|
-
| Score | Configuration | Description |
|
|
1003
|
-
| ------------- | -------------------------------------------------- | --------------------------------------------- |
|
|
1004
|
-
| **LOW** ✅ | `authProviderMapper` or `securityResolver` | Auth from context - Production ready |
|
|
1005
|
-
| **MEDIUM** ⚠️ | `securitySchemesInInput` with `authProviderMapper` | Hybrid: some user-provided, some from context |
|
|
1006
|
-
| **MEDIUM** ⚠️ | `staticAuth` or default | Static credentials - Secure but less flexible |
|
|
1007
|
-
| **HIGH** ❌ | `includeSecurityInInput: true` | User provides auth - High security risk |
|
|
1008
|
-
|
|
1009
|
-
### Example: Missing Auth Configuration
|
|
1010
|
-
|
|
1011
|
-
If you have an OpenAPI spec with security schemes but no auth configuration:
|
|
1012
|
-
|
|
1013
|
-
```typescript
|
|
1014
|
-
// OpenAPI spec has security schemes
|
|
1015
|
-
{
|
|
1016
|
-
"components": {
|
|
1017
|
-
"securitySchemes": {
|
|
1018
|
-
"GitHubAuth": { "type": "http", "scheme": "bearer" },
|
|
1019
|
-
"SlackAuth": { "type": "http", "scheme": "bearer" }
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
// ❌ This will FAIL on startup
|
|
1025
|
-
const adapter = new OpenapiAdapter({
|
|
1026
|
-
name: 'my-api',
|
|
1027
|
-
url: 'https://api.example.com/openapi.json',
|
|
1028
|
-
baseUrl: 'https://api.example.com',
|
|
1029
|
-
// No auth configuration provided!
|
|
1030
|
-
});
|
|
1031
|
-
```
|
|
1032
|
-
|
|
1033
|
-
**Error message you'll see:**
|
|
1034
|
-
|
|
1035
|
-
```
|
|
1036
|
-
[OpenAPI Adapter: my-api] Invalid security configuration.
|
|
1037
|
-
Missing auth provider mappings for security schemes: GitHubAuth, SlackAuth
|
|
1038
|
-
|
|
1039
|
-
Your OpenAPI spec requires these security schemes, but no auth configuration was provided.
|
|
1040
|
-
|
|
1041
|
-
Add one of the following to your adapter configuration:
|
|
1042
|
-
|
|
1043
|
-
1. authProviderMapper (recommended):
|
|
1044
|
-
authProviderMapper: {
|
|
1045
|
-
'GitHubAuth': (authInfo) => authInfo.user?.githubauthToken,
|
|
1046
|
-
'SlackAuth': (authInfo) => authInfo.user?.slackauthToken,
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
2. securityResolver:
|
|
1050
|
-
securityResolver: (tool, authInfo) => ({ jwt: authInfo.token })
|
|
1051
|
-
|
|
1052
|
-
3. staticAuth:
|
|
1053
|
-
staticAuth: { jwt: process.env.API_TOKEN }
|
|
1054
|
-
|
|
1055
|
-
4. Include security in input (NOT recommended for production):
|
|
1056
|
-
generateOptions: { includeSecurityInInput: true }
|
|
1057
|
-
```
|
|
1058
|
-
|
|
1059
|
-
### Example: Valid Configuration
|
|
1060
|
-
|
|
1061
|
-
```typescript
|
|
1062
|
-
// ✅ This will SUCCEED with security risk score: LOW
|
|
1063
|
-
const adapter = new OpenapiAdapter({
|
|
1064
|
-
name: 'my-api',
|
|
1065
|
-
url: 'https://api.example.com/openapi.json',
|
|
1066
|
-
baseUrl: 'https://api.example.com',
|
|
1067
|
-
|
|
1068
|
-
authProviderMapper: {
|
|
1069
|
-
GitHubAuth: (authInfo) => authInfo.user?.githubToken,
|
|
1070
|
-
SlackAuth: (authInfo) => authInfo.user?.slackToken,
|
|
1071
|
-
},
|
|
1072
|
-
});
|
|
1073
|
-
```
|
|
1074
|
-
|
|
1075
|
-
**Console output:**
|
|
1076
|
-
|
|
1077
|
-
```
|
|
1078
|
-
[OpenAPI Adapter: my-api] Security Analysis:
|
|
1079
|
-
Security Risk Score: LOW
|
|
1080
|
-
Valid Configuration: YES
|
|
1081
|
-
|
|
1082
|
-
Messages:
|
|
1083
|
-
- INFO: Using authProviderMapper for auth resolution.
|
|
1084
|
-
```
|
|
1085
|
-
|
|
1086
|
-
### Runtime Validation
|
|
1087
|
-
|
|
1088
|
-
Each tool execution also validates authentication:
|
|
1089
|
-
|
|
1090
|
-
```typescript
|
|
1091
|
-
// If a tool requires GitHubAuth but no token is available
|
|
1092
|
-
await tool.execute(
|
|
1093
|
-
{
|
|
1094
|
-
/* ... */
|
|
1095
|
-
},
|
|
1096
|
-
ctx,
|
|
1097
|
-
);
|
|
1098
|
-
|
|
1099
|
-
// Error:
|
|
1100
|
-
// Authentication required for tool 'github_getRepos' but no auth configuration found.
|
|
1101
|
-
// Required security schemes: GitHubAuth
|
|
1102
|
-
```
|
|
1103
|
-
|
|
1104
|
-
### Bypassing Validation (Not Recommended)
|
|
1105
|
-
|
|
1106
|
-
For testing or internal tools only:
|
|
1107
|
-
|
|
1108
|
-
```typescript
|
|
1109
|
-
const adapter = new OpenapiAdapter({
|
|
1110
|
-
name: 'my-api',
|
|
1111
|
-
url: 'https://api.example.com/openapi.json',
|
|
1112
|
-
baseUrl: 'https://api.example.com',
|
|
1113
|
-
|
|
1114
|
-
generateOptions: {
|
|
1115
|
-
// ⚠️ HIGH SECURITY RISK: Users provide auth directly
|
|
1116
|
-
includeSecurityInInput: true,
|
|
1117
|
-
},
|
|
1118
|
-
});
|
|
1119
|
-
|
|
1120
|
-
// Console output:
|
|
1121
|
-
// [OpenAPI Adapter: my-api] Security Analysis:
|
|
1122
|
-
// Security Risk Score: HIGH
|
|
1123
|
-
// Valid Configuration: YES
|
|
1124
|
-
//
|
|
1125
|
-
// Messages:
|
|
1126
|
-
// - SECURITY WARNING: includeSecurityInInput is enabled. Users will provide
|
|
1127
|
-
// authentication directly in tool inputs. This increases security risk as
|
|
1128
|
-
// credentials may be logged or exposed.
|
|
1129
|
-
```
|
|
1130
|
-
|
|
1131
|
-
## Best Practices
|
|
1132
|
-
|
|
1133
|
-
### 1. Use Environment Variables
|
|
1134
|
-
|
|
1135
|
-
```typescript
|
|
1136
|
-
const adapter = new OpenapiAdapter({
|
|
1137
|
-
name: 'my-api',
|
|
1138
|
-
url: process.env.OPENAPI_URL,
|
|
1139
|
-
baseUrl: process.env.API_BASE_URL,
|
|
1140
|
-
additionalHeaders: {
|
|
1141
|
-
'X-API-Key': process.env.API_KEY,
|
|
1142
|
-
},
|
|
1143
|
-
});
|
|
1144
|
-
```
|
|
1145
|
-
|
|
1146
|
-
### 2. Filter Endpoints
|
|
1147
|
-
|
|
1148
|
-
```typescript
|
|
1149
|
-
generateOptions: {
|
|
1150
|
-
filterFn: (operation) => {
|
|
1151
|
-
return (
|
|
1152
|
-
operation.tags?.includes('public') &&
|
|
1153
|
-
!operation.deprecated
|
|
1154
|
-
);
|
|
1155
|
-
},
|
|
1156
|
-
}
|
|
1157
|
-
```
|
|
1158
|
-
|
|
1159
|
-
### 3. Handle Multi-Tenant
|
|
1160
|
-
|
|
1161
|
-
```typescript
|
|
1162
|
-
headersMapper: (authInfo, headers) => {
|
|
1163
|
-
const tenantId = authInfo.user?.organizationId;
|
|
1164
|
-
if (!tenantId) {
|
|
1165
|
-
throw new Error('Tenant ID required');
|
|
1166
|
-
}
|
|
1167
|
-
headers.set('X-Tenant-ID', tenantId);
|
|
1168
|
-
return headers;
|
|
1169
|
-
},
|
|
1170
|
-
```
|
|
1171
|
-
|
|
1172
|
-
## Troubleshooting
|
|
1173
|
-
|
|
1174
|
-
### Tools not generated
|
|
1175
|
-
|
|
1176
|
-
- Check that your OpenAPI spec is valid (`validate: true`)
|
|
1177
|
-
- Verify `filterFn` isn't excluding all operations
|
|
1178
|
-
- Check console for validation errors
|
|
1179
|
-
|
|
1180
|
-
### Authentication not working
|
|
1181
|
-
|
|
1182
|
-
- Ensure security is defined in OpenAPI spec
|
|
1183
|
-
- Verify `ctx.authInfo.token` is available
|
|
1184
|
-
- Add `additionalHeaders` if needed
|
|
1185
|
-
- Check auth type routing matches your scheme (Bearer → `jwt`, API Key → `apiKey`)
|
|
1186
|
-
|
|
1187
|
-
### Type errors
|
|
1188
|
-
|
|
1189
|
-
- Ensure `dereference: true` to resolve `$ref` objects
|
|
1190
|
-
- Check that JSON schemas are valid
|
|
1191
|
-
|
|
1192
|
-
### Empty string token error
|
|
1193
|
-
|
|
1194
|
-
- `authProviderMapper` returned empty string instead of `undefined`
|
|
1195
|
-
- Return `undefined` or `null` when no token is available
|
|
1196
|
-
|
|
1197
|
-
### Header injection error
|
|
1198
|
-
|
|
1199
|
-
- Header values contain control characters (`\r`, `\n`, `\x00`)
|
|
1200
|
-
- Sanitize dynamic header values before passing to the adapter
|
|
1201
|
-
|
|
1202
|
-
### Invalid base URL error
|
|
1203
|
-
|
|
1204
|
-
- Server URL from OpenAPI spec failed SSRF validation
|
|
1205
|
-
- Only `http://` and `https://` protocols are allowed
|
|
1206
|
-
|
|
1207
|
-
## Links
|
|
1208
|
-
|
|
1209
|
-
- [mcp-from-openapi](https://github.com/frontmcp/mcp-from-openapi) - Core OpenAPI to MCP converter
|
|
1210
|
-
- [Security Guide](../../../../mcp-from-openapi/SECURITY.md) - Comprehensive authentication guide
|
|
1211
|
-
- [FrontMCP SDK](../../../../sdk) - FrontMCP core SDK
|
|
1212
|
-
|
|
1213
|
-
## License
|
|
1214
|
-
|
|
1215
|
-
MIT
|