swagger-tools/docs/analysis/feature-enhancements-analysis.md
rimskij a4fc2df4ea feat: add TypeScript generation options to generate-types tool
Add configurable options for customizing TypeScript output:
- enumAsUnion/enumAsEnum: control enum generation style
- interfacePrefix/interfaceSuffix: naming conventions for interfaces
- indentation: 2 spaces, 4 spaces, or tab

Includes validation for mutually exclusive options and valid
TypeScript identifier prefixes/suffixes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 15:42:39 +01:00

470 lines
14 KiB
Markdown

# Analysis: Feature Enhancements (Caching, OpenAPI 3.1, Batch, TypeScript Options)
## Scope Affected
- [x] Backend
- [ ] Frontend
- [ ] Database
- [ ] Infrastructure
## Summary
Analysis of four proposed feature enhancements for the swagger-tools MCP server: remote spec caching, OpenAPI 3.1 support improvements, batch operations, and TypeScript generation options. The current codebase is well-structured with clear separation of concerns, making these enhancements straightforward to implement.
## Files Involved
| File | Purpose |
|------|---------|
| `/Users/rimskij/projects/swagger-tools/src/index.ts` | MCP server entry point - tool registration |
| `/Users/rimskij/projects/swagger-tools/src/lib/parser.ts` | Spec parsing with dereference fallback |
| `/Users/rimskij/projects/swagger-tools/src/lib/validator.ts` | Spec validation wrapper |
| `/Users/rimskij/projects/swagger-tools/src/lib/types.ts` | TypeScript interfaces for parsed data |
| `/Users/rimskij/projects/swagger-tools/src/tools/parse.ts` | parse-spec tool handler |
| `/Users/rimskij/projects/swagger-tools/src/tools/validate.ts` | validate-spec tool handler |
| `/Users/rimskij/projects/swagger-tools/src/tools/query.ts` | query-endpoints tool handler |
| `/Users/rimskij/projects/swagger-tools/src/tools/schema.ts` | get-schema tool handler |
| `/Users/rimskij/projects/swagger-tools/src/tools/generate.ts` | generate-types tool with sanitizeName() |
| `/Users/rimskij/projects/swagger-tools/src/utils/format.ts` | Human-readable output formatters |
## Current Architecture
```
src/
├── index.ts # Entry: registers 5 tools, starts stdio server
├── lib/
│ ├── parser.ts # parseSpec() - dereference with fallback
│ ├── validator.ts # validateSpec(), validateWithWarnings()
│ └── types.ts # TypeScript interfaces
├── tools/
│ ├── parse.ts # Calls parseSpec(), returns metadata
│ ├── validate.ts # Calls validateWithWarnings()
│ ├── query.ts # Filters endpoints from parsed spec
│ ├── schema.ts # Looks up schema by name
│ └── generate.ts # Converts schemas to TypeScript
└── utils/
└── format.ts # Markdown formatters
```
### Execution Flow
1. **Entry**: MCP client sends JSON-RPC request via stdio
2. **Routing**: McpServer routes to registered tool handler
3. **Parsing**: Tool handler calls `parseSpec()` with spec path
4. **Processing**: Tool-specific logic (filter, validate, generate)
5. **Response**: Returns `{content: [...], structuredContent: {...}}`
### Key Dependencies
- `@apidevtools/swagger-parser` v10.1.1 - Supports OpenAPI 3.1 (with workarounds)
- `@modelcontextprotocol/sdk` v1.25.2 - MCP protocol
- `openapi-types` v12.1.3 - TypeScript types (OpenAPIV3_1 namespace)
- `zod` v3.23.0 - Schema validation for tool parameters
---
## Feature 1: Caching for Remote Specs
### Current Behavior
Every tool call to `parseSpec()` fetches and parses the spec fresh:
```typescript
// src/lib/parser.ts:7-11
export async function parseSpec(specPath: string, options?: { dereference?: boolean }): Promise<{...}>
// Always fetches/reads from scratch
spec = await SwaggerParser.dereference(specPath);
```
### Proposed Solution
Add an in-memory LRU cache in `src/lib/parser.ts`:
```typescript
// New file: src/lib/cache.ts
interface CacheEntry {
spec: OpenAPISpec;
metadata: ParsedSpec;
dereferenced: boolean;
timestamp: number;
etag?: string;
}
class SpecCache {
private cache: Map<string, CacheEntry>;
private maxSize: number;
private ttlMs: number;
constructor(maxSize = 10, ttlMinutes = 15) {...}
get(key: string): CacheEntry | undefined {...}
set(key: string, entry: CacheEntry): void {...}
invalidate(key?: string): void {...}
}
```
### Files to Modify
| File | Change |
|------|--------|
| `src/lib/cache.ts` | NEW: LRU cache with TTL |
| `src/lib/parser.ts` | Import cache, wrap parseSpec |
| `src/tools/parse.ts` | Add optional `noCache` parameter |
| `src/tools/validate.ts` | Add optional `noCache` parameter |
| `src/tools/query.ts` | Add optional `noCache` parameter |
| `src/tools/schema.ts` | Add optional `noCache` parameter |
| `src/tools/generate.ts` | Add optional `noCache` parameter |
### Cache Key Strategy
```typescript
// For remote URLs: normalize URL (remove trailing slash, sort query params)
// For local files: use absolute path + file mtime
function getCacheKey(specPath: string): string {
if (specPath.startsWith('http://') || specPath.startsWith('https://')) {
return normalizeUrl(specPath);
}
const resolved = path.resolve(specPath);
const stat = fs.statSync(resolved);
return `${resolved}:${stat.mtimeMs}`;
}
```
### Risks
| Risk | Impact | Mitigation |
|------|--------|------------|
| Stale cache for rapidly changing specs | MEDIUM | Add `noCache` param, short TTL (15 min) |
| Memory pressure with large specs | MEDIUM | LRU eviction, max 10 entries |
| Local file changes not detected | LOW | Include mtime in cache key |
---
## Feature 2: OpenAPI 3.1 Support
### Current State
**Good news**: The underlying `@apidevtools/swagger-parser` v10.1.1 already supports OpenAPI 3.1:
```javascript
// node_modules/@apidevtools/swagger-parser/lib/index.js
const supported31Versions = ["3.1.0", "3.1.1"];
```
**Gap**: The TypeScript generation in `generate.ts` doesn't handle 3.1-specific JSON Schema features.
### OpenAPI 3.1 Differences from 3.0
| Feature | 3.0 | 3.1 | Code Impact |
|---------|-----|-----|-------------|
| Schema dialect | OpenAPI Schema | JSON Schema 2020-12 | Type generation |
| `type` field | Single string | Array allowed | schemaToType() |
| `nullable` | `nullable: true` | `type: ['string', 'null']` | schemaToType() |
| `const` | Not supported | Supported | schemaToType() |
| `exclusiveMinimum` | boolean | number | Not TS-relevant |
| `webhooks` | Not supported | Top-level field | metadata extraction |
| `$ref` siblings | Not allowed | Allowed with summary/desc | Reference handling |
| `pathItems` | Not in components | In components | Schema lookup |
### Proposed Changes
1. **Detect 3.1 version** in parser.ts metadata extraction
2. **Handle array types** in generate.ts:
```typescript
// type: ['string', 'null'] -> string | null
if (Array.isArray(s.type)) {
return s.type.map(t => t === 'null' ? 'null' : mapPrimitiveType(t)).join(' | ');
}
```
3. **Handle `const`** in generate.ts:
```typescript
if (s.const !== undefined) {
return typeof s.const === 'string' ? `'${s.const}'` : String(s.const);
}
```
4. **Extract webhooks** in parser.ts metadata
### Files to Modify
| File | Change |
|------|--------|
| `src/lib/parser.ts` | Add webhook count to metadata |
| `src/lib/types.ts` | Add `webhookCount?: number` to ParsedSpec |
| `src/tools/generate.ts` | Handle 3.1 type arrays, const |
| `src/utils/format.ts` | Display webhook count |
### Risks
| Risk | Impact | Mitigation |
|------|--------|------------|
| Array type combinations complex | LOW | Map each type, join with `\|` |
| `const` type inference tricky | LOW | Use literal types |
| Existing 3.0 specs break | LOW | Changes are additive |
---
## Feature 3: Batch Operations
### Proposed Design
Add a new tool `batch-process` that accepts multiple specs:
```typescript
// src/tools/batch.ts
export const batchToolSchema = {
paths: z.array(z.string()).describe('Paths to OpenAPI specs'),
operation: z.enum(['parse', 'validate']).describe('Operation to perform'),
};
```
### Implementation Options
**Option A: Sequential Processing** (Simpler)
```typescript
const results = [];
for (const path of paths) {
results.push(await parseSpec(path));
}
```
**Option B: Parallel Processing** (Faster)
```typescript
const results = await Promise.allSettled(
paths.map(path => parseSpec(path))
);
```
### Recommended Approach
Use **Option B** with concurrency limit:
```typescript
// src/lib/batch.ts
async function batchProcess<T>(
items: string[],
processor: (item: string) => Promise<T>,
concurrency = 5
): Promise<Array<{ path: string; result?: T; error?: string }>> {
const results: Array<...> = [];
const executing: Promise<void>[] = [];
for (const item of items) {
const p = processor(item)
.then(result => results.push({ path: item, result }))
.catch(error => results.push({ path: item, error: error.message }));
executing.push(p);
if (executing.length >= concurrency) {
await Promise.race(executing);
}
}
await Promise.all(executing);
return results;
}
```
### Files to Modify
| File | Change |
|------|--------|
| `src/lib/batch.ts` | NEW: Parallel processing utility |
| `src/tools/batch.ts` | NEW: batch-process tool |
| `src/index.ts` | Register batch-process tool |
| `src/utils/format.ts` | Add formatBatchResults() |
### Output Format
```typescript
interface BatchResult {
success: boolean;
total: number;
succeeded: number;
failed: number;
results: Array<{
path: string;
success: boolean;
data?: ParsedSpec | ValidationResult;
error?: string;
}>;
}
```
### Risks
| Risk | Impact | Mitigation |
|------|--------|------------|
| Resource exhaustion with many specs | HIGH | Concurrency limit (5) |
| One failure stops all | LOW | Promise.allSettled continues |
| Output too large | MEDIUM | Summarize in text, full in structured |
---
## Feature 4: TypeScript Generation Options
### Current Behavior
`generate-types` produces a fixed output format:
- `export interface` for objects
- `export type` for enums, arrays, primitives
- JSDoc comments for descriptions
- Properties marked optional with `?`
### Proposed Options
```typescript
export const generateToolSchema = {
path: z.string().describe('Path to the OpenAPI/Swagger spec file'),
schemas: z.array(z.string()).optional(),
options: z.object({
// Output style
interfacePrefix: z.string().optional().describe('Prefix for interfaces (e.g., "I")'),
interfaceSuffix: z.string().optional().describe('Suffix for interfaces (e.g., "Type")'),
enumAsUnion: z.boolean().optional().describe('Generate enums as union types (default: true)'),
enumAsEnum: z.boolean().optional().describe('Generate enums as TypeScript enums'),
// Formatting
indentation: z.enum(['2', '4', 'tab']).optional().describe('Indentation style'),
semicolons: z.boolean().optional().describe('Add trailing semicolons'),
// Optionality
allOptional: z.boolean().optional().describe('Make all properties optional'),
allRequired: z.boolean().optional().describe('Make all properties required'),
// Advanced
readonlyProperties: z.boolean().optional().describe('Mark properties as readonly'),
exportDefault: z.boolean().optional().describe('Use default exports'),
includeRefs: z.boolean().optional().describe('Include $ref comments'),
}).optional(),
};
```
### Implementation Priority
**Phase 1** (High value, low effort):
- `enumAsUnion` vs `enumAsEnum`
- `interfacePrefix` / `interfaceSuffix`
**Phase 2** (Medium value):
- `indentation` options
- `allOptional` / `allRequired`
**Phase 3** (Lower priority):
- `readonlyProperties`
- `includeRefs`
### Files to Modify
| File | Change |
|------|--------|
| `src/tools/generate.ts` | Add options parameter, conditional generation |
| `src/lib/types.ts` | Add TypeScriptOptions interface |
### Example Output Variations
**Default (current)**:
```typescript
export type PetStatus = 'available' | 'pending' | 'sold';
export interface Pet {
id: number;
name: string;
status?: PetStatus;
}
```
**With `enumAsEnum: true, interfacePrefix: 'I'`**:
```typescript
export enum PetStatus {
Available = 'available',
Pending = 'pending',
Sold = 'sold'
}
export interface IPet {
id: number;
name: string;
status?: PetStatus;
}
```
### Risks
| Risk | Impact | Mitigation |
|------|--------|------------|
| Option combinations create invalid TS | LOW | Validate combinations |
| Too many options confuse users | MEDIUM | Good defaults, clear docs |
| Breaking existing behavior | LOW | All options optional with current defaults |
---
## Dependencies
### Upstream (What calls this)
- MCP Clients (Claude Code, etc.) call tools via JSON-RPC
- All tools use `parseSpec()` from lib/parser.ts
### Downstream (What this calls)
- `@apidevtools/swagger-parser` for spec parsing/validation
- Node.js `fs` module for local file access
- Node.js `https`/`http` for remote fetch (via swagger-parser)
---
## Edge Cases
### Caching
- Spec URL returns 304 Not Modified - should reuse cache
- Local file deleted between cache set and get - invalidate on error
- Concurrent requests for same URL - deduplicate in-flight requests
### OpenAPI 3.1
- Mixed array types: `type: ['string', 'integer', 'null']`
- `const` with complex objects: `const: {foo: 'bar'}`
- `$ref` with sibling `description` - merge into output
### Batch Operations
- Empty paths array - return immediately with success
- Mix of local and remote paths - process in parallel
- Single path failure mid-batch - continue, report in results
### TypeScript Options
- Invalid combination: `enumAsUnion` + `enumAsEnum` both true
- Schema name collision after prefix/suffix added
- Circular references with option changes
---
## Testing Strategy
1. **Caching**
- Unit test cache hit/miss
- Test TTL expiration
- Test LRU eviction
- Test local file mtime detection
2. **OpenAPI 3.1**
- Add 3.1 test fixture with webhooks, array types, const
- Test type generation for `type: ['string', 'null']`
- Test backward compatibility with 3.0 spec
3. **Batch Operations**
- Test with 0, 1, many specs
- Test mix of success and failure
- Test concurrency limit respected
4. **TypeScript Options**
- Snapshot tests for each option combination
- Test prefix/suffix application
- Test enum generation modes
---
## Implementation Order (Recommended)
1. **TypeScript Options** - Most user-visible, low risk
2. **Caching** - Performance win, moderate complexity
3. **OpenAPI 3.1** - Already mostly working, polish needed
4. **Batch Operations** - New tool, can be done independently
---
## Next Steps
- `/implement` - Proceed with implementation (specify which feature)
- `/create work` - Create detailed task documentation for each feature