@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.
@@ -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