openapi-dynamic-mcp 0.1.3 → 0.2.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/README.md +150 -105
- package/dist/auth/env.d.ts +7 -1
- package/dist/auth/env.js +25 -12
- package/dist/auth/env.js.map +1 -1
- package/dist/auth/oauthClient.d.ts +1 -1
- package/dist/auth/oauthClient.js +13 -13
- package/dist/auth/oauthClient.js.map +1 -1
- package/dist/auth/resolveAuth.d.ts +3 -3
- package/dist/auth/resolveAuth.js +58 -19
- package/dist/auth/resolveAuth.js.map +1 -1
- package/dist/cli.js +11 -11
- package/dist/cli.js.map +1 -1
- package/dist/config/loadConfig.d.ts +1 -1
- package/dist/config/loadConfig.js +46 -34
- package/dist/config/loadConfig.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +7 -7
- package/dist/http/requestExecutor.d.ts +3 -2
- package/dist/http/requestExecutor.js +201 -67
- package/dist/http/requestExecutor.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +6 -6
- package/dist/mcp/context.d.ts +2 -2
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +86 -59
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools/common.d.ts +6 -6
- package/dist/mcp/tools/common.js +15 -14
- package/dist/mcp/tools/common.js.map +1 -1
- package/dist/mcp/tools/getApiEndpoint.d.ts +2 -2
- package/dist/mcp/tools/getApiEndpoint.js +19 -15
- package/dist/mcp/tools/getApiEndpoint.js.map +1 -1
- package/dist/mcp/tools/getApiSchema.d.ts +2 -2
- package/dist/mcp/tools/getApiSchema.js +6 -6
- package/dist/mcp/tools/getApiSchema.js.map +1 -1
- package/dist/mcp/tools/index.d.ts +5 -5
- package/dist/mcp/tools/index.js +5 -5
- package/dist/mcp/tools/listApiEndpoints.d.ts +2 -2
- package/dist/mcp/tools/listApiEndpoints.js +4 -4
- package/dist/mcp/tools/listApis.d.ts +2 -2
- package/dist/mcp/tools/listApis.js +2 -2
- package/dist/mcp/tools/makeEndpointRequest.d.ts +2 -2
- package/dist/mcp/tools/makeEndpointRequest.js +21 -19
- package/dist/mcp/tools/makeEndpointRequest.js.map +1 -1
- package/dist/openapi/endpointIndex.d.ts +2 -2
- package/dist/openapi/endpointIndex.js +13 -13
- package/dist/openapi/endpointIndex.js.map +1 -1
- package/dist/openapi/jsonPointer.js +15 -12
- package/dist/openapi/jsonPointer.js.map +1 -1
- package/dist/openapi/loadSpec.d.ts +1 -1
- package/dist/openapi/loadSpec.js +46 -17
- package/dist/openapi/loadSpec.js.map +1 -1
- package/dist/types.d.ts +29 -9
- package/dist/types.js +9 -1
- package/dist/types.js.map +1 -1
- package/package.json +16 -2
package/README.md
CHANGED
|
@@ -1,38 +1,64 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h1>openapi-dynamic-mcp</h1>
|
|
3
|
+
|
|
4
|
+
<p>
|
|
5
|
+
<strong>A TypeScript MCP stdio server that seamlessly loads multiple OpenAPI 2.x and 3.x specifications and exposes powerful, generic tools for AI agents.</strong>
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<p>
|
|
9
|
+
<a href="https://www.npmjs.com/package/openapi-dynamic-mcp"><img src="https://img.shields.io/npm/v/openapi-dynamic-mcp?color=blue&style=flat-square" alt="NPM Version" /></a>
|
|
10
|
+
<a href="https://github.com/mayorandrew/openapi-dynamic-mcp/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/openapi-dynamic-mcp?style=flat-square" alt="License" /></a>
|
|
11
|
+
<img src="https://img.shields.io/node/v/openapi-dynamic-mcp?style=flat-square" alt="Node.js Version" />
|
|
12
|
+
</p>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
## 📖 Table of Contents
|
|
16
|
+
|
|
17
|
+
- [What It Does](#-what-it-does)
|
|
18
|
+
- [Requirements](#-requirements)
|
|
19
|
+
- [Quick Start](#-quick-start)
|
|
20
|
+
- [Client Configuration](#-client-configuration)
|
|
21
|
+
- [Claude Desktop / Claude Code](#claude-desktop--claude-code)
|
|
22
|
+
- [Cursor](#cursor)
|
|
23
|
+
- [Configuration](#-configuration)
|
|
24
|
+
- [Environment Variables](#-environment-variables)
|
|
25
|
+
- [Advanced Features](#-advanced-features)
|
|
26
|
+
- [File Uploads and Binary Data](#file-uploads-and-binary-data)
|
|
27
|
+
- [Available MCP Tools](#-available-mcp-tools)
|
|
28
|
+
- [Development](#-development)
|
|
29
|
+
- [License](#-license)
|
|
30
|
+
|
|
31
|
+
## ✨ What It Does
|
|
32
|
+
|
|
33
|
+
`openapi-dynamic-mcp` runs as a single Model Context Protocol (MCP) server over `stdio` for multiple APIs. It acts as a bridge between your LLMs and your API, taking care of parsing, request execution, authentication, and error handling.
|
|
34
|
+
|
|
35
|
+
- 🔄 **Multi-API Support**: Run a single server for any number of APIs simultaneously.
|
|
36
|
+
- 📄 **Specification Compatibility**: Seamlessly supports both OpenAPI `3.x` and Swagger `2.0` specifications.
|
|
37
|
+
- 🔌 **Dynamic Resolution**: Supports local spec files via `specPath` or remote URL specs via `specUrl`.
|
|
38
|
+
- 🔐 **Robust Authentication**: Handles API Keys, HTTP `bearer`/`basic`, and OAuth2 client credentials out-of-the-box. Supports complex OpenAPI security requirements (AND/OR logic).
|
|
39
|
+
- 🌍 **Environment Overrides**: Easily override base URLs, tokens, and extra headers per API.
|
|
40
|
+
- 🔁 **Resilience**: Configurable exponential retries on `429 Too Many Requests` responses.
|
|
41
|
+
- ✅ **Tested**: Continuously tested against real-world APIs.
|
|
42
|
+
|
|
43
|
+
## 🚀 Requirements
|
|
24
44
|
|
|
25
45
|
- Node.js `20+`
|
|
26
46
|
|
|
27
|
-
## Quick Start
|
|
47
|
+
## 🏃 Quick Start
|
|
48
|
+
|
|
49
|
+
Run the server directly using `npx`:
|
|
28
50
|
|
|
29
51
|
```bash
|
|
30
|
-
npx openapi-dynamic-mcp --config ./
|
|
52
|
+
npx -y openapi-dynamic-mcp@latest --config ./config.yaml
|
|
31
53
|
```
|
|
32
54
|
|
|
33
|
-
## Client Configuration
|
|
55
|
+
## 🔌 Client Configuration
|
|
56
|
+
|
|
57
|
+
To use this with your favorite MCP-compatible client, add it to their respective config files.
|
|
34
58
|
|
|
35
|
-
### Claude Code
|
|
59
|
+
### Claude Desktop / Claude Code
|
|
60
|
+
|
|
61
|
+
Add the following to your `claude_desktop_config.json` or equivalent:
|
|
36
62
|
|
|
37
63
|
```json
|
|
38
64
|
{
|
|
@@ -58,6 +84,8 @@ npx openapi-dynamic-mcp --config ./examples/config.yaml
|
|
|
58
84
|
|
|
59
85
|
### Cursor
|
|
60
86
|
|
|
87
|
+
Add to your MCP servers in Cursor settings:
|
|
88
|
+
|
|
61
89
|
```json
|
|
62
90
|
{
|
|
63
91
|
"mcpServers": {
|
|
@@ -70,26 +98,28 @@ npx openapi-dynamic-mcp --config ./examples/config.yaml
|
|
|
70
98
|
"/absolute/path/to/config.yaml"
|
|
71
99
|
],
|
|
72
100
|
"env": {
|
|
73
|
-
"PET_API_BASE_URL": "http://localhost:3000"
|
|
74
|
-
"PET_API_APIKEY_API_KEY": "secret",
|
|
75
|
-
"PET_API_OAUTH2_CLIENT_ID": "client_id",
|
|
76
|
-
"PET_API_OAUTH2_CLIENT_SECRET": "client_secret"
|
|
101
|
+
"PET_API_BASE_URL": "http://localhost:3000"
|
|
77
102
|
}
|
|
78
103
|
}
|
|
79
104
|
}
|
|
80
105
|
}
|
|
81
106
|
```
|
|
82
107
|
|
|
83
|
-
## Configuration
|
|
108
|
+
## ⚙️ Configuration
|
|
109
|
+
|
|
110
|
+
Create a YAML configuration file to define your APIs.
|
|
84
111
|
|
|
85
112
|
```yaml
|
|
86
113
|
# Config file version
|
|
87
114
|
version: 1
|
|
115
|
+
|
|
88
116
|
apis:
|
|
89
117
|
# Unique ID for this API
|
|
90
118
|
- name: pet-api
|
|
91
|
-
# Path to local OpenAPI spec
|
|
119
|
+
# Path to local OpenAPI spec (use specUrl for remote definitions)
|
|
92
120
|
specPath: ./pet-api.yaml
|
|
121
|
+
# Alternative: remote OpenAPI spec URL
|
|
122
|
+
# specUrl: https://api.example.com/openapi.yaml
|
|
93
123
|
# Base URL override
|
|
94
124
|
baseUrl: https://api.example.com/v1
|
|
95
125
|
# Request timeout in milliseconds
|
|
@@ -124,24 +154,24 @@ apis:
|
|
|
124
154
|
### Validation Rules
|
|
125
155
|
|
|
126
156
|
- `apis[].name` must be unique (case-insensitive after normalization).
|
|
127
|
-
- `apis[].specPath`
|
|
128
|
-
- OpenAPI
|
|
157
|
+
- Exactly one of `apis[].specPath` (local file) or `apis[].specUrl` (remote URL) must be provided.
|
|
158
|
+
- Supported specifications: OpenAPI `3.x` and Swagger `2.0`.
|
|
129
159
|
- Base URL resolution order: env -> config -> `openapi.servers[0].url`.
|
|
130
160
|
|
|
131
|
-
## Environment Variables
|
|
161
|
+
## 🔐 Environment Variables
|
|
132
162
|
|
|
133
163
|
Environment variables allow specifying sensitive or environment-specific configuration for APIs. Variables are defined for each API separately.
|
|
134
164
|
|
|
135
165
|
### Name Normalization
|
|
136
166
|
|
|
137
|
-
API and auth scheme names are normalized
|
|
167
|
+
API and auth scheme names are normalized automatically:
|
|
138
168
|
|
|
139
|
-
-
|
|
140
|
-
-
|
|
141
|
-
-
|
|
142
|
-
-
|
|
169
|
+
- Uppercase
|
|
170
|
+
- Non-alphanumeric -> `_`
|
|
171
|
+
- Repeated `_` collapsed
|
|
172
|
+
- Leading/trailing `_` removed
|
|
143
173
|
|
|
144
|
-
|
|
174
|
+
_Examples:_
|
|
145
175
|
|
|
146
176
|
- `pet-api` -> `PET_API`
|
|
147
177
|
- `OAuth2` -> `OAUTH2`
|
|
@@ -151,97 +181,112 @@ Examples:
|
|
|
151
181
|
- `<API>_BASE_URL` - Overrides the API's base URL.
|
|
152
182
|
- `<API>_HEADERS` (JSON object string) - Adds custom headers to all requests.
|
|
153
183
|
|
|
154
|
-
###
|
|
155
|
-
|
|
156
|
-
For each API key security scheme defined in the OpenAPI spec, the following environment variables can be set:
|
|
157
|
-
|
|
158
|
-
- `<API>_<SCHEME>_API_KEY` - The API key value for the specified security scheme.
|
|
184
|
+
### Authentication Variables
|
|
159
185
|
|
|
160
|
-
|
|
186
|
+
**API Key** (`<API>_<SCHEME>_API_KEY`)
|
|
161
187
|
|
|
162
|
-
|
|
188
|
+
- The API key value for the specified security scheme.
|
|
163
189
|
|
|
164
|
-
|
|
165
|
-
- `<API>_<SCHEME>_CLIENT_SECRET` - The client secret for OAuth2.
|
|
166
|
-
- `<API>_<SCHEME>_TOKEN_URL` - The token endpoint URL for OAuth2.
|
|
167
|
-
- `<API>_<SCHEME>_SCOPES` (space-delimited) - The scopes required for the OAuth2 token.
|
|
168
|
-
- `<API>_<SCHEME>_TOKEN_AUTH_METHOD` (`client_secret_basic` or `client_secret_post`) - The authentication method for the token endpoint.
|
|
190
|
+
**HTTP Authentication**
|
|
169
191
|
|
|
170
|
-
|
|
192
|
+
- `<API>_<SCHEME>_TOKEN` - Bearer token value.
|
|
193
|
+
- `<API>_<SCHEME>_USERNAME` - Basic auth username.
|
|
194
|
+
- `<API>_<SCHEME>_PASSWORD` - Basic auth password.
|
|
171
195
|
|
|
172
|
-
|
|
173
|
-
- OAuth token URL: scheme env > config override > OpenAPI flow `tokenUrl`.
|
|
174
|
-
- OAuth scopes: scheme env > config scopes > OpenAPI flow scopes.
|
|
175
|
-
- Headers: config headers + env headers + tool-request headers (later wins), then auth is applied.
|
|
196
|
+
**OAuth2 Client Credentials**
|
|
176
197
|
|
|
177
|
-
|
|
198
|
+
- `<API>_<SCHEME>_CLIENT_ID` - Client ID.
|
|
199
|
+
- `<API>_<SCHEME>_CLIENT_SECRET` - Client secret.
|
|
200
|
+
- `<API>_<SCHEME>_TOKEN_URL` - Token endpoint URL.
|
|
201
|
+
- `<API>_<SCHEME>_SCOPES` (space-delimited) - Scopes required for the OAuth2 token.
|
|
202
|
+
- `<API>_<SCHEME>_TOKEN_AUTH_METHOD` (`client_secret_basic` or `client_secret_post`) - Auth method for the token endpoint.
|
|
178
203
|
|
|
179
|
-
|
|
204
|
+
_Precedence Rules:_
|
|
180
205
|
|
|
181
|
-
|
|
206
|
+
- **Base URL:** env > config > OpenAPI servers.
|
|
207
|
+
- **OAuth token URL:** scheme env > config override > OpenAPI flow `tokenUrl`.
|
|
208
|
+
- **OAuth scopes:** scheme env > config scopes > OpenAPI flow scopes.
|
|
209
|
+
- **Headers:** config headers + env headers + tool-request headers (later wins), then auth is applied.
|
|
182
210
|
|
|
183
|
-
|
|
211
|
+
## 🛠️ Advanced Features
|
|
184
212
|
|
|
185
|
-
###
|
|
213
|
+
### File Uploads and Binary Data
|
|
186
214
|
|
|
187
|
-
|
|
215
|
+
When your AI needs to send a file to an endpoint (either raw `application/octet-stream`, or inside a `multipart/form-data` payload), MCP passes messages as JSON. The LLM formats the corresponding file using the `files` parameter mapping, and `make_endpoint_request` processes it natively (converting to Blobs and FormData).
|
|
188
216
|
|
|
189
|
-
|
|
217
|
+
#### MCP File Descriptor format
|
|
190
218
|
|
|
191
|
-
|
|
192
|
-
- optional: `method`, `tag`, `pathContains`, `search`, `limit`, `cursor`
|
|
219
|
+
Each key in the `files` object maps to a form field name. You must provide exactly one of `base64`, `text`, or `filePath`:
|
|
193
220
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
Input fields: `apiName`, `endpointId`
|
|
221
|
+
```jsonc
|
|
222
|
+
{
|
|
223
|
+
"name": "avatar.png", // (Optional) Explicit file name
|
|
224
|
+
"contentType": "image/png", // (Optional) Explicit mime type
|
|
199
225
|
|
|
200
|
-
|
|
226
|
+
// Choose EXACTLY ONE content source:
|
|
227
|
+
"base64": "iVBORw0KGgo...", // Base64 encoded bytes
|
|
228
|
+
"text": "File contents", // Raw text content
|
|
229
|
+
"filePath": "/path/to/img", // Local absolute file path to read
|
|
230
|
+
}
|
|
231
|
+
```
|
|
201
232
|
|
|
202
|
-
|
|
233
|
+
#### Example: Multipart Form-Data
|
|
203
234
|
|
|
204
|
-
|
|
235
|
+
```json
|
|
236
|
+
{
|
|
237
|
+
"apiName": "pet-api",
|
|
238
|
+
"endpointId": "uploadProfile",
|
|
239
|
+
"contentType": "multipart/form-data",
|
|
240
|
+
"body": {
|
|
241
|
+
"description": "A photo of Fido"
|
|
242
|
+
},
|
|
243
|
+
"files": {
|
|
244
|
+
"profileImage": {
|
|
245
|
+
"name": "fido.jpg",
|
|
246
|
+
"contentType": "image/jpeg",
|
|
247
|
+
"filePath": "/Users/local/images/fido.jpg"
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
```
|
|
205
252
|
|
|
206
|
-
|
|
253
|
+
#### Example: Raw Octet Stream
|
|
207
254
|
|
|
208
|
-
|
|
255
|
+
```json
|
|
256
|
+
{
|
|
257
|
+
"apiName": "pet-api",
|
|
258
|
+
"endpointId": "uploadRaw",
|
|
259
|
+
"contentType": "application/octet-stream",
|
|
260
|
+
"files": {
|
|
261
|
+
"body": {
|
|
262
|
+
"filePath": "/Users/local/data.bin"
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
209
267
|
|
|
210
|
-
|
|
268
|
+
## 🧰 Available MCP Tools
|
|
211
269
|
|
|
212
|
-
|
|
213
|
-
- `endpointId`
|
|
214
|
-
- `pathParams`
|
|
215
|
-
- `query`
|
|
216
|
-
- `headers`
|
|
217
|
-
- `cookies`
|
|
218
|
-
- `body`
|
|
219
|
-
- `contentType`
|
|
220
|
-
- `accept`
|
|
221
|
-
- `timeoutMs`
|
|
222
|
-
- `retry429` object
|
|
223
|
-
- `maxRetries`
|
|
224
|
-
- `baseDelayMs`
|
|
225
|
-
- `maxDelayMs`
|
|
226
|
-
- `jitterRatio` (0..1)
|
|
227
|
-
- `respectRetryAfter`
|
|
270
|
+
These tools are exposed to your MCP client:
|
|
228
271
|
|
|
229
|
-
|
|
272
|
+
| Tool | Description | Inputs |
|
|
273
|
+
| ----------------------- | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
274
|
+
| `list_apis` | Returns all available configured APIs | _None_ |
|
|
275
|
+
| `list_api_endpoints` | Paginate or search endpoints in an API | `apiName` (req), `method`, `tag`, `pathContains`, `search`, `limit`, `cursor` |
|
|
276
|
+
| `get_api_endpoint` | Endpoint metadata (parameters, body types, responses, security) | `apiName`, `endpointId` |
|
|
277
|
+
| `get_api_schema` | Detailed API schema object specification | `apiName`, `pointer` (JSON Pointer, optional) |
|
|
278
|
+
| `make_endpoint_request` | Executes the actual API endpoint request | `apiName`, `endpointId`, `pathParams`, `query`, `headers`, `cookies`, `body`, `contentType`, `accept`, `timeoutMs`, `maxRetries429` |
|
|
230
279
|
|
|
231
|
-
|
|
232
|
-
- `response` status, headers, and body
|
|
233
|
-
- `timingMs`
|
|
234
|
-
- `authUsed`
|
|
280
|
+
## 💻 Development
|
|
235
281
|
|
|
236
|
-
|
|
282
|
+
Install dependencies and run tests:
|
|
237
283
|
|
|
238
284
|
```bash
|
|
285
|
+
npm install
|
|
239
286
|
npm test
|
|
240
287
|
npm run build
|
|
241
288
|
```
|
|
242
289
|
|
|
243
|
-
##
|
|
290
|
+
## 📄 License
|
|
244
291
|
|
|
245
|
-
|
|
246
|
-
- 429 retries are supported and disabled by default (`maxRetries: 0`).
|
|
247
|
-
- JSON responses are parsed first; non-JSON is returned as text or base64 binary.
|
|
292
|
+
This project is licensed under the MIT License.
|
package/dist/auth/env.d.ts
CHANGED
|
@@ -3,7 +3,12 @@ export interface OAuthClientCredentialsFromEnv {
|
|
|
3
3
|
clientSecret?: string;
|
|
4
4
|
tokenUrl?: string;
|
|
5
5
|
scopes?: string[];
|
|
6
|
-
tokenAuthMethod?:
|
|
6
|
+
tokenAuthMethod?: 'client_secret_basic' | 'client_secret_post';
|
|
7
|
+
}
|
|
8
|
+
export interface HttpAuthCredentialsFromEnv {
|
|
9
|
+
token?: string;
|
|
10
|
+
username?: string;
|
|
11
|
+
password?: string;
|
|
7
12
|
}
|
|
8
13
|
export declare function normalizeEnvSegment(value: string): string;
|
|
9
14
|
export declare function apiPrefix(apiName: string): string;
|
|
@@ -12,3 +17,4 @@ export declare function readApiBaseUrl(apiName: string, env?: NodeJS.ProcessEnv)
|
|
|
12
17
|
export declare function readApiExtraHeaders(apiName: string, env?: NodeJS.ProcessEnv): Record<string, string>;
|
|
13
18
|
export declare function readApiKeyValue(apiName: string, schemeName: string, env?: NodeJS.ProcessEnv): string | undefined;
|
|
14
19
|
export declare function readOAuthClientCredentials(apiName: string, schemeName: string, env?: NodeJS.ProcessEnv): OAuthClientCredentialsFromEnv;
|
|
20
|
+
export declare function readHttpAuthCredentials(apiName: string, schemeName: string, env?: NodeJS.ProcessEnv): HttpAuthCredentialsFromEnv;
|
package/dist/auth/env.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { OpenApiMcpError } from
|
|
1
|
+
import { OpenApiMcpError } from '../errors.js';
|
|
2
2
|
export function normalizeEnvSegment(value) {
|
|
3
3
|
return value
|
|
4
4
|
.toUpperCase()
|
|
5
|
-
.replace(/[^A-Z0-9]+/g,
|
|
6
|
-
.replace(/_+/g,
|
|
7
|
-
.replace(/^_+|_+$/g,
|
|
5
|
+
.replace(/[^A-Z0-9]+/g, '_')
|
|
6
|
+
.replace(/_+/g, '_')
|
|
7
|
+
.replace(/^_+|_+$/g, '');
|
|
8
8
|
}
|
|
9
9
|
export function apiPrefix(apiName) {
|
|
10
10
|
return normalizeEnvSegment(apiName);
|
|
@@ -25,15 +25,17 @@ export function readApiExtraHeaders(apiName, env = process.env) {
|
|
|
25
25
|
parsed = JSON.parse(raw);
|
|
26
26
|
}
|
|
27
27
|
catch {
|
|
28
|
-
throw new OpenApiMcpError(
|
|
28
|
+
throw new OpenApiMcpError('CONFIG_ERROR', `Invalid JSON in ${apiPrefix(apiName)}_HEADERS`, {
|
|
29
|
+
value: raw,
|
|
30
|
+
});
|
|
29
31
|
}
|
|
30
|
-
if (!parsed || typeof parsed !==
|
|
31
|
-
throw new OpenApiMcpError(
|
|
32
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
33
|
+
throw new OpenApiMcpError('CONFIG_ERROR', `${apiPrefix(apiName)}_HEADERS must be a JSON object`);
|
|
32
34
|
}
|
|
33
35
|
const out = {};
|
|
34
36
|
for (const [key, value] of Object.entries(parsed)) {
|
|
35
|
-
if (typeof value !==
|
|
36
|
-
throw new OpenApiMcpError(
|
|
37
|
+
if (typeof value !== 'string') {
|
|
38
|
+
throw new OpenApiMcpError('CONFIG_ERROR', `${apiPrefix(apiName)}_HEADERS values must be strings`, { key });
|
|
37
39
|
}
|
|
38
40
|
out[key] = value;
|
|
39
41
|
}
|
|
@@ -47,18 +49,29 @@ export function readOAuthClientCredentials(apiName, schemeName, env = process.en
|
|
|
47
49
|
const scopesRaw = env[`${prefix}_SCOPES`];
|
|
48
50
|
const tokenAuthMethodRaw = env[`${prefix}_TOKEN_AUTH_METHOD`];
|
|
49
51
|
let tokenAuthMethod;
|
|
50
|
-
if (tokenAuthMethodRaw ===
|
|
52
|
+
if (tokenAuthMethodRaw === 'client_secret_basic' ||
|
|
53
|
+
tokenAuthMethodRaw === 'client_secret_post') {
|
|
51
54
|
tokenAuthMethod = tokenAuthMethodRaw;
|
|
52
55
|
}
|
|
53
56
|
else if (tokenAuthMethodRaw) {
|
|
54
|
-
throw new OpenApiMcpError(
|
|
57
|
+
throw new OpenApiMcpError('CONFIG_ERROR', `Invalid ${prefix}_TOKEN_AUTH_METHOD value`, {
|
|
58
|
+
value: tokenAuthMethodRaw,
|
|
59
|
+
});
|
|
55
60
|
}
|
|
56
61
|
return {
|
|
57
62
|
clientId: env[`${prefix}_CLIENT_ID`],
|
|
58
63
|
clientSecret: env[`${prefix}_CLIENT_SECRET`],
|
|
59
64
|
tokenUrl: env[`${prefix}_TOKEN_URL`],
|
|
60
65
|
scopes: scopesRaw ? scopesRaw.split(/\s+/).filter(Boolean) : undefined,
|
|
61
|
-
tokenAuthMethod
|
|
66
|
+
tokenAuthMethod,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function readHttpAuthCredentials(apiName, schemeName, env = process.env) {
|
|
70
|
+
const prefix = schemePrefix(apiName, schemeName);
|
|
71
|
+
return {
|
|
72
|
+
token: env[`${prefix}_TOKEN`],
|
|
73
|
+
username: env[`${prefix}_USERNAME`],
|
|
74
|
+
password: env[`${prefix}_PASSWORD`],
|
|
62
75
|
};
|
|
63
76
|
}
|
|
64
77
|
//# sourceMappingURL=env.js.map
|
package/dist/auth/env.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/auth/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/auth/env.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@ export interface OAuthTokenRequest {
|
|
|
4
4
|
clientId: string;
|
|
5
5
|
clientSecret: string;
|
|
6
6
|
scopes: string[];
|
|
7
|
-
tokenEndpointAuthMethod:
|
|
7
|
+
tokenEndpointAuthMethod: 'client_secret_basic' | 'client_secret_post';
|
|
8
8
|
}
|
|
9
9
|
export declare class OAuthClient {
|
|
10
10
|
private readonly cache;
|
package/dist/auth/oauthClient.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as oauth from
|
|
2
|
-
import { OpenApiMcpError } from
|
|
1
|
+
import * as oauth from 'oauth4webapi';
|
|
2
|
+
import { OpenApiMcpError } from '../errors.js';
|
|
3
3
|
const TOKEN_EXPIRY_SAFETY_MS = 60_000;
|
|
4
4
|
export class OAuthClient {
|
|
5
5
|
cache = new Map();
|
|
@@ -10,44 +10,44 @@ export class OAuthClient {
|
|
|
10
10
|
}
|
|
11
11
|
const as = {
|
|
12
12
|
issuer: new URL(request.tokenUrl).origin,
|
|
13
|
-
token_endpoint: request.tokenUrl
|
|
13
|
+
token_endpoint: request.tokenUrl,
|
|
14
14
|
};
|
|
15
15
|
const client = {
|
|
16
|
-
client_id: request.clientId
|
|
16
|
+
client_id: request.clientId,
|
|
17
17
|
};
|
|
18
|
-
const clientAuth = request.tokenEndpointAuthMethod ===
|
|
18
|
+
const clientAuth = request.tokenEndpointAuthMethod === 'client_secret_post'
|
|
19
19
|
? oauth.ClientSecretPost(request.clientSecret)
|
|
20
20
|
: oauth.ClientSecretBasic(request.clientSecret);
|
|
21
21
|
const parameters = new URLSearchParams();
|
|
22
22
|
if (request.scopes.length > 0) {
|
|
23
|
-
parameters.set(
|
|
23
|
+
parameters.set('scope', request.scopes.join(' '));
|
|
24
24
|
}
|
|
25
25
|
try {
|
|
26
26
|
const tokenResponse = await oauth.clientCredentialsGrantRequest(as, client, clientAuth, parameters);
|
|
27
27
|
const tokenResult = await oauth.processClientCredentialsResponse(as, client, tokenResponse);
|
|
28
28
|
this.cache.set(request.cacheKey, {
|
|
29
29
|
accessToken: tokenResult.access_token,
|
|
30
|
-
expiresAtMs: Date.now() + Math.max(tokenResult.expires_in ?? 3600, 1) * 1000
|
|
30
|
+
expiresAtMs: Date.now() + Math.max(tokenResult.expires_in ?? 3600, 1) * 1000,
|
|
31
31
|
});
|
|
32
32
|
return tokenResult.access_token;
|
|
33
33
|
}
|
|
34
34
|
catch (error) {
|
|
35
35
|
if (error instanceof oauth.ResponseBodyError) {
|
|
36
|
-
throw new OpenApiMcpError(
|
|
36
|
+
throw new OpenApiMcpError('AUTH_ERROR', 'OAuth2 token request failed', {
|
|
37
37
|
tokenUrl: request.tokenUrl,
|
|
38
|
-
oauthError: error.cause
|
|
38
|
+
oauthError: error.cause,
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
if (error instanceof oauth.OperationProcessingError) {
|
|
42
|
-
throw new OpenApiMcpError(
|
|
42
|
+
throw new OpenApiMcpError('AUTH_ERROR', 'OAuth2 operation failed', {
|
|
43
43
|
tokenUrl: request.tokenUrl,
|
|
44
44
|
code: error.code,
|
|
45
|
-
cause: error.message
|
|
45
|
+
cause: error.message,
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
|
-
throw new OpenApiMcpError(
|
|
48
|
+
throw new OpenApiMcpError('AUTH_ERROR', 'OAuth2 token request failed', {
|
|
49
49
|
tokenUrl: request.tokenUrl,
|
|
50
|
-
cause: error instanceof Error ? error.message : String(error)
|
|
50
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauthClient.js","sourceRoot":"","sources":["../../src/auth/oauthClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAgB/C,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAEtC,MAAM,OAAO,WAAW;IACL,KAAK,GAAG,IAAI,GAAG,EAA2B,CAAC;IAE5D,KAAK,CAAC,yBAAyB,CAAC,OAA0B;QACxD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAChD,IAAI,MAAM,IAAI,MAAM,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,sBAAsB,EAAE,CAAC;YACvE,OAAO,MAAM,CAAC,WAAW,CAAC;QAC5B,CAAC;QAED,MAAM,EAAE,GAA8B;YACpC,MAAM,EAAE,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,MAAM;YACxC,cAAc,EAAE,OAAO,CAAC,QAAQ;SACjC,CAAC;QACF,MAAM,MAAM,GAAiB;YAC3B,SAAS,EAAE,OAAO,CAAC,QAAQ;SAC5B,CAAC;QACF,MAAM,UAAU,GACd,OAAO,CAAC,uBAAuB,KAAK,oBAAoB;YACtD,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAAC,YAAY,CAAC;YAC9C,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QACpD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACpD,CAAC;QAED,IAAI,CAAC;YACH,MAAM,aAAa,GAAG,MAAM,KAAK,CAAC,6BAA6B,CAC7D,EAAE,EACF,MAAM,EACN,UAAU,EACV,UAAU,CACX,CAAC;YACF,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC,gCAAgC,
|
|
1
|
+
{"version":3,"file":"oauthClient.js","sourceRoot":"","sources":["../../src/auth/oauthClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAgB/C,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAEtC,MAAM,OAAO,WAAW;IACL,KAAK,GAAG,IAAI,GAAG,EAA2B,CAAC;IAE5D,KAAK,CAAC,yBAAyB,CAAC,OAA0B;QACxD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAChD,IAAI,MAAM,IAAI,MAAM,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,sBAAsB,EAAE,CAAC;YACvE,OAAO,MAAM,CAAC,WAAW,CAAC;QAC5B,CAAC;QAED,MAAM,EAAE,GAA8B;YACpC,MAAM,EAAE,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,MAAM;YACxC,cAAc,EAAE,OAAO,CAAC,QAAQ;SACjC,CAAC;QACF,MAAM,MAAM,GAAiB;YAC3B,SAAS,EAAE,OAAO,CAAC,QAAQ;SAC5B,CAAC;QACF,MAAM,UAAU,GACd,OAAO,CAAC,uBAAuB,KAAK,oBAAoB;YACtD,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAAC,YAAY,CAAC;YAC9C,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QACpD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACpD,CAAC;QAED,IAAI,CAAC;YACH,MAAM,aAAa,GAAG,MAAM,KAAK,CAAC,6BAA6B,CAC7D,EAAE,EACF,MAAM,EACN,UAAU,EACV,UAAU,CACX,CAAC;YACF,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC,gCAAgC,CAC9D,EAAE,EACF,MAAM,EACN,aAAa,CACd,CAAC;YACF,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE;gBAC/B,WAAW,EAAE,WAAW,CAAC,YAAY;gBACrC,WAAW,EACT,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI;aAClE,CAAC,CAAC;YAEH,OAAO,WAAW,CAAC,YAAY,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,KAAK,CAAC,iBAAiB,EAAE,CAAC;gBAC7C,MAAM,IAAI,eAAe,CAAC,YAAY,EAAE,6BAA6B,EAAE;oBACrE,QAAQ,EAAE,OAAO,CAAC,QAAQ;oBAC1B,UAAU,EAAE,KAAK,CAAC,KAAK;iBACxB,CAAC,CAAC;YACL,CAAC;YAED,IAAI,KAAK,YAAY,KAAK,CAAC,wBAAwB,EAAE,CAAC;gBACpD,MAAM,IAAI,eAAe,CAAC,YAAY,EAAE,yBAAyB,EAAE;oBACjE,QAAQ,EAAE,OAAO,CAAC,QAAQ;oBAC1B,IAAI,EAAE,KAAK,CAAC,IAAI;oBAChB,KAAK,EAAE,KAAK,CAAC,OAAO;iBACrB,CAAC,CAAC;YACL,CAAC;YAED,MAAM,IAAI,eAAe,CAAC,YAAY,EAAE,6BAA6B,EAAE;gBACrE,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC9D,CAAC,CAAC;QACL,CAAC;IACH,CAAC;CACF"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { OAuthClient } from
|
|
2
|
-
import type { EndpointDefinition, LoadedApi, ResolvedAuthResult } from
|
|
1
|
+
import { OAuthClient } from './oauthClient.js';
|
|
2
|
+
import type { EndpointDefinition, LoadedApi, ResolvedAuthResult } from '../types.js';
|
|
3
3
|
interface ResolveAuthInput {
|
|
4
4
|
api: LoadedApi;
|
|
5
5
|
endpoint: EndpointDefinition;
|
|
6
6
|
oauthClient: OAuthClient;
|
|
7
7
|
env?: NodeJS.ProcessEnv;
|
|
8
8
|
}
|
|
9
|
-
export declare function resolveAuth({ api, endpoint, oauthClient, env }: ResolveAuthInput): Promise<ResolvedAuthResult>;
|
|
9
|
+
export declare function resolveAuth({ api, endpoint, oauthClient, env, }: ResolveAuthInput): Promise<ResolvedAuthResult>;
|
|
10
10
|
export {};
|