Compare commits
34 Commits
coderabbit
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ee9fc383d | ||
|
|
f29c277860 | ||
|
|
33220c1e82 | ||
|
|
eda4505a63 | ||
|
|
4bf66d9efd | ||
|
|
7a31e1f6e9 | ||
|
|
82ff2b726d | ||
|
|
1c806b8dab | ||
|
|
c9015f395e | ||
|
|
af1b664653 | ||
|
|
1f001bb47a | ||
|
|
b1e29c22de | ||
|
|
e6352d13b6 | ||
|
|
26307f26ad | ||
|
|
b7bc67e444 | ||
|
|
47292c89cf | ||
|
|
2cab8aeada | ||
|
|
ea89e0498e | ||
|
|
34a1f6df12 | ||
|
|
e17ab1a3d0 | ||
|
|
0ca1310c56 | ||
|
|
aa388035ad | ||
|
|
c98d652933 | ||
|
|
9c6fed9beb | ||
|
|
67e06c3c34 | ||
|
|
df758359e8 | ||
|
|
78c3d91ec2 | ||
|
|
4f7417890f | ||
|
|
aaf8613216 | ||
|
|
4c84306814 | ||
|
|
e2fd79c2ec | ||
|
|
2c6c05988e | ||
|
|
c607d9c759 | ||
|
|
6141d2e8ae |
@ -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
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal 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
7
.github/ISSUE_TEMPLATE/custom.md
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
name: Custom issue template
|
||||||
|
about: Describe this issue template's purpose here.
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal 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
44
.github/PULL_REQUEST_TEMPLATE/mcpcat.md
vendored
Normal 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` (0–1)
|
||||||
|
|
||||||
|
### 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
30
.github/workflows/check.yml
vendored
Normal 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
|
||||||
3
.github/workflows/npm-publish.yml
vendored
3
.github/workflows/npm-publish.yml
vendored
@ -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
|
||||||
|
|||||||
78
CHANGELOG.md
78
CHANGELOG.md
@ -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
21
LICENSE
Normal 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.
|
||||||
@ -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'
|
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
47
README.md
47
README.md
@ -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
6
bin/openapi-mcp-generator.js
Executable 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);
|
||||||
@ -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",
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
@ -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",
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
@ -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
355
package-lock.json
generated
@ -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": {
|
||||||
|
|||||||
25
package.json
25
package.json
@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
26
src/api.ts
26
src/api.ts
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
35
src/index.ts
35
src/index.ts
@ -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
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user