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>
14 KiB
Analysis: Feature Enhancements (Caching, OpenAPI 3.1, Batch, TypeScript Options)
Scope Affected
- 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
- Entry: MCP client sends JSON-RPC request via stdio
- Routing: McpServer routes to registered tool handler
- Parsing: Tool handler calls
parseSpec()with spec path - Processing: Tool-specific logic (filter, validate, generate)
- Response: Returns
{content: [...], structuredContent: {...}}
Key Dependencies
@apidevtools/swagger-parserv10.1.1 - Supports OpenAPI 3.1 (with workarounds)@modelcontextprotocol/sdkv1.25.2 - MCP protocolopenapi-typesv12.1.3 - TypeScript types (OpenAPIV3_1 namespace)zodv3.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:
// 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:
// 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
// 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:
// 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
- Detect 3.1 version in parser.ts metadata extraction
- Handle array types in generate.ts:
// type: ['string', 'null'] -> string | null if (Array.isArray(s.type)) { return s.type.map(t => t === 'null' ? 'null' : mapPrimitiveType(t)).join(' | '); } - Handle
constin generate.ts:if (s.const !== undefined) { return typeof s.const === 'string' ? `'${s.const}'` : String(s.const); } - 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:
// 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)
const results = [];
for (const path of paths) {
results.push(await parseSpec(path));
}
Option B: Parallel Processing (Faster)
const results = await Promise.allSettled(
paths.map(path => parseSpec(path))
);
Recommended Approach
Use Option B with concurrency limit:
// 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
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 interfacefor objectsexport typefor enums, arrays, primitives- JSDoc comments for descriptions
- Properties marked optional with
?
Proposed Options
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):
enumAsUnionvsenumAsEnuminterfacePrefix/interfaceSuffix
Phase 2 (Medium value):
indentationoptionsallOptional/allRequired
Phase 3 (Lower priority):
readonlyPropertiesincludeRefs
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):
export type PetStatus = 'available' | 'pending' | 'sold';
export interface Pet {
id: number;
name: string;
status?: PetStatus;
}
With enumAsEnum: true, interfacePrefix: 'I':
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-parserfor spec parsing/validation- Node.js
fsmodule for local file access - Node.js
https/httpfor 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'] constwith complex objects:const: {foo: 'bar'}$refwith siblingdescription- 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+enumAsEnumboth true - Schema name collision after prefix/suffix added
- Circular references with option changes
Testing Strategy
-
Caching
- Unit test cache hit/miss
- Test TTL expiration
- Test LRU eviction
- Test local file mtime detection
-
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
-
Batch Operations
- Test with 0, 1, many specs
- Test mix of success and failure
- Test concurrency limit respected
-
TypeScript Options
- Snapshot tests for each option combination
- Test prefix/suffix application
- Test enum generation modes
Implementation Order (Recommended)
- TypeScript Options - Most user-visible, low risk
- Caching - Performance win, moderate complexity
- OpenAPI 3.1 - Already mostly working, polish needed
- 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