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

5.7 KiB

swagger-tools

MCP server for parsing, validating, and querying OpenAPI/Swagger specifications.

Quick Start

npm install
npm run build
npm start

MCP Configuration

Add to ~/.claude.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:

// 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:

// 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:

{
  "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:

{
  "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:

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:

return {
  content: [{ type: 'text', text: humanReadable }],
  structuredContent: { success: true, data: ... }
};

Error Handling

Wrap in try/catch, return error in both content and structuredContent:

catch (err) {
  return {
    content: [{ type: 'text', text: `Error: ${err.message}` }],
    structuredContent: { success: false, error: err.message }
  };
}

Development

npm run build      # Compile TypeScript
npm run typecheck  # Type check only
npm run dev        # Run with tsx (hot reload)

Testing

# 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:
    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