feat: initial MCP server for OpenAPI/Swagger parsing
- Parse and validate OpenAPI 3.x / Swagger 2.0 specs - Query endpoints by method, path pattern, tag, operationId - Get component schema details - Generate TypeScript interfaces from schemas - Support local files and remote URLs Tools: parse-spec, validate-spec, query-endpoints, get-schema, generate-types Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
commit
cc789d3b32
17 changed files with 3315 additions and 0 deletions
85
.claude/commands/openapi.md
Normal file
85
.claude/commands/openapi.md
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
# OpenAPI Spec Tools
|
||||
|
||||
Parse, validate, and explore OpenAPI/Swagger specifications.
|
||||
|
||||
## Arguments
|
||||
|
||||
- `$ARGUMENTS` - Subcommand and arguments
|
||||
|
||||
## Subcommands
|
||||
|
||||
### parse <file>
|
||||
Parse an OpenAPI spec and show overview (version, paths, schemas, servers).
|
||||
|
||||
### validate <file>
|
||||
Validate a spec against OpenAPI schema. Reports errors and warnings.
|
||||
|
||||
### endpoints <file> [--method METHOD] [--path PATTERN] [--tag TAG]
|
||||
List API endpoints. Optional filters:
|
||||
- `--method GET|POST|PUT|DELETE|...` - Filter by HTTP method
|
||||
- `--path /users.*` - Filter by path regex pattern
|
||||
- `--tag users` - Filter by tag name
|
||||
|
||||
### schema <file> <name>
|
||||
Get details of a component schema by name.
|
||||
|
||||
### types <file> [schema1 schema2 ...]
|
||||
Generate TypeScript interfaces from schemas. Optionally specify schema names.
|
||||
|
||||
## Execution
|
||||
|
||||
Based on the subcommand in `$ARGUMENTS`, use the appropriate MCP tool:
|
||||
|
||||
### If subcommand is "parse":
|
||||
Use the `mcp__swagger-tools__parse-spec` tool with the file path.
|
||||
|
||||
### If subcommand is "validate":
|
||||
Use the `mcp__swagger-tools__validate-spec` tool with the file path.
|
||||
|
||||
### If subcommand is "endpoints":
|
||||
Use the `mcp__swagger-tools__query-endpoints` tool with:
|
||||
- `path`: The spec file path
|
||||
- `method`: Value after `--method` (if provided)
|
||||
- `pathPattern`: Value after `--path` (if provided)
|
||||
- `tag`: Value after `--tag` (if provided)
|
||||
|
||||
### If subcommand is "schema":
|
||||
Use the `mcp__swagger-tools__get-schema` tool with:
|
||||
- `path`: The spec file path
|
||||
- `schemaName`: The schema name argument
|
||||
|
||||
### If subcommand is "types":
|
||||
Use the `mcp__swagger-tools__generate-types` tool with:
|
||||
- `path`: The spec file path
|
||||
- `schemas`: Array of schema names (if any specified after the file)
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
# Parse and show overview
|
||||
/openapi parse api.yaml
|
||||
|
||||
# Validate spec
|
||||
/openapi validate openapi.json
|
||||
|
||||
# List all GET endpoints
|
||||
/openapi endpoints api.yaml --method GET
|
||||
|
||||
# List endpoints matching /users path
|
||||
/openapi endpoints api.yaml --path /users
|
||||
|
||||
# Get User schema details
|
||||
/openapi schema api.yaml User
|
||||
|
||||
# Generate all TypeScript types
|
||||
/openapi types api.yaml
|
||||
|
||||
# Generate specific schemas only
|
||||
/openapi types api.yaml User Pet Order
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Supports OpenAPI 3.0, 3.1, and Swagger 2.0 specifications
|
||||
- Files can be YAML or JSON format
|
||||
- References ($ref) are automatically dereferenced
|
||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
node_modules/
|
||||
dist/
|
||||
*.log
|
||||
.env
|
||||
.DS_Store
|
||||
96
CLAUDE.md
Normal file
96
CLAUDE.md
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# swagger-tools
|
||||
|
||||
MCP server for parsing, validating, and querying OpenAPI/Swagger specifications.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Build
|
||||
npm run build
|
||||
|
||||
# Run (for testing)
|
||||
npm start
|
||||
```
|
||||
|
||||
## MCP Server Configuration
|
||||
|
||||
Add to `~/.claude.json` (or project `.mcp.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"swagger-tools": {
|
||||
"command": "node",
|
||||
"args": ["/absolute/path/to/swagger-tools/dist/index.js"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After configuring, restart Claude Code. The tools will be available as:
|
||||
- `mcp__swagger-tools__parse-spec`
|
||||
- `mcp__swagger-tools__validate-spec`
|
||||
- `mcp__swagger-tools__query-endpoints`
|
||||
- `mcp__swagger-tools__get-schema`
|
||||
- `mcp__swagger-tools__generate-types`
|
||||
|
||||
## Claude Code Slash Command
|
||||
|
||||
Use `/openapi` for convenient access:
|
||||
|
||||
```bash
|
||||
/openapi parse api.yaml # Parse and show overview
|
||||
/openapi validate api.yaml # Validate spec
|
||||
/openapi endpoints api.yaml # List all endpoints
|
||||
/openapi schema api.yaml User # Get schema details
|
||||
/openapi types api.yaml # Generate TypeScript types
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── index.ts # MCP server entry point (stdio)
|
||||
├── tools/ # MCP tool implementations
|
||||
│ ├── parse.ts # parse-spec tool
|
||||
│ ├── validate.ts # validate-spec tool
|
||||
│ ├── query.ts # query-endpoints tool
|
||||
│ ├── schema.ts # get-schema tool
|
||||
│ └── generate.ts # generate-types tool
|
||||
├── lib/ # Core library
|
||||
│ ├── parser.ts # OpenAPI parsing wrapper
|
||||
│ ├── validator.ts # Validation logic
|
||||
│ └── types.ts # TypeScript types
|
||||
└── utils/
|
||||
└── format.ts # Output formatting
|
||||
|
||||
.claude/
|
||||
└── commands/
|
||||
└── openapi.md # Slash command definition
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Type check without building
|
||||
npm run typecheck
|
||||
|
||||
# Run with tsx (development)
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Supported Formats
|
||||
|
||||
- OpenAPI 3.0.x
|
||||
- OpenAPI 3.1.x
|
||||
- Swagger 2.0
|
||||
- YAML and JSON files
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `@modelcontextprotocol/sdk` - MCP server framework
|
||||
- `@apidevtools/swagger-parser` - OpenAPI parsing and validation
|
||||
- `zod` - Schema validation for tool inputs
|
||||
1816
package-lock.json
generated
Normal file
1816
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
37
package.json
Normal file
37
package.json
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"name": "swagger-tools",
|
||||
"version": "0.1.0",
|
||||
"description": "MCP server for parsing, validating, and querying OpenAPI/Swagger specifications",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"bin": {
|
||||
"swagger-tools": "dist/index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsx src/index.ts",
|
||||
"start": "node dist/index.js",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"keywords": [
|
||||
"openapi",
|
||||
"swagger",
|
||||
"mcp",
|
||||
"claude",
|
||||
"api"
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apidevtools/swagger-parser": "^10.1.0",
|
||||
"@modelcontextprotocol/sdk": "^1.0.0",
|
||||
"zod": "^3.23.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"tsx": "^4.0.0",
|
||||
"typescript": "^5.0.0"
|
||||
}
|
||||
}
|
||||
91
src/index.ts
Normal file
91
src/index.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
#!/usr/bin/env node
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
|
||||
import {
|
||||
parseToolName,
|
||||
parseToolDescription,
|
||||
parseToolSchema,
|
||||
parseToolHandler,
|
||||
} from './tools/parse.js';
|
||||
|
||||
import {
|
||||
validateToolName,
|
||||
validateToolDescription,
|
||||
validateToolSchema,
|
||||
validateToolHandler,
|
||||
} from './tools/validate.js';
|
||||
|
||||
import {
|
||||
queryToolName,
|
||||
queryToolDescription,
|
||||
queryToolSchema,
|
||||
queryToolHandler,
|
||||
} from './tools/query.js';
|
||||
|
||||
import {
|
||||
schemaToolName,
|
||||
schemaToolDescription,
|
||||
schemaToolSchema,
|
||||
schemaToolHandler,
|
||||
} from './tools/schema.js';
|
||||
|
||||
import {
|
||||
generateToolName,
|
||||
generateToolDescription,
|
||||
generateToolSchema,
|
||||
generateToolHandler,
|
||||
} from './tools/generate.js';
|
||||
|
||||
const server = new McpServer({
|
||||
name: 'swagger-tools',
|
||||
version: '0.1.0',
|
||||
});
|
||||
|
||||
// Register parse-spec tool
|
||||
server.tool(
|
||||
parseToolName,
|
||||
parseToolDescription,
|
||||
parseToolSchema,
|
||||
parseToolHandler
|
||||
);
|
||||
|
||||
// Register validate-spec tool
|
||||
server.tool(
|
||||
validateToolName,
|
||||
validateToolDescription,
|
||||
validateToolSchema,
|
||||
validateToolHandler
|
||||
);
|
||||
|
||||
// Register query-endpoints tool
|
||||
server.tool(
|
||||
queryToolName,
|
||||
queryToolDescription,
|
||||
queryToolSchema,
|
||||
queryToolHandler
|
||||
);
|
||||
|
||||
// Register get-schema tool
|
||||
server.tool(
|
||||
schemaToolName,
|
||||
schemaToolDescription,
|
||||
schemaToolSchema,
|
||||
schemaToolHandler
|
||||
);
|
||||
|
||||
// Register generate-types tool
|
||||
server.tool(
|
||||
generateToolName,
|
||||
generateToolDescription,
|
||||
generateToolSchema,
|
||||
generateToolHandler
|
||||
);
|
||||
|
||||
// Start the server with stdio transport
|
||||
async function main() {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
90
src/lib/parser.ts
Normal file
90
src/lib/parser.ts
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import SwaggerParser from '@apidevtools/swagger-parser';
|
||||
import type { OpenAPI, OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';
|
||||
import type { ParsedSpec, OpenAPISpec } from './types.js';
|
||||
|
||||
const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] as const;
|
||||
|
||||
export async function parseSpec(specPath: string): Promise<{
|
||||
spec: OpenAPISpec;
|
||||
metadata: ParsedSpec;
|
||||
}> {
|
||||
const spec = await SwaggerParser.dereference(specPath);
|
||||
const metadata = extractMetadata(spec);
|
||||
return { spec, metadata };
|
||||
}
|
||||
|
||||
export async function bundleSpec(specPath: string): Promise<OpenAPISpec> {
|
||||
return SwaggerParser.bundle(specPath);
|
||||
}
|
||||
|
||||
function extractMetadata(spec: OpenAPISpec): ParsedSpec {
|
||||
const version = getSpecVersion(spec);
|
||||
const info = spec.info;
|
||||
|
||||
const paths = spec.paths || {};
|
||||
const pathCount = Object.keys(paths).length;
|
||||
|
||||
let operationCount = 0;
|
||||
const tagsSet = new Set<string>();
|
||||
|
||||
for (const pathItem of Object.values(paths)) {
|
||||
if (!pathItem) continue;
|
||||
for (const method of HTTP_METHODS) {
|
||||
const operation = (pathItem as Record<string, unknown>)[method] as OpenAPIV3.OperationObject | undefined;
|
||||
if (operation) {
|
||||
operationCount++;
|
||||
if (operation.tags) {
|
||||
operation.tags.forEach(tag => tagsSet.add(tag));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const schemaCount = getSchemaCount(spec);
|
||||
const servers = getServers(spec);
|
||||
|
||||
return {
|
||||
version,
|
||||
title: info.title,
|
||||
description: info.description,
|
||||
servers,
|
||||
pathCount,
|
||||
schemaCount,
|
||||
operationCount,
|
||||
tags: Array.from(tagsSet).sort(),
|
||||
};
|
||||
}
|
||||
|
||||
function getSpecVersion(spec: OpenAPISpec): string {
|
||||
if ('openapi' in spec) {
|
||||
return `OpenAPI ${spec.openapi}`;
|
||||
}
|
||||
if ('swagger' in spec) {
|
||||
return `Swagger ${spec.swagger}`;
|
||||
}
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
function getSchemaCount(spec: OpenAPISpec): number {
|
||||
if ('components' in spec && spec.components?.schemas) {
|
||||
return Object.keys(spec.components.schemas).length;
|
||||
}
|
||||
if ('definitions' in spec && spec.definitions) {
|
||||
return Object.keys(spec.definitions).length;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getServers(spec: OpenAPISpec): string[] {
|
||||
if ('servers' in spec && spec.servers) {
|
||||
return spec.servers.map(s => s.url);
|
||||
}
|
||||
if ('host' in spec) {
|
||||
const swagger2 = spec as { host?: string; basePath?: string; schemes?: string[] };
|
||||
const scheme = swagger2.schemes?.[0] || 'https';
|
||||
const host = swagger2.host || 'localhost';
|
||||
const basePath = swagger2.basePath || '';
|
||||
return [`${scheme}://${host}${basePath}`];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
75
src/lib/types.ts
Normal file
75
src/lib/types.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import type { OpenAPI, OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';
|
||||
|
||||
export type OpenAPISpec = OpenAPI.Document;
|
||||
export type OpenAPIV3Spec = OpenAPIV3.Document | OpenAPIV3_1.Document;
|
||||
|
||||
export interface ParsedSpec {
|
||||
version: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
servers: string[];
|
||||
pathCount: number;
|
||||
schemaCount: number;
|
||||
operationCount: number;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
export interface ValidationError {
|
||||
path: string;
|
||||
message: string;
|
||||
severity: 'error' | 'warning';
|
||||
}
|
||||
|
||||
export interface ValidationResult {
|
||||
valid: boolean;
|
||||
errors: ValidationError[];
|
||||
warnings: ValidationError[];
|
||||
}
|
||||
|
||||
export interface EndpointInfo {
|
||||
method: string;
|
||||
path: string;
|
||||
operationId?: string;
|
||||
summary?: string;
|
||||
description?: string;
|
||||
tags: string[];
|
||||
parameters: ParameterInfo[];
|
||||
requestBody?: RequestBodyInfo;
|
||||
responses: ResponseInfo[];
|
||||
}
|
||||
|
||||
export interface ParameterInfo {
|
||||
name: string;
|
||||
in: 'query' | 'header' | 'path' | 'cookie';
|
||||
required: boolean;
|
||||
description?: string;
|
||||
schema?: object;
|
||||
}
|
||||
|
||||
export interface RequestBodyInfo {
|
||||
required: boolean;
|
||||
description?: string;
|
||||
contentTypes: string[];
|
||||
}
|
||||
|
||||
export interface ResponseInfo {
|
||||
statusCode: string;
|
||||
description?: string;
|
||||
contentTypes: string[];
|
||||
}
|
||||
|
||||
export interface EndpointFilter {
|
||||
method?: string;
|
||||
pathPattern?: string;
|
||||
tag?: string;
|
||||
operationId?: string;
|
||||
}
|
||||
|
||||
export interface SchemaInfo {
|
||||
name: string;
|
||||
type?: string;
|
||||
description?: string;
|
||||
properties?: Record<string, object>;
|
||||
required?: string[];
|
||||
schema: object;
|
||||
}
|
||||
89
src/lib/validator.ts
Normal file
89
src/lib/validator.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import SwaggerParser from '@apidevtools/swagger-parser';
|
||||
import type { ValidationResult, ValidationError } from './types.js';
|
||||
|
||||
export async function validateSpec(specPath: string): Promise<ValidationResult> {
|
||||
const errors: ValidationError[] = [];
|
||||
const warnings: ValidationError[] = [];
|
||||
|
||||
try {
|
||||
// Validate will throw on errors, but also returns the parsed spec
|
||||
await SwaggerParser.validate(specPath);
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
errors: [],
|
||||
warnings: [],
|
||||
};
|
||||
} catch (err) {
|
||||
const error = err as Error & { details?: Array<{ path: string[]; message: string }> };
|
||||
|
||||
// swagger-parser throws errors with details array for validation issues
|
||||
if (error.details && Array.isArray(error.details)) {
|
||||
for (const detail of error.details) {
|
||||
errors.push({
|
||||
path: detail.path?.join('.') || '',
|
||||
message: detail.message,
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Single error without details
|
||||
errors.push({
|
||||
path: '',
|
||||
message: error.message,
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
valid: false,
|
||||
errors,
|
||||
warnings,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function validateWithWarnings(specPath: string): Promise<ValidationResult> {
|
||||
const result = await validateSpec(specPath);
|
||||
|
||||
// Add additional checks that produce warnings
|
||||
try {
|
||||
const spec = await SwaggerParser.dereference(specPath);
|
||||
|
||||
// Check for operations without operationId
|
||||
if (spec.paths) {
|
||||
for (const [path, pathItem] of Object.entries(spec.paths)) {
|
||||
if (!pathItem) continue;
|
||||
for (const method of ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']) {
|
||||
const operation = (pathItem as Record<string, unknown>)[method] as { operationId?: string } | undefined;
|
||||
if (operation && !operation.operationId) {
|
||||
result.warnings.push({
|
||||
path: `paths.${path}.${method}`,
|
||||
message: 'Operation is missing operationId',
|
||||
severity: 'warning',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for missing descriptions on schemas
|
||||
const schemas = ('components' in spec && spec.components?.schemas) ||
|
||||
('definitions' in spec && (spec as { definitions?: object }).definitions) ||
|
||||
{};
|
||||
|
||||
for (const [name, schema] of Object.entries(schemas)) {
|
||||
if (schema && typeof schema === 'object' && !('description' in schema)) {
|
||||
result.warnings.push({
|
||||
path: `components.schemas.${name}`,
|
||||
message: 'Schema is missing description',
|
||||
severity: 'warning',
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// If we can't parse for warnings, just return validation result
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
241
src/tools/generate.ts
Normal file
241
src/tools/generate.ts
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
import { z } from 'zod';
|
||||
import { parseSpec } from '../lib/parser.js';
|
||||
import { formatTypes } from '../utils/format.js';
|
||||
|
||||
export const generateToolName = 'generate-types';
|
||||
|
||||
export const generateToolDescription = 'Generate TypeScript interfaces from OpenAPI component schemas. Optionally specify which schemas to generate.';
|
||||
|
||||
export const generateToolSchema = {
|
||||
path: z.string().describe('Path to the OpenAPI/Swagger spec file'),
|
||||
schemas: z.array(z.string()).optional().describe('Specific schema names to generate (all if omitted)'),
|
||||
};
|
||||
|
||||
export async function generateToolHandler({ path, schemas }: {
|
||||
path: string;
|
||||
schemas?: string[];
|
||||
}): Promise<{
|
||||
content: Array<{ type: 'text'; text: string }>;
|
||||
structuredContent: Record<string, unknown>;
|
||||
}> {
|
||||
try {
|
||||
const { spec } = await parseSpec(path);
|
||||
|
||||
const allSchemas = getSchemas(spec);
|
||||
|
||||
const schemasToGenerate = schemas
|
||||
? Object.fromEntries(
|
||||
Object.entries(allSchemas).filter(([name]) => schemas.includes(name))
|
||||
)
|
||||
: allSchemas;
|
||||
|
||||
if (Object.keys(schemasToGenerate).length === 0) {
|
||||
return {
|
||||
content: [{ type: 'text', text: 'No schemas found to generate.' }],
|
||||
structuredContent: {
|
||||
success: false,
|
||||
error: 'No schemas found',
|
||||
availableSchemas: Object.keys(allSchemas),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const types = generateTypeScript(schemasToGenerate);
|
||||
const text = formatTypes(types);
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text }],
|
||||
structuredContent: {
|
||||
success: true,
|
||||
types,
|
||||
generatedCount: Object.keys(schemasToGenerate).length,
|
||||
},
|
||||
};
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
return {
|
||||
content: [{ type: 'text', text: `Error generating types: ${error.message}` }],
|
||||
structuredContent: {
|
||||
success: false,
|
||||
error: error.message,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getSchemas(spec: object): Record<string, object> {
|
||||
// OpenAPI 3.x
|
||||
const spec3 = spec as { components?: { schemas?: Record<string, object> } };
|
||||
if (spec3.components?.schemas) {
|
||||
return spec3.components.schemas;
|
||||
}
|
||||
|
||||
// Swagger 2.0
|
||||
const spec2 = spec as { definitions?: Record<string, object> };
|
||||
if (spec2.definitions) {
|
||||
return spec2.definitions;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
function generateTypeScript(schemas: Record<string, object>): string {
|
||||
const lines: string[] = [];
|
||||
|
||||
for (const [name, schema] of Object.entries(schemas)) {
|
||||
const typeDef = schemaToTypeScript(name, schema);
|
||||
lines.push(typeDef);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function schemaToTypeScript(name: string, schema: object): string {
|
||||
const s = schema as {
|
||||
type?: string;
|
||||
description?: string;
|
||||
properties?: Record<string, object>;
|
||||
required?: string[];
|
||||
items?: object;
|
||||
enum?: (string | number)[];
|
||||
allOf?: object[];
|
||||
oneOf?: object[];
|
||||
anyOf?: object[];
|
||||
$ref?: string;
|
||||
};
|
||||
|
||||
const lines: string[] = [];
|
||||
|
||||
// Add JSDoc comment if description exists
|
||||
if (s.description) {
|
||||
lines.push(`/** ${s.description} */`);
|
||||
}
|
||||
|
||||
// Handle enums
|
||||
if (s.enum) {
|
||||
const values = s.enum.map(v => typeof v === 'string' ? `'${v}'` : v).join(' | ');
|
||||
lines.push(`export type ${name} = ${values};`);
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
// Handle object types
|
||||
if (s.type === 'object' || s.properties) {
|
||||
lines.push(`export interface ${name} {`);
|
||||
|
||||
if (s.properties) {
|
||||
const required = new Set(s.required || []);
|
||||
|
||||
for (const [propName, propSchema] of Object.entries(s.properties)) {
|
||||
const optional = required.has(propName) ? '' : '?';
|
||||
const propType = schemaToType(propSchema);
|
||||
const propDesc = (propSchema as { description?: string }).description;
|
||||
|
||||
if (propDesc) {
|
||||
lines.push(` /** ${propDesc} */`);
|
||||
}
|
||||
|
||||
lines.push(` ${propName}${optional}: ${propType};`);
|
||||
}
|
||||
}
|
||||
|
||||
lines.push('}');
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
// Handle array types
|
||||
if (s.type === 'array' && s.items) {
|
||||
const itemType = schemaToType(s.items);
|
||||
lines.push(`export type ${name} = ${itemType}[];`);
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
// Handle simple types
|
||||
const simpleType = schemaToType(schema);
|
||||
lines.push(`export type ${name} = ${simpleType};`);
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function schemaToType(schema: object): string {
|
||||
const s = schema as {
|
||||
type?: string;
|
||||
format?: string;
|
||||
$ref?: string;
|
||||
items?: object;
|
||||
enum?: (string | number)[];
|
||||
properties?: Record<string, object>;
|
||||
required?: string[];
|
||||
allOf?: object[];
|
||||
oneOf?: object[];
|
||||
anyOf?: object[];
|
||||
additionalProperties?: boolean | object;
|
||||
};
|
||||
|
||||
// Handle $ref (after dereferencing, these should be resolved, but handle just in case)
|
||||
if (s.$ref) {
|
||||
const refName = s.$ref.split('/').pop() || 'unknown';
|
||||
return refName;
|
||||
}
|
||||
|
||||
// Handle enums
|
||||
if (s.enum) {
|
||||
return s.enum.map(v => typeof v === 'string' ? `'${v}'` : v).join(' | ');
|
||||
}
|
||||
|
||||
// Handle allOf (intersection)
|
||||
if (s.allOf) {
|
||||
return s.allOf.map(schemaToType).join(' & ');
|
||||
}
|
||||
|
||||
// Handle oneOf/anyOf (union)
|
||||
if (s.oneOf || s.anyOf) {
|
||||
const schemas = s.oneOf || s.anyOf || [];
|
||||
return schemas.map(schemaToType).join(' | ');
|
||||
}
|
||||
|
||||
// Handle arrays
|
||||
if (s.type === 'array') {
|
||||
const itemType = s.items ? schemaToType(s.items) : 'unknown';
|
||||
return `${itemType}[]`;
|
||||
}
|
||||
|
||||
// Handle objects with additionalProperties
|
||||
if (s.type === 'object' && s.additionalProperties) {
|
||||
const valueType = typeof s.additionalProperties === 'object'
|
||||
? schemaToType(s.additionalProperties)
|
||||
: 'unknown';
|
||||
return `Record<string, ${valueType}>`;
|
||||
}
|
||||
|
||||
// Handle inline objects
|
||||
if (s.type === 'object' && s.properties) {
|
||||
const required = new Set(s.required || []);
|
||||
const props = Object.entries(s.properties)
|
||||
.map(([name, prop]) => {
|
||||
const optional = required.has(name) ? '' : '?';
|
||||
return `${name}${optional}: ${schemaToType(prop)}`;
|
||||
})
|
||||
.join('; ');
|
||||
return `{ ${props} }`;
|
||||
}
|
||||
|
||||
// Handle primitive types
|
||||
switch (s.type) {
|
||||
case 'string':
|
||||
if (s.format === 'date' || s.format === 'date-time') {
|
||||
return 'string'; // Could be Date, but string is safer
|
||||
}
|
||||
return 'string';
|
||||
case 'integer':
|
||||
case 'number':
|
||||
return 'number';
|
||||
case 'boolean':
|
||||
return 'boolean';
|
||||
case 'null':
|
||||
return 'null';
|
||||
case 'object':
|
||||
return 'Record<string, unknown>';
|
||||
default:
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
40
src/tools/parse.ts
Normal file
40
src/tools/parse.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import { z } from 'zod';
|
||||
import { parseSpec } from '../lib/parser.js';
|
||||
import { formatMetadata } from '../utils/format.js';
|
||||
|
||||
export const parseToolName = 'parse-spec';
|
||||
|
||||
export const parseToolDescription = 'Parse and analyze an OpenAPI/Swagger specification file. Returns metadata including version, title, server count, path count, and schema count.';
|
||||
|
||||
export const parseToolSchema = {
|
||||
path: z.string().describe('Path to the OpenAPI/Swagger spec file (YAML or JSON)'),
|
||||
};
|
||||
|
||||
export async function parseToolHandler({ path }: { path: string }): Promise<{
|
||||
content: Array<{ type: 'text'; text: string }>;
|
||||
structuredContent: Record<string, unknown>;
|
||||
}> {
|
||||
try {
|
||||
const { spec, metadata } = await parseSpec(path);
|
||||
|
||||
const text = formatMetadata(metadata);
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text }],
|
||||
structuredContent: {
|
||||
success: true,
|
||||
metadata,
|
||||
spec,
|
||||
},
|
||||
};
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
return {
|
||||
content: [{ type: 'text', text: `Error parsing spec: ${error.message}` }],
|
||||
structuredContent: {
|
||||
success: false,
|
||||
error: error.message,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
168
src/tools/query.ts
Normal file
168
src/tools/query.ts
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
import { z } from 'zod';
|
||||
import { parseSpec } from '../lib/parser.js';
|
||||
import { formatEndpoints } from '../utils/format.js';
|
||||
import type { EndpointInfo, EndpointFilter, ParameterInfo, ResponseInfo } from '../lib/types.js';
|
||||
import type { OpenAPIV3 } from 'openapi-types';
|
||||
|
||||
export const queryToolName = 'query-endpoints';
|
||||
|
||||
export const queryToolDescription = 'Search and filter API endpoints in an OpenAPI spec. Filter by method, path pattern, tag, or operation ID.';
|
||||
|
||||
export const queryToolSchema = {
|
||||
path: z.string().describe('Path to the OpenAPI/Swagger spec file'),
|
||||
method: z.string().optional().describe('Filter by HTTP method (GET, POST, etc.)'),
|
||||
pathPattern: z.string().optional().describe('Regex pattern to match path'),
|
||||
tag: z.string().optional().describe('Filter by tag name'),
|
||||
operationId: z.string().optional().describe('Filter by exact operation ID'),
|
||||
};
|
||||
|
||||
const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] as const;
|
||||
|
||||
export async function queryToolHandler(args: {
|
||||
path: string;
|
||||
method?: string;
|
||||
pathPattern?: string;
|
||||
tag?: string;
|
||||
operationId?: string;
|
||||
}): Promise<{
|
||||
content: Array<{ type: 'text'; text: string }>;
|
||||
structuredContent: Record<string, unknown>;
|
||||
}> {
|
||||
try {
|
||||
const { spec } = await parseSpec(args.path);
|
||||
|
||||
const filter: EndpointFilter = {
|
||||
method: args.method?.toLowerCase(),
|
||||
pathPattern: args.pathPattern,
|
||||
tag: args.tag,
|
||||
operationId: args.operationId,
|
||||
};
|
||||
|
||||
const endpoints = extractEndpoints(spec, filter);
|
||||
const text = formatEndpoints(endpoints);
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text }],
|
||||
structuredContent: {
|
||||
success: true,
|
||||
count: endpoints.length,
|
||||
endpoints,
|
||||
},
|
||||
};
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
return {
|
||||
content: [{ type: 'text', text: `Error querying endpoints: ${error.message}` }],
|
||||
structuredContent: {
|
||||
success: false,
|
||||
error: error.message,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function extractEndpoints(spec: object, filter: EndpointFilter): EndpointInfo[] {
|
||||
const endpoints: EndpointInfo[] = [];
|
||||
const paths = (spec as { paths?: Record<string, object> }).paths || {};
|
||||
|
||||
for (const [pathName, pathItem] of Object.entries(paths)) {
|
||||
if (!pathItem) continue;
|
||||
|
||||
for (const method of HTTP_METHODS) {
|
||||
const operation = (pathItem as Record<string, unknown>)[method] as OpenAPIV3.OperationObject | undefined;
|
||||
if (!operation) continue;
|
||||
|
||||
// Apply filters
|
||||
if (filter.method && method !== filter.method) continue;
|
||||
|
||||
if (filter.pathPattern) {
|
||||
try {
|
||||
const regex = new RegExp(filter.pathPattern);
|
||||
if (!regex.test(pathName)) continue;
|
||||
} catch {
|
||||
// Invalid regex, skip filter
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.tag && (!operation.tags || !operation.tags.includes(filter.tag))) continue;
|
||||
|
||||
if (filter.operationId && operation.operationId !== filter.operationId) continue;
|
||||
|
||||
// Extract endpoint info
|
||||
const endpoint: EndpointInfo = {
|
||||
method,
|
||||
path: pathName,
|
||||
operationId: operation.operationId,
|
||||
summary: operation.summary,
|
||||
description: operation.description,
|
||||
tags: operation.tags || [],
|
||||
parameters: extractParameters(operation, pathItem as OpenAPIV3.PathItemObject),
|
||||
requestBody: extractRequestBody(operation),
|
||||
responses: extractResponses(operation),
|
||||
};
|
||||
|
||||
endpoints.push(endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
function extractParameters(
|
||||
operation: OpenAPIV3.OperationObject,
|
||||
pathItem: OpenAPIV3.PathItemObject
|
||||
): ParameterInfo[] {
|
||||
const params: ParameterInfo[] = [];
|
||||
|
||||
// Combine path-level and operation-level parameters
|
||||
const allParams = [
|
||||
...(pathItem.parameters || []),
|
||||
...(operation.parameters || []),
|
||||
] as OpenAPIV3.ParameterObject[];
|
||||
|
||||
for (const param of allParams) {
|
||||
if ('$ref' in param) continue; // Skip refs (should be dereferenced)
|
||||
|
||||
params.push({
|
||||
name: param.name,
|
||||
in: param.in as 'query' | 'header' | 'path' | 'cookie',
|
||||
required: param.required || false,
|
||||
description: param.description,
|
||||
schema: param.schema as object | undefined,
|
||||
});
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
function extractRequestBody(operation: OpenAPIV3.OperationObject): EndpointInfo['requestBody'] {
|
||||
if (!operation.requestBody) return undefined;
|
||||
|
||||
const body = operation.requestBody as OpenAPIV3.RequestBodyObject;
|
||||
if ('$ref' in body) return undefined; // Skip refs
|
||||
|
||||
return {
|
||||
required: body.required || false,
|
||||
description: body.description,
|
||||
contentTypes: Object.keys(body.content || {}),
|
||||
};
|
||||
}
|
||||
|
||||
function extractResponses(operation: OpenAPIV3.OperationObject): ResponseInfo[] {
|
||||
const responses: ResponseInfo[] = [];
|
||||
|
||||
if (!operation.responses) return responses;
|
||||
|
||||
for (const [statusCode, response] of Object.entries(operation.responses)) {
|
||||
const resp = response as OpenAPIV3.ResponseObject;
|
||||
if ('$ref' in resp) continue; // Skip refs
|
||||
|
||||
responses.push({
|
||||
statusCode,
|
||||
description: resp.description,
|
||||
contentTypes: Object.keys(resp.content || {}),
|
||||
});
|
||||
}
|
||||
|
||||
return responses;
|
||||
}
|
||||
99
src/tools/schema.ts
Normal file
99
src/tools/schema.ts
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
import { z } from 'zod';
|
||||
import { parseSpec } from '../lib/parser.js';
|
||||
import { formatSchema } from '../utils/format.js';
|
||||
import type { SchemaInfo } from '../lib/types.js';
|
||||
|
||||
export const schemaToolName = 'get-schema';
|
||||
|
||||
export const schemaToolDescription = 'Get details of a component schema from an OpenAPI spec. Returns the full schema definition with resolved references.';
|
||||
|
||||
export const schemaToolSchema = {
|
||||
path: z.string().describe('Path to the OpenAPI/Swagger spec file'),
|
||||
schemaName: z.string().describe('Name of the schema in components/schemas (or definitions for Swagger 2.0)'),
|
||||
};
|
||||
|
||||
export async function schemaToolHandler({ path, schemaName }: {
|
||||
path: string;
|
||||
schemaName: string;
|
||||
}): Promise<{
|
||||
content: Array<{ type: 'text'; text: string }>;
|
||||
structuredContent: Record<string, unknown>;
|
||||
}> {
|
||||
try {
|
||||
const { spec } = await parseSpec(path);
|
||||
|
||||
const schema = findSchema(spec, schemaName);
|
||||
|
||||
if (!schema) {
|
||||
const available = getAvailableSchemas(spec);
|
||||
return {
|
||||
content: [{ type: 'text', text: `Schema '${schemaName}' not found. Available schemas: ${available.join(', ')}` }],
|
||||
structuredContent: {
|
||||
success: false,
|
||||
error: `Schema '${schemaName}' not found`,
|
||||
availableSchemas: available,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const schemaInfo: SchemaInfo = {
|
||||
name: schemaName,
|
||||
type: (schema as { type?: string }).type,
|
||||
description: (schema as { description?: string }).description,
|
||||
properties: (schema as { properties?: Record<string, object> }).properties,
|
||||
required: (schema as { required?: string[] }).required,
|
||||
schema,
|
||||
};
|
||||
|
||||
const text = formatSchema(schemaInfo);
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text }],
|
||||
structuredContent: {
|
||||
success: true,
|
||||
schema: schemaInfo,
|
||||
},
|
||||
};
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
return {
|
||||
content: [{ type: 'text', text: `Error getting schema: ${error.message}` }],
|
||||
structuredContent: {
|
||||
success: false,
|
||||
error: error.message,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function findSchema(spec: object, schemaName: string): object | null {
|
||||
// OpenAPI 3.x: components.schemas
|
||||
const spec3 = spec as { components?: { schemas?: Record<string, object> } };
|
||||
if (spec3.components?.schemas?.[schemaName]) {
|
||||
return spec3.components.schemas[schemaName];
|
||||
}
|
||||
|
||||
// Swagger 2.0: definitions
|
||||
const spec2 = spec as { definitions?: Record<string, object> };
|
||||
if (spec2.definitions?.[schemaName]) {
|
||||
return spec2.definitions[schemaName];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getAvailableSchemas(spec: object): string[] {
|
||||
// OpenAPI 3.x
|
||||
const spec3 = spec as { components?: { schemas?: Record<string, object> } };
|
||||
if (spec3.components?.schemas) {
|
||||
return Object.keys(spec3.components.schemas);
|
||||
}
|
||||
|
||||
// Swagger 2.0
|
||||
const spec2 = spec as { definitions?: Record<string, object> };
|
||||
if (spec2.definitions) {
|
||||
return Object.keys(spec2.definitions);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
38
src/tools/validate.ts
Normal file
38
src/tools/validate.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { z } from 'zod';
|
||||
import { validateWithWarnings } from '../lib/validator.js';
|
||||
import { formatValidation } from '../utils/format.js';
|
||||
|
||||
export const validateToolName = 'validate-spec';
|
||||
|
||||
export const validateToolDescription = 'Validate an OpenAPI/Swagger specification against the schema. Reports errors and warnings.';
|
||||
|
||||
export const validateToolSchema = {
|
||||
path: z.string().describe('Path to the OpenAPI/Swagger spec file (YAML or JSON)'),
|
||||
};
|
||||
|
||||
export async function validateToolHandler({ path }: { path: string }): Promise<{
|
||||
content: Array<{ type: 'text'; text: string }>;
|
||||
structuredContent: Record<string, unknown>;
|
||||
}> {
|
||||
try {
|
||||
const result = await validateWithWarnings(path);
|
||||
const text = formatValidation(result);
|
||||
|
||||
return {
|
||||
content: [{ type: 'text', text }],
|
||||
structuredContent: {
|
||||
success: true,
|
||||
...result,
|
||||
},
|
||||
};
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
return {
|
||||
content: [{ type: 'text', text: `Error validating spec: ${error.message}` }],
|
||||
structuredContent: {
|
||||
success: false,
|
||||
error: error.message,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
146
src/utils/format.ts
Normal file
146
src/utils/format.ts
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
import type { EndpointInfo, ParsedSpec, ValidationResult, SchemaInfo } from '../lib/types.js';
|
||||
|
||||
export function formatMetadata(metadata: ParsedSpec): string {
|
||||
const lines = [
|
||||
`# ${metadata.title}`,
|
||||
`Version: ${metadata.version}`,
|
||||
];
|
||||
|
||||
if (metadata.description) {
|
||||
lines.push(`Description: ${metadata.description}`);
|
||||
}
|
||||
|
||||
lines.push('');
|
||||
lines.push(`## Statistics`);
|
||||
lines.push(`- Paths: ${metadata.pathCount}`);
|
||||
lines.push(`- Operations: ${metadata.operationCount}`);
|
||||
lines.push(`- Schemas: ${metadata.schemaCount}`);
|
||||
|
||||
if (metadata.servers.length > 0) {
|
||||
lines.push('');
|
||||
lines.push(`## Servers`);
|
||||
metadata.servers.forEach(s => lines.push(`- ${s}`));
|
||||
}
|
||||
|
||||
if (metadata.tags.length > 0) {
|
||||
lines.push('');
|
||||
lines.push(`## Tags`);
|
||||
metadata.tags.forEach(t => lines.push(`- ${t}`));
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export function formatValidation(result: ValidationResult): string {
|
||||
const lines: string[] = [];
|
||||
|
||||
if (result.valid) {
|
||||
lines.push('Validation: PASSED');
|
||||
} else {
|
||||
lines.push('Validation: FAILED');
|
||||
}
|
||||
|
||||
if (result.errors.length > 0) {
|
||||
lines.push('');
|
||||
lines.push('## Errors');
|
||||
for (const err of result.errors) {
|
||||
const path = err.path ? `[${err.path}] ` : '';
|
||||
lines.push(`- ${path}${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.warnings.length > 0) {
|
||||
lines.push('');
|
||||
lines.push('## Warnings');
|
||||
for (const warn of result.warnings) {
|
||||
const path = warn.path ? `[${warn.path}] ` : '';
|
||||
lines.push(`- ${path}${warn.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export function formatEndpoints(endpoints: EndpointInfo[]): string {
|
||||
if (endpoints.length === 0) {
|
||||
return 'No endpoints found matching criteria.';
|
||||
}
|
||||
|
||||
const lines = [`Found ${endpoints.length} endpoint(s):`, ''];
|
||||
|
||||
for (const ep of endpoints) {
|
||||
lines.push(`## ${ep.method.toUpperCase()} ${ep.path}`);
|
||||
|
||||
if (ep.operationId) {
|
||||
lines.push(`Operation ID: ${ep.operationId}`);
|
||||
}
|
||||
|
||||
if (ep.summary) {
|
||||
lines.push(`Summary: ${ep.summary}`);
|
||||
}
|
||||
|
||||
if (ep.tags.length > 0) {
|
||||
lines.push(`Tags: ${ep.tags.join(', ')}`);
|
||||
}
|
||||
|
||||
if (ep.parameters.length > 0) {
|
||||
lines.push('');
|
||||
lines.push('### Parameters');
|
||||
for (const param of ep.parameters) {
|
||||
const required = param.required ? ' (required)' : '';
|
||||
lines.push(`- **${param.name}** [${param.in}]${required}`);
|
||||
if (param.description) {
|
||||
lines.push(` ${param.description}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ep.requestBody) {
|
||||
lines.push('');
|
||||
lines.push('### Request Body');
|
||||
const required = ep.requestBody.required ? ' (required)' : '';
|
||||
lines.push(`Content types${required}: ${ep.requestBody.contentTypes.join(', ')}`);
|
||||
if (ep.requestBody.description) {
|
||||
lines.push(ep.requestBody.description);
|
||||
}
|
||||
}
|
||||
|
||||
if (ep.responses.length > 0) {
|
||||
lines.push('');
|
||||
lines.push('### Responses');
|
||||
for (const resp of ep.responses) {
|
||||
lines.push(`- **${resp.statusCode}**: ${resp.description || 'No description'}`);
|
||||
}
|
||||
}
|
||||
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export function formatSchema(schema: SchemaInfo): string {
|
||||
const lines = [
|
||||
`# ${schema.name}`,
|
||||
];
|
||||
|
||||
if (schema.description) {
|
||||
lines.push(schema.description);
|
||||
}
|
||||
|
||||
if (schema.type) {
|
||||
lines.push(`Type: ${schema.type}`);
|
||||
}
|
||||
|
||||
lines.push('');
|
||||
lines.push('## Schema');
|
||||
lines.push('```json');
|
||||
lines.push(JSON.stringify(schema.schema, null, 2));
|
||||
lines.push('```');
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export function formatTypes(types: string): string {
|
||||
return ['## Generated TypeScript Types', '', '```typescript', types, '```'].join('\n');
|
||||
}
|
||||
181
test/fixtures/petstore.yaml
vendored
Normal file
181
test/fixtures/petstore.yaml
vendored
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
openapi: 3.0.3
|
||||
info:
|
||||
title: Pet Store API
|
||||
description: A sample Pet Store API for testing swagger-tools
|
||||
version: 1.0.0
|
||||
servers:
|
||||
- url: https://api.petstore.example.com/v1
|
||||
description: Production server
|
||||
- url: https://staging.petstore.example.com/v1
|
||||
description: Staging server
|
||||
tags:
|
||||
- name: pets
|
||||
description: Pet operations
|
||||
- name: store
|
||||
description: Store operations
|
||||
paths:
|
||||
/pets:
|
||||
get:
|
||||
operationId: listPets
|
||||
summary: List all pets
|
||||
tags:
|
||||
- pets
|
||||
parameters:
|
||||
- name: limit
|
||||
in: query
|
||||
description: Maximum number of pets to return
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
maximum: 100
|
||||
- name: status
|
||||
in: query
|
||||
description: Filter by status
|
||||
schema:
|
||||
$ref: '#/components/schemas/PetStatus'
|
||||
responses:
|
||||
'200':
|
||||
description: A list of pets
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
'400':
|
||||
description: Bad request
|
||||
post:
|
||||
operationId: createPet
|
||||
summary: Create a new pet
|
||||
tags:
|
||||
- pets
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/NewPet'
|
||||
responses:
|
||||
'201':
|
||||
description: Pet created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
'400':
|
||||
description: Invalid input
|
||||
/pets/{petId}:
|
||||
get:
|
||||
operationId: getPet
|
||||
summary: Get a pet by ID
|
||||
tags:
|
||||
- pets
|
||||
parameters:
|
||||
- name: petId
|
||||
in: path
|
||||
required: true
|
||||
description: The pet ID
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
responses:
|
||||
'200':
|
||||
description: Pet details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
'404':
|
||||
description: Pet not found
|
||||
delete:
|
||||
operationId: deletePet
|
||||
summary: Delete a pet
|
||||
tags:
|
||||
- pets
|
||||
parameters:
|
||||
- name: petId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
responses:
|
||||
'204':
|
||||
description: Pet deleted
|
||||
'404':
|
||||
description: Pet not found
|
||||
/store/inventory:
|
||||
get:
|
||||
operationId: getInventory
|
||||
summary: Get store inventory
|
||||
tags:
|
||||
- store
|
||||
responses:
|
||||
'200':
|
||||
description: Inventory counts by status
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: integer
|
||||
components:
|
||||
schemas:
|
||||
Pet:
|
||||
type: object
|
||||
description: A pet in the store
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Unique identifier
|
||||
name:
|
||||
type: string
|
||||
description: Pet name
|
||||
status:
|
||||
$ref: '#/components/schemas/PetStatus'
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Tag'
|
||||
NewPet:
|
||||
type: object
|
||||
description: Data for creating a new pet
|
||||
required:
|
||||
- name
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: Pet name
|
||||
status:
|
||||
$ref: '#/components/schemas/PetStatus'
|
||||
PetStatus:
|
||||
type: string
|
||||
description: Pet availability status
|
||||
enum:
|
||||
- available
|
||||
- pending
|
||||
- sold
|
||||
Tag:
|
||||
type: object
|
||||
description: A tag for categorizing pets
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
name:
|
||||
type: string
|
||||
Error:
|
||||
type: object
|
||||
description: Error response
|
||||
required:
|
||||
- code
|
||||
- message
|
||||
properties:
|
||||
code:
|
||||
type: integer
|
||||
message:
|
||||
type: string
|
||||
18
tsconfig.json
Normal file
18
tsconfig.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue