swagger-tools/CLAUDE.md
rimskij cae5f7fce1 feat: add in-memory LRU cache for parsed specs
Add caching layer to improve performance when repeatedly accessing
the same OpenAPI specs:

- LRU cache with max 10 entries and 15-minute TTL
- Cache key includes mtime for local files (change detection)
- URL normalization for consistent remote spec caching
- noCache parameter on all tools to bypass cache
- Response includes cached:true/false indicator

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 17:09:21 +01:00

210 lines
5.7 KiB
Markdown

# swagger-tools
MCP server for parsing, validating, and querying OpenAPI/Swagger specifications.
## Quick Start
```bash
npm install
npm run build
npm start
```
## MCP Configuration
Add to `~/.claude.json`:
```json
{
"mcpServers": {
"swagger-tools": {
"command": "node",
"args": ["/Users/rimskij/projects/swagger-tools/dist/index.js"]
}
}
}
```
Restart Claude Code after configuration.
## Available Tools
| Tool | Purpose | Key Parameters |
|------|---------|----------------|
| `parse-spec` | Parse spec, show metadata | `path` |
| `validate-spec` | Validate against schema | `path` |
| `query-endpoints` | Search/filter endpoints | `path`, `method?`, `pathPattern?`, `tag?` |
| `get-schema` | Get schema details | `path`, `schemaName` |
| `generate-types` | Generate TypeScript | `path`, `schemas?`, `options?` |
## Usage Examples
```
# Natural language - Claude calls the right tool
"Parse the API spec at ./api.yaml"
"Show me all POST endpoints in api.yaml"
"Generate TypeScript types from https://petstore.swagger.io/v2/swagger.json"
"Is api.yaml valid?"
"What does the User schema look like?"
```
## Key Features
### Broken $ref Fallback
When specs have broken `$ref` pointers, parser falls back to non-dereferenced mode:
```typescript
// In lib/parser.ts
try {
spec = await SwaggerParser.dereference(specPath); // Try first
} catch {
spec = await SwaggerParser.parse(specPath); // Fallback
}
```
### .NET Name Sanitization
Converts .NET-style schema names to valid TypeScript:
```typescript
// In tools/generate.ts - sanitizeName()
"Namespace.Type" "Type"
"Generic`1[Inner]" "Generic_Inner"
"Company.Api.V5.ViewModel" "ViewModel"
```
### Spec Caching
Parsed specs are cached in-memory for improved performance:
- LRU cache with max 10 entries
- 15-minute TTL
- Local files: cache key includes mtime for change detection
- Remote URLs: normalized URL as cache key
All tools support `noCache: true` to bypass the cache:
```json
{
"name": "parse-spec",
"arguments": {
"path": "https://api.example.com/spec.json",
"noCache": true
}
}
```
### TypeScript Generation Options
The `generate-types` tool supports customization via `options`:
| Option | Type | Description |
|--------|------|-------------|
| `enumAsUnion` | boolean | Generate enums as union types (default) |
| `enumAsEnum` | boolean | Generate enums as TypeScript enums |
| `interfacePrefix` | string | Prefix for interface names (e.g., "I") |
| `interfaceSuffix` | string | Suffix for interface names (e.g., "Type") |
| `indentation` | '2'\|'4'\|'tab' | Indentation style (default: '2') |
Example:
```json
{
"name": "generate-types",
"arguments": {
"path": "api.yaml",
"options": {
"enumAsEnum": true,
"interfacePrefix": "I",
"indentation": "4"
}
}
}
```
## Project Structure
```
src/
├── index.ts # Entry point - registers tools, starts stdio server
├── tools/ # Tool implementations
│ ├── parse.ts # parseSpec() → metadata extraction
│ ├── validate.ts # validateSpec() → error collection
│ ├── query.ts # queryEndpoints() → filtering logic
│ ├── schema.ts # getSchema() → schema lookup
│ └── generate.ts # generateTypes() → TS code generation + sanitizeName()
├── lib/ # Shared utilities
│ ├── parser.ts # SwaggerParser wrapper (dereference with fallback)
│ ├── validator.ts # SwaggerParser.validate() wrapper
│ └── types.ts # TypeScript interfaces
└── utils/
└── format.ts # Human-readable output formatting
```
## Code Patterns
### Tool Structure
Each tool exports:
```typescript
export const toolName = 'tool-name';
export const toolDescription = '...';
export const toolSchema = { param: z.string() };
export async function toolHandler({ param }) { ... }
```
### Return Format
Tools return MCP-compatible responses:
```typescript
return {
content: [{ type: 'text', text: humanReadable }],
structuredContent: { success: true, data: ... }
};
```
### Error Handling
Wrap in try/catch, return error in both content and structuredContent:
```typescript
catch (err) {
return {
content: [{ type: 'text', text: `Error: ${err.message}` }],
structuredContent: { success: false, error: err.message }
};
}
```
## Development
```bash
npm run build # Compile TypeScript
npm run typecheck # Type check only
npm run dev # Run with tsx (hot reload)
```
## Testing
```bash
# List tools
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | node dist/index.js
# Local spec
echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"parse-spec","arguments":{"path":"test/fixtures/petstore.yaml"}}}' | node dist/index.js
# Remote URL
echo '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"parse-spec","arguments":{"path":"https://petstore.swagger.io/v2/swagger.json"}}}' | node dist/index.js
# Enterprise API with broken refs
echo '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"parse-spec","arguments":{"path":"https://projects.moravia.com/api/swagger/v5/swagger.json"}}}' | node dist/index.js
```
## Adding New Tools
1. Create `src/tools/newtool.ts` following existing pattern
2. Export name, description, schema, handler
3. Import and register in `src/index.ts`:
```typescript
server.tool(name, description, schema, handler);
```
4. Rebuild: `npm run build`
## Dependencies
| Package | Version | Purpose |
|---------|---------|---------|
| `@modelcontextprotocol/sdk` | ^1.0.0 | MCP protocol |
| `@apidevtools/swagger-parser` | ^10.1.0 | OpenAPI parsing |
| `zod` | ^3.23.0 | Schema validation |