Compare commits

...

34 Commits

Author SHA1 Message Date
Harsha v
8ee9fc383d
Create mcpcat.md 2025-09-30 12:15:47 -07:00
Harsha v
f29c277860
Merge pull request #39 from harsha-iiiv/copilot/fix-33b9d782-1c7b-44d1-89a9-adbd77a32aa8
Upgrade package to version 3.2.0 and address code quality improvements based on review feedback
2025-08-24 22:03:57 -07:00
copilot-swe-agent[bot]
33220c1e82 Address coderabbitai review feedback: improve docs, error handling, and boolean normalization
Co-authored-by: harsha-iiiv <31560965+harsha-iiiv@users.noreply.github.com>
2025-08-25 04:59:26 +00:00
copilot-swe-agent[bot]
eda4505a63 Update CHANGELOG.md with corrected version details and proper categorization
Co-authored-by: harsha-iiiv <31560965+harsha-iiiv@users.noreply.github.com>
2025-08-25 04:41:52 +00:00
copilot-swe-agent[bot]
4bf66d9efd Upgrade package version to 3.2.0 and update CHANGELOG.md
Co-authored-by: harsha-iiiv <31560965+harsha-iiiv@users.noreply.github.com>
2025-08-24 23:14:41 +00:00
copilot-swe-agent[bot]
7a31e1f6e9 Initial plan 2025-08-24 23:09:33 +00:00
Harsha v
82ff2b726d
Merge pull request #37 from atomicpages/main
feat: allow folks to BYO OpenAPIV3.Document harsha-iiiv/openapi-mcp-generator#35
2025-08-24 16:04:56 -07:00
Harsha v
1c806b8dab
Merge pull request #38 from FabriBorgobello/feature/endpoint-filtering
feature: Add endpoint filtering
2025-08-24 16:04:01 -07:00
Fabricio Borgobello
c9015f395e
Update src/utils/helpers.ts
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-08-22 11:58:27 +02:00
Fabricio Borgobello
af1b664653
Adjust boolean validation 2025-08-22 11:57:58 +02:00
Fabricio Borgobello
1f001bb47a
Remove comment 2025-08-22 11:04:08 +02:00
Fabricio Borgobello
b1e29c22de
feature/endpoint-filtering 2025-08-22 11:00:13 +02:00
Dennis Thompson
e6352d13b6 chore: fix clerical error on docs 2025-08-17 21:48:46 -07:00
Dennis Thompson
26307f26ad
Update src/api.ts
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-08-17 21:48:06 -07:00
Dennis Thompson
b7bc67e444 fix: addresses harsha-iiiv/openapi-mcp-generator#35 2025-08-17 21:26:27 -07:00
Harsha v
47292c89cf
Merge pull request #30 from oneWalker/dev
feat: style and ci optimization
2025-06-19 22:00:22 +05:30
Brian,Kun Liu
2cab8aeada style: format the code with the prettier config 2025-06-19 11:31:27 +08:00
Brian,Kun Liu
ea89e0498e ci: add build and check on PR before merged 2025-06-19 11:31:07 +08:00
Brian,Kun Liu
34a1f6df12 perf: keep code with the same style and format command 2025-06-19 11:28:24 +08:00
Harsha v
e17ab1a3d0
Merge pull request #29 from oneWalker/dev
feat: remove the lowercase transfer to make toolName readable and versionNo from package.json
2025-06-18 15:30:57 +05:30
Brian,Kun Liu
0ca1310c56 perf: remove the lowercase transfer to make toolName readable 2025-06-16 16:12:00 +08:00
Brian,Kun Liu
aa388035ad fix: use the versionNo in package.json 2025-06-16 16:11:10 +08:00
Harsha v
c98d652933
Merge pull request #27 from oneWalker/dev
fix: cannot find the package after building and the problem during th…
2025-06-15 00:19:04 +05:30
Brian,Kun Liu
9c6fed9beb
Update the version serial No 2025-06-12 19:17:09 +08:00
Brian,Kun Liu
67e06c3c34
fix: cannot find the package after building and the problem during the building 2025-06-12 19:00:34 +08:00
Harsha v
df758359e8
Update issue templates 2025-06-09 14:51:07 +05:30
Harsha v
78c3d91ec2
Merge pull request #22 from harsha-iiiv/fix/schema-cycle-detection
feat: Enhance schema mapping with cycle protection
2025-06-08 23:49:38 +05:30
Harsha v
4f7417890f
Create LICENSE 2025-06-08 23:45:58 +05:30
harsha-iiiv
aaf8613216 chore: Update changelog and enhance schema mapping error handling 2025-06-08 23:35:23 +05:30
harsha-iiiv
4c84306814 chore: Bump version to 3.1.2 and update changelog 2025-06-08 23:29:13 +05:30
harsha-iiiv
e2fd79c2ec feat: Enhance schema mapping with cycle protection
- Added cycle detection to the mapOpenApiSchemaToJsonSchema function to prevent infinite recursion when processing schemas.
- Introduced a WeakSet to track visited schema objects, ensuring robust handling of circular references.
2025-06-08 23:10:43 +05:30
Harsha v
2c6c05988e
Update npm-publish.yml 2025-05-26 09:18:29 +05:30
Harsha v
c607d9c759
Merge pull request #18 from harsha-iiiv/fix/cli-path
chore: Update version to 3.1.1 and modify CLI entry point
2025-05-26 08:55:21 +05:30
harsha-iiiv
6141d2e8ae chore: Update version to 3.1.1 and modify CLI entry point
- Bumped package version to 3.1.1 in package.json to match the new release.
- Changed the CLI entry point to use the new bin/openapi-mcp-generator.js file.
- Updated build script to ensure the new CLI file has the correct permissions.
- Refactored index.ts to streamline argument parsing and error handling.
2025-05-26 08:43:41 +05:30
41 changed files with 9505 additions and 7791 deletions

View File

