Point it at an API source and get back a TypeScript SDK, a Python SDK, a CLI, an MCP server and docs. All of them are generated from one content-hashed intermediate representation, so they stay in sync.
Every team that consumes an API ends up hand-writing the same plumbing: a client library, a CLI, agent tooling, reference docs. Then the spec changes and all of those fall out of sync.
API Hub turns that around. You give it a source (an OpenAPI document, a GitHub repository, an existing SDK, or a running MCP server) and it pulls that source through a fixed pipeline down to a single normalized, content-hashed intermediate representation (IR). Every output surface is then projected from that IR. The surfaces are pure functions of the IR, so the same source always produces the same output, and only a change in the source can change a surface.
Around the engine sits a Studio, a Next.js web app with version control for inputs, IR-level diffs between versions, one-click hosting of generated MCP servers and CLIs, and one-click publishing of the SDKs to npm and PyPI.
I built this as an AI Engineering intern at September AI (September Platforms), as one of the agentic-UI prototypes in the company's internal platform work. This repository is the standalone version of the connector pipeline and its Studio.
- Four input adapters, one contract. Acquire an API from a GitHub repo (cloned with a PAT, pinned to a commit SHA), an OpenAPI file or URL, an existing SDK directory, or a live MCP server.
- Spec-less sources. With no OpenAPI doc, the SDK adapter derives operations by introspecting TypeScript and Python client signatures, and the MCP adapter reads a server's advertised tools and maps each
inputSchemato an operation. - Five generated surfaces from every source. A TypeScript SDK, a Python SDK, a CLI, an MCP server with no dependencies (Streamable HTTP), and reference docs.
- Deterministic, content-hashed IR. The canonical artifact and the IR are both hashed with sha256 over canonical (key-sorted) content, excluding timestamps and provenance. Same source in, same hash out.
- Stable operation IDs. Identifiers come from
operationId(or method + path), not file position, so reordering or editing the spec never renames an operation. Collisions are de-duplicated and reported. - Bad specs fail loudly. Validation blocks on real errors (undeclared path params, duplicate operation IDs, unresolved
$refs), andcn buildrefuses to emit IR from an invalid spec. - Repair proposals that never mutate. The ingest stage drafts deterministic fix suggestions for a human to accept. It never rewrites your spec on its own.
- Studio version control. Each re-sync of a source records a new version with an IR diff (operations added, removed or changed, with severity). An optional watcher re-checks GitHub and URL sources and snapshots a new version whenever the content hash changes.
- One-click distribution. Host a generated MCP server (
POST /mcp) or CLI (as an HTTP command service at/run) as a managed local process with start / stop / status / live logs, download the CLI package, or publish the SDKs. - Auth and secrets. Email/password accounts with hand-written JWT (HS256) sessions. Upstream API tokens are AES-GCM encrypted at rest and never sent to the browser.
- Round-trip conversion tests. After projection, the generated surfaces are tested back against the IR so the pipeline catches its own regressions.
A source "climbs once" through the pipeline. Each stage strips out one kind of mess, and once you reach the IR, every surface fans out from it.
SourceRef --acquire--> CanonicalArtifact --ingest--> ValidatedArtifact --build--> IR --project--> surfaces --test--> ok
(4 inputs) (origin-blind, pinned) (adapt/assemble/ (hashed) SDK/MCP/CLI/docs (round-trip)
validate/repair)
Each input type has its own adapter (packages/acquire/src/adapters). The GitHub adapter clones with a PAT, pins the commit SHA, finds the spec, and bundles its $refs. The OpenAPI adapter loads a file or URL and bundles external refs into one self-contained document. The SDK adapter uses an embedded spec if present and otherwise introspects client signatures. The MCP adapter reads advertised tools over a manifest or stdio. The result is an origin-blind canonical artifact: no repo paths or URLs survive into the document, and a contentHash plus the pinned revision are attached as provenance.
Ingestion is three steps in one package (packages/ingest):
- Adapt normalizes any version down to one OpenAPI 3.1 target shape (3.0 to 3.1 normalization, best-effort Swagger 2.0).
- Assemble merges everything into a single self-contained document, namespaces multi-spec inputs, and notes circular references.
- Validate + Repair runs a blocking lint ("is it correct?") and a separate advisory pass ("is it clear?") that drafts repair proposals. Validation errors stop the pipeline; repair proposals are never auto-applied.
packages/ir-core walks the validated document and produces the normalized IR. Every input was already flattened to one OpenAPI 3.1 shape, so the builder is the same whatever the origin: an operation gives the same IR node whether it came from a repo, a spec, an SDK, or an MCP server. Operations get stable, identity-based IDs and a deterministic sort. The hashed core leaves out provenance, so what the API is drives the hash, not where it came from.
packages/projection holds one generator per surface: sdk-typescript, sdk-python, mcp, cli, and docs. They all read the same operations, parameters, verbs, and paths off the IR. The MCP generator emits a server with no dependencies that exposes POST /mcp and GET /health, which an agent can bind to directly.
After projection, conversion tests round-trip the generated surfaces back against the IR to confirm nothing was lost. The pipeline reports passed and failed counts and marks the run ok only when no test fails.
The engine is wrapped in a Next.js 15 / React 19 app (apps/studio) with a status-driven UI and a streaming run view (Server-Sent Events per stage). The pipeline lives in one place (lib/cn/pipeline.ts) and is called by both the live streaming run and the headless watcher. The watcher can run in acquireOnly mode to fingerprint a source and skip codegen when nothing changed. Projects, versions, IR snapshots, PATs, and encrypted secrets are stored in PostgreSQL through pg (no ORM). Distribution always re-projects from the pinned IR with the current generator instead of trusting a stored snapshot, so an old version still picks up newer generator features.
- 5 output surfaces from 4 input types, all reduced through a single IR, across 6 engine packages plus the Studio.
- Determinism by construction: sha256 hashing over canonical content at both the artifact and IR layers means a byte-identical source yields a byte-identical IR.
- Self-checking pipeline: every run ends with round-trip conversion tests, and vitest unit tests cover acquire, ingest, ir-core, projection, contracts, and the CLI (including its lock and dotenv logic).
- Recovering specs that were never published: TypeScript and Python SDK introspection and MCP tool discovery rebuild an OpenAPI contract where none exists.
- Language: TypeScript (Node.js >= 20)
- Monorepo: npm workspaces, with 6 engine packages (
contracts,acquire,ingest,ir-core,projection,cli) plus the Studio app - Engine libraries:
@apidevtools/swagger-parser,js-yaml(few other dependencies) - Studio: Next.js 15 (App Router), React 19, Tailwind CSS,
lucide-react,jszip - Data: PostgreSQL 16 via
pg(no ORM); custom store/diff layer - Security: hand-written JWT (HS256) sessions, AES-GCM secret encryption at rest
- Tooling: vitest, tsx, tsc
- Publishing: npm (TypeScript SDK); Python
build+twine(Python SDK) - Infra: Docker Compose (PostgreSQL 16 on port 5433)
- Node.js 20 or newer
- Docker (for the Studio's PostgreSQL), optional if you only use the CLI
- A GitHub PAT if you want to acquire from private repos
git clone https://github.com/DCode-v05/API-Hub.git
cd API-Hub
npm installA single .env at the repo root is read by both the CLI and the Studio.
cp .env.example .envCN_GITHUB_PAT=ghp_your_token_here # for --github inputs
DATABASE_URL=postgres://postgres:postgres@localhost:5433/connector_network # Studio DB
# Optional, only needed to publish SDKs from the Studio:
STUDIO_NPM_SCOPE=@your-scope
STUDIO_NPM_TOKEN=npm_xxx
STUDIO_PYPI_PREFIX=cn-
STUDIO_PYPI_TOKEN=pypi-xxxdocker compose up -d # PostgreSQL 16 on port 5433; schema bootstraps itself on first request# CLI: full pipeline (acquire → ingest → build → project → test) into ./out
node packages/cli/bin/cn.mjs run --openapi ./samples/openapi/tasks-api.yaml
# Studio: Next.js dev server on http://localhost:4000
npm run studioCall the bin launcher directly: node packages/cli/bin/cn.mjs <command> …. On macOS/Linux a shell shim helps (cn() { node packages/cli/bin/cn.mjs "$@"; }), or run npm link once for a global cn.
# Run the whole pipeline (repeat input flags to process several at once)
cn run --openapi ./samples/openapi/tasks-api.yaml
cn run --sdk ./fixtures/sdk-sample
cn run --mcp ./fixtures/mcp-sample/tools.json
# …or drive the stages one at a time
cn acquire --openapi ./samples/openapi/tasks-api.yaml # → canonical artifact
cn ingest --openapi ./samples/openapi/tasks-api.yaml # → validated artifact + repair proposals
cn build --openapi ./samples/openapi/tasks-api.yaml -o out/ # → content-hashed IR (fails loudly)
cn project --openapi ./samples/openapi/tasks-api.yaml # → surfaces/ (SDK · MCP · CLI · docs)
cn <command> --help # full flag listDon't use
npm run cn -- …, because npm intercepts the--openapi/-oflags. Call the bin launcher directly.
A bare cn run with no flags reads the input list from cn.config.json. Its first entry is a private GitHub repo (DCode-v05/Test), so edit or remove that entry unless you have access to it.
- Open
http://localhost:4000and create an account. - Pick an input (OpenAPI / GitHub / SDK / MCP), give it a Project Name, and save. This creates version 1.
- Each re-sync (manual or via the auto-sync watcher) records a new version with an IR diff.
- Open a project → Distribute to host the MCP server / CLI, download the CLI package, or publish the SDKs to npm / PyPI.
npm run typecheck # tsc --noEmit across all packages
npm test # vitestAPI-Hub/
│
├── packages/ # The connector engine (npm workspaces)
│ ├── contracts/ # Shared types: SourceRef, CanonicalArtifact, ValidatedArtifact, IR…
│ ├── acquire/ # 4 input adapters + OpenAPI bundling + content-hash pinning
│ ├── ingest/ # adapt · assemble · validate · repair
│ ├── ir-core/ # stable operation IDs · build IR · canonicalize + hash
│ ├── projection/ # generators: sdk-typescript · sdk-python · mcp · cli · docs
│ └── cli/ # the `cn` command (run | acquire | ingest | build | project)
│
├── apps/
│ └── studio/ # Next.js 15 web app (the Studio)
│ ├── app/ # App Router pages + API routes (projects, runs, host, publish…)
│ ├── components/ # React UI (inputs, projects, distribute, history, cli, run)
│ ├── lib/ # server (db · store · hosts · publish · jwt) + client + pipeline glue
│ └── scripts/ # DB init / import / migration scripts
│
├── samples/ # Example inputs (openapi · github · mcp · sdk-*)
├── fixtures/ # Test fixtures (lumen · adversarial · *-sample)
├── docs/ # QA write-up (quality-infra-qa.html)
├── scripts/ # qa.mts
├── docker-compose.yml # PostgreSQL 16 for the Studio (port 5433)
├── cn.config.json # Default input list for a bare `cn run`
├── .env.example # Single env template (CLI + Studio)
├── package.json # npm workspaces root + scripts
└── README.md
out/andsurfaces/hold generated CLI output and are regenerable (gitignored).
| Portfolio: Denistan | LinkedIn: denistanb |
| GitHub: DCode-v05 | LeetCode: Denistan_B |
| Email: denistanb05@gmail.com | |
Made with ❤️ by Denistan B