@frontmcp/adapters 0.5.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/openapi/README.md +473 -11
- package/src/openapi/openapi.adapter.d.ts +39 -1
- package/src/openapi/openapi.adapter.js +342 -10
- package/src/openapi/openapi.adapter.js.map +1 -1
- package/src/openapi/openapi.frontmcp-schema.d.ts +91 -0
- package/src/openapi/openapi.frontmcp-schema.js +358 -0
- package/src/openapi/openapi.frontmcp-schema.js.map +1 -0
- package/src/openapi/openapi.security.d.ts +7 -7
- package/src/openapi/openapi.security.js +98 -30
- package/src/openapi/openapi.security.js.map +1 -1
- package/src/openapi/openapi.tool.d.ts +3 -1
- package/src/openapi/openapi.tool.js +218 -32
- package/src/openapi/openapi.tool.js.map +1 -1
- package/src/openapi/openapi.types.d.ts +486 -15
- package/src/openapi/openapi.types.js +26 -0
- package/src/openapi/openapi.types.js.map +1 -1
- package/src/openapi/openapi.utils.d.ts +17 -1
- package/src/openapi/openapi.utils.js +133 -30
- package/src/openapi/openapi.utils.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/adapters",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Adapters for the FrontMCP framework",
|
|
5
5
|
"author": "AgentFront <info@agentfront.dev>",
|
|
6
6
|
"homepage": "https://docs.agentfront.dev",
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@frontmcp/sdk": "0.
|
|
35
|
+
"@frontmcp/sdk": "0.6.1",
|
|
36
36
|
"zod": "^4.0.0",
|
|
37
37
|
"openapi-types": "^12.1.3",
|
|
38
|
-
"@modelcontextprotocol/sdk": "1.
|
|
39
|
-
"mcp-from-openapi": "2.
|
|
38
|
+
"@modelcontextprotocol/sdk": "1.25.1",
|
|
39
|
+
"mcp-from-openapi": "2.1.0",
|
|
40
40
|
"zod-from-json-schema": "^0.5.2"
|
|
41
41
|
},
|
|
42
42
|
"type": "commonjs"
|
package/src/openapi/README.md
CHANGED
|
@@ -17,7 +17,7 @@ FrontMCP tools with full type safety, authentication support, and automatic requ
|
|
|
17
17
|
|
|
18
18
|
✅ **Custom Mappers** - Transform headers and body based on session data
|
|
19
19
|
|
|
20
|
-
✅ **Production Ready** - Comprehensive error handling and
|
|
20
|
+
✅ **Production Ready** - Comprehensive error handling, validation, and security protections
|
|
21
21
|
|
|
22
22
|
## Quick Start
|
|
23
23
|
|
|
@@ -41,7 +41,7 @@ import spec from './openapi.json';
|
|
|
41
41
|
|
|
42
42
|
const adapter = new OpenapiAdapter({
|
|
43
43
|
name: 'my-api',
|
|
44
|
-
spec: spec,
|
|
44
|
+
spec: spec, // Accepts object, OpenAPIV3.Document, or OpenAPIV3_1.Document
|
|
45
45
|
baseUrl: 'https://api.example.com',
|
|
46
46
|
});
|
|
47
47
|
```
|
|
@@ -258,6 +258,7 @@ const adapter = new OpenapiAdapter({
|
|
|
258
258
|
- The adapter looks up the scheme name in `authProviderMapper`
|
|
259
259
|
- It calls the corresponding extractor to get the token from `authInfo.user`
|
|
260
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.
|
|
261
262
|
|
|
262
263
|
#### Approach 2: Custom Security Resolver
|
|
263
264
|
|
|
@@ -315,14 +316,81 @@ const adapter = new OpenapiAdapter({
|
|
|
315
316
|
});
|
|
316
317
|
```
|
|
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
|
+
|
|
318
383
|
### Auth Resolution Priority
|
|
319
384
|
|
|
320
385
|
The adapter resolves authentication in this order:
|
|
321
386
|
|
|
322
387
|
1. **Custom `securityResolver`** (highest priority) - Full control per tool
|
|
323
|
-
2. **`authProviderMapper`** -
|
|
324
|
-
3. **`
|
|
325
|
-
4.
|
|
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.
|
|
326
394
|
|
|
327
395
|
## Real-World Examples
|
|
328
396
|
|
|
@@ -448,6 +516,362 @@ const adapter = new OpenapiAdapter({
|
|
|
448
516
|
- `stripe_getCustomers` tool → Uses Stripe key from `authInfo.user.integrations.stripe.apiKey`
|
|
449
517
|
- Each tool automatically gets the correct authentication!
|
|
450
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
|
+
|
|
451
875
|
## How It Works
|
|
452
876
|
|
|
453
877
|
### 1. Spec Loading & Validation
|
|
@@ -502,7 +926,14 @@ The adapter automatically builds requests using the parameter mapper:
|
|
|
502
926
|
|
|
503
927
|
### 4. Authentication Resolution
|
|
504
928
|
|
|
505
|
-
Security is resolved automatically using the `SecurityResolver
|
|
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` |
|
|
506
937
|
|
|
507
938
|
```typescript
|
|
508
939
|
// 1. Extract security from OpenAPI spec
|
|
@@ -519,6 +950,20 @@ fetch(url, {
|
|
|
519
950
|
});
|
|
520
951
|
```
|
|
521
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
|
+
|
|
522
967
|
## Supported Authentication Types
|
|
523
968
|
|
|
524
969
|
| Type | OpenAPI | Auto-Resolved From |
|
|
@@ -554,11 +999,12 @@ When the adapter loads, it:
|
|
|
554
999
|
|
|
555
1000
|
### Security Risk Scores
|
|
556
1001
|
|
|
557
|
-
| Score | Configuration
|
|
558
|
-
| ------------- |
|
|
559
|
-
| **LOW** ✅ | `authProviderMapper` or `securityResolver`
|
|
560
|
-
| **MEDIUM** ⚠️ | `
|
|
561
|
-
| **
|
|
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 |
|
|
562
1008
|
|
|
563
1009
|
### Example: Missing Auth Configuration
|
|
564
1010
|
|
|
@@ -736,12 +1182,28 @@ headersMapper: (authInfo, headers) => {
|
|
|
736
1182
|
- Ensure security is defined in OpenAPI spec
|
|
737
1183
|
- Verify `ctx.authInfo.token` is available
|
|
738
1184
|
- Add `additionalHeaders` if needed
|
|
1185
|
+
- Check auth type routing matches your scheme (Bearer → `jwt`, API Key → `apiKey`)
|
|
739
1186
|
|
|
740
1187
|
### Type errors
|
|
741
1188
|
|
|
742
1189
|
- Ensure `dereference: true` to resolve `$ref` objects
|
|
743
1190
|
- Check that JSON schemas are valid
|
|
744
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
|
+
|
|
745
1207
|
## Links
|
|
746
1208
|
|
|
747
1209
|
- [mcp-from-openapi](https://github.com/frontmcp/mcp-from-openapi) - Core OpenAPI to MCP converter
|
|
@@ -1,13 +1,51 @@
|
|
|
1
|
-
import { DynamicAdapter, FrontMcpAdapterResponse } from '@frontmcp/sdk';
|
|
1
|
+
import { DynamicAdapter, FrontMcpAdapterResponse, FrontMcpLogger } from '@frontmcp/sdk';
|
|
2
2
|
import { OpenApiAdapterOptions } from './openapi.types';
|
|
3
3
|
export default class OpenapiAdapter extends DynamicAdapter<OpenApiAdapterOptions> {
|
|
4
4
|
private generator?;
|
|
5
|
+
private logger;
|
|
5
6
|
options: OpenApiAdapterOptions;
|
|
6
7
|
constructor(options: OpenApiAdapterOptions);
|
|
8
|
+
/**
|
|
9
|
+
* Receive the SDK logger. Called by the SDK before fetch().
|
|
10
|
+
*/
|
|
11
|
+
setLogger(logger: FrontMcpLogger): void;
|
|
7
12
|
fetch(): Promise<FrontMcpAdapterResponse>;
|
|
8
13
|
/**
|
|
9
14
|
* Initialize the OpenAPI tool generator from URL or spec
|
|
10
15
|
* @private
|
|
11
16
|
*/
|
|
12
17
|
private initializeGenerator;
|
|
18
|
+
/**
|
|
19
|
+
* Apply description mode to generate description from summary/description
|
|
20
|
+
* @private
|
|
21
|
+
*/
|
|
22
|
+
private applyDescriptionMode;
|
|
23
|
+
/**
|
|
24
|
+
* Collect tool transforms for a specific tool
|
|
25
|
+
* @private
|
|
26
|
+
*/
|
|
27
|
+
private collectToolTransforms;
|
|
28
|
+
/**
|
|
29
|
+
* Apply tool transforms to an OpenAPI tool
|
|
30
|
+
* @private
|
|
31
|
+
*/
|
|
32
|
+
private applyToolTransforms;
|
|
33
|
+
/**
|
|
34
|
+
* Collect all input transforms for a specific tool
|
|
35
|
+
* @private
|
|
36
|
+
*/
|
|
37
|
+
private collectTransformsForTool;
|
|
38
|
+
/**
|
|
39
|
+
* Apply input transforms to an OpenAPI tool
|
|
40
|
+
* - Removes transformed inputKeys from the inputSchema
|
|
41
|
+
* - Stores transform metadata for runtime injection
|
|
42
|
+
* @private
|
|
43
|
+
*/
|
|
44
|
+
private applyInputTransforms;
|
|
45
|
+
/**
|
|
46
|
+
* Filter security schemes in tool input based on securitySchemesInInput option.
|
|
47
|
+
* Removes security inputs that should be resolved from context instead of user input.
|
|
48
|
+
* @private
|
|
49
|
+
*/
|
|
50
|
+
private filterSecuritySchemes;
|
|
13
51
|
}
|