diff --git a/.eslintrc.json b/.eslintrc.json index b707bc3..0483701 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,18 +1,15 @@ { - "parser": "@typescript-eslint/parser", - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended" - ], - "plugins": ["@typescript-eslint"], - "env": { - "node": true, - "es2022": true - }, - "rules": { - "no-console": "off", - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }] - } - } \ No newline at end of file + "parser": "@typescript-eslint/parser", + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + "plugins": ["@typescript-eslint"], + "env": { + "node": true, + "es2022": true + }, + "rules": { + "no-console": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }] + } +} diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dd84ea7..b5c68e5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,7 +4,6 @@ about: Create a report to help us improve title: '' labels: '' assignees: '' - --- **Describe the bug** @@ -12,6 +11,7 @@ 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 '....' @@ -24,15 +24,17 @@ A clear and concise description of what you expected to happen. 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] + +- 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] + +- 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. diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md index 48d5f81..96a4735 100644 --- a/.github/ISSUE_TEMPLATE/custom.md +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -4,7 +4,4 @@ about: Describe this issue template's purpose here. title: '' labels: '' assignees: '' - --- - - diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bbcbbe7..2f28cea 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,7 +4,6 @@ about: Suggest an idea for this project title: '' labels: '' assignees: '' - --- **Is your feature request related to a problem? Please describe.** diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index bebc36b..ced66b9 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -23,7 +23,7 @@ jobs: with: node-version: '20' registry-url: 'https://registry.npmjs.org' - cache: 'npm' # Enables npm dependency caching + cache: 'npm' # Enables npm dependency caching cache-dependency-path: '**/package-lock.json' # Cache key based on lockfile - name: Install dependencies diff --git a/.prettierrc b/.prettierrc index 0c6a633..3de448e 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,7 +1,7 @@ { - "semi": true, - "trailingComma": "es5", - "singleQuote": true, - "printWidth": 100, - "tabWidth": 2 - } \ No newline at end of file + "semi": true, + "trailingComma": "es5", + "singleQuote": true, + "printWidth": 100, + "tabWidth": 2 +} diff --git a/CHANGELOG.md b/CHANGELOG.md index e5d2d88..6db8a35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,23 +8,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [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 ### Added + - Programmatic API to extract MCP tool definitions from OpenAPI specs - New exportable `getToolsFromOpenApi` function for direct integration in code - Advanced filtering capabilities for programmatic tool extraction @@ -32,20 +35,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated README with programmatic API usage examples ### Changed + - Improved module structure with better exports - 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. @@ -53,6 +60,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [2.0.0] - 2025-04-12 ### Added + - Runtime argument validation using Zod - JSON Schema to Zod schema conversion - Improved error handling and formatting @@ -63,6 +71,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support for multiple content types ### Changed + - Simplified transport layer to only support stdio transport - Removed support for WebSocket and HTTP transports - Updated to use @modelcontextprotocol/sdk v1.9.0 @@ -72,6 +81,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - More robust OpenAPI schema processing ### Fixed + - Path parameter resolution in URLs - Content-Type header handling - Response processing for different content types @@ -81,6 +91,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.0] - Initial Release ### Added + - Basic OpenAPI to MCP server generation - Support for GET, POST, PUT, DELETE methods - Basic error handling diff --git a/PROGRAMMATIC_API.md b/PROGRAMMATIC_API.md index 33517cf..3e16400 100644 --- a/PROGRAMMATIC_API.md +++ b/PROGRAMMATIC_API.md @@ -21,16 +21,19 @@ import { getToolsFromOpenApi } from 'openapi-mcp-generator'; This function extracts an array of tools from an OpenAPI specification. **Parameters:** + - `specPathOrUrl`: Path to a local OpenAPI spec file or URL to a remote spec - `options`: (Optional) Configuration options **Options:** + - `baseUrl`: Override the base URL in the OpenAPI spec - `dereference`: Whether to resolve $refs (default: false) - `excludeOperationIds`: Array of operation IDs to exclude from the results - `filterFn`: Custom function to filter tools (receives tool, returns boolean) **Returns:** + - Promise that resolves to an array of McpToolDefinition objects **Example:** @@ -42,12 +45,15 @@ import { getToolsFromOpenApi } from 'openapi-mcp-generator'; const tools = await getToolsFromOpenApi('./petstore.json'); // With options -const filteredTools = await getToolsFromOpenApi('https://petstore3.swagger.io/api/v3/openapi.json', { - baseUrl: 'https://petstore3.swagger.io/api/v3', - dereference: true, - excludeOperationIds: ['addPet', 'updatePet'], - filterFn: (tool) => tool.method.toLowerCase() === 'get' -}); +const filteredTools = await getToolsFromOpenApi( + 'https://petstore3.swagger.io/api/v3/openapi.json', + { + baseUrl: 'https://petstore3.swagger.io/api/v3', + dereference: true, + excludeOperationIds: ['addPet', 'updatePet'], + filterFn: (tool) => tool.method.toLowerCase() === 'get', + } +); // Process the results for (const tool of filteredTools) { @@ -66,34 +72,34 @@ Each tool definition (`McpToolDefinition`) has the following properties: interface McpToolDefinition { /** Name of the tool, must be unique */ name: string; - + /** Human-readable description of the tool */ description: string; - + /** JSON Schema that defines the input parameters */ inputSchema: JSONSchema7 | boolean; - + /** HTTP method for the operation (get, post, etc.) */ method: string; - + /** URL path template with parameter placeholders */ pathTemplate: string; - + /** OpenAPI parameter objects for this operation */ parameters: OpenAPIV3.ParameterObject[]; - + /** Parameter names and locations for execution */ executionParameters: { name: string; in: string }[]; - + /** Content type for request body, if applicable */ requestBodyContentType?: string; - + /** Security requirements for this operation */ securityRequirements: OpenAPIV3.SecurityRequirementObject[]; - + /** Original operation ID from the OpenAPI spec */ operationId: string; - + /** Base URL for the API (if available) */ baseUrl?: string; } @@ -105,7 +111,7 @@ interface McpToolDefinition { ```typescript const getTools = await getToolsFromOpenApi(specUrl, { - filterFn: (tool) => tool.method.toLowerCase() === 'get' + filterFn: (tool) => tool.method.toLowerCase() === 'get', }); ``` @@ -113,7 +119,7 @@ const getTools = await getToolsFromOpenApi(specUrl, { ```typescript const secureTools = await getToolsFromOpenApi(specUrl, { - filterFn: (tool) => tool.securityRequirements.length > 0 + filterFn: (tool) => tool.securityRequirements.length > 0, }); ``` @@ -121,7 +127,7 @@ const secureTools = await getToolsFromOpenApi(specUrl, { ```typescript const userTools = await getToolsFromOpenApi(specUrl, { - filterFn: (tool) => tool.pathTemplate.includes('/user') + filterFn: (tool) => tool.pathTemplate.includes('/user'), }); ``` @@ -130,8 +136,6 @@ const userTools = await getToolsFromOpenApi(specUrl, { ```typescript const safeUserTools = await getToolsFromOpenApi(specUrl, { excludeOperationIds: ['deleteUser', 'updateUser'], - filterFn: (tool) => - tool.pathTemplate.includes('/user') && - tool.method.toLowerCase() === 'get' + filterFn: (tool) => tool.pathTemplate.includes('/user') && tool.method.toLowerCase() === 'get', }); -``` \ No newline at end of file +``` diff --git a/bin/openapi-mcp-generator.js b/bin/openapi-mcp-generator.js index 3f1bb95..c42eee0 100755 --- a/bin/openapi-mcp-generator.js +++ b/bin/openapi-mcp-generator.js @@ -3,4 +3,4 @@ import { program } from '../dist/index.js'; // Parse CLI arguments and run the program -program.parse(process.argv); \ No newline at end of file +program.parse(process.argv); diff --git a/examples/pet-store-sse/.eslintrc.json b/examples/pet-store-sse/.eslintrc.json index 8cb5c8e..2c368f9 100644 --- a/examples/pet-store-sse/.eslintrc.json +++ b/examples/pet-store-sse/.eslintrc.json @@ -1,12 +1,7 @@ { "parser": "@typescript-eslint/parser", - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended" - ], - "plugins": [ - "@typescript-eslint" - ], + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + "plugins": ["@typescript-eslint"], "env": { "node": true, "es2022": true @@ -15,10 +10,7 @@ "no-console": [ "error", { - "allow": [ - "error", - "warn" - ] + "allow": ["error", "warn"] } ], "@typescript-eslint/explicit-function-return-type": "off", @@ -30,4 +22,4 @@ } ] } -} \ No newline at end of file +} diff --git a/examples/pet-store-sse/.prettierrc b/examples/pet-store-sse/.prettierrc index 494b947..3de448e 100644 --- a/examples/pet-store-sse/.prettierrc +++ b/examples/pet-store-sse/.prettierrc @@ -4,4 +4,4 @@ "singleQuote": true, "printWidth": 100, "tabWidth": 2 -} \ No newline at end of file +} diff --git a/examples/pet-store-sse/docs/oauth2-configuration.md b/examples/pet-store-sse/docs/oauth2-configuration.md index 9f3625b..1a5a040 100644 --- a/examples/pet-store-sse/docs/oauth2-configuration.md +++ b/examples/pet-store-sse/docs/oauth2-configuration.md @@ -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_SECRET_PETSTORE_AUTH`: Your OAuth client secret + ## 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. When making API requests, the server will: + 1. Check for a cached token that's still valid 2. Use the cached token if available 3. Request a new token if no valid cached token exists diff --git a/examples/pet-store-sse/package.json b/examples/pet-store-sse/package.json index 13ef081..d94f636 100644 --- a/examples/pet-store-sse/package.json +++ b/examples/pet-store-sse/package.json @@ -34,4 +34,4 @@ "typescript": "^5.8.3", "@types/uuid": "^10.0.0" } -} \ No newline at end of file +} diff --git a/examples/pet-store-sse/public/index.html b/examples/pet-store-sse/public/index.html index aa97ea3..9b4213e 100644 --- a/examples/pet-store-sse/public/index.html +++ b/examples/pet-store-sse/public/index.html @@ -1,393 +1,406 @@ - + -
- - -Disconnected
- -