Skip to main content
Glama
README.md
# swagger-mcp

An MCP (Model Context Protocol) server that reads Swagger/OpenAPI specs and lets MCP clients explore API schemas and descriptions through natural language.

The core idea: register a project once, and the whole team can query it conversationally — no need to re-upload specs every time.

## Supported Clients

| Client | MCP Support | STDIO | Streamable HTTP |
|--------|-------------|-------|-----------------|
| Claude Desktop | O | O | O |
| Claude Code | O | O | O |
| Cursor | O | O | O |
| Gemini (Google) | O | — | O |
| ChatGPT (OpenAI) | X | — | — |

> Streamable HTTP mode is recommended for clients that only support URL-based connections (e.g., Gemini).

## Quick Start

### 1. Install & Build

```bash
git clone https://github.com/yunhwane/swagger-mcp.git
cd swagger-mcp
npm install
npm run build
```

### 2. Configure your MCP client

Two transport modes are available: **STDIO** (default) and **Streamable HTTP**.

#### Option A: STDIO (default)

**Claude Desktop** — edit `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "swagger-mcp": {
      "command": "node",
      "args": ["/absolute/path/to/swagger-mcp/dist/index.js"]
    }
  }
}
```

**Claude Code** — add `.mcp.json` in your project root:

```json
{
  "mcpServers": {
    "swagger-mcp": {
      "command": "node",
      "args": ["/absolute/path/to/swagger-mcp/dist/index.js"]
    }
  }
}
```

#### Option B: Streamable HTTP

Start the HTTP server separately, then point your client to the URL. This mode is ideal for development — `tsx watch` auto-restarts on code changes without requiring manual MCP reconnection.

```bash
# Start the server (dev mode with hot reload)
npm run dev:http

# Or production mode
npm run build && npm run start:http
```

**Claude Code** — add `.mcp.json` in your project root:

```json
{
  "mcpServers": {
    "swagger-mcp": {
      "url": "http://localhost:3000/mcp"
    }
  }
}
```

**Gemini CLI** — edit `~/.gemini/settings.json`:

```json
{
  "mcpServers": {
    "swagger-mcp": {
      "url": "http://localhost:3000/mcp"
    }
  }
}
```

The HTTP server listens on port 3000 by default (override with `PORT` env var).

### 3. Try it with the Petstore API

Once connected, just ask your MCP client:

> "Register the Petstore API from https://petstore3.swagger.io/api/v3/openapi.json and explore its endpoints."

Or walk through the drill-down workflow:

```
1. add_project        →  Register "petstore" with the spec URL
2. list_services      →  See registered services and their API groups
3. list_apis          →  Browse all endpoints for "petstore"
4. describe_api       →  "GET /pet/{petId}" → see parameters, request/response schemas
5. describe_component →  "#/components/schemas/Pet" → drill into a specific schema
```

### Example Conversations

- "Add the Petstore API from `https://petstore3.swagger.io/api/v3/openapi.json`."
- "What endpoints are available for managing pets?"
- "Describe the `GET /pet/{petId}` endpoint."
- "What fields does the `Pet` schema have?"
- "Compare the current spec against this new version URL."

## Features

- OpenAPI 3.0.x / 3.1.x support (JSON & YAML, URL or local file)
- 4-step drill-down: services → APIs → endpoint detail → component schemas
- Shallow `$ref` resolution — keeps responses concise while letting the LLM decide which schemas to explore
- Spec diff with breaking change detection (responses, requestBody, parameters, schemas)
- Snapshot store — auto-saves normalized specs on registration and after diffs, enabling offline comparison (max 5 per project)
- LRU spec cache (max 20 entries, 5 min TTL)
- Project registry persisted to `~/.swagger-mcp/registry.json`
- Built-in `help` tool for discoverability

## Tools (8)

| Tool | Description | Inputs |
|------|-------------|--------|
| `help` | Show available tools and recommended workflow | — |
| `add_project` | Register a new OpenAPI project (URL) | `projectId`, `name`, `source` |
| `list_projects` | List all registered projects | — |
| `list_services` | List registered services with their API groups (tags) | — |
| `list_apis` | List all API endpoints for a service | `serviceName` |
| `describe_api` | Get detailed info about a specific endpoint (parameters, request body, responses) | `serviceName`, `path`, `method` |
| `describe_component` | Look up component schemas by `$ref` paths | `serviceName`, `refs` |
| `diff_apis` | Compare saved snapshot (or registered spec) against a new source, with breaking change detection | `serviceName`, `newSource` |

### 4-Step Drill-Down Pattern

The center tools (`list_services` → `list_apis` → `describe_api` → `describe_component`) use **shallow resolution**: endpoint schemas are expanded one level, but component `$ref`s are preserved. This lets the LLM decide which schemas to drill into, keeping responses concise and navigable.

## Core Concepts

### Project

A reusable unit representing an API spec source. Each project has a `projectId`, name, and source URL. Project metadata is persisted to `~/.swagger-mcp/registry.json`.

```
Example: "petstore" project pointing to https://petstore3.swagger.io/api/v3/openapi.json
```

### Spec Cache

Parsed OpenAPI documents are cached in-memory (LRU, max 20 entries, 5-minute TTL) to avoid re-fetching on every query.

### Snapshot Store

When a project is registered via `add_project`, the spec is automatically normalized and saved as a snapshot. Each call to `diff_apis` that detects changes also saves a new snapshot. Snapshots are stored in `~/.swagger-mcp/snapshots/<projectId>/` (max 5 per project, deduplicated by content hash).