@ -1,9 +1,6 @@
{ {
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"extends": [ "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"plugins": ["@typescript-eslint"], "plugins": ["@typescript-eslint"],
"env": { "env": {
"node": true, "node": true,

40
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,40 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

7
.github/ISSUE_TEMPLATE/custom.md vendored Normal file
View File

@ -0,0 +1,7 @@
---
name: Custom issue template
about: Describe this issue template's purpose here.
title: ''
labels: ''
assignees: ''
---

View File

@ -0,0 +1,19 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

44
.github/PULL_REQUEST_TEMPLATE/mcpcat.md vendored Normal file
View File

@ -0,0 +1,44 @@
# Add optional MCPcat analytics scaffolding (`--with-mcpcat`) to generated MCP servers
> **TL;DR**: This PR adds an **opt-in** flag to scaffold privacy-safe analytics wiring for MCPcat in projects generated by `openapi-mcp-generator`.
## Summary
This PR introduces a `--with-mcpcat` CLI flag that scaffolds:
- A tiny analytics shim to emit initialize/tool-call events.
- A default **local redaction** helper to scrub sensitive data before export.
- Minimal config via environment variables.
No behavior changes unless the flag and env vars are set.
## Motivation
- Make freshly generated MCP servers **observable in minutes**.
- Encourage **privacy-by-default** analytics patterns.
- Reduce copy/paste wiring; standardize event shape (operationId, path, duration, status).
## Changes
### CLI
- `generate` accepts `--with-mcpcat` (default: off).
### Template files (added conditionally)
- `src/analytics/mcpcat.ts` lazy import + safe no-op if SDK absent.
- `src/analytics/redact.ts` OpenAPI-aware heuristics (e.g., `*token*`, `password`, `apiKey`, `authorization`, `email`).
- `src/analytics/config.ts` reads env:
- `MCPCAT_ENABLED=true|false` (default `false`)
- `MCPCAT_PROJECT_ID=<id>`
- `MCPCAT_ENDPOINT=<optional override>`
- `MCPCAT_SAMPLE_RATE=1.0` (01)
### Server wiring
- Hooks server `.initialize` and each tool invocation to record:
- `operationId`, HTTP `method`, `path`
- redacted `args`
- `outcome` (`ok`/`error`) + truncated error message
- `duration_ms`
### Docs
- Adds a “Enable analytics (MCPcat)” section to generated README with privacy notes and quickstart.
## Implementation Notes
- **Compile-time optional**: no imports unless flag is used.
- **Runtime safe**: try/catch around SDK import → graceful no-op if not installed.
- **Transport-agnostic**: compatible with stdio, SSE/web, and StreamableHTTP templates.
- **Edge-friendly**: avoids Node-only APIs in scaffolding to support edge runtimes (e.g., Workers).

30
.github/workflows/check.yml vendored Normal file
View File

@ -0,0 +1,30 @@
name: check
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
format-and-build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- name: Install dependencies
run: npm ci
- name: Format check
run: npm run format.check
- name: Build
run: npm run build

View File

@ -35,9 +35,8 @@ jobs:
- name: Check version change - name: Check version change
id: check_version id: check_version
run: | run: |
git fetch origin main
CURRENT_VERSION=$(node -p "require('./package.json').version") CURRENT_VERSION=$(node -p "require('./package.json').version")
PREV_VERSION=$(git show origin/main:package.json | grep '"version":' | sed -E 's/.*"version": *"([^"]+)".*/\1/') PREV_VERSION=$(git show HEAD^:package.json | grep '"version":' | sed -E 's/.*"version": *"([^"]+)".*/\1/')
if [ "$CURRENT_VERSION" != "$PREV_VERSION" ]; then if [ "$CURRENT_VERSION" != "$PREV_VERSION" ]; then
echo "Version changed from $PREV_VERSION to $CURRENT_VERSION" echo "Version changed from $PREV_VERSION to $CURRENT_VERSION"
echo "version_changed=true" >> $GITHUB_OUTPUT echo "version_changed=true" >> $GITHUB_OUTPUT

View File

@ -5,9 +5,64 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [3.2.0] - 2025-08-24
### Added
- Endpoint filtering using `x-mcp` OpenAPI extension to control which operations are exposed as MCP tools
- CLI option `--default-include` to change default behavior for endpoint inclusion
- Precedence rules for `x-mcp` extension (operation > path > root level)
- Enhanced programmatic API with `defaultInclude` option in `getToolsFromOpenApi`
### Changed
- Improved documentation with examples for endpoint filtering and OpenAPI extensions.
- Version bump to next minor release
- Updated package version to reflect accumulated features and improvements
## [3.1.4] - 2025-06-18
### Chores
- Updated the application version to 3.1.4 and ensured the CLI displays the version dynamically.
### Style
- Improved code formatting for better readability.
### Bug Fixes
- Tool names now retain their original casing during extraction.
## [3.1.3] - 2025-06-12
### Fixed
- Cannot find the package after building and the problem during the building.
## [3.1.2] - 2025-06-08
### Fixed
- Prevent stack overflow (RangeError: Maximum call stack size exceeded) when processing recursive or cyclic OpenAPI schemas (e.g., self-referencing objects).
- Added cycle detection to schema mapping, ensuring robust handling of recursive structures.
## [3.1.1] - 2025-05-26
### Added
- Introduced a new executable command-line script for easier usage in Unix-like environments.
### Changed
- Use new CLI entry point to use the new `bin/openapi-mcp-generator.js` file.
- Updated build script to ensure the new CLI file has the correct permissions.
- Refactored `index.ts` to streamline argument parsing and error handling.
## [3.1.0] - 2025-05-18 ## [3.1.0] - 2025-05-18
### Added ### Added
- Programmatic API to extract MCP tool definitions from OpenAPI specs - Programmatic API to extract MCP tool definitions from OpenAPI specs
- New exportable `getToolsFromOpenApi` function for direct integration in code - New exportable `getToolsFromOpenApi` function for direct integration in code
- Advanced filtering capabilities for programmatic tool extraction - Advanced filtering capabilities for programmatic tool extraction
@ -15,12 +70,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Updated README with programmatic API usage examples - Updated README with programmatic API usage examples
### Changed ### Changed
- Improved module structure with better exports - Improved module structure with better exports
- Enhanced detection of module execution context - Enhanced detection of module execution context
## [3.0.0] - 2025-04-26
### Added
- Streamable HTTP support for OpenAPI MCP generator, enabling efficient handling of large payloads and real-time data transfer.
- Major architectural refactor to support streaming responses and requests.
### Fixed
- Multiple bugs related to HTTP/HTTPS connection handling, stream closure, and error propagation in streaming scenarios.
- Fixed resource leak issues on server aborts and client disconnects during streaming.
### Changed
- Major version bump due to breaking changes in API and internal structures to support streaming.
- Updated documentation to reflect new streaming capabilities and usage instructions.
- Enhanced performance and robustness of HTTP/HTTPS transport layers.
## [2.0.0] - 2025-04-12 ## [2.0.0] - 2025-04-12
### Added ### Added
- Runtime argument validation using Zod - Runtime argument validation using Zod
- JSON Schema to Zod schema conversion - JSON Schema to Zod schema conversion
- Improved error handling and formatting - Improved error handling and formatting
@ -31,6 +106,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support for multiple content types - Support for multiple content types
### Changed ### Changed
- Simplified transport layer to only support stdio transport - Simplified transport layer to only support stdio transport
- Removed support for WebSocket and HTTP transports - Removed support for WebSocket and HTTP transports
- Updated to use @modelcontextprotocol/sdk v1.9.0 - Updated to use @modelcontextprotocol/sdk v1.9.0
@ -40,6 +116,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- More robust OpenAPI schema processing - More robust OpenAPI schema processing
### Fixed ### Fixed
- Path parameter resolution in URLs - Path parameter resolution in URLs
- Content-Type header handling - Content-Type header handling
- Response processing for different content types - Response processing for different content types
@ -49,6 +126,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [1.0.0] - Initial Release ## [1.0.0] - Initial Release
### Added ### Added
- Basic OpenAPI to MCP server generation - Basic OpenAPI to MCP server generation
- Support for GET, POST, PUT, DELETE methods - Support for GET, POST, PUT, DELETE methods
- Basic error handling - Basic error handling

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 harsha-iiiv
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -21,16 +21,19 @@ import { getToolsFromOpenApi } from 'openapi-mcp-generator';
This function extracts an array of tools from an OpenAPI specification. This function extracts an array of tools from an OpenAPI specification.
**Parameters:** **Parameters:**
- `specPathOrUrl`: Path to a local OpenAPI spec file or URL to a remote spec - `specPathOrUrl`: Path to a local OpenAPI spec file or URL to a remote spec
- `options`: (Optional) Configuration options - `options`: (Optional) Configuration options
**Options:** **Options:**
- `baseUrl`: Override the base URL in the OpenAPI spec - `baseUrl`: Override the base URL in the OpenAPI spec
- `dereference`: Whether to resolve $refs (default: false) - `dereference`: Whether to resolve $refs (default: false)
- `excludeOperationIds`: Array of operation IDs to exclude from the results - `excludeOperationIds`: Array of operation IDs to exclude from the results
- `filterFn`: Custom function to filter tools (receives tool, returns boolean) - `filterFn`: Custom function to filter tools (receives tool, returns boolean)
**Returns:** **Returns:**
- Promise that resolves to an array of McpToolDefinition objects - Promise that resolves to an array of McpToolDefinition objects
**Example:** **Example:**
@ -42,12 +45,15 @@ import { getToolsFromOpenApi } from 'openapi-mcp-generator';
const tools = await getToolsFromOpenApi('./petstore.json'); const tools = await getToolsFromOpenApi('./petstore.json');
// With options // With options
const filteredTools = await getToolsFromOpenApi('https://petstore3.swagger.io/api/v3/openapi.json', { const filteredTools = await getToolsFromOpenApi(
'https://petstore3.swagger.io/api/v3/openapi.json',
{
baseUrl: 'https://petstore3.swagger.io/api/v3', baseUrl: 'https://petstore3.swagger.io/api/v3',
dereference: true, dereference: true,
excludeOperationIds: ['addPet', 'updatePet'], excludeOperationIds: ['addPet', 'updatePet'],
filterFn: (tool) => tool.method.toLowerCase() === 'get' filterFn: (tool) => tool.method.toLowerCase() === 'get',
}); }
);
// Process the results // Process the results
for (const tool of filteredTools) { for (const tool of filteredTools) {
@ -58,6 +64,20 @@ for (const tool of filteredTools) {
} }
``` ```
you can also provide a `OpenAPIV3.Document` to the parser:
```typescript
import { parser } from '@readme/openapi-parser';
const api = await parser('https://petstore3.swagger.io/api/v3/openapi.json', {
dereference: {
circular: true,
},
});
const tools = await getToolsFromOpenApi(api);
```
## Tool Definition Structure ## Tool Definition Structure
Each tool definition (`McpToolDefinition`) has the following properties: Each tool definition (`McpToolDefinition`) has the following properties:
@ -105,7 +125,7 @@ interface McpToolDefinition {
```typescript ```typescript
const getTools = await getToolsFromOpenApi(specUrl, { const getTools = await getToolsFromOpenApi(specUrl, {
filterFn: (tool) => tool.method.toLowerCase() === 'get' filterFn: (tool) => tool.method.toLowerCase() === 'get',
}); });
``` ```
@ -113,7 +133,7 @@ const getTools = await getToolsFromOpenApi(specUrl, {
```typescript ```typescript
const secureTools = await getToolsFromOpenApi(specUrl, { const secureTools = await getToolsFromOpenApi(specUrl, {
filterFn: (tool) => tool.securityRequirements.length > 0 filterFn: (tool) => tool.securityRequirements.length > 0,
}); });
``` ```
@ -121,7 +141,7 @@ const secureTools = await getToolsFromOpenApi(specUrl, {
```typescript ```typescript
const userTools = await getToolsFromOpenApi(specUrl, { const userTools = await getToolsFromOpenApi(specUrl, {
filterFn: (tool) => tool.pathTemplate.includes('/user') filterFn: (tool) => tool.pathTemplate.includes('/user'),
}); });
``` ```
@ -130,8 +150,6 @@ const userTools = await getToolsFromOpenApi(specUrl, {
```typescript ```typescript
const safeUserTools = await getToolsFromOpenApi(specUrl, { const safeUserTools = await getToolsFromOpenApi(specUrl, {
excludeOperationIds: ['deleteUser', 'updateUser'], excludeOperationIds: ['deleteUser', 'updateUser'],
filterFn: (tool) => filterFn: (tool) => tool.pathTemplate.includes('/user') && tool.method.toLowerCase() === 'get',
tool.pathTemplate.includes('/user') &&
tool.method.toLowerCase() === 'get'
}); });
``` ```

View File

@ -49,7 +49,7 @@ openapi-mcp-generator --input path/to/openapi.json --output path/to/output/dir -
### CLI Options ### CLI Options
| Option | Alias | Description | Default | | Option | Alias | Description | Default |
|--------------------|-------|-----------------------------------------------------------------------------------------------------|---------------------------------| | ------------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- |
| `--input` | `-i` | Path or URL to OpenAPI specification (YAML or JSON) | **Required** | | `--input` | `-i` | Path or URL to OpenAPI specification (YAML or JSON) | **Required** |
| `--output` | `-o` | Directory to output the generated MCP project | **Required** | | `--output` | `-o` | Directory to output the generated MCP project | **Required** |
| `--server-name` | `-n` | Name of the MCP server (`package.json:name`) | OpenAPI title or `mcp-api-server` | | `--server-name` | `-n` | Name of the MCP server (`package.json:name`) | OpenAPI title or `mcp-api-server` |
@ -57,6 +57,7 @@ openapi-mcp-generator --input path/to/openapi.json --output path/to/output/dir -
| `--base-url` | `-b` | Base URL for API requests. Required if OpenAPI `servers` missing or ambiguous. | Auto-detected if possible | | `--base-url` | `-b` | Base URL for API requests. Required if OpenAPI `servers` missing or ambiguous. | Auto-detected if possible |
| `--transport` | `-t` | Transport mode: `"stdio"` (default), `"web"`, or `"streamable-http"` | `"stdio"` | | `--transport` | `-t` | Transport mode: `"stdio"` (default), `"web"`, or `"streamable-http"` | `"stdio"` |
| `--port` | `-p` | Port for web-based transports | `3000` | | `--port` | `-p` | Port for web-based transports | `3000` |
| `--default-include` | | Default behavior for x-mcp filtering. Accepts `true` or `false` (case-insensitive). `true` = include by default, `false` = exclude by default. | `true` |
| `--force` | | Overwrite existing files in the output directory without confirmation | `false` | | `--force` | | Overwrite existing files in the output directory without confirmation | `false` |
## 📦 Programmatic API ## 📦 Programmatic API
@ -74,7 +75,7 @@ const filteredTools = await getToolsFromOpenApi('https://example.com/api-spec.js
baseUrl: 'https://api.example.com', baseUrl: 'https://api.example.com',
dereference: true, dereference: true,
excludeOperationIds: ['deletePet'], excludeOperationIds: ['deletePet'],
filterFn: (tool) => tool.method.toLowerCase() === 'get' filterFn: (tool) => tool.method.toLowerCase() === 'get',
}); });
``` ```
@ -100,6 +101,7 @@ The generated project includes:
``` ```
Core dependencies: Core dependencies:
- `@modelcontextprotocol/sdk` - MCP protocol implementation - `@modelcontextprotocol/sdk` - MCP protocol implementation
- `axios` - HTTP client for API requests - `axios` - HTTP client for API requests
- `zod` - Runtime validation - `zod` - Runtime validation
@ -139,7 +141,7 @@ Implements the MCP StreamableHTTP transport which offers:
### Transport Comparison ### Transport Comparison
| Feature | stdio | web (SSE) | streamable-http | | Feature | stdio | web (SSE) | streamable-http |
|---------|-------|-----------|----------------| | ------------------ | ------------------- | ----------------- | ------------------ |
| Protocol | JSON-RPC over stdio | JSON-RPC over SSE | JSON-RPC over HTTP | | Protocol | JSON-RPC over stdio | JSON-RPC over SSE | JSON-RPC over HTTP |
| Connection | Persistent | Persistent | Request/response | | Connection | Persistent | Persistent | Request/response |
| Bidirectional | Yes | Yes | Yes (stateful) | | Bidirectional | Yes | Yes | Yes (stateful) |
@ -158,7 +160,7 @@ Implements the MCP StreamableHTTP transport which offers:
Configure auth credentials in your environment: Configure auth credentials in your environment:
| Auth Type | Variable Format | | Auth Type | Variable Format |
|-------------|----------------------------------------------------------| | ---------- | -------------------------------------------------------------------------------------------------- |
| API Key | `API_KEY_<SCHEME_NAME>` | | API Key | `API_KEY_<SCHEME_NAME>` |
| Bearer | `BEARER_TOKEN_<SCHEME_NAME>` | | Bearer | `BEARER_TOKEN_<SCHEME_NAME>` |
| Basic Auth | `BASIC_USERNAME_<SCHEME_NAME>`, `BASIC_PASSWORD_<SCHEME_NAME>` | | Basic Auth | `BASIC_USERNAME_<SCHEME_NAME>`, `BASIC_PASSWORD_<SCHEME_NAME>` |
@ -166,6 +168,38 @@ Configure auth credentials in your environment:
--- ---
## 🔎 Filtering Endpoints with OpenAPI Extensions
You can control which operations are exposed as MCP tools using a vendor extension flag `x-mcp`. This extension is supported at the root, path, and operation levels. By default, endpoints are included unless explicitly excluded.
- Extension: `x-mcp: true | false`
- Default: `true` (include by default)
- Precedence: operation > path > root (first non-undefined wins)
- CLI option: `--default-include false` to change default to exclude by default
Examples:
```yaml
# Optional root-level default
x-mcp: true
paths:
/pets:
x-mcp: false # exclude all ops under /pets
get:
x-mcp: true # include this operation anyway
/users/{id}:
get:
# no x-mcp -> included by default
```
This uses standard OpenAPI extensions (x-… fields). See the [OpenAPI Extensions guide](https://swagger.io/docs/specification/v3_0/openapi-extensions/) for details.
Note: `x-mcp` must be a boolean or the strings `"true"`/`"false"` (case-insensitive). Other values are ignored in favor of higher-precedence or default behavior.
---
## ▶️ Running the Generated Server ## ▶️ Running the Generated Server
```bash ```bash
@ -214,8 +248,9 @@ Contributions are welcome!
1. Fork the repo 1. Fork the repo
2. Create a feature branch: `git checkout -b feature/amazing-feature` 2. Create a feature branch: `git checkout -b feature/amazing-feature`
3. Commit your changes: `git commit -m "Add amazing feature"` 3. Run `npm run format.write` to format your code
4. Push and open a PR 4. Commit your changes: `git commit -m "Add amazing feature"`
5. Push and open a PR
📌 Repository: [github.com/harsha-iiiv/openapi-mcp-generator](https://github.com/harsha-iiiv/openapi-mcp-generator) 📌 Repository: [github.com/harsha-iiiv/openapi-mcp-generator](https://github.com/harsha-iiiv/openapi-mcp-generator)

6
bin/openapi-mcp-generator.js Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env node
import { program } from '../dist/index.js';
// Parse CLI arguments and run the program
program.parse(process.argv);

View File

@ -1,12 +1,7 @@
{ {
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"extends": [ "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"eslint:recommended", "plugins": ["@typescript-eslint"],
"plugin:@typescript-eslint/recommended"
],
"plugins": [
"@typescript-eslint"
],
"env": { "env": {
"node": true, "node": true,
"es2022": true "es2022": true
@ -15,10 +10,7 @@
"no-console": [ "no-console": [
"error", "error",
{ {
"allow": [ "allow": ["error", "warn"]
"error",
"warn"
]
} }
], ],
"@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/explicit-function-return-type": "off",

View File

@ -13,11 +13,13 @@ This API uses OAuth2 for authentication. The MCP server can handle OAuth2 authen
- `OAUTH_CLIENT_ID_PETSTORE_AUTH`: Your OAuth client ID - `OAUTH_CLIENT_ID_PETSTORE_AUTH`: Your OAuth client ID
- `OAUTH_CLIENT_SECRET_PETSTORE_AUTH`: Your OAuth client secret - `OAUTH_CLIENT_SECRET_PETSTORE_AUTH`: Your OAuth client secret
## Token Caching ## Token Caching
The MCP server automatically caches OAuth tokens obtained via client credentials flow. Tokens are cached for their lifetime (as specified by the `expires_in` parameter in the token response) minus 60 seconds as a safety margin. The MCP server automatically caches OAuth tokens obtained via client credentials flow. Tokens are cached for their lifetime (as specified by the `expires_in` parameter in the token response) minus 60 seconds as a safety margin.
When making API requests, the server will: When making API requests, the server will:
1. Check for a cached token that's still valid 1. Check for a cached token that's still valid
2. Use the cached token if available 2. Use the cached token if available
3. Request a new token if no valid cached token exists 3. Request a new token if no valid cached token exists

View File

@ -1,18 +1,26 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>swagger-petstore---openapi-3-0 MCP Test Client</title> <title>swagger-petstore---openapi-3-0 MCP Test Client</title>
<style> <style>
body { body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
sans-serif;
max-width: 800px; max-width: 800px;
margin: 0 auto; margin: 0 auto;
padding: 20px; padding: 20px;
line-height: 1.5; line-height: 1.5;
} }
h1 { margin-bottom: 10px; } h1 {
margin-bottom: 10px;
}
.container { .container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -39,13 +47,15 @@
} }
#sendButton { #sendButton {
padding: 8px 16px; padding: 8px 16px;
background-color: #4CAF50; background-color: #4caf50;
color: white; color: white;
border: none; border: none;
cursor: pointer; cursor: pointer;
border-radius: 0 5px 5px 0; border-radius: 0 5px 5px 0;
} }
#sendButton:hover { background-color: #45a049; } #sendButton:hover {
background-color: #45a049;
}
.message { .message {
margin-bottom: 10px; margin-bottom: 10px;
padding: 8px 12px; padding: 8px 12px;
@ -124,7 +134,7 @@
<div id="conversation"></div> <div id="conversation"></div>
<div class="input-area"> <div class="input-area">
<input type="text" id="userInput" placeholder="Type a message..." disabled> <input type="text" id="userInput" placeholder="Type a message..." disabled />
<button id="sendButton" disabled>Send</button> <button id="sendButton" disabled>Send</button>
</div> </div>
</div> </div>
@ -294,8 +304,8 @@
method: 'callTool', method: 'callTool',
params: { params: {
name: toolName, name: toolName,
arguments: parseArguments(text) arguments: parseArguments(text),
} },
}; };
log('REQUEST', JSON.stringify(requestBody)); log('REQUEST', JSON.stringify(requestBody));
@ -307,15 +317,18 @@
const response = await fetch(fullEndpoint, { const response = await fetch(fullEndpoint, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
}, },
body: JSON.stringify(requestBody) body: JSON.stringify(requestBody),
}); });
if (!response.ok) { if (!response.ok) {
const errorText = await response.text(); const errorText = await response.text();
log('ERROR', `Error response: ${response.status} ${response.statusText} ${errorText}`); log('ERROR', `Error response: ${response.status} ${response.statusText} ${errorText}`);
appendMessage('system', `Error: ${response.status} ${response.statusText}\n${errorText}`); appendMessage(
'system',
`Error: ${response.status} ${response.statusText}\n${errorText}`
);
} else { } else {
log('INFO', `Request sent successfully`); log('INFO', `Request sent successfully`);
// Note: We don't handle the response content here because the response // Note: We don't handle the response content here because the response

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,3 @@
/** /**
* Web server setup for HTTP-based MCP communication using Hono * Web server setup for HTTP-based MCP communication using Hono
*/ */
@ -7,11 +6,11 @@ import { cors } from 'hono/cors';
import { serve } from '@hono/node-server'; import { serve } from '@hono/node-server';
import { streamSSE } from 'hono/streaming'; import { streamSSE } from 'hono/streaming';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { JSONRPCMessage, JSONRPCMessageSchema } from "@modelcontextprotocol/sdk/types.js"; import { JSONRPCMessage, JSONRPCMessageSchema } from '@modelcontextprotocol/sdk/types.js';
import type { Context } from 'hono'; import type { Context } from 'hono';
import type { SSEStreamingApi } from 'hono/streaming'; import type { SSEStreamingApi } from 'hono/streaming';
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
// Import server configuration constants // Import server configuration constants
import { SERVER_NAME, SERVER_VERSION } from './index.js'; import { SERVER_NAME, SERVER_VERSION } from './index.js';
@ -52,7 +51,7 @@ async start(): Promise<void> {
// Send the endpoint information // Send the endpoint information
await this.stream.writeSSE({ await this.stream.writeSSE({
event: 'endpoint', event: 'endpoint',
data: `${this.messageUrl}?sessionId=${this._sessionId}` data: `${this.messageUrl}?sessionId=${this._sessionId}`,
}); });
// Send session ID and connection info in a format the client can understand // Send session ID and connection info in a format the client can understand
@ -60,22 +59,22 @@ async start(): Promise<void> {
event: 'session', event: 'session',
data: JSON.stringify({ data: JSON.stringify({
type: 'session_id', type: 'session_id',
session_id: this._sessionId session_id: this._sessionId,
}) }),
}); });
// Send a welcome notification // Send a welcome notification
await this.send({ await this.send({
jsonrpc: "2.0", jsonrpc: '2.0',
method: "notification", method: 'notification',
params: { params: {
type: "welcome", type: 'welcome',
clientInfo: { clientInfo: {
sessionId: this._sessionId, sessionId: this._sessionId,
serverName: SERVER_NAME, serverName: SERVER_NAME,
serverVersion: SERVER_VERSION serverVersion: SERVER_VERSION,
} },
} },
}); });
} }
@ -132,7 +131,7 @@ async send(message: JSONRPCMessage): Promise<void> {
await this.stream.writeSSE({ await this.stream.writeSSE({
event: 'message', event: 'message',
data: JSON.stringify(message) data: JSON.stringify(message),
}); });
} }
} }
@ -160,7 +159,7 @@ app.get('/health', (c) => {
}); });
// SSE endpoint for clients to connect to // SSE endpoint for clients to connect to
app.get("/sse", (c) => { app.get('/sse', (c) => {
return streamSSE(c, async (stream) => { return streamSSE(c, async (stream) => {
// Create SSE transport // Create SSE transport
const transport = new SSETransport('/api/messages', stream); const transport = new SSETransport('/api/messages', stream);
@ -202,7 +201,7 @@ app.get("/sse", (c) => {
}); });
// API endpoint for clients to send messages // API endpoint for clients to send messages
app.post("/api/messages", async (c) => { app.post('/api/messages', async (c) => {
const sessionId = c.req.query('sessionId'); const sessionId = c.req.query('sessionId');
if (!sessionId) { if (!sessionId) {
@ -246,17 +245,31 @@ app.get('/*', async (c) => {
let contentType = 'text/plain'; let contentType = 'text/plain';
switch (ext) { switch (ext) {
case '.html': contentType = 'text/html'; break; case '.html':
case '.css': contentType = 'text/css'; break; contentType = 'text/html';
case '.js': contentType = 'text/javascript'; break; break;
case '.json': contentType = 'application/json'; break; case '.css':
case '.png': contentType = 'image/png'; break; contentType = 'text/css';
case '.jpg': contentType = 'image/jpeg'; break; break;
case '.svg': contentType = 'image/svg+xml'; break; case '.js':
contentType = 'text/javascript';
break;
case '.json':
contentType = 'application/json';
break;
case '.png':
contentType = 'image/png';
break;
case '.jpg':
contentType = 'image/jpeg';
break;
case '.svg':
contentType = 'image/svg+xml';
break;
} }
return new Response(content, { return new Response(content, {
headers: { 'Content-Type': contentType } headers: { 'Content-Type': contentType },
}); });
} }
} catch (err) { } catch (err) {
@ -272,15 +285,20 @@ app.get('/*', async (c) => {
}); });
// Start the server // Start the server
serve({ serve(
{
fetch: app.fetch, fetch: app.fetch,
port port,
}, (info) => { },
(info) => {
console.error(`MCP Web Server running at http://localhost:${info.port}`); console.error(`MCP Web Server running at http://localhost:${info.port}`);
console.error(`- SSE Endpoint: http://localhost:${info.port}/sse`); console.error(`- SSE Endpoint: http://localhost:${info.port}/sse`);
console.error(`- Messages Endpoint: http://localhost:${info.port}/api/messages?sessionId=YOUR_SESSION_ID`); console.error(
`- Messages Endpoint: http://localhost:${info.port}/api/messages?sessionId=YOUR_SESSION_ID`
);
console.error(`- Health Check: http://localhost:${info.port}/health`); console.error(`- Health Check: http://localhost:${info.port}/health`);
}); }
);
return app; return app;
} }

View File

@ -17,12 +17,6 @@
"sourceMap": true, "sourceMap": true,
"forceConsistentCasingInFileNames": true "forceConsistentCasingInFileNames": true
}, },
"include": [ "include": ["src/**/*"],
"src/**/*" "exclude": ["node_modules", "build", "**/*.test.ts"]
],
"exclude": [
"node_modules",
"build",
"**/*.test.ts"
]
} }

View File

@ -1,12 +1,7 @@
{ {
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"extends": [ "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"eslint:recommended", "plugins": ["@typescript-eslint"],
"plugin:@typescript-eslint/recommended"
],
"plugins": [
"@typescript-eslint"
],
"env": { "env": {
"node": true, "node": true,
"es2022": true "es2022": true
@ -15,10 +10,7 @@
"no-console": [ "no-console": [
"error", "error",
{ {
"allow": [ "allow": ["error", "warn"]
"error",
"warn"
]
} }
], ],
"@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/explicit-function-return-type": "off",

View File

@ -13,11 +13,13 @@ This API uses OAuth2 for authentication. The MCP server can handle OAuth2 authen
- `OAUTH_CLIENT_ID_PETSTORE_AUTH`: Your OAuth client ID - `OAUTH_CLIENT_ID_PETSTORE_AUTH`: Your OAuth client ID
- `OAUTH_CLIENT_SECRET_PETSTORE_AUTH`: Your OAuth client secret - `OAUTH_CLIENT_SECRET_PETSTORE_AUTH`: Your OAuth client secret
## Token Caching ## Token Caching
The MCP server automatically caches OAuth tokens obtained via client credentials flow. Tokens are cached for their lifetime (as specified by the `expires_in` parameter in the token response) minus 60 seconds as a safety margin. The MCP server automatically caches OAuth tokens obtained via client credentials flow. Tokens are cached for their lifetime (as specified by the `expires_in` parameter in the token response) minus 60 seconds as a safety margin.
When making API requests, the server will: When making API requests, the server will:
1. Check for a cached token that's still valid 1. Check for a cached token that's still valid
2. Use the cached token if available 2. Use the cached token if available
3. Request a new token if no valid cached token exists 3. Request a new token if no valid cached token exists

View File

@ -12,6 +12,8 @@
"scripts": { "scripts": {
"start": "node build/index.js", "start": "node build/index.js",
"build": "tsc && chmod 755 build/index.js", "build": "tsc && chmod 755 build/index.js",
"format.check": "prettier --check .",
"format.write": "prettier --write .",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"prestart": "npm run build", "prestart": "npm run build",
"start:http": "node build/index.js --transport=streamable-http" "start:http": "node build/index.js --transport=streamable-http"

View File

@ -1,18 +1,26 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>swagger-petstore---openapi-3-0 MCP StreamableHTTP Test Client</title> <title>swagger-petstore---openapi-3-0 MCP StreamableHTTP Test Client</title>
<style> <style>
body { body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
sans-serif;
max-width: 800px; max-width: 800px;
margin: 0 auto; margin: 0 auto;
padding: 20px; padding: 20px;
line-height: 1.5; line-height: 1.5;
} }
h1 { margin-bottom: 10px; } h1 {
margin-bottom: 10px;
}
.container { .container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -39,13 +47,15 @@
} }
#sendButton { #sendButton {
padding: 8px 16px; padding: 8px 16px;
background-color: #4CAF50; background-color: #4caf50;
color: white; color: white;
border: none; border: none;
cursor: pointer; cursor: pointer;
border-radius: 0 5px 5px 0; border-radius: 0 5px 5px 0;
} }
#sendButton:hover { background-color: #45a049; } #sendButton:hover {
background-color: #45a049;
}
.message { .message {
margin-bottom: 10px; margin-bottom: 10px;
padding: 8px 12px; padding: 8px 12px;
@ -124,7 +134,7 @@
<div id="conversation"></div> <div id="conversation"></div>
<div class="input-area"> <div class="input-area">
<input type="text" id="userInput" placeholder="Type a message..." disabled> <input type="text" id="userInput" placeholder="Type a message..." disabled />
<button id="sendButton" disabled>Send</button> <button id="sendButton" disabled>Send</button>
</div> </div>
</div> </div>
@ -191,8 +201,8 @@
params: { params: {
clientName: 'MCP StreamableHTTP Test Client', clientName: 'MCP StreamableHTTP Test Client',
clientVersion: '1.0.0', clientVersion: '1.0.0',
capabilities: {} capabilities: {},
} },
}; };
log('REQUEST', JSON.stringify(requestBody)); log('REQUEST', JSON.stringify(requestBody));
@ -200,15 +210,18 @@
const response = await fetch('/mcp', { const response = await fetch('/mcp', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
}, },
body: JSON.stringify(requestBody) body: JSON.stringify(requestBody),
}); });
if (!response.ok) { if (!response.ok) {
const errorText = await response.text(); const errorText = await response.text();
log('ERROR', `Error response: ${response.status} ${response.statusText} ${errorText}`); log('ERROR', `Error response: ${response.status} ${response.statusText} ${errorText}`);
appendMessage('system', `Error: ${response.status} ${response.statusText}\n${errorText}`); appendMessage(
'system',
`Error: ${response.status} ${response.statusText}\n${errorText}`
);
statusEl.textContent = 'Connection error. Try again.'; statusEl.textContent = 'Connection error. Try again.';
return; return;
} }
@ -254,7 +267,7 @@
jsonrpc: '2.0', jsonrpc: '2.0',
id: messageId++, id: messageId++,
method: 'listTools', method: 'listTools',
params: {} params: {},
}; };
log('REQUEST', JSON.stringify(requestBody)); log('REQUEST', JSON.stringify(requestBody));
@ -263,14 +276,17 @@
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'mcp-session-id': sessionId 'mcp-session-id': sessionId,
}, },
body: JSON.stringify(requestBody) body: JSON.stringify(requestBody),
}); });
if (!response.ok) { if (!response.ok) {
const errorText = await response.text(); const errorText = await response.text();
log('ERROR', `Error listing tools: ${response.status} ${response.statusText} ${errorText}`); log(
'ERROR',
`Error listing tools: ${response.status} ${response.statusText} ${errorText}`
);
return; return;
} }
@ -278,7 +294,10 @@
log('TOOLS', JSON.stringify(data)); log('TOOLS', JSON.stringify(data));
if (data.result?.tools && Array.isArray(data.result.tools)) { if (data.result?.tools && Array.isArray(data.result.tools)) {
appendMessage('system', `Available tools: ${data.result.tools.map(t => t.name).join(', ')}`); appendMessage(
'system',
`Available tools: ${data.result.tools.map((t) => t.name).join(', ')}`
);
} }
} catch (error) { } catch (error) {
log('ERROR', `Error listing tools: ${error.message}`); log('ERROR', `Error listing tools: ${error.message}`);
@ -305,8 +324,8 @@
method: 'callTool', method: 'callTool',
params: { params: {
name: toolName, name: toolName,
arguments: parseArguments(text) arguments: parseArguments(text),
} },
}; };
log('REQUEST', JSON.stringify(requestBody)); log('REQUEST', JSON.stringify(requestBody));
@ -315,15 +334,18 @@
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'mcp-session-id': sessionId 'mcp-session-id': sessionId,
}, },
body: JSON.stringify(requestBody) body: JSON.stringify(requestBody),
}); });
if (!response.ok) { if (!response.ok) {
const errorText = await response.text(); const errorText = await response.text();
log('ERROR', `Error response: ${response.status} ${response.statusText} ${errorText}`); log('ERROR', `Error response: ${response.status} ${response.statusText} ${errorText}`);
appendMessage('system', `Error: ${response.status} ${response.statusText}\n${errorText}`); appendMessage(
'system',
`Error: ${response.status} ${response.statusText}\n${errorText}`
);
return; return;
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,3 @@
/** /**
* StreamableHTTP server setup for HTTP-based MCP communication using Hono * StreamableHTTP server setup for HTTP-based MCP communication using Hono
*/ */
@ -6,17 +5,17 @@ import { Hono } from 'hono';
import { cors } from 'hono/cors'; import { cors } from 'hono/cors';
import { serve } from '@hono/node-server'; import { serve } from '@hono/node-server';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { InitializeRequestSchema, JSONRPCError } from "@modelcontextprotocol/sdk/types.js"; import { InitializeRequestSchema, JSONRPCError } from '@modelcontextprotocol/sdk/types.js';
import { toReqRes, toFetchResponse } from 'fetch-to-node'; import { toReqRes, toFetchResponse } from 'fetch-to-node';
// Import server configuration constants // Import server configuration constants
import { SERVER_NAME, SERVER_VERSION } from './index.js'; import { SERVER_NAME, SERVER_VERSION } from './index.js';
// Constants // Constants
const SESSION_ID_HEADER_NAME = "mcp-session-id"; const SESSION_ID_HEADER_NAME = 'mcp-session-id';
const JSON_RPC = "2.0"; const JSON_RPC = '2.0';
/** /**
* StreamableHTTP MCP Server handler * StreamableHTTP MCP Server handler
@ -34,9 +33,9 @@ class MCPStreamableHttpServer {
* Handle GET requests (typically used for static files) * Handle GET requests (typically used for static files)
*/ */
async handleGetRequest(c: any) { async handleGetRequest(c: any) {
console.error("GET request received - StreamableHTTP transport only supports POST"); console.error('GET request received - StreamableHTTP transport only supports POST');
return c.text('Method Not Allowed', 405, { return c.text('Method Not Allowed', 405, {
'Allow': 'POST' Allow: 'POST',
}); });
} }
@ -45,7 +44,9 @@ class MCPStreamableHttpServer {
*/ */
async handlePostRequest(c: any) { async handlePostRequest(c: any) {
const sessionId = c.req.header(SESSION_ID_HEADER_NAME); const sessionId = c.req.header(SESSION_ID_HEADER_NAME);
console.error(`POST request received ${sessionId ? 'with session ID: ' + sessionId : 'without session ID'}`); console.error(
`POST request received ${sessionId ? 'with session ID: ' + sessionId : 'without session ID'}`
);
try { try {
const body = await c.req.json(); const body = await c.req.json();
@ -71,7 +72,7 @@ class MCPStreamableHttpServer {
// Create new transport for initialize requests // Create new transport for initialize requests
if (!sessionId && this.isInitializeRequest(body)) { if (!sessionId && this.isInitializeRequest(body)) {
console.error("Creating new StreamableHTTP transport for initialize request"); console.error('Creating new StreamableHTTP transport for initialize request');
const transport = new StreamableHTTPServerTransport({ const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => uuid(), sessionIdGenerator: () => uuid(),
@ -111,16 +112,10 @@ class MCPStreamableHttpServer {
} }
// Invalid request (no session ID and not initialize) // Invalid request (no session ID and not initialize)
return c.json( return c.json(this.createErrorResponse('Bad Request: invalid session ID or method.'), 400);
this.createErrorResponse("Bad Request: invalid session ID or method."),
400
);
} catch (error) { } catch (error) {
console.error('Error handling MCP request:', error); console.error('Error handling MCP request:', error);
return c.json( return c.json(this.createErrorResponse('Internal server error.'), 500);
this.createErrorResponse("Internal server error."),
500
);
} }
} }
@ -148,7 +143,7 @@ class MCPStreamableHttpServer {
}; };
if (Array.isArray(body)) { if (Array.isArray(body)) {
return body.some(request => isInitial(request)); return body.some((request) => isInitial(request));
} }
return isInitial(body); return isInitial(body);
@ -178,8 +173,8 @@ export async function setupStreamableHttpServer(server: Server, port = 3000) {
}); });
// Main MCP endpoint supporting both GET and POST // Main MCP endpoint supporting both GET and POST
app.get("/mcp", (c) => mcpHandler.handleGetRequest(c)); app.get('/mcp', (c) => mcpHandler.handleGetRequest(c));
app.post("/mcp", (c) => mcpHandler.handlePostRequest(c)); app.post('/mcp', (c) => mcpHandler.handlePostRequest(c));
// Static files for the web client (if any) // Static files for the web client (if any)
app.get('/*', async (c) => { app.get('/*', async (c) => {
@ -209,17 +204,31 @@ export async function setupStreamableHttpServer(server: Server, port = 3000) {
let contentType = 'text/plain'; let contentType = 'text/plain';
switch (ext) { switch (ext) {
case '.html': contentType = 'text/html'; break; case '.html':
case '.css': contentType = 'text/css'; break; contentType = 'text/html';
case '.js': contentType = 'text/javascript'; break; break;
case '.json': contentType = 'application/json'; break; case '.css':
case '.png': contentType = 'image/png'; break; contentType = 'text/css';
case '.jpg': contentType = 'image/jpeg'; break; break;
case '.svg': contentType = 'image/svg+xml'; break; case '.js':
contentType = 'text/javascript';
break;
case '.json':
contentType = 'application/json';
break;
case '.png':
contentType = 'image/png';
break;
case '.jpg':
contentType = 'image/jpeg';
break;
case '.svg':
contentType = 'image/svg+xml';
break;
} }
return new Response(content, { return new Response(content, {
headers: { 'Content-Type': contentType } headers: { 'Content-Type': contentType },
}); });
} }
} catch (err) { } catch (err) {
@ -235,14 +244,17 @@ export async function setupStreamableHttpServer(server: Server, port = 3000) {
}); });
// Start the server // Start the server
serve({ serve(
{
fetch: app.fetch, fetch: app.fetch,
port port,
}, (info) => { },
(info) => {
console.error(`MCP StreamableHTTP Server running at http://localhost:${info.port}`); console.error(`MCP StreamableHTTP Server running at http://localhost:${info.port}`);
console.error(`- MCP Endpoint: http://localhost:${info.port}/mcp`); console.error(`- MCP Endpoint: http://localhost:${info.port}/mcp`);
console.error(`- Health Check: http://localhost:${info.port}/health`); console.error(`- Health Check: http://localhost:${info.port}/health`);
}); }
);
return app; return app;
} }

View File

@ -17,12 +17,6 @@
"sourceMap": true, "sourceMap": true,
"forceConsistentCasingInFileNames": true "forceConsistentCasingInFileNames": true
}, },
"include": [ "include": ["src/**/*"],
"src/**/*" "exclude": ["node_modules", "build", "**/*.test.ts"]
],
"exclude": [
"node_modules",
"build",
"**/*.test.ts"
]
} }

