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