Skip to main content
Glama
README.md
# oaslint MCP — The Hard Way

Build a small MCP server that **lints OpenAPI specs and detects breaking changes between two
versions of an API**, from the first byte of YAML to an agent calling your tools over stdio.

At the end you will have built a compact version of
[oaslint-mcp](https://github.com/0xmortuex/oaslint-mcp) yourself, and you will understand the idea
that almost every API-diff tool gets subtly wrong: **a request and a response vary in opposite
directions**, so the very same edit is breaking in one and harmless in the other.

## Why This Exists

Most MCP tutorials wrap a REST call. The interesting work in a useful server is elsewhere:

- reading hostile, messy input safely (YAML bombs, `$ref` loops, `../../` paths, URLs that
  want to be fetched);
- turning three dialects of a format into one model, so the rules are written once;
- deciding *what a change means*, not just that it happened;
- failing in ways that tell the agent what to do next.

## Who This Is For

Developers who have a "hello world" MCP server and want to build one that earns its keep, and
anyone who has shipped an API change and found out afterwards that it broke a client.

**Prerequisites:** Python and a rough idea of what OpenAPI is. Nothing to install except
`pip install -r requirements.txt` (the MCP SDK, PyYAML and anyio). No network, no other services.

## Modules

| # | Module | What You'll Build |
|---|--------|-------------------|
| 01 | [Load and Normalise](01-load-and-normalise/) | Read Swagger 2.0, OpenAPI 3.0 and 3.1 safely and squash them into one operation model |
| 02 | [Resolving Refs](02-resolving-refs/) | `$ref` with cycle detection, relative file refs in a sandbox, and remote refs that are never fetched |
| 03 | [Walking Schemas](03-walking-schemas/) | A schema walker that survives recursive schemas, and the outline an agent reads |
| 04 | [Flattening allOf](04-flattening-allof/) | Merge `allOf`, fold `nullable` / `type: [x, "null"]` / `const`, so equal things look equal |
| 05 | [Classifying Changes](05-classifying-changes/) | The core idea: request vs response variance, and a diff that says BREAKING, POTENTIALLY BREAKING or safe |
| 06 | [Validating Examples](06-validating-examples/) | A JSON Schema subset validator that reports a JSON pointer for every violation |
| 07 | [The MCP Wrapper](07-the-mcp-wrapper/) | Three tools, errors that instruct, a sandboxed `--dir`, and testing over real stdio |

Each module contains:

- `README.md`: the concept, the listing of the new `lite/` file (embedded from the real file, and
  CI fails if they drift apart), a walk-through, how to run it, how to break it, and what the full
  server does differently;
- `main.py`: runnable code that uses the new file and **asserts** its results.

## Running It

```
pip install -r requirements.txt
python verify.py          # every module, the way CI does
python verify.py 05       # one module
python 05-classifying-changes/main.py
```

The sample APIs are in `specs/`: the same small shop written as Swagger 2.0, OpenAPI 3.0 and 3.1, a
second version with real breaking changes, a recursive schema, and a split spec with file refs
plus an `evil.yaml` that tries to read outside the sandbox.

## The Library, File by File

```
lite/loader.py    01  parse, detect the version, normalise operations
lite/refs.py      02  Resolver: $ref, cycles, sandboxed files
lite/walk.py      03  outline of a (possibly recursive) schema
lite/flat.py      04  flatten allOf, normalise nullable
lite/diff.py      05  classify changes by direction
lite/validate.py  06  validate a JSON document
lite/server.py    07  the MCP server
```

Each module adds exactly one file. Nothing is hidden in a framework.

MIT licensed.