openapi-dynamic-mcp 0.1.0
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/LICENSE +21 -0
- package/README.md +247 -0
- package/dist/auth/env.d.ts +14 -0
- package/dist/auth/env.js +64 -0
- package/dist/auth/env.js.map +1 -0
- package/dist/auth/oauthClient.d.ts +12 -0
- package/dist/auth/oauthClient.js +55 -0
- package/dist/auth/oauthClient.js.map +1 -0
- package/dist/auth/resolveAuth.d.ts +10 -0
- package/dist/auth/resolveAuth.js +118 -0
- package/dist/auth/resolveAuth.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +50 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/loadConfig.d.ts +2 -0
- package/dist/config/loadConfig.js +89 -0
- package/dist/config/loadConfig.js.map +1 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.js +31 -0
- package/dist/errors.js.map +1 -0
- package/dist/http/requestExecutor.d.ts +19 -0
- package/dist/http/requestExecutor.js +342 -0
- package/dist/http/requestExecutor.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/context.d.ts +7 -0
- package/dist/mcp/context.js +2 -0
- package/dist/mcp/context.js.map +1 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.js +125 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/common.d.ts +16 -0
- package/dist/mcp/tools/common.js +60 -0
- package/dist/mcp/tools/common.js.map +1 -0
- package/dist/mcp/tools/getApiEndpoint.d.ts +3 -0
- package/dist/mcp/tools/getApiEndpoint.js +59 -0
- package/dist/mcp/tools/getApiEndpoint.js.map +1 -0
- package/dist/mcp/tools/getApiSchema.d.ts +3 -0
- package/dist/mcp/tools/getApiSchema.js +27 -0
- package/dist/mcp/tools/getApiSchema.js.map +1 -0
- package/dist/mcp/tools/index.d.ts +5 -0
- package/dist/mcp/tools/index.js +6 -0
- package/dist/mcp/tools/index.js.map +1 -0
- package/dist/mcp/tools/listApiEndpoints.d.ts +3 -0
- package/dist/mcp/tools/listApiEndpoints.js +55 -0
- package/dist/mcp/tools/listApiEndpoints.js.map +1 -0
- package/dist/mcp/tools/listApis.d.ts +3 -0
- package/dist/mcp/tools/listApis.js +18 -0
- package/dist/mcp/tools/listApis.js.map +1 -0
- package/dist/mcp/tools/makeEndpointRequest.d.ts +3 -0
- package/dist/mcp/tools/makeEndpointRequest.js +63 -0
- package/dist/mcp/tools/makeEndpointRequest.js.map +1 -0
- package/dist/openapi/endpointIndex.d.ts +6 -0
- package/dist/openapi/endpointIndex.js +75 -0
- package/dist/openapi/endpointIndex.js.map +1 -0
- package/dist/openapi/jsonPointer.d.ts +1 -0
- package/dist/openapi/jsonPointer.js +40 -0
- package/dist/openapi/jsonPointer.js.map +1 -0
- package/dist/openapi/loadSpec.d.ts +2 -0
- package/dist/openapi/loadSpec.js +67 -0
- package/dist/openapi/loadSpec.js.map +1 -0
- package/dist/types.d.ts +98 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andrey Starostin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# openapi-dynamic-mcp
|
|
2
|
+
|
|
3
|
+
TypeScript MCP stdio server that loads one or more OpenAPI 3.x specs from YAML config and exposes generic tools for API discovery and request execution.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
- Runs as a single MCP stdio server for multiple APIs.
|
|
8
|
+
- Supports OpenAPI `3.x` local spec files.
|
|
9
|
+
- Exposes generic MCP tools:
|
|
10
|
+
- `list_apis`
|
|
11
|
+
- `list_api_endpoints`
|
|
12
|
+
- `get_api_endpoint`
|
|
13
|
+
- `get_api_schema`
|
|
14
|
+
- `make_endpoint_request`
|
|
15
|
+
- Supports auth:
|
|
16
|
+
- `apiKey`
|
|
17
|
+
- OAuth2 client credentials (`oauth2`)
|
|
18
|
+
- combined OpenAPI security requirements (AND inside object, OR across array)
|
|
19
|
+
- Supports per-API environment overrides for:
|
|
20
|
+
- base URL
|
|
21
|
+
- extra headers
|
|
22
|
+
|
|
23
|
+
## Requirements
|
|
24
|
+
|
|
25
|
+
- Node.js `20+`
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx openapi-dynamic-mcp --config ./examples/config.yaml
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Client Configuration
|
|
34
|
+
|
|
35
|
+
### Claude Code
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"mcpServers": {
|
|
40
|
+
"openapi": {
|
|
41
|
+
"command": "npx",
|
|
42
|
+
"args": [
|
|
43
|
+
"-y",
|
|
44
|
+
"openapi-dynamic-mcp@latest",
|
|
45
|
+
"--config",
|
|
46
|
+
"/absolute/path/to/config.yaml"
|
|
47
|
+
],
|
|
48
|
+
"env": {
|
|
49
|
+
"PET_API_BASE_URL": "http://localhost:3000",
|
|
50
|
+
"PET_API_APIKEY_API_KEY": "secret",
|
|
51
|
+
"PET_API_OAUTH2_CLIENT_ID": "client_id",
|
|
52
|
+
"PET_API_OAUTH2_CLIENT_SECRET": "client_secret"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Cursor
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"mcpServers": {
|
|
64
|
+
"openapi": {
|
|
65
|
+
"command": "npx",
|
|
66
|
+
"args": [
|
|
67
|
+
"-y",
|
|
68
|
+
"openapi-dynamic-mcp@latest",
|
|
69
|
+
"--config",
|
|
70
|
+
"/absolute/path/to/config.yaml"
|
|
71
|
+
],
|
|
72
|
+
"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"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Configuration
|
|
84
|
+
|
|
85
|
+
```yaml
|
|
86
|
+
# Config file version
|
|
87
|
+
version: 1
|
|
88
|
+
apis:
|
|
89
|
+
# Unique ID for this API
|
|
90
|
+
- name: pet-api
|
|
91
|
+
# Path to local OpenAPI spec
|
|
92
|
+
specPath: ./pet-api.yaml
|
|
93
|
+
# Base URL override
|
|
94
|
+
baseUrl: https://api.example.com/v1
|
|
95
|
+
# Request timeout in milliseconds
|
|
96
|
+
timeoutMs: 30000
|
|
97
|
+
headers:
|
|
98
|
+
# Custom headers for all requests
|
|
99
|
+
X-Client: openapi-dynamic-mcp
|
|
100
|
+
# Configuration for exponential retries on 429 Too Many Requests responses
|
|
101
|
+
retry429:
|
|
102
|
+
# Maximum number of retries
|
|
103
|
+
maxRetries: 2
|
|
104
|
+
# Initial retry delay in milliseconds
|
|
105
|
+
baseDelayMs: 250
|
|
106
|
+
# Maximum retry delay in milliseconds
|
|
107
|
+
maxDelayMs: 5000
|
|
108
|
+
# Jitter factor (0-1)
|
|
109
|
+
jitterRatio: 0.2
|
|
110
|
+
# Respect Retry-After header
|
|
111
|
+
respectRetryAfter: true
|
|
112
|
+
# OAuth2 client credentials configuration
|
|
113
|
+
oauth2:
|
|
114
|
+
# Optional token URL override
|
|
115
|
+
tokenUrlOverride: https://auth.example.com/oauth2/token
|
|
116
|
+
# Scopes to request
|
|
117
|
+
scopes: [read:pets, write:pets]
|
|
118
|
+
# How to pass Client Credentials to the token endpoint:
|
|
119
|
+
# Via HTTP Basic Authorization header: "client_secret_basic"
|
|
120
|
+
# Via POST body: "client_secret_post"
|
|
121
|
+
tokenEndpointAuthMethod: client_secret_basic
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Validation Rules
|
|
125
|
+
|
|
126
|
+
- `apis[].name` must be unique (case-insensitive after normalization).
|
|
127
|
+
- `apis[].specPath` must point to a readable local file.
|
|
128
|
+
- OpenAPI version must be `3.x`.
|
|
129
|
+
- Base URL resolution order: env -> config -> `openapi.servers[0].url`.
|
|
130
|
+
|
|
131
|
+
## Environment Variables
|
|
132
|
+
|
|
133
|
+
Environment variables allow specifying sensitive or environment-specific configuration for APIs. Variables are defined for each API separately.
|
|
134
|
+
|
|
135
|
+
### Name Normalization
|
|
136
|
+
|
|
137
|
+
API and auth scheme names are normalized as:
|
|
138
|
+
|
|
139
|
+
- uppercase
|
|
140
|
+
- non-alphanumeric -> `_`
|
|
141
|
+
- repeated `_` collapsed
|
|
142
|
+
- leading/trailing `_` removed
|
|
143
|
+
|
|
144
|
+
Examples:
|
|
145
|
+
|
|
146
|
+
- `pet-api` -> `PET_API`
|
|
147
|
+
- `OAuth2` -> `OAUTH2`
|
|
148
|
+
|
|
149
|
+
### API-Level Variables
|
|
150
|
+
|
|
151
|
+
- `<API>_BASE_URL` - Overrides the API's base URL.
|
|
152
|
+
- `<API>_HEADERS` (JSON object string) - Adds custom headers to all requests.
|
|
153
|
+
|
|
154
|
+
### API Key Variables
|
|
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.
|
|
159
|
+
|
|
160
|
+
### OAuth2 Client Credentials Variables
|
|
161
|
+
|
|
162
|
+
For each OAuth2 client credentials security scheme defined in the OpenAPI spec, the following environment variables can be set:
|
|
163
|
+
|
|
164
|
+
- `<API>_<SCHEME>_CLIENT_ID` - The client ID for OAuth2.
|
|
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.
|
|
169
|
+
|
|
170
|
+
### Precedence
|
|
171
|
+
|
|
172
|
+
- Base URL: env > config > OpenAPI servers.
|
|
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.
|
|
176
|
+
|
|
177
|
+
## MCP Tools
|
|
178
|
+
|
|
179
|
+
### `list_apis`
|
|
180
|
+
|
|
181
|
+
Returns all available APIs.
|
|
182
|
+
|
|
183
|
+
Input: Nothing
|
|
184
|
+
|
|
185
|
+
### `list_api_endpoints`
|
|
186
|
+
|
|
187
|
+
Paginate or search through endpoints in a given APIs.
|
|
188
|
+
|
|
189
|
+
Input fields:
|
|
190
|
+
|
|
191
|
+
- required: `apiName`
|
|
192
|
+
- optional: `method`, `tag`, `pathContains`, `search`, `limit`, `cursor`
|
|
193
|
+
|
|
194
|
+
### `get_api_endpoint`
|
|
195
|
+
|
|
196
|
+
Returns endpoint metadata including parameters, request body content types, responses, and security requirements.
|
|
197
|
+
|
|
198
|
+
Input fields: `apiName`, `endpointId`
|
|
199
|
+
|
|
200
|
+
### `get_api_schema`
|
|
201
|
+
|
|
202
|
+
Returns the detailed specification of a given schema object.
|
|
203
|
+
|
|
204
|
+
Input fields: `apiName`, optional `pointer` (JSON Pointer)
|
|
205
|
+
|
|
206
|
+
### `make_endpoint_request`
|
|
207
|
+
|
|
208
|
+
Executes an API endpoint request.
|
|
209
|
+
|
|
210
|
+
Input fields:
|
|
211
|
+
|
|
212
|
+
- `apiName`
|
|
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`
|
|
228
|
+
|
|
229
|
+
Output includes:
|
|
230
|
+
|
|
231
|
+
- `request` metadata with redacted sensitive headers
|
|
232
|
+
- `response` status, headers, and body
|
|
233
|
+
- `timingMs`
|
|
234
|
+
- `authUsed`
|
|
235
|
+
|
|
236
|
+
## Development
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
npm test
|
|
240
|
+
npm run build
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Notes
|
|
244
|
+
|
|
245
|
+
- Supports local OpenAPI files only.
|
|
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.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface OAuthClientCredentialsFromEnv {
|
|
2
|
+
clientId?: string;
|
|
3
|
+
clientSecret?: string;
|
|
4
|
+
tokenUrl?: string;
|
|
5
|
+
scopes?: string[];
|
|
6
|
+
tokenAuthMethod?: "client_secret_basic" | "client_secret_post";
|
|
7
|
+
}
|
|
8
|
+
export declare function normalizeEnvSegment(value: string): string;
|
|
9
|
+
export declare function apiPrefix(apiName: string): string;
|
|
10
|
+
export declare function schemePrefix(apiName: string, schemeName: string): string;
|
|
11
|
+
export declare function readApiBaseUrl(apiName: string, env?: NodeJS.ProcessEnv): string | undefined;
|
|
12
|
+
export declare function readApiExtraHeaders(apiName: string, env?: NodeJS.ProcessEnv): Record<string, string>;
|
|
13
|
+
export declare function readApiKeyValue(apiName: string, schemeName: string, env?: NodeJS.ProcessEnv): string | undefined;
|
|
14
|
+
export declare function readOAuthClientCredentials(apiName: string, schemeName: string, env?: NodeJS.ProcessEnv): OAuthClientCredentialsFromEnv;
|
package/dist/auth/env.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { OpenApiMcpError } from "../errors.js";
|
|
2
|
+
export function normalizeEnvSegment(value) {
|
|
3
|
+
return value
|
|
4
|
+
.toUpperCase()
|
|
5
|
+
.replace(/[^A-Z0-9]+/g, "_")
|
|
6
|
+
.replace(/_+/g, "_")
|
|
7
|
+
.replace(/^_+|_+$/g, "");
|
|
8
|
+
}
|
|
9
|
+
export function apiPrefix(apiName) {
|
|
10
|
+
return normalizeEnvSegment(apiName);
|
|
11
|
+
}
|
|
12
|
+
export function schemePrefix(apiName, schemeName) {
|
|
13
|
+
return `${apiPrefix(apiName)}_${normalizeEnvSegment(schemeName)}`;
|
|
14
|
+
}
|
|
15
|
+
export function readApiBaseUrl(apiName, env = process.env) {
|
|
16
|
+
return env[`${apiPrefix(apiName)}_BASE_URL`];
|
|
17
|
+
}
|
|
18
|
+
export function readApiExtraHeaders(apiName, env = process.env) {
|
|
19
|
+
const raw = env[`${apiPrefix(apiName)}_HEADERS`];
|
|
20
|
+
if (!raw) {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
let parsed;
|
|
24
|
+
try {
|
|
25
|
+
parsed = JSON.parse(raw);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
throw new OpenApiMcpError("CONFIG_ERROR", `Invalid JSON in ${apiPrefix(apiName)}_HEADERS`, { value: raw });
|
|
29
|
+
}
|
|
30
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
31
|
+
throw new OpenApiMcpError("CONFIG_ERROR", `${apiPrefix(apiName)}_HEADERS must be a JSON object`);
|
|
32
|
+
}
|
|
33
|
+
const out = {};
|
|
34
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
35
|
+
if (typeof value !== "string") {
|
|
36
|
+
throw new OpenApiMcpError("CONFIG_ERROR", `${apiPrefix(apiName)}_HEADERS values must be strings`, { key });
|
|
37
|
+
}
|
|
38
|
+
out[key] = value;
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
export function readApiKeyValue(apiName, schemeName, env = process.env) {
|
|
43
|
+
return env[`${schemePrefix(apiName, schemeName)}_API_KEY`];
|
|
44
|
+
}
|
|
45
|
+
export function readOAuthClientCredentials(apiName, schemeName, env = process.env) {
|
|
46
|
+
const prefix = schemePrefix(apiName, schemeName);
|
|
47
|
+
const scopesRaw = env[`${prefix}_SCOPES`];
|
|
48
|
+
const tokenAuthMethodRaw = env[`${prefix}_TOKEN_AUTH_METHOD`];
|
|
49
|
+
let tokenAuthMethod;
|
|
50
|
+
if (tokenAuthMethodRaw === "client_secret_basic" || tokenAuthMethodRaw === "client_secret_post") {
|
|
51
|
+
tokenAuthMethod = tokenAuthMethodRaw;
|
|
52
|
+
}
|
|
53
|
+
else if (tokenAuthMethodRaw) {
|
|
54
|
+
throw new OpenApiMcpError("CONFIG_ERROR", `Invalid ${prefix}_TOKEN_AUTH_METHOD value`, { value: tokenAuthMethodRaw });
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
clientId: env[`${prefix}_CLIENT_ID`],
|
|
58
|
+
clientSecret: env[`${prefix}_CLIENT_SECRET`],
|
|
59
|
+
tokenUrl: env[`${prefix}_TOKEN_URL`],
|
|
60
|
+
scopes: scopesRaw ? scopesRaw.split(/\s+/).filter(Boolean) : undefined,
|
|
61
|
+
tokenAuthMethod
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=env.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/auth/env.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface OAuthTokenRequest {
|
|
2
|
+
cacheKey: string;
|
|
3
|
+
tokenUrl: string;
|
|
4
|
+
clientId: string;
|
|
5
|
+
clientSecret: string;
|
|
6
|
+
scopes: string[];
|
|
7
|
+
tokenEndpointAuthMethod: "client_secret_basic" | "client_secret_post";
|
|
8
|
+
}
|
|
9
|
+
export declare class OAuthClient {
|
|
10
|
+
private readonly cache;
|
|
11
|
+
getClientCredentialsToken(request: OAuthTokenRequest): Promise<string>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as oauth from "oauth4webapi";
|
|
2
|
+
import { OpenApiMcpError } from "../errors.js";
|
|
3
|
+
const TOKEN_EXPIRY_SAFETY_MS = 60_000;
|
|
4
|
+
export class OAuthClient {
|
|
5
|
+
cache = new Map();
|
|
6
|
+
async getClientCredentialsToken(request) {
|
|
7
|
+
const cached = this.cache.get(request.cacheKey);
|
|
8
|
+
if (cached && cached.expiresAtMs - Date.now() > TOKEN_EXPIRY_SAFETY_MS) {
|
|
9
|
+
return cached.accessToken;
|
|
10
|
+
}
|
|
11
|
+
const as = {
|
|
12
|
+
issuer: new URL(request.tokenUrl).origin,
|
|
13
|
+
token_endpoint: request.tokenUrl
|
|
14
|
+
};
|
|
15
|
+
const client = {
|
|
16
|
+
client_id: request.clientId
|
|
17
|
+
};
|
|
18
|
+
const clientAuth = request.tokenEndpointAuthMethod === "client_secret_post"
|
|
19
|
+
? oauth.ClientSecretPost(request.clientSecret)
|
|
20
|
+
: oauth.ClientSecretBasic(request.clientSecret);
|
|
21
|
+
const parameters = new URLSearchParams();
|
|
22
|
+
if (request.scopes.length > 0) {
|
|
23
|
+
parameters.set("scope", request.scopes.join(" "));
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const tokenResponse = await oauth.clientCredentialsGrantRequest(as, client, clientAuth, parameters);
|
|
27
|
+
const tokenResult = await oauth.processClientCredentialsResponse(as, client, tokenResponse);
|
|
28
|
+
this.cache.set(request.cacheKey, {
|
|
29
|
+
accessToken: tokenResult.access_token,
|
|
30
|
+
expiresAtMs: Date.now() + Math.max(tokenResult.expires_in ?? 3600, 1) * 1000
|
|
31
|
+
});
|
|
32
|
+
return tokenResult.access_token;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error instanceof oauth.ResponseBodyError) {
|
|
36
|
+
throw new OpenApiMcpError("AUTH_ERROR", "OAuth2 token request failed", {
|
|
37
|
+
tokenUrl: request.tokenUrl,
|
|
38
|
+
oauthError: error.cause
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (error instanceof oauth.OperationProcessingError) {
|
|
42
|
+
throw new OpenApiMcpError("AUTH_ERROR", "OAuth2 operation failed", {
|
|
43
|
+
tokenUrl: request.tokenUrl,
|
|
44
|
+
code: error.code,
|
|
45
|
+
cause: error.message
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
throw new OpenApiMcpError("AUTH_ERROR", "OAuth2 token request failed", {
|
|
49
|
+
tokenUrl: request.tokenUrl,
|
|
50
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=oauthClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauthClient.js","sourceRoot":"","sources":["../../src/auth/oauthClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { OAuthClient } from "./oauthClient.js";
|
|
2
|
+
import type { EndpointDefinition, LoadedApi, ResolvedAuthResult } from "../types.js";
|
|
3
|
+
interface ResolveAuthInput {
|
|
4
|
+
api: LoadedApi;
|
|
5
|
+
endpoint: EndpointDefinition;
|
|
6
|
+
oauthClient: OAuthClient;
|
|
7
|
+
env?: NodeJS.ProcessEnv;
|
|
8
|
+
}
|
|
9
|
+
export declare function resolveAuth({ api, endpoint, oauthClient, env }: ResolveAuthInput): Promise<ResolvedAuthResult>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { readApiKeyValue, readOAuthClientCredentials, schemePrefix } from "./env.js";
|
|
2
|
+
import { OpenApiMcpError } from "../errors.js";
|
|
3
|
+
export async function resolveAuth({ api, endpoint, oauthClient, env = process.env }) {
|
|
4
|
+
const requirements = endpoint.operation.security ?? api.schema.security ?? [];
|
|
5
|
+
if (!requirements || requirements.length === 0) {
|
|
6
|
+
return { authUsed: [], schemes: [] };
|
|
7
|
+
}
|
|
8
|
+
const securitySchemes = api.schema.components?.securitySchemes ?? {};
|
|
9
|
+
const failures = [];
|
|
10
|
+
for (const requirementObject of requirements) {
|
|
11
|
+
if (Object.keys(requirementObject).length === 0) {
|
|
12
|
+
return { authUsed: [], schemes: [] };
|
|
13
|
+
}
|
|
14
|
+
const resolved = [];
|
|
15
|
+
const missingEnv = new Set();
|
|
16
|
+
let failedReason;
|
|
17
|
+
for (const [schemeName, requestedScopes] of Object.entries(requirementObject)) {
|
|
18
|
+
const scheme = securitySchemes[schemeName];
|
|
19
|
+
if (!scheme || "$ref" in scheme) {
|
|
20
|
+
failedReason = `Security scheme '${schemeName}' not found or unresolved`;
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
if (scheme.type === "apiKey") {
|
|
24
|
+
const value = readApiKeyValue(api.config.name, schemeName, env);
|
|
25
|
+
if (!value) {
|
|
26
|
+
missingEnv.add(`${schemePrefix(api.config.name, schemeName)}_API_KEY`);
|
|
27
|
+
failedReason = `Missing API key for scheme '${schemeName}'`;
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
resolved.push({
|
|
31
|
+
type: "apiKey",
|
|
32
|
+
schemeName,
|
|
33
|
+
in: scheme.in,
|
|
34
|
+
name: scheme.name,
|
|
35
|
+
value
|
|
36
|
+
});
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (scheme.type === "oauth2") {
|
|
40
|
+
const flow = scheme.flows.clientCredentials;
|
|
41
|
+
if (!flow) {
|
|
42
|
+
failedReason = `Scheme '${schemeName}' does not support clientCredentials flow`;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
const fromEnv = readOAuthClientCredentials(api.config.name, schemeName, env);
|
|
46
|
+
const clientId = fromEnv.clientId;
|
|
47
|
+
const clientSecret = fromEnv.clientSecret;
|
|
48
|
+
if (!clientId || !clientSecret) {
|
|
49
|
+
missingEnv.add(`${schemePrefix(api.config.name, schemeName)}_CLIENT_ID`);
|
|
50
|
+
missingEnv.add(`${schemePrefix(api.config.name, schemeName)}_CLIENT_SECRET`);
|
|
51
|
+
failedReason = `Missing OAuth2 client credentials for '${schemeName}'`;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
const tokenUrl = fromEnv.tokenUrl ?? api.config.oauth2?.tokenUrlOverride ?? flow.tokenUrl;
|
|
55
|
+
if (!tokenUrl) {
|
|
56
|
+
failedReason = `No OAuth2 token URL resolved for scheme '${schemeName}'`;
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
const tokenEndpointAuthMethod = fromEnv.tokenAuthMethod ??
|
|
60
|
+
api.config.oauth2?.tokenEndpointAuthMethod ??
|
|
61
|
+
"client_secret_basic";
|
|
62
|
+
const scopes = resolveScopes(requestedScopes, fromEnv.scopes, api.config.oauth2?.scopes, flow);
|
|
63
|
+
const cacheKey = [
|
|
64
|
+
api.config.name,
|
|
65
|
+
schemeName,
|
|
66
|
+
clientId,
|
|
67
|
+
tokenUrl,
|
|
68
|
+
tokenEndpointAuthMethod,
|
|
69
|
+
scopes.sort().join(",")
|
|
70
|
+
].join("|");
|
|
71
|
+
const token = await oauthClient.getClientCredentialsToken({
|
|
72
|
+
cacheKey,
|
|
73
|
+
tokenUrl,
|
|
74
|
+
clientId,
|
|
75
|
+
clientSecret,
|
|
76
|
+
scopes,
|
|
77
|
+
tokenEndpointAuthMethod
|
|
78
|
+
});
|
|
79
|
+
resolved.push({
|
|
80
|
+
type: "oauth2",
|
|
81
|
+
schemeName,
|
|
82
|
+
token
|
|
83
|
+
});
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
failedReason = `Unsupported security scheme type '${scheme.type}' for '${schemeName}'`;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
if (!failedReason) {
|
|
90
|
+
return {
|
|
91
|
+
authUsed: resolved.map((item) => item.schemeName),
|
|
92
|
+
schemes: resolved
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
failures.push({
|
|
96
|
+
requirement: Object.keys(requirementObject),
|
|
97
|
+
reason: failedReason,
|
|
98
|
+
missingEnv: [...missingEnv]
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
throw new OpenApiMcpError("AUTH_ERROR", `Could not resolve authentication for '${api.config.name}'`, {
|
|
102
|
+
endpointId: endpoint.endpointId,
|
|
103
|
+
failures
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function resolveScopes(requestedScopes, envScopes, configScopes, flow) {
|
|
107
|
+
if (envScopes && envScopes.length > 0) {
|
|
108
|
+
return envScopes;
|
|
109
|
+
}
|
|
110
|
+
if (configScopes && configScopes.length > 0) {
|
|
111
|
+
return configScopes;
|
|
112
|
+
}
|
|
113
|
+
if (requestedScopes && requestedScopes.length > 0) {
|
|
114
|
+
return requestedScopes;
|
|
115
|
+
}
|
|
116
|
+
return Object.keys(flow.scopes ?? {});
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=resolveAuth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolveAuth.js","sourceRoot":"","sources":["../../src/auth/resolveAuth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,0BAA0B,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAErF,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAgB/C,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,EAChC,GAAG,EACH,QAAQ,EACR,WAAW,EACX,GAAG,GAAG,OAAO,CAAC,GAAG,EACA;IACjB,MAAM,YAAY,GAAG,QAAQ,CAAC,SAAS,CAAC,QAAQ,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC;IAC9E,IAAI,CAAC,YAAY,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/C,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACvC,CAAC;IAED,MAAM,eAAe,GAAG,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,eAAe,IAAI,EAAE,CAAC;IACrE,MAAM,QAAQ,GAAyB,EAAE,CAAC;IAE1C,KAAK,MAAM,iBAAiB,IAAI,YAAY,EAAE,CAAC;QAC7C,IAAI,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAChD,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QACvC,CAAC;QAED,MAAM,QAAQ,GAAyB,EAAE,CAAC;QAC1C,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;QACrC,IAAI,YAAgC,CAAC;QAErC,KAAK,MAAM,CAAC,UAAU,EAAE,eAAe,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,EAAE,CAAC;YAC9E,MAAM,MAAM,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;YAC3C,IAAI,CAAC,MAAM,IAAI,MAAM,IAAI,MAAM,EAAE,CAAC;gBAChC,YAAY,GAAG,oBAAoB,UAAU,2BAA2B,CAAC;gBACzE,MAAM;YACR,CAAC;YAED,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC7B,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;gBAChE,IAAI,CAAC,KAAK,EAAE,CAAC;oBACX,UAAU,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;oBACvE,YAAY,GAAG,+BAA+B,UAAU,GAAG,CAAC;oBAC5D,MAAM;gBACR,CAAC;gBAED,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,QAAQ;oBACd,UAAU;oBACV,EAAE,EAAE,MAAM,CAAC,EAAmC;oBAC9C,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,KAAK;iBACN,CAAC,CAAC;gBACH,SAAS;YACX,CAAC;YAED,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC;gBAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;oBACV,YAAY,GAAG,WAAW,UAAU,2CAA2C,CAAC;oBAChF,MAAM;gBACR,CAAC;gBAED,MAAM,OAAO,GAAG,0BAA0B,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;gBAC7E,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;gBAClC,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;gBAE1C,IAAI,CAAC,QAAQ,IAAI,CAAC,YAAY,EAAE,CAAC;oBAC/B,UAAU,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,YAAY,CAAC,CAAC;oBACzE,UAAU,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,gBAAgB,CAAC,CAAC;oBAC7E,YAAY,GAAG,0CAA0C,UAAU,GAAG,CAAC;oBACvE,MAAM;gBACR,CAAC;gBAED,MAAM,QAAQ,GACZ,OAAO,CAAC,QAAQ,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,IAAI,IAAI,CAAC,QAAQ,CAAC;gBAC3E,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACd,YAAY,GAAG,4CAA4C,UAAU,GAAG,CAAC;oBACzE,MAAM;gBACR,CAAC;gBACD,MAAM,uBAAuB,GAC3B,OAAO,CAAC,eAAe;oBACvB,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,uBAAuB;oBAC1C,qBAAqB,CAAC;gBAExB,MAAM,MAAM,GAAG,aAAa,CAAC,eAAe,EAAE,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;gBAC/F,MAAM,QAAQ,GAAG;oBACf,GAAG,CAAC,MAAM,CAAC,IAAI;oBACf,UAAU;oBACV,QAAQ;oBACR,QAAQ;oBACR,uBAAuB;oBACvB,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC;iBACxB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAEZ,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,yBAAyB,CAAC;oBACxD,QAAQ;oBACR,QAAQ;oBACR,QAAQ;oBACR,YAAY;oBACZ,MAAM;oBACN,uBAAuB;iBACxB,CAAC,CAAC;gBAEH,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,QAAQ;oBACd,UAAU;oBACV,KAAK;iBACN,CAAC,CAAC;gBACH,SAAS;YACX,CAAC;YAED,YAAY,GAAG,qCAAqC,MAAM,CAAC,IAAI,UAAU,UAAU,GAAG,CAAC;YACvF,MAAM;QACR,CAAC;QAED,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,OAAO;gBACL,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC;gBACjD,OAAO,EAAE,QAAQ;aAClB,CAAC;QACJ,CAAC;QAED,QAAQ,CAAC,IAAI,CAAC;YACZ,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC;YAC3C,MAAM,EAAE,YAAY;YACpB,UAAU,EAAE,CAAC,GAAG,UAAU,CAAC;SAC5B,CAAC,CAAC;IACL,CAAC;IAED,MAAM,IAAI,eAAe,CAAC,YAAY,EAAE,yCAAyC,GAAG,CAAC,MAAM,CAAC,IAAI,GAAG,EAAE;QACnG,UAAU,EAAE,QAAQ,CAAC,UAAU;QAC/B,QAAQ;KACT,CAAC,CAAC;AACL,CAAC;AAED,SAAS,aAAa,CACpB,eAAqC,EACrC,SAA+B,EAC/B,YAAkC,EAClC,IAAyC;IAEzC,IAAI,SAAS,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,YAAY,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5C,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,IAAI,eAAe,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClD,OAAO,eAAe,CAAC;IACzB,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;AACxC,CAAC"}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { loadConfig } from "./config/loadConfig.js";
|
|
3
|
+
import { OAuthClient } from "./auth/oauthClient.js";
|
|
4
|
+
import { OpenApiMcpError } from "./errors.js";
|
|
5
|
+
import { startMcpServer } from "./mcp/server.js";
|
|
6
|
+
import { loadApiRegistry } from "./openapi/loadSpec.js";
|
|
7
|
+
function parseConfigPath(argv) {
|
|
8
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
9
|
+
if (argv[i] === "--config") {
|
|
10
|
+
const value = argv[i + 1];
|
|
11
|
+
if (!value) {
|
|
12
|
+
throw new OpenApiMcpError("CONFIG_ERROR", "Missing value for --config");
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
throw new OpenApiMcpError("CONFIG_ERROR", "Missing required argument --config");
|
|
18
|
+
}
|
|
19
|
+
export async function runCli(argv = process.argv.slice(2)) {
|
|
20
|
+
const configPath = parseConfigPath(argv);
|
|
21
|
+
const config = await loadConfig(configPath);
|
|
22
|
+
const registry = await loadApiRegistry(config, process.env);
|
|
23
|
+
console.error(`[openapi-mcp] loaded ${registry.byName.size} API(s)`);
|
|
24
|
+
for (const api of registry.byName.values()) {
|
|
25
|
+
console.error(`[openapi-mcp] api=${api.config.name} endpoints=${api.endpoints.length} authSchemes=${api.authSchemeNames.join(",")}`);
|
|
26
|
+
}
|
|
27
|
+
await startMcpServer({
|
|
28
|
+
registry,
|
|
29
|
+
oauthClient: new OAuthClient(),
|
|
30
|
+
env: process.env
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
runCli().catch((error) => {
|
|
34
|
+
if (error instanceof OpenApiMcpError) {
|
|
35
|
+
console.error(JSON.stringify({
|
|
36
|
+
code: error.code,
|
|
37
|
+
message: error.message,
|
|
38
|
+
details: error.details
|
|
39
|
+
}, null, 2));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
if (error instanceof Error) {
|
|
43
|
+
console.error(error.stack ?? error.message);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
console.error(String(error));
|
|
47
|
+
}
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
|
50
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,SAAS,eAAe,CAAC,IAAc;IACrC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,UAAU,EAAE,CAAC;YAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC1B,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,IAAI,eAAe,CAAC,cAAc,EAAE,4BAA4B,CAAC,CAAC;YAC1E,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,MAAM,IAAI,eAAe,CAAC,cAAc,EAAE,oCAAoC,CAAC,CAAC;AAClF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IACvD,MAAM,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,UAAU,CAAC,CAAC;IAC5C,MAAM,QAAQ,GAAG,MAAM,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IAE5D,OAAO,CAAC,KAAK,CAAC,wBAAwB,QAAQ,CAAC,MAAM,CAAC,IAAI,SAAS,CAAC,CAAC;IACrE,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;QAC3C,OAAO,CAAC,KAAK,CACX,qBAAqB,GAAG,CAAC,MAAM,CAAC,IAAI,cAAc,GAAG,CAAC,SAAS,CAAC,MAAM,gBAAgB,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CACtH,CAAC;IACJ,CAAC;IAED,MAAM,cAAc,CAAC;QACnB,QAAQ;QACR,WAAW,EAAE,IAAI,WAAW,EAAE;QAC9B,GAAG,EAAE,OAAO,CAAC,GAAG;KACjB,CAAC,CAAC;AACL,CAAC;AAED,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAChC,IAAI,KAAK,YAAY,eAAe,EAAE,CAAC;QACrC,OAAO,CAAC,KAAK,CACX,IAAI,CAAC,SAAS,CACZ;YACE,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,OAAO,EAAE,KAAK,CAAC,OAAO;SACvB,EACD,IAAI,EACJ,CAAC,CACF,CACF,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IAC9C,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|