Compare commits
15 Commits
copilot/fi
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ee9fc383d | ||
|
|
f29c277860 | ||
|
|
33220c1e82 | ||
|
|
eda4505a63 | ||
|
|
4bf66d9efd | ||
|
|
7a31e1f6e9 | ||
|
|
82ff2b726d | ||
|
|
1c806b8dab | ||
|
|
c9015f395e | ||
|
|
af1b664653 | ||
|
|
1f001bb47a | ||
|
|
b1e29c22de | ||
|
|
e6352d13b6 | ||
|
|
26307f26ad | ||
|
|
b7bc67e444 |
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).
|
||||||
35
CHANGELOG.md
35
CHANGELOG.md
@ -5,6 +5,41 @@ 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
|
## [3.1.2] - 2025-06-08
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@ -64,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:
|
||||||
|
|||||||
35
README.md
35
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
|
||||||
@ -167,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
|
||||||
|
|||||||
353
package-lock.json
generated
353
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
18
package.json
18
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "openapi-mcp-generator",
|
"name": "openapi-mcp-generator",
|
||||||
"version": "3.1.4",
|
"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",
|
||||||
@ -25,7 +25,7 @@
|
|||||||
"format.check": "prettier --check .",
|
"format.check": "prettier --check .",
|
||||||
"format.write": "prettier --write .",
|
"format.write": "prettier --write .",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"build": "tsc && chmod 755 dist/index.js && chmod 755 bin/openapi-mcp-generator.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"
|
||||||
@ -53,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"
|
||||||
}
|
}
|
||||||
|
|||||||
18
src/api.ts
18
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)
|
||||||
|
? specPathOrUrl
|
||||||
|
: options.dereference
|
||||||
? ((await SwaggerParser.dereference(specPathOrUrl)) as OpenAPIV3.Document)
|
? ((await SwaggerParser.dereference(specPathOrUrl)) as OpenAPIV3.Document)
|
||||||
: ((await SwaggerParser.parse(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);
|
||||||
@ -71,7 +80,8 @@ export async function getToolsFromOpenApi(
|
|||||||
} 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);
|
||||||
|
|||||||
14
src/index.ts
14
src/index.ts
@ -30,6 +30,7 @@ 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' };
|
import pkg from '../package.json' with { type: 'json' };
|
||||||
|
|
||||||
// Export programmatic API
|
// Export programmatic API
|
||||||
@ -72,6 +73,19 @@ 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(pkg.version) // Match package.json version
|
.version(pkg.version) // Match package.json version
|
||||||
.action((options) => {
|
.action((options) => {
|
||||||
|
|||||||
@ -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,6 +30,37 @@ 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;
|
||||||
|
|||||||
@ -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