File diff suppressed because it is too large Load Diff

View File

@ -17,11 +17,6 @@
"sourceMap": true, "sourceMap": true,
"forceConsistentCasingInFileNames": true "forceConsistentCasingInFileNames": true
}, },
"include": [ "include": ["src/**/*"],
"src/**/*" "exclude": ["node_modules", "build"]
],
"exclude": [
"node_modules",
"build"
]
} }

355
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "openapi-mcp-generator", "name": "openapi-mcp-generator",
"version": "3.1.0", "version": "3.2.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "openapi-mcp-generator", "name": "openapi-mcp-generator",
"version": "3.1.0", "version": "3.2.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@apidevtools/swagger-parser": "^10.1.1", "@apidevtools/swagger-parser": "^10.1.1",
@ -14,22 +14,22 @@
"openapi-types": "^12.1.3" "openapi-types": "^12.1.3"
}, },
"bin": { "bin": {
"openapi-mcp-generator": "dist/index.js" "openapi-mcp-generator": "bin/openapi-mcp-generator.js"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.15.2", "@types/node": "^22.17.2",
"@typescript-eslint/eslint-plugin": "^8.31.0", "@typescript-eslint/eslint-plugin": "^8.39.1",
"@typescript-eslint/parser": "^8.31.0", "@typescript-eslint/parser": "^8.39.1",
"eslint": "^9.25.1", "eslint": "^9.33.0",
"prettier": "^3.5.3", "prettier": "^3.6.2",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"typescript": "^5.8.3" "typescript": "^5.9.2"
}, },
"engines": { "engines": {
"node": ">=20.0.0" "node": ">=20.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"@modelcontextprotocol/sdk": "^1.10.0", "@modelcontextprotocol/sdk": "^1.10.2",
"json-schema-to-zod": "^2.6.1", "json-schema-to-zod": "^2.6.1",
"zod": "^3.24.3" "zod": "^3.24.3"
} }
@ -85,9 +85,9 @@
} }
}, },
"node_modules/@eslint-community/eslint-utils": { "node_modules/@eslint-community/eslint-utils": {
"version": "4.6.0", "version": "4.7.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.0.tgz", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
"integrity": "sha512-WhCn7Z7TauhBtmzhvKpoQs0Wwb/kBcy4CwpuI0/eEIr2Lx2auxmulAzLr91wVZJaz47iUZdkXOK7WlAfxGKCnA==", "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -114,9 +114,9 @@
} }
}, },
"node_modules/@eslint/config-array": { "node_modules/@eslint/config-array": {
"version": "0.20.0", "version": "0.21.0",
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
"integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@ -129,9 +129,9 @@
} }
}, },
"node_modules/@eslint/config-array/node_modules/brace-expansion": { "node_modules/@eslint/config-array/node_modules/brace-expansion": {
"version": "1.1.11", "version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -153,9 +153,9 @@
} }
}, },
"node_modules/@eslint/config-helpers": { "node_modules/@eslint/config-helpers": {
"version": "0.2.1", "version": "0.3.1",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz",
"integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
@ -163,9 +163,9 @@
} }
}, },
"node_modules/@eslint/core": { "node_modules/@eslint/core": {
"version": "0.13.0", "version": "0.15.2",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz",
"integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@ -217,9 +217,9 @@
} }
}, },
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": { "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
"version": "1.1.11", "version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -227,6 +227,16 @@
"concat-map": "0.0.1" "concat-map": "0.0.1"
} }
}, },
"node_modules/@eslint/eslintrc/node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@ -248,13 +258,16 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "9.25.1", "version": "9.33.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.25.1.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.33.0.tgz",
"integrity": "sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==", "integrity": "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://eslint.org/donate"
} }
}, },
"node_modules/@eslint/object-schema": { "node_modules/@eslint/object-schema": {
@ -268,13 +281,13 @@
} }
}, },
"node_modules/@eslint/plugin-kit": { "node_modules/@eslint/plugin-kit": {
"version": "0.2.8", "version": "0.3.5",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz",
"integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@eslint/core": "^0.13.0", "@eslint/core": "^0.15.2",
"levn": "^0.4.1" "levn": "^0.4.1"
}, },
"engines": { "engines": {
@ -334,9 +347,9 @@
} }
}, },
"node_modules/@humanwhocodes/retry": { "node_modules/@humanwhocodes/retry": {
"version": "0.4.2", "version": "0.4.3",
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
"integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
@ -432,9 +445,9 @@
} }
}, },
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.7", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@ -445,9 +458,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "22.15.2", "version": "22.17.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.2.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.17.2.tgz",
"integrity": "sha512-uKXqKN9beGoMdBfcaTY1ecwz6ctxuJAcUlwE55938g0ZJ8lRxwAZqRz2AJ4pzpt5dHdTPMB863UZ0ESiFUcP7A==", "integrity": "sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -455,21 +468,21 @@
} }
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.31.0", "version": "8.39.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.1.tgz",
"integrity": "sha512-evaQJZ/J/S4wisevDvC1KFZkPzRetH8kYZbkgcTRyql3mcKsf+ZFDV1BVWUGTCAW5pQHoqn5gK5b8kn7ou9aFQ==", "integrity": "sha512-yYegZ5n3Yr6eOcqgj2nJH8cH/ZZgF+l0YIdKILSDjYFRjgYQMgv/lRjV5Z7Up04b9VYUondt8EPMqg7kTWgJ2g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.31.0", "@typescript-eslint/scope-manager": "8.39.1",
"@typescript-eslint/type-utils": "8.31.0", "@typescript-eslint/type-utils": "8.39.1",
"@typescript-eslint/utils": "8.31.0", "@typescript-eslint/utils": "8.39.1",
"@typescript-eslint/visitor-keys": "8.31.0", "@typescript-eslint/visitor-keys": "8.39.1",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.3.1", "ignore": "^7.0.0",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
"ts-api-utils": "^2.0.1" "ts-api-utils": "^2.1.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -479,22 +492,22 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependencies": {
"@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "@typescript-eslint/parser": "^8.39.1",
"eslint": "^8.57.0 || ^9.0.0", "eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0" "typescript": ">=4.8.4 <6.0.0"
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.31.0", "version": "8.39.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.1.tgz",
"integrity": "sha512-67kYYShjBR0jNI5vsf/c3WG4u+zDnCTHTPqVMQguffaWWFs7artgwKmfwdifl+r6XyM5LYLas/dInj2T0SgJyw==", "integrity": "sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.31.0", "@typescript-eslint/scope-manager": "8.39.1",
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/types": "8.39.1",
"@typescript-eslint/typescript-estree": "8.31.0", "@typescript-eslint/typescript-estree": "8.39.1",
"@typescript-eslint/visitor-keys": "8.31.0", "@typescript-eslint/visitor-keys": "8.39.1",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@ -506,18 +519,40 @@
}, },
"peerDependencies": { "peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0", "eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0" "typescript": ">=4.8.4 <6.0.0"
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/project-service": {
"version": "8.31.0", "version": "8.39.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.1.tgz",
"integrity": "sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw==", "integrity": "sha512-8fZxek3ONTwBu9ptw5nCKqZOSkXshZB7uAxuFF0J/wTMkKydjXCzqqga7MlFMpHi9DoG4BadhmTkITBcg8Aybw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/tsconfig-utils": "^8.39.1",
"@typescript-eslint/visitor-keys": "8.31.0" "@typescript-eslint/types": "^8.39.1",
"debug": "^4.3.4"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.39.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.1.tgz",
"integrity": "sha512-RkBKGBrjgskFGWuyUGz/EtD8AF/GW49S21J8dvMzpJitOF1slLEbbHnNEtAHtnDAnx8qDEdRrULRnWVx27wGBw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.39.1",
"@typescript-eslint/visitor-keys": "8.39.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -527,17 +562,35 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
} }
}, },
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.39.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.1.tgz",
"integrity": "sha512-ePUPGVtTMR8XMU2Hee8kD0Pu4NDE1CN9Q1sxGSGd/mbOtGZDM7pnhXNJnzW63zk/q+Z54zVzj44HtwXln5CvHA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.31.0", "version": "8.39.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.39.1.tgz",
"integrity": "sha512-DJ1N1GdjI7IS7uRlzJuEDCgDQix3ZVYVtgeWEyhyn4iaoitpMBX6Ndd488mXSx0xah/cONAkEaYyylDyAeHMHg==", "integrity": "sha512-gu9/ahyatyAdQbKeHnhT4R+y3YLtqqHyvkfDxaBYk97EcbfChSJXyaJnIL3ygUv7OuZatePHmQvuH5ru0lnVeA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "8.31.0", "@typescript-eslint/types": "8.39.1",
"@typescript-eslint/utils": "8.31.0", "@typescript-eslint/typescript-estree": "8.39.1",
"@typescript-eslint/utils": "8.39.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^2.0.1" "ts-api-utils": "^2.1.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -548,13 +601,13 @@
}, },
"peerDependencies": { "peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0", "eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0" "typescript": ">=4.8.4 <6.0.0"
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.31.0", "version": "8.39.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.1.tgz",
"integrity": "sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ==", "integrity": "sha512-7sPDKQQp+S11laqTrhHqeAbsCfMkwJMrV7oTDvtDds4mEofJYir414bYKUEb8YPUm9QL3U+8f6L6YExSoAGdQw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -566,20 +619,22 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.31.0", "version": "8.39.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.1.tgz",
"integrity": "sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ==", "integrity": "sha512-EKkpcPuIux48dddVDXyQBlKdeTPMmALqBUbEk38McWv0qVEZwOpVJBi7ugK5qVNgeuYjGNQxrrnoM/5+TI/BPw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/project-service": "8.39.1",
"@typescript-eslint/visitor-keys": "8.31.0", "@typescript-eslint/tsconfig-utils": "8.39.1",
"@typescript-eslint/types": "8.39.1",
"@typescript-eslint/visitor-keys": "8.39.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
"minimatch": "^9.0.4", "minimatch": "^9.0.4",
"semver": "^7.6.0", "semver": "^7.6.0",
"ts-api-utils": "^2.0.1" "ts-api-utils": "^2.1.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -589,20 +644,20 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": ">=4.8.4 <5.9.0" "typescript": ">=4.8.4 <6.0.0"
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.31.0", "version": "8.39.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.1.tgz",
"integrity": "sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww==", "integrity": "sha512-VF5tZ2XnUSTuiqZFXCZfZs1cgkdd3O/sSYmdo2EpSyDlC86UM/8YytTmKnehOW3TGAlivqTDT6bS87B/GQ/jyg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.31.0", "@typescript-eslint/scope-manager": "8.39.1",
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/types": "8.39.1",
"@typescript-eslint/typescript-estree": "8.31.0" "@typescript-eslint/typescript-estree": "8.39.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -613,18 +668,18 @@
}, },
"peerDependencies": { "peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0", "eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <5.9.0" "typescript": ">=4.8.4 <6.0.0"
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.31.0", "version": "8.39.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.1.tgz",
"integrity": "sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ==", "integrity": "sha512-W8FQi6kEh2e8zVhQ0eeRnxdvIoOkAp/CPAahcNio6nO9dsIwb9b34z90KOlheoyuVf6LSOEdjlkxSkapNEc+4A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.31.0", "@typescript-eslint/types": "8.39.1",
"eslint-visitor-keys": "^4.2.0" "eslint-visitor-keys": "^4.2.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -635,9 +690,9 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
"version": "4.2.0", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
@ -662,9 +717,9 @@
} }
}, },
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.14.1", "version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
@ -1115,20 +1170,20 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "9.25.1", "version": "9.33.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.25.1.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.33.0.tgz",
"integrity": "sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ==", "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.20.0", "@eslint/config-array": "^0.21.0",
"@eslint/config-helpers": "^0.2.1", "@eslint/config-helpers": "^0.3.1",
"@eslint/core": "^0.13.0", "@eslint/core": "^0.15.2",
"@eslint/eslintrc": "^3.3.1", "@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.25.1", "@eslint/js": "9.33.0",
"@eslint/plugin-kit": "^0.2.8", "@eslint/plugin-kit": "^0.3.5",
"@humanfs/node": "^0.16.6", "@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2", "@humanwhocodes/retry": "^0.4.2",
@ -1139,9 +1194,9 @@
"cross-spawn": "^7.0.6", "cross-spawn": "^7.0.6",
"debug": "^4.3.2", "debug": "^4.3.2",
"escape-string-regexp": "^4.0.0", "escape-string-regexp": "^4.0.0",
"eslint-scope": "^8.3.0", "eslint-scope": "^8.4.0",
"eslint-visitor-keys": "^4.2.0", "eslint-visitor-keys": "^4.2.1",
"espree": "^10.3.0", "espree": "^10.4.0",
"esquery": "^1.5.0", "esquery": "^1.5.0",
"esutils": "^2.0.2", "esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
@ -1176,9 +1231,9 @@
} }
}, },
"node_modules/eslint-scope": { "node_modules/eslint-scope": {
"version": "8.3.0", "version": "8.4.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
"integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
@ -1223,9 +1278,9 @@
} }
}, },
"node_modules/eslint/node_modules/brace-expansion": { "node_modules/eslint/node_modules/brace-expansion": {
"version": "1.1.11", "version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -1234,9 +1289,9 @@
} }
}, },
"node_modules/eslint/node_modules/eslint-visitor-keys": { "node_modules/eslint/node_modules/eslint-visitor-keys": {
"version": "4.2.0", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
@ -1246,6 +1301,16 @@
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/eslint/node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/eslint/node_modules/json-schema-traverse": { "node_modules/eslint/node_modules/json-schema-traverse": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@ -1267,15 +1332,15 @@
} }
}, },
"node_modules/espree": { "node_modules/espree": {
"version": "10.3.0", "version": "10.4.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
"integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"acorn": "^8.14.0", "acorn": "^8.15.0",
"acorn-jsx": "^5.3.2", "acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^4.2.0" "eslint-visitor-keys": "^4.2.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -1285,9 +1350,9 @@
} }
}, },
"node_modules/espree/node_modules/eslint-visitor-keys": { "node_modules/espree/node_modules/eslint-visitor-keys": {
"version": "4.2.0", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
@ -1832,9 +1897,9 @@
} }
}, },
"node_modules/ignore": { "node_modules/ignore": {
"version": "5.3.2", "version": "7.0.5",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -2397,9 +2462,9 @@
} }
}, },
"node_modules/prettier": { "node_modules/prettier": {
"version": "3.5.3", "version": "3.6.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
@ -2619,9 +2684,9 @@
"peer": true "peer": true
}, },
"node_modules/semver": { "node_modules/semver": {
"version": "7.7.1", "version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"bin": { "bin": {
@ -2992,9 +3057,9 @@
} }
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.8.3", "version": "5.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {

View File

@ -1,6 +1,6 @@
{ {
"name": "openapi-mcp-generator", "name": "openapi-mcp-generator",
"version": "3.1.0", "version": "3.2.0",
"description": "Generates MCP server code from OpenAPI specifications", "description": "Generates MCP server code from OpenAPI specifications",
"license": "MIT", "license": "MIT",
"author": "Harsha", "author": "Harsha",
@ -9,18 +9,23 @@
"node": ">=20.0.0" "node": ">=20.0.0"
}, },
"bin": { "bin": {
"openapi-mcp-generator": "./dist/index.js" "openapi-mcp-generator": "./bin/openapi-mcp-generator.js"
}, },
"main": "dist/index.js",
"files": [ "files": [
"dist", "dist",
"bin",
"README.md", "README.md",
"LICENSE" "LICENSE"
], ],
"types": "./dist/index.d.ts",
"scripts": { "scripts": {
"start": "node dist/index.js", "start": "node dist/index.js",
"clean": "rimraf dist", "clean": "rimraf dist",
"format.check": "prettier --check .",
"format.write": "prettier --write .",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"build": "tsc && chmod 755 dist/index.js", "build": "tsc && chmod 755 dist/index.js bin/openapi-mcp-generator.js",
"version:patch": "npm version patch", "version:patch": "npm version patch",
"version:minor": "npm version minor", "version:minor": "npm version minor",
"version:major": "npm version major" "version:major": "npm version major"
@ -48,16 +53,16 @@
"openapi-types": "^12.1.3" "openapi-types": "^12.1.3"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.15.2", "@types/node": "^22.17.2",
"@typescript-eslint/eslint-plugin": "^8.31.0", "@typescript-eslint/eslint-plugin": "^8.39.1",
"@typescript-eslint/parser": "^8.31.0", "@typescript-eslint/parser": "^8.39.1",
"eslint": "^9.25.1", "eslint": "^9.33.0",
"prettier": "^3.5.3", "prettier": "^3.6.2",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"typescript": "^5.8.3" "typescript": "^5.9.2"
}, },
"peerDependencies": { "peerDependencies": {
"@modelcontextprotocol/sdk": "^1.10.0", "@modelcontextprotocol/sdk": "^1.10.2",
"json-schema-to-zod": "^2.6.1", "json-schema-to-zod": "^2.6.1",
"zod": "^3.24.3" "zod": "^3.24.3"
} }

View File

@ -24,6 +24,13 @@ export interface GetToolsOptions {
/** Optional filter function to exclude tools based on custom criteria */ /** Optional filter function to exclude tools based on custom criteria */
filterFn?: (tool: McpToolDefinition) => boolean; filterFn?: (tool: McpToolDefinition) => boolean;
/** Default behavior for x-mcp filtering (default: true = include by default) */
defaultInclude?: boolean;
}
function isOpenApiDocument(spec: string | OpenAPIV3.Document): spec is OpenAPIV3.Document {
return typeof spec === 'object' && spec !== null && 'openapi' in spec;
} }
/** /**
@ -34,17 +41,19 @@ export interface GetToolsOptions {
* @returns Promise that resolves to an array of tool definitions * @returns Promise that resolves to an array of tool definitions
*/ */
export async function getToolsFromOpenApi( export async function getToolsFromOpenApi(
specPathOrUrl: string, specPathOrUrl: string | OpenAPIV3.Document,
options: GetToolsOptions = {} options: GetToolsOptions = {}
): Promise<McpToolDefinition[]> { ): Promise<McpToolDefinition[]> {
try { try {
// Parse the OpenAPI spec // Parse the OpenAPI spec
const api = options.dereference const api = isOpenApiDocument(specPathOrUrl)
? (await SwaggerParser.dereference(specPathOrUrl)) as OpenAPIV3.Document ? specPathOrUrl
: (await SwaggerParser.parse(specPathOrUrl)) as OpenAPIV3.Document; : options.dereference
? ((await SwaggerParser.dereference(specPathOrUrl)) as OpenAPIV3.Document)
: ((await SwaggerParser.parse(specPathOrUrl)) as OpenAPIV3.Document);
// Extract tools from the API // Extract tools from the API
const allTools = extractToolsFromApi(api); const allTools = extractToolsFromApi(api, options.defaultInclude ?? true);
// Add base URL to each tool // Add base URL to each tool
const baseUrl = determineBaseUrl(api, options.baseUrl); const baseUrl = determineBaseUrl(api, options.baseUrl);
@ -55,7 +64,7 @@ export async function getToolsFromOpenApi(
// Filter by excluded operation IDs if provided // Filter by excluded operation IDs if provided
if (options.excludeOperationIds && options.excludeOperationIds.length > 0) { if (options.excludeOperationIds && options.excludeOperationIds.length > 0) {
const excludeSet = new Set(options.excludeOperationIds); const excludeSet = new Set(options.excludeOperationIds);
filteredTools = filteredTools.filter(tool => !excludeSet.has(tool.operationId)); filteredTools = filteredTools.filter((tool) => !excludeSet.has(tool.operationId));
} }
// Apply custom filter function if provided // Apply custom filter function if provided
@ -64,14 +73,15 @@ export async function getToolsFromOpenApi(
} }
// Return the filtered tools with base URL added // Return the filtered tools with base URL added
return filteredTools.map(tool => ({ return filteredTools.map((tool) => ({
...tool, ...tool,
baseUrl: baseUrl || '', baseUrl: baseUrl || '',
})); }));
} catch (error) { } catch (error) {
// Provide more context for the error // Provide more context for the error
if (error instanceof Error) { if (error instanceof Error) {
throw new Error(`Failed to extract tools from OpenAPI: ${error.message}`); // Preserve original stack/context
throw new Error(`Failed to extract tools from OpenAPI: ${error.message}`, { cause: error });
} }
throw error; throw error;
} }

View File

@ -25,7 +25,7 @@ export function generateMcpServerCode(
serverVersion: string serverVersion: string
): string { ): string {
// Extract tools from API // Extract tools from API
const tools = extractToolsFromApi(api); const tools = extractToolsFromApi(api, options.defaultInclude ?? true);
// Determine base URL // Determine base URL
const determinedBaseUrl = determineBaseUrl(api, options.baseUrl); const determinedBaseUrl = determineBaseUrl(api, options.baseUrl);

View File

@ -1,4 +1,3 @@
/** /**
* Generator for StreamableHTTP server code for the MCP server using Hono * Generator for StreamableHTTP server code for the MCP server using Hono
*/ */

View File

@ -30,6 +30,8 @@ import {
// Import types // Import types
import { CliOptions, TransportType } from './types/index.js'; import { CliOptions, TransportType } from './types/index.js';
import { normalizeBoolean } from './utils/helpers.js';
import pkg from '../package.json' with { type: 'json' };
// Export programmatic API // Export programmatic API
export { getToolsFromOpenApi, McpToolDefinition, GetToolsOptions } from './api.js'; export { getToolsFromOpenApi, McpToolDefinition, GetToolsOptions } from './api.js';
@ -71,23 +73,30 @@ program
'Port for web or streamable-http transport (default: 3000)', 'Port for web or streamable-http transport (default: 3000)',
(val) => parseInt(val, 10) (val) => parseInt(val, 10)
) )
.option(
'--default-include <boolean>',
'Default behavior for x-mcp filtering (true|false, case-insensitive). Default: true (include by default), false = exclude by default',
(val) => {
const parsed = normalizeBoolean(val);
if (typeof parsed === 'boolean') return parsed;
console.warn(
`Invalid value for --default-include: "${val}". Expected true/false (case-insensitive). Using default: true.`
);
return true;
},
true
)
.option('--force', 'Overwrite existing files without prompting') .option('--force', 'Overwrite existing files without prompting')
.version('3.1.0'); // Match package.json version .version(pkg.version) // Match package.json version
.action((options) => {
// Check if module is being run directly (not imported) runGenerator(options).catch((error) => {
const isMainModule = process.argv[1] === new URL(import.meta.url).pathname;
if (isMainModule) {
// Parse arguments explicitly from process.argv
program.parse(process.argv);
// Run with the parsed options
runGenerator(program.opts<CliOptions & { force?: boolean }>())
.catch((error) => {
console.error('Unhandled error:', error); console.error('Unhandled error:', error);
process.exit(1); process.exit(1);
}); });
} });
// Export the program object for use in bin stub
export { program };
/** /**
* Main function to run the generator * Main function to run the generator

View File

@ -5,6 +5,7 @@ import { OpenAPIV3 } from 'openapi-types';
import type { JSONSchema7, JSONSchema7TypeName } from 'json-schema'; import type { JSONSchema7, JSONSchema7TypeName } from 'json-schema';
import { generateOperationId } from '../utils/code-gen.js'; import { generateOperationId } from '../utils/code-gen.js';
import { McpToolDefinition } from '../types/index.js'; import { McpToolDefinition } from '../types/index.js';
import { shouldIncludeOperationForMcp } from '../utils/helpers.js';
/** /**
* Extracts tool definitions from an OpenAPI document * Extracts tool definitions from an OpenAPI document
@ -12,7 +13,10 @@ import { McpToolDefinition } from '../types/index.js';
* @param api OpenAPI document * @param api OpenAPI document
* @returns Array of MCP tool definitions * @returns Array of MCP tool definitions
*/ */
export function extractToolsFromApi(api: OpenAPIV3.Document): McpToolDefinition[] { export function extractToolsFromApi(
api: OpenAPIV3.Document,
defaultInclude: boolean = true
): McpToolDefinition[] {
const tools: McpToolDefinition[] = []; const tools: McpToolDefinition[] = [];
const usedNames = new Set<string>(); const usedNames = new Set<string>();
const globalSecurity = api.security || []; const globalSecurity = api.security || [];
@ -26,15 +30,43 @@ export function extractToolsFromApi(api: OpenAPIV3.Document): McpToolDefinition[
const operation = pathItem[method]; const operation = pathItem[method];
if (!operation) continue; if (!operation) continue;
// Apply x-mcp filtering, precedence: operation > path > root
try {
if (
!shouldIncludeOperationForMcp(
api,
pathItem as OpenAPIV3.PathItemObject,
operation,
defaultInclude
)
) {
continue;
}
} catch (error) {
const loc = operation.operationId || `${method} ${path}`;
const extVal =
(operation as any)['x-mcp'] ?? (pathItem as any)['x-mcp'] ?? (api as any)['x-mcp'];
let extPreview: string;
try {
extPreview = JSON.stringify(extVal);
} catch {
extPreview = String(extVal);
}
console.warn(
`Error evaluating x-mcp extension for operation ${loc} (x-mcp=${extPreview}):`,
error
);
if (!defaultInclude) {
continue;
}
}
// Generate a unique name for the tool // Generate a unique name for the tool
let baseName = operation.operationId || generateOperationId(method, path); let baseName = operation.operationId || generateOperationId(method, path);
if (!baseName) continue; if (!baseName) continue;
// Sanitize the name to be MCP-compatible (only a-z, 0-9, _, -) // Sanitize the name to be MCP-compatible (only a-z, 0-9, _, -)
baseName = baseName baseName = baseName.replace(/\./g, '_').replace(/[^a-z0-9_-]/gi, '_');
.replace(/\./g, '_')
.replace(/[^a-z0-9_-]/gi, '_')
.toLowerCase();
let finalToolName = baseName; let finalToolName = baseName;
let counter = 1; let counter = 1;
@ -153,13 +185,15 @@ export function generateInputSchemaAndDetails(operation: OpenAPIV3.OperationObje
} }
/** /**
* Maps an OpenAPI schema to a JSON Schema * Maps an OpenAPI schema to a JSON Schema with cycle protection.
* *
* @param schema OpenAPI schema object or reference * @param schema OpenAPI schema object or reference
* @param seen WeakSet tracking already visited schema objects
* @returns JSON Schema representation * @returns JSON Schema representation
*/ */
export function mapOpenApiSchemaToJsonSchema( export function mapOpenApiSchemaToJsonSchema(
schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject,
seen: WeakSet<object> = new WeakSet()
): JSONSchema7 | boolean { ): JSONSchema7 | boolean {
// Handle reference objects // Handle reference objects
if ('$ref' in schema) { if ('$ref' in schema) {
@ -170,6 +204,16 @@ export function mapOpenApiSchemaToJsonSchema(
// Handle boolean schemas // Handle boolean schemas
if (typeof schema === 'boolean') return schema; if (typeof schema === 'boolean') return schema;
// Detect cycles
if (seen.has(schema)) {
console.warn(
`Cycle detected in schema${schema.title ? ` "${schema.title}"` : ''}, returning generic object to break recursion.`
);
return { type: 'object' };
}
seen.add(schema);
try {
// Create a copy of the schema to modify // Create a copy of the schema to modify
const jsonSchema: JSONSchema7 = { ...schema } as any; const jsonSchema: JSONSchema7 = { ...schema } as any;
@ -202,7 +246,10 @@ export function mapOpenApiSchemaToJsonSchema(
for (const [key, propSchema] of Object.entries(jsonSchema.properties)) { for (const [key, propSchema] of Object.entries(jsonSchema.properties)) {
if (typeof propSchema === 'object' && propSchema !== null) { if (typeof propSchema === 'object' && propSchema !== null) {
mappedProps[key] = mapOpenApiSchemaToJsonSchema(propSchema as OpenAPIV3.SchemaObject); mappedProps[key] = mapOpenApiSchemaToJsonSchema(
propSchema as OpenAPIV3.SchemaObject,
seen
);
} else if (typeof propSchema === 'boolean') { } else if (typeof propSchema === 'boolean') {
mappedProps[key] = propSchema; mappedProps[key] = propSchema;
} }
@ -218,9 +265,12 @@ export function mapOpenApiSchemaToJsonSchema(
jsonSchema.items !== null jsonSchema.items !== null
) { ) {
jsonSchema.items = mapOpenApiSchemaToJsonSchema( jsonSchema.items = mapOpenApiSchemaToJsonSchema(
jsonSchema.items as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject jsonSchema.items as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject,
seen
); );
} }
return jsonSchema; return jsonSchema;
} finally {
seen.delete(schema);
}
} }

View File

@ -29,6 +29,12 @@ export interface CliOptions {
transport?: TransportType; transport?: TransportType;
/** Server port (for web and streamable-http transports) */ /** Server port (for web and streamable-http transports) */
port?: number; port?: number;
/**
* Default behavior for x-mcp filtering.
* true (default) = include by default when x-mcp is missing or invalid;
* false = exclude by default unless x-mcp explicitly enables.
*/
defaultInclude?: boolean;
} }
/** /**

View File

@ -1,6 +1,7 @@
/** /**
* General helper utilities for OpenAPI to MCP generator * General helper utilities for OpenAPI to MCP generator
*/ */
import { OpenAPIV3 } from 'openapi-types';
/** /**
* Safely stringify a JSON object with proper error handling * Safely stringify a JSON object with proper error handling
@ -110,3 +111,63 @@ export function formatComment(str: string, maxLineLength: number = 80): string {
return lines.join('\n * '); return lines.join('\n * ');
} }
/**
* Normalize a value to boolean if it looks like a boolean; otherwise undefined.
*/
export function normalizeBoolean(value: unknown): boolean | undefined {
if (typeof value === 'boolean') return value;
if (typeof value === 'string') {
const normalized = value.trim().toLowerCase();
if (['true', '1', 'yes', 'on'].includes(normalized)) return true;
if (['false', '0', 'no', 'off'].includes(normalized)) return false;
return undefined;
}
return undefined;
}
/**
* Determine if an operation should be included in MCP generation based on x-mcp.
* Precedence: operation > path > root; uses provided default when all undefined.
*/
export function shouldIncludeOperationForMcp(
api: OpenAPIV3.Document,
pathItem: OpenAPIV3.PathItemObject,
operation: OpenAPIV3.OperationObject,
defaultInclude: boolean = true
): boolean {
const opRaw = (operation as any)['x-mcp'];
const opVal = normalizeBoolean(opRaw);
if (typeof opVal !== 'undefined') return opVal;
if (typeof opRaw !== 'undefined') {
console.warn(
`Invalid x-mcp value on operation '${operation.operationId ?? '[no operationId]'}':`,
opRaw,
`-> expected boolean or 'true'/'false'. Falling back to path/root/default.`
);
}
const pathRaw = (pathItem as any)['x-mcp'];
const pathVal = normalizeBoolean(pathRaw);
if (typeof pathVal !== 'undefined') return pathVal;
if (typeof pathRaw !== 'undefined') {
console.warn(
`Invalid x-mcp value on path item:`,
pathRaw,
`-> expected boolean or 'true'/'false'. Falling back to root/default.`
);
}
const rootRaw = (api as any)['x-mcp'];
const rootVal = normalizeBoolean(rootRaw);
if (typeof rootVal !== 'undefined') return rootVal;
if (typeof rootRaw !== 'undefined') {
console.warn(
`Invalid x-mcp value at API root:`,
rootRaw,
`-> expected boolean or 'true'/'false'. Falling back to defaultInclude=${defaultInclude}.`
);
}
return defaultInclude;
}