feat: Add programmatic API documentation and implementation
- Introduced PROGRAMMATIC_API.md to document the usage of the OpenAPI to MCP Generator programmatically. - Implemented the getToolsFromOpenApi function in src/api.ts for extracting MCP tool definitions from OpenAPI specifications. - Updated README.md to reference the new programmatic API documentation. - Exported the programmatic API and related types from src/index.ts for easier access.
This commit is contained in:
parent
536714a31b
commit
5b88b5e58d
137
PROGRAMMATIC_API.md
Normal file
137
PROGRAMMATIC_API.md
Normal file
@ -0,0 +1,137 @@
|
||||
# OpenAPI to MCP Generator - Programmatic API
|
||||
|
||||
This document describes how to use the OpenAPI to MCP Generator programmatically in your Node.js applications.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install openapi-mcp-generator
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
The package exports a simple, focused API for extracting MCP tool definitions from OpenAPI specifications:
|
||||
|
||||
```typescript
|
||||
import { getToolsFromOpenApi } from 'openapi-mcp-generator';
|
||||
```
|
||||
|
||||
### `getToolsFromOpenApi(specPathOrUrl, options)`
|
||||
|
||||
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:**
|
||||
|
||||
```typescript
|
||||
import { getToolsFromOpenApi } from 'openapi-mcp-generator';
|
||||
|
||||
// Basic usage
|
||||
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'
|
||||
});
|
||||
|
||||
// Process the results
|
||||
for (const tool of filteredTools) {
|
||||
console.log(`Tool: ${tool.name}`);
|
||||
console.log(` Description: ${tool.description}`);
|
||||
console.log(` Method: ${tool.method.toUpperCase()} ${tool.pathTemplate}`);
|
||||
console.log(` OperationId: ${tool.operationId}`);
|
||||
}
|
||||
```
|
||||
|
||||
## Tool Definition Structure
|
||||
|
||||
Each tool definition (`McpToolDefinition`) has the following properties:
|
||||
|
||||
```typescript
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Filtering Examples
|
||||
|
||||
### Filter by HTTP Method
|
||||
|
||||
```typescript
|
||||
const getTools = await getToolsFromOpenApi(specUrl, {
|
||||
filterFn: (tool) => tool.method.toLowerCase() === 'get'
|
||||
});
|
||||
```
|
||||
|
||||
### Filter by Security Requirements
|
||||
|
||||
```typescript
|
||||
const secureTools = await getToolsFromOpenApi(specUrl, {
|
||||
filterFn: (tool) => tool.securityRequirements.length > 0
|
||||
});
|
||||
```
|
||||
|
||||
### Filter by Path Pattern
|
||||
|
||||
```typescript
|
||||
const userTools = await getToolsFromOpenApi(specUrl, {
|
||||
filterFn: (tool) => tool.pathTemplate.includes('/user')
|
||||
});
|
||||
```
|
||||
|
||||
### Combining Multiple Filters
|
||||
|
||||
```typescript
|
||||
const safeUserTools = await getToolsFromOpenApi(specUrl, {
|
||||
excludeOperationIds: ['deleteUser', 'updateUser'],
|
||||
filterFn: (tool) =>
|
||||
tool.pathTemplate.includes('/user') &&
|
||||
tool.method.toLowerCase() === 'get'
|
||||
});
|
||||
```
|
||||
22
README.md
22
README.md
@ -58,6 +58,28 @@ openapi-mcp-generator --input path/to/openapi.json --output path/to/output/dir -
|
||||
| `--transport` | `-t` | Transport mode: `"stdio"` (default), `"web"`, or `"streamable-http"` | `"stdio"` |
|
||||
| `--port` | `-p` | Port for web-based transports | `3000` |
|
||||
| `--force` | | Overwrite existing files in the output directory without confirmation | `false` |
|
||||
|
||||
## 📦 Programmatic API
|
||||
|
||||
You can also use this package programmatically in your Node.js applications:
|
||||
|
||||
```javascript
|
||||
import { getToolsFromOpenApi } from 'openapi-mcp-generator';
|
||||
|
||||
// Extract MCP tool definitions from an OpenAPI spec
|
||||
const tools = await getToolsFromOpenApi('./petstore.json');
|
||||
|
||||
// With options
|
||||
const filteredTools = await getToolsFromOpenApi('https://example.com/api-spec.json', {
|
||||
baseUrl: 'https://api.example.com',
|
||||
dereference: true,
|
||||
excludeOperationIds: ['deletePet'],
|
||||
filterFn: (tool) => tool.method.toLowerCase() === 'get'
|
||||
});
|
||||
```
|
||||
|
||||
For full documentation of the programmatic API, see [PROGRAMMATIC_API.md](./PROGRAMMATIC_API.md).
|
||||
|
||||
---
|
||||
|
||||
## 🧱 Project Structure
|
||||
|
||||
81
src/api.ts
Normal file
81
src/api.ts
Normal file
@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Programmatic API for OpenAPI to MCP Generator
|
||||
* Allows direct usage from other Node.js applications
|
||||
*/
|
||||
|
||||
import SwaggerParser from '@apidevtools/swagger-parser';
|
||||
import { OpenAPIV3 } from 'openapi-types';
|
||||
import { extractToolsFromApi } from './parser/extract-tools.js';
|
||||
import { McpToolDefinition } from './types/index.js';
|
||||
import { determineBaseUrl } from './utils/url.js';
|
||||
|
||||
/**
|
||||
* Options for generating the MCP tools
|
||||
*/
|
||||
export interface GetToolsOptions {
|
||||
/** Optional base URL to override the one in the OpenAPI spec */
|
||||
baseUrl?: string;
|
||||
|
||||
/** Whether to dereference the OpenAPI spec */
|
||||
dereference?: boolean;
|
||||
|
||||
/** Array of operation IDs to exclude from the tools list */
|
||||
excludeOperationIds?: string[];
|
||||
|
||||
/** Optional filter function to exclude tools based on custom criteria */
|
||||
filterFn?: (tool: McpToolDefinition) => boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of tools from an OpenAPI specification
|
||||
*
|
||||
* @param specPathOrUrl Path or URL to the OpenAPI specification
|
||||
* @param options Options for generating the tools
|
||||
* @returns Promise that resolves to an array of tool definitions
|
||||
*/
|
||||
export async function getToolsFromOpenApi(
|
||||
specPathOrUrl: string,
|
||||
options: GetToolsOptions = {}
|
||||
): Promise<McpToolDefinition[]> {
|
||||
try {
|
||||
// Parse the OpenAPI spec
|
||||
const api = options.dereference
|
||||
? (await SwaggerParser.dereference(specPathOrUrl)) as OpenAPIV3.Document
|
||||
: (await SwaggerParser.parse(specPathOrUrl)) as OpenAPIV3.Document;
|
||||
|
||||
// Extract tools from the API
|
||||
const allTools = extractToolsFromApi(api);
|
||||
|
||||
// Add base URL to each tool
|
||||
const baseUrl = determineBaseUrl(api, options.baseUrl);
|
||||
|
||||
// Apply filters to exclude specified operationIds and custom filter function
|
||||
let filteredTools = allTools;
|
||||
|
||||
// Filter by excluded operation IDs if provided
|
||||
if (options.excludeOperationIds && options.excludeOperationIds.length > 0) {
|
||||
const excludeSet = new Set(options.excludeOperationIds);
|
||||
filteredTools = filteredTools.filter(tool => !excludeSet.has(tool.operationId));
|
||||
}
|
||||
|
||||
// Apply custom filter function if provided
|
||||
if (options.filterFn) {
|
||||
filteredTools = filteredTools.filter(options.filterFn);
|
||||
}
|
||||
|
||||
// Return the filtered tools with base URL added
|
||||
return filteredTools.map(tool => ({
|
||||
...tool,
|
||||
baseUrl: baseUrl || '',
|
||||
}));
|
||||
} catch (error) {
|
||||
// Provide more context for the error
|
||||
if (error instanceof Error) {
|
||||
throw new Error(`Failed to extract tools from OpenAPI: ${error.message}`);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Export types for convenience
|
||||
export { McpToolDefinition };
|
||||
30
src/index.ts
30
src/index.ts
@ -31,6 +31,9 @@ import {
|
||||
// Import types
|
||||
import { CliOptions, TransportType } from './types/index.js';
|
||||
|
||||
// Export programmatic API
|
||||
export { getToolsFromOpenApi, McpToolDefinition, GetToolsOptions } from './api.js';
|
||||
|
||||
// Configure CLI
|
||||
const program = new Command();
|
||||
|
||||
@ -69,18 +72,27 @@ program
|
||||
(val) => parseInt(val, 10)
|
||||
)
|
||||
.option('--force', 'Overwrite existing files without prompting')
|
||||
.version('2.0.0'); // Match package.json version
|
||||
.version('3.0.0'); // Match package.json version
|
||||
|
||||
// Parse arguments explicitly from process.argv
|
||||
program.parse(process.argv);
|
||||
// Check if module is being run directly (not imported)
|
||||
const isMainModule = process.argv[1] === new URL(import.meta.url).pathname;
|
||||
|
||||
// Retrieve the options AFTER parsing
|
||||
const options = program.opts<CliOptions & { force?: boolean }>();
|
||||
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);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function to run the generator
|
||||
*/
|
||||
async function main() {
|
||||
async function runGenerator(options: CliOptions & { force?: boolean }) {
|
||||
// Use the parsed options directly
|
||||
const outputDir = options.output;
|
||||
const inputSpec = options.input;
|
||||
@ -275,7 +287,5 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('Unhandled error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
// Export the run function for programmatic usage
|
||||
export { runGenerator as generateMcpServer };
|
||||
Loading…
x
Reference in New Issue
Block a user