### Spec Diff

`diff_apis` compares the latest saved snapshot against a new spec source. If no snapshot exists, it falls back to fetching from the registered URL. The diff engine detects:

- Endpoint additions, removals, and modifications
- Parameter changes (type, required, location)
- Response status code and media type changes (with breaking change flags)
- RequestBody additions, removals, and schema changes
- Schema property and `$ref` changes

## Architecture

```
┌─────────────────────────────────────────────┐
│                MCP Client                    │
│   (Claude Desktop / Code / Cursor / Gemini)  │
└──────────────────┬──────────────────────────┘
                   │ MCP Protocol
                   │ (STDIO or Streamable HTTP)
┌──────────────────▼──────────────────────────┐
│              swagger-mcp Server              │
│                                              │
│  ┌────────────┐ ┌────────────┐ ┌─────────┐  │
│  │  Project   │ │   Center   │ │  Diff   │  │
│  │  Tools (2) │ │  Tools (4) │ │ Tool (1)│  │
│  └─────┬──────┘ └─────┬──────┘ └────┬────┘  │
│        │              │             │        │
│  ┌─────▼──────┐ ┌─────▼─────────────▼────┐  │
│  │  Registry  │ │      Spec Cache        │  │
│  │ (~/.swagger│ │    (in-memory LRU)     │  │
│  │  -mcp/)    │ │                        │  │
│  └────────────┘ └─────────┬──────────────┘  │
│                           │                  │
│                 ┌─────────▼──────────────┐   │
│                 │  Loader + Normalizer   │   │
│                 │  (fetch, parse,        │   │
│                 │   resolve $refs)       │   │
│                 └────────────────────────┘   │
└──────────────────────────────────────────────┘
```

1. **Registry** — stores project metadata, persists to disk
2. **Loader** — fetches OpenAPI specs from URLs or local files, parses JSON/YAML
3. **Normalizer** — resolves `$ref` references recursively with circular ref detection
4. **Differ** — computes structural diff between two normalized specs (endpoints, parameters, responses, requestBody, schemas)
5. **Spec Cache** — LRU in-memory cache for parsed OpenAPI documents
6. **Snapshot Store** — persists normalized specs to disk for reliable diff comparisons

## Tech Stack

- **Runtime**: Node.js 20+
- **Language**: TypeScript (strict mode, `noUncheckedIndexedAccess`)
- **MCP SDK**: `@modelcontextprotocol/sdk`
- **Validation**: `zod`
- **Build**: `tsup` (ESM-only, target `node20`)
- **Test**: `vitest`

## Development

```bash
npm run dev        # Run STDIO mode with tsx
npm run dev:http   # Run HTTP mode with tsx watch (auto-reload)
npm run build      # Build with tsup → dist/
npm run check      # TypeScript type check
npm run start:http # Run HTTP mode in production
npm test           # Run all tests (vitest)

# Test with MCP Inspector
npx @modelcontextprotocol/inspector node dist/index.js
```

### TDD Workflow

This project follows the Red-Green-Refactor cycle:

1. **RED** — Write a failing test first (`tests/` mirrors `src/` structure)
2. **GREEN** — Write the minimum implementation to pass the test
3. **REFACTOR** — Clean up while keeping tests green

Always run `npm run check && npm test` before finishing a change.

## Project Structure

```
src/
├── index.ts           # STDIO entry point
├── http.ts            # Streamable HTTP entry point
├── http-handler.ts    # HTTP request handler (session management, DNS rebinding protection)
├── server.ts          # Shared McpServer creation (tool registration)
├── registry.ts        # Project registry state management
├── loader.ts          # OpenAPI spec fetcher (URL/file, JSON/YAML)
├── normalizer.ts      # $ref resolution and spec normalization
├── differ.ts          # Spec diff engine (endpoints, responses, requestBody, schemas)
├── snapshot-store.ts  # Persistent snapshot storage for diff comparisons
├── spec-cache.ts      # In-memory LRU cache for parsed specs
├── types.ts           # TypeScript type definitions
└── tools/
    ├── project.ts     # add_project, list_projects
    ├── center.ts      # list_services, list_apis, describe_api, describe_component
    ├── diff.ts        # diff_apis
    └── help.ts        # help
tests/                 # Mirrors src/ structure (vitest)
  ├── tools/           # Tool unit tests
  ├── fixtures/        # Test OpenAPI specs (petstore variants)
  └── *.test.ts        # Unit tests for loader, normalizer, registry, etc.
```

## License

MIT

TDQS

B3.2/5.0

Scored across 9 tools

Disambiguation3/5

list_projects and list_services both list registered services, creating overlap, though descriptions distinguish them by detail. describe_component and describe_common_types both deal with component schemas, which could cause misselection. Other tools are clearly distinct.

Naming Consistency4/5

Most tools follow a verb_noun pattern in snake_case (add_project, list_projects, describe_api), but 'help' is a standalone verb and 'diff_apis' uses an unconventional verb. Minor deviations from an otherwise consistent style.

Tool Count5/5

With 9 tools, the set is well-scoped for managing OpenAPI services, fitting the ideal 3-15 range. Each tool addresses a specific aspect of the domain without bloat.

Completeness3/5

The set covers registration, listing, and detailed descriptions, but lacks update/delete operations for projects and a general component listing tool. These are notable gaps for a complete lifecycle, though core workflows are supported.

Maintenance

ActivityInactive
ResponsivenessNo issues