Skip to main content
Glama
Ramzanelayodath

json-to-model

README.md
# json-to-model MCP Server

An MCP (Model Context Protocol) server for Claude Code, Codex CLI, or any
other MCP-compatible client that works like Postman combined with a code
generator: give it an API endpoint, it fetches the response and returns a
ready-to-use model/POJO class in your configured language — with login and
refresh-token handling built in, so you don't have to keep pasting fresh
tokens by hand.

---

## What it does

1. You configure it once — your target language, optional `baseUrl`, and
   (optionally) your login/refresh token endpoints. Either run `setup_auth`
   directly, or type `/json-to-model` in Claude Code and it will interview
   you for these values (see [Setup process](#setup-process) below).
2. From then on, you ask Claude Code to hit an endpoint — e.g. *"generate a
   model from `https://api.example.com/users/1` called `User`"*, or (if you
   configured a `baseUrl`) just *"generate a model from `/users/1`"*.
3. The server calls the API (attaching a valid auth token automatically if
   one is configured), reads the JSON response, and returns:
   - a status/shape summary line (e.g. `Status: 200 OK | Response: array with
     12 items`) — so you can tell "genuinely empty" apart from "auth silently
     rejected, API returned 200 + `[]`",
   - the full response body, pretty-printed (truncated past 4000 chars),
   - the generated source code in your configured language.

Code generation is powered by [`quicktype-core`](https://github.com/glideapps/quicktype),
which infers types (including nested objects, arrays, optional/nullable
fields) from the JSON shape.

---

## Project structure

```
json-to-model/
├── package.json          Dependencies & npm scripts (build/start/dev)
├── tsconfig.json          TypeScript compiler config
├── .gitignore
├── README.md
├── .claude/
│   └── commands/
│       └── json-to-model.md   Claude Code slash command — `/json-to-model`
│                              walks you through setup or a generate call
└── src/
    ├── index.ts           MCP server entrypoint — registers the tools
    │                      (setup_auth, generate_model_from_api,
    │                      list_supported_languages), the `json-to-model`
    │                      MCP prompt, and wires request building, error
    │                      handling, and responses
    ├── types.ts           Shared types: AuthConfig, SessionState, ToolTextResult
    ├── store.ts           Reads/writes config.json and session.json under
    │                      ./.json-to-model/ in the current project root
    │                      (per-project, not shared globally)
    ├── auth.ts            Token resolution chain: explicit token → cached
    │                      token → refresh → login → unauthenticated.
    │                      Handles rotating refresh tokens.
    ├── language.ts        Supported language enum, case-insensitive
    │                      normalization, and aliases (c# → csharp, ts →
    │                      typescript, py → python, etc.)
    └── codegen.ts          Wraps quicktype-core: JSON string → generated
                            source code for the configured language, with
                            per-language serializer/framework choices
```

### How a request flows through the code

```
generate_model_from_api tool call
        │
        ▼
  index.ts: check setup exists (store.ts) ──► no? return setup error
        │ yes
        ▼
  auth.ts: resolveToken()
    1. explicit `token` param?          → use it
    2. cached access token still valid? → use it
    3. refresh token + endpoint?        → refresh, cache, use it
    4. login endpoint configured?       → login, cache, use it
    5. none of the above                → proceed unauthenticated
        │
        ▼
  index.ts: resolve `url` (or join `path` onto configured baseUrl),
            build headers + body (json or form), fetch(url)
        │
        ▼
  codegen.ts: quicktype-core turns the JSON response into
              source code in the configured language (using that
              language's configured serializer — see table below)
        │
        ▼
  Status/shape summary + full response body + generated class
  returned to Claude / you
```

---

## Prerequisites

- Node.js v18 or later (the code uses the built-in `fetch` API)
- Claude Code, or OpenAI's Codex CLI — anything that can spawn a stdio MCP
  server. Instructions below cover Claude Code first, Codex CLI in its own
  section further down.

---

## Dependencies

Runtime:

| Package | Purpose |
|---|---|
| [`@modelcontextprotocol/sdk`](https://www.npmjs.com/package/@modelcontextprotocol/sdk) | MCP server framework — tools, prompts, stdio transport |
| [`quicktype-core`](https://www.npmjs.com/package/quicktype-core) | JSON → model code generation engine |
| [`zod`](https://www.npmjs.com/package/zod) | Schema validation for tool parameters |

Dev-only (not shipped, used to build/run from source):

| Package | Purpose |
|---|---|
| `@types/node` | Node.js type definitions |
| `tsx` | Runs TypeScript directly for `npm run dev` |
| `typescript` | Compiler (`tsc`) for `npm run build` |

Everything else is Node.js built-ins (`fs`, `path`, `fetch`) — no other
third-party libraries.

---

## Setup process

### 1. Install and build

```bash
cd json-to-model
npm install
npm run build
```

This compiles `src/*.ts` → `dist/*.js`. `dist/index.js` is what actually runs.

### 2. Register the server with Claude Code

```bash
claude mcp add json-to-model -- node /absolute/path/to/json-to-model/dist/index.js
```

Use an **absolute path** — Claude Code spawns this as a subprocess on demand
(stdio transport), so there's nothing to host or deploy for local/personal use.

### 3. Run first-time setup (in a Claude Code conversation)

**Easiest: type the slash command.**

```
/json-to-model
```

This is defined in `.claude/commands/json-to-model.md`. If no setup exists
yet, Claude interviews you one question at a time — language (offering to
call `list_supported_languages` if you're unsure), optional `baseUrl`, login
endpoint + body + token field, refresh endpoint + fields — then calls
`setup_auth` with your answers. If setup already exists, it shows a summary
and asks whether to reconfigure or go straight to generating a model.

The same interview flow is also exposed as an MCP **prompt** named
`json-to-model` (works from any MCP client, not just the slash command).

**Or ask directly / call `setup_auth` yourself.** Fields:

| Field | Required? | Notes |
|---|---|---|
| `language` | **Yes** | java, kotlin, typescript, python, csharp, go, swift, dart, rust. Fixed at setup — not chosen per call. Call `list_supported_languages` if unsure. |
| `baseUrl` | No | Host to join with a relative `path` on later `generate_model_from_api` calls, so you don't have to repeat the full host every time. |
| `loginEndpoint` | No | POST endpoint that returns an access token. Skip this if you'll always pass a `token` manually. |
| `loginBody` | No | JSON body sent to the login endpoint (credentials, API key, etc.) |
| `loginTokenField` | No | Field in the login response holding the token. Default: `access_token` |
| `refreshEndpoint` | No | POST endpoint that exchanges a refresh token for a new access token |
| `refreshTokenField` | No | Field holding the refresh token. Default: `refresh_token` |
| `expiryField` | No | Field holding token lifetime in seconds. Default: `expires_in` |

Example:

> "Set up json-to-model. Language: Kotlin. Login endpoint:
> `https://api.example.com/auth/login` with body `{ "apiKey": "..." }`."

Config is saved to `<project-root>/.json-to-model/config.json` — **scoped to
whichever project directory Claude Code/Codex was running in when the MCP
server was spawned**, not shared globally. Different projects get their own
`language`, `baseUrl`, login/refresh endpoints, etc. Session tokens are
cached separately in `<project-root>/.json-to-model/session.json` (file
permissions `600`, never logged, never echoed back in any tool response).

Add `.json-to-model/` to that project's `.gitignore` — `session.json` holds
live tokens and shouldn't be committed.

### 4. Use it

**GET request (full URL):**
> "Generate a model from `https://api.example.com/users/1` called `User`"

**GET request (relative path, requires `baseUrl` configured):**
> "Generate a model from `/users/1` called `User`"

**POST with JSON body:**
> "POST `https://api.example.com/clients` with body `{ "name": "Acme" }` as
> JSON, call the model `Client`"

**POST with form data:**
> "POST `https://api.example.com/clients` as form data with `name=Acme`,
> call the model `Client`"

**One-off call with a manual token** (bypasses stored login/refresh):
> "Generate a model from `https://api.example.com/orders/5` using token
> `eyJhbGciOi...`" — the `Bearer` prefix is optional, it's stripped and
> re-added automatically either way.

Every successful call returns three things, in order: a status/shape summary
line, the full pretty-printed response body, then the generated model code.
The generated code also carries the same response JSON baked in as a comment
header (plus a "Generated by json-to-model MCP server" line), so the pairing
between response and model survives even if the code is saved to its own
file or the chat reply gets trimmed/summarized.

---

## Tool reference

### `setup_auth`
Run once (or again to reconfigure). See the setup table above for fields.

### `generate_model_from_api`

| Param | Required? | Default | Notes |
|---|---|---|---|
| `url` | One of `url`/`path` | — | Full endpoint URL |
| `path` | One of `url`/`path` | — | Relative path, joined onto the configured `baseUrl`. Errors clearly if `baseUrl` isn't set. |
| `method` | No | `GET` | Any HTTP method |
| `token` | No | — | Per-call override; falls back to configured login/refresh flow |
| `className` | No | `Model` | Name for the generated class/type |
| `headers` | No | — | Extra headers to send |
| `body` | No | — | Request body/params for POST/PUT/PATCH/DELETE |
| `bodyType` | No | `json` | `json` → `application/json`, `form` → `application/x-www-form-urlencoded` |

`GET`/`HEAD` requests reject a `body` with a clear error, since those methods
don't carry one.

### `list_supported_languages`
No params. Returns the current list of valid `language` values for
`setup_auth` — use this if you're not sure what's supported.

### Supported HTTP methods

`method` accepts any HTTP verb — it's passed straight through to `fetch()`.

| Method | Body allowed? | Notes |
|---|---|---|
| `GET` | No | Rejected with an error if `body` is passed |
| `HEAD` | No | Rejected with an error if `body` is passed |
| `POST` | Yes | Use `body` + `bodyType` (`json` or `form`) |
| `PUT` | Yes | Full resource replacement — same body handling as POST |
| `PATCH` | Yes | Partial update — same body handling as POST |
| `DELETE` | Yes (permissive) | Body is allowed but not required — some APIs expect the resource ID in the URL instead, others (e.g. bulk-delete-by-filter) expect a body. Since a body on DELETE is unusual, the response is prefixed with a note (`Note: DELETE request included a body — unusual, verify the API expects this.`) so it's never silently ambiguous. |

Examples:

> "PATCH `https://api.example.com/clients/5` with body
> `{ "status": "active" }`, call the model `Client`"

> "DELETE `https://api.example.com/clients/5`"

---

## Generated code: serializer per language

`codegen.ts` picks a serialization approach per language before handing off
to `quicktype-core`. Most languages only have one sensible option upstream;
Kotlin and C# support multiple frameworks — this project pins the one below
as the default.

| Language | Serializer / framework used | Notes |
|---|---|---|
| Kotlin | [`kotlinx.serialization`](https://github.com/Kotlin/kotlinx.serialization) (`@Serializable`, `@SerialName`) | Explicitly pinned via `rendererOptions.framework = "kotlinx"` in `codegen.ts`. quicktype's own default is [Klaxon](https://github.com/cbeust/klaxon) — if you see `import com.beust.klaxon.*` you're on an older build; kotlinx was chosen instead since it's the JetBrains-maintained, dependency-light option. Needs the `org.jetbrains.kotlinx:kotlinx-serialization-json` Gradle dependency. Package name is pinned to `models` via `rendererOptions.package` (quicktype-core's own default is `quicktype`, which isn't meaningful in a real project) — edit `codegen.ts` if you want a different package. |
| Java | [Jackson](https://github.com/FasterXML/jackson) (`@JsonProperty`) | quicktype's only Java renderer. Needs `jackson-databind` on the classpath. |
| C# | [Newtonsoft.Json / Json.NET](https://www.newtonsoft.com/json) | quicktype-core's default C# renderer; `System.Text.Json` is available upstream but not wired up here. Needs the `Newtonsoft.Json` NuGet package. |
| TypeScript | Plain interfaces, no library | quicktype emits `interface` declarations plus optional runtime type-guard helpers; no external dependency. |
| Python | `dataclasses` (stdlib) | quicktype emits `@dataclass`-based models with hand-rolled `from_dict`/`to_dict` — no third-party serializer needed. |
| Go | `encoding/json` struct tags (stdlib) | Plain structs with `` `json:"field"` `` tags; no external module required. |
| Swift | `Codable` (stdlib protocol) | Structs conform to `Codable`; no external dependency. |
| Dart | Hand-written `fromJson`/`toJson`, no library | Works directly in Flutter projects — no `json_serializable`/build_runner step required, the generated code is usable as-is. |
| Rust | [`serde`](https://serde.rs/) (`#[derive(Serialize, Deserialize)]`) | The de-facto standard for Rust; needs the `serde` + `serde_json` crates. |

To change Kotlin's or C#'s framework, edit the `rendererOptions` block in
`src/codegen.ts` (valid Kotlin values: `klaxon`, `kotlinx`, `jackson`; valid
C# values: `newtonsoft`, `systemtextjson`), then `npm run build`.

---

## Using with Codex CLI

The server itself is plain MCP over stdio, so it works with
[OpenAI's Codex CLI](https://github.com/openai/codex) too — same build, same
tools. Two things differ from the Claude Code setup above:

- **No `/json-to-model` slash command.** That file lives under
  `.claude/commands/`, which is Claude-Code-specific — Codex doesn't read it.
  Codex has its own custom-prompt mechanism instead: drop a markdown file
  under `~/.codex/prompts/<name>.md` and it becomes `/<name>` inside Codex.
  You can copy the interview instructions out of
  `.claude/commands/json-to-model.md` into a file there if you want the same
  guided flow in Codex; it's plain markdown, no Claude-specific syntax.
- **The MCP `prompt` primitive may not surface.** Codex's MCP client support
  has historically focused on **tools** — `setup_auth`,
  `generate_model_from_api`, `list_supported_languages` all work normally,
  but the `json-to-model` prompt registered in `index.ts` might not appear
  in Codex's UI even though it's a spec-compliant MCP prompt. Check your
  installed Codex version if you're relying on it; the tools work regardless.

### 1. Build

Same as Claude Code — see [Install and build](#1-install-and-build) above.

### 2. Register the server with Codex

Add an entry to Codex's config (`~/.codex/config.toml`):

```toml
[mcp_servers.json-to-model]
command = "node"
args = ["/absolute/path/to/json-to-model/dist/index.js"]
```

Use an **absolute path**, same reasoning as the Claude Code setup — Codex
spawns this as a subprocess on demand. Restart Codex (or reload MCP servers)
to pick up the config change.

### 3. Use it

Without the slash command or prompt, just ask Codex directly and it calls
the tools for you:

> "Set up json-to-model. Language: Kotlin. Login endpoint:
> `https://api.example.com/auth/login` with body `{ "apiKey": "..." }`."

> "Generate a model from `https://api.example.com/users/1` called `User`"

Everything else — `baseUrl`/`path` support, the status/shape summary line,
full response body display, per-language serializers — behaves identically
to Claude Code, since it's all server-side logic in `index.ts`/`codegen.ts`,
not client-specific.

---

## Design notes

- **No profiles within a project** — one auth config per project directory,
  by design (kept intentionally simple; a profile system would be needed
  only if you're juggling multiple unrelated APIs with different auth from
  the same project). Across projects, config is already isolated since it's
  stored per-project under `.json-to-model/` in each project root, not
  globally.
- **Language is fixed at setup**, not selectable per call — this removes the
  need for any "which language?" fallback logic in the main tool.
- **Symbolic endpoint references** (e.g. a constant like
  `ApiEndpoints.GET_CLIENT` in your codebase) aren't resolved by this MCP
  server itself — it only ever sees the literal URL string it's given. In
  practice this still works conversationally because Claude Code uses its
  own separate file-reading tools to resolve the constant to a real URL
  *before* calling this tool.
- **Rotating refresh tokens** are supported — if a refresh response includes
  a new refresh token, it overwrites the stored one; if not, the old one is
  kept.
- **Errors** are returned with `isError: true` per the MCP convention, with
  a distinct message for: no setup found, incomplete config, auth failure,
  network failure, non-2xx response, and JSON parse/codegen failure. Tokens
  are never included in error text.
- **Response body is always shown**, not just the generated code — this was
  added after an empty-array response turned out to be ambiguous (genuinely
  empty data vs. auth silently rejected + API returning `200 []`). It's
  truncated past 4000 characters to avoid flooding the terminal on huge
  payloads.
- **Response body is also embedded in the generated code** as a comment
  header (`withHeaderComment()` in `codegen.ts`), along with a "Generated by
  json-to-model MCP server" line — so if only the code gets saved/copied out
  of the chat, the exact response it was modeled from isn't lost.
- **`/json-to-model` and the MCP prompt are two separate mechanisms** that
  do the same interview: the slash command (`.claude/commands/json-to-model.md`)
  is Claude-Code-specific and needs to ship alongside this server if used
  from another project directory; the MCP prompt (registered in `index.ts`)
  works from any MCP client that supports prompts.

TDQS

C2.1/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: setup_auth handles authentication, generate_model_from_api performs the core generation, and list_supported_languages provides a utility. There is no overlap or ambiguity.

Naming Consistency5/5

All tools follow a consistent verb_noun pattern in snake_case: setup_auth, generate_model_from_api, list_supported_languages. The naming convention is uniform and predictable.

Tool Count5/5

With 3 tools, the set is well-scoped for a focused json-to-model server. Each tool serves a necessary role (auth, generation, languages), and the count falls within the ideal 3-15 range.

Completeness4/5

The core generation and prerequisite authentication are covered, and language discovery is useful. However, the server name suggests generating from raw JSON, while the tool only mentions API, so there may be a gap for direct JSON input.

Maintenance

ActivityMaintained
ResponsivenessNo issues