Content
# open-design-mcp
[Website](https://nano-step.github.io/open-design-mcp/) · [Changelog](https://nano-step.github.io/open-design-mcp/changelog.html) · [npm](https://www.npmjs.com/package/open-design-mcp) · [GitHub](https://github.com/nano-step/open-design-mcp)
A stdio [Model Context Protocol](https://modelcontextprotocol.io/) server that bridges coding agents (OpenCode, Claude Code, Cursor, Zed) to a running [Open Design](https://github.com/nexu-io/open-design) daemon — so you can list projects, fetch artifacts, and (in upcoming releases) generate full design artifacts via BYOK from inside your editor.
## Status
**v0.17.0 — 13 MCP tools live.** The server activates the full Open Design feature surface: list/inspect projects, save and lint artifacts, persist files inside projects, format Turn 3 prompts, and generate designs via BYOK with the full upstream system-prompt fidelity. See [open OpenSpec changes](openspec/changes/) for in-flight work and [closed changes](openspec/changes/archive/) for historical decisions.
## Tools
| Tool | Verb | Env vars required | Description |
|---|---|---|---|
| `od_list_projects` | read | `OD_DAEMON_URL` | List all projects from the OD daemon. Returns `{projects: [{id, name, kind?, status?}, …]}`. |
| `od_get_project` | read | `OD_DAEMON_URL` | Fetch a project + its artifact files. Merges `GET /api/projects/:id` and `GET /api/projects/:id/files`. Now also surfaces customInstructions, fidelity, skillId, designSystemId, createdAt, updatedAt when set on the project ([#56](https://github.com/nano-step/open-design-mcp/issues/56)). |
| `od_create_project` | write | `OD_DAEMON_URL` | Create a new project. Requires a client-supplied `id` matching `/^[A-Za-z0-9._-]{1,128}$/` plus `name`; optional `kind`, `fidelity`, `customInstructions`, `skillId`, `designSystemId`, `pendingPrompt`. Returns the project details and an auto-seeded conversation ID. `customInstructions` is also mirrored to `metadata.customInstructions` for daemon compat ([#43](https://github.com/nano-step/open-design-mcp/issues/43)). |
| `od_update_project` | write | `OD_DAEMON_URL` | Update a project's name, custom instructions, or metadata. At least one mutable field is required. `customInstructions` is also mirrored to `metadata.customInstructions` for daemon compat ([#43](https://github.com/nano-step/open-design-mcp/issues/43)). |
| `od_delete_project` | write | `OD_DAEMON_URL` | PERMANENTLY delete a project (database row + on-disk directory). Cannot be undone. |
| `od_save_artifact` | write | `OD_DAEMON_URL` | Persist an HTML artifact to the daemon's **global** artifact store at `/app/.od/artifacts/<timestamp>-<identifier>/index.html`. Args: `identifier` (URL-safe slug, `/^[a-z0-9-]+$/`, 3–64 chars), `title` (human-readable, 1–200 chars), `html` (full document). NOT project-scoped — saved artifacts do not appear in `od_get_project.files`. Returns the saved path + URL. |
| `od_save_project_file` | write | `OD_DAEMON_URL` | Persist a file **inside a project** so it appears in `od_get_project.files[]` and renders in the daemon UI. Wraps `POST /api/projects/:id/files`. Args: `projectId` (1–128 chars), `name` (file name, no path separators, 1–255 chars), `content` (string, max ~5 MB). Unlike `od_save_artifact` (global store), this is project-scoped — use it when you want the file to show up under the project's viewer. |
| `od_extract_design_system` | read | none | Parse a `design-system.html` artifact and return the JSON manifest + the three CSS blocks (`od-tokens`, `od-components`, `od-layout`). Pure function — no network calls, no env vars. Part of the design-system-first workflow. |
| `od_generate_design_system` | generate (streaming) | `OD_DAEMON_URL` + `BYOK_BASE_URL` + `BYOK_API_KEY` + `BYOK_MODEL` | Generate a design system artifact (`design-system.html`) via BYOK. Produces tokens, components, and layout CSS plus a JSON manifest and human-reviewable gallery. Post-generation validation ensures the output has all required marker slots. |
| `od_update_design_system` | write | `OD_DAEMON_URL` (delta mode: none; semantic mode: + BYOK vars) | Update an existing design system. Two modes: `semantic` (BYOK-driven natural-language patch) and `delta` (deterministic JSON merge, no network). Bumps `data-od-version` on every successful update. |
| `od_lint_artifact` | validate | `OD_DAEMON_URL` | Lint a raw HTML document. Takes `{ html }` only — inline content, not a project/slug reference. Returns findings + agent message. New optional arg `designSystemHtml` enables design-system-specific checks (DS001–DS005). |
| `od_compose_brief` | format | none | Format a Turn 3 prompt for od_generate_design. Combines form answers, brand spec, and page brief into a string upstream Open Design recognizes. Pure function — no network, no env vars. New optional arg `designSystemSummary` for including design system context in the Turn 3 prompt. |
| `od_generate_design` | generate (streaming) | `OD_DAEMON_URL` + `BYOK_BASE_URL` + `BYOK_API_KEY` + `BYOK_MODEL` (`BYOK_PROVIDER` optional, defaults to `openai`) | Generate a design via the BYOK pipeline. Composes the upstream Open Design system prompt and proxies through the OD daemon's `/api/proxy/<provider>/stream`. Returns the accumulated text. Reads `customInstructions` from `metadata.customInstructions` first, then top-level field ([#43](https://github.com/nano-step/open-design-mcp/issues/43)). Accepts `maxTokens` (default 64000) to control completion length — see [#36](https://github.com/nano-step/open-design-mcp/issues/36). When `projectId` resolves a project with `designSystemId`, auto-injects the design system contract. New optional arg `designSystemMode` (`strict`/`advisory`/`off`). |
Only `od_generate_design` and `od_generate_design_system` require the BYOK vars. The other 11 tools work with just `OD_DAEMON_URL` (or no env vars in the case of `od_compose_brief` and `od_extract_design_system`).
**`od_save_project_file` vs `od_save_artifact`:** These two write tools serve different storage scopes. `od_save_artifact` writes to the daemon's **global** artifact store — useful for shareable URLs that aren't tied to a project. `od_save_project_file` writes **inside a project's directory** so the file appears in `od_get_project.files[]` and renders in the daemon's project UI. Use `od_save_project_file` when you want the generated design to live inside the project; use `od_save_artifact` for standalone, project-unaware artifacts.
## How it works
When you invoke `od_generate_design` with a PRD-style prompt, the MCP server composes a ~20–50 KB system prompt locally (vendored `composeSystemPrompt` injects designer charter + `kind`-specific rules), then POSTs to the OD daemon's `/api/proxy/<provider>/stream` endpoint. The daemon is a thin pass-through proxy — it forwards to your BYOK provider, transcodes the upstream stream into its own SSE format, and streams tokens back. The MCP server accumulates deltas and emits `notifications/progress` every 25 tokens (only when the client supplied a `progressToken`, per MCP spec).
**Typical timing:** ~10 seconds for small sections (single hero, paragraph rewrite); 1–5 minutes for full pages; up to 10 minutes for complex multi-section designs. Set `maxTokens` (default 64000) to control completion length — full pages typically need 30000+ tokens; the daemon's silent default was 8192 ([#36](https://github.com/nano-step/open-design-mcp/issues/36)). Server timeout defaults to 10 minutes (`OD_GENERATE_TIMEOUT_MS`, configurable). On abort or timeout mid-stream, accumulated tokens are returned as partial HTML with a trailing `<!-- Generation timed out... -->` comment marker and `isError: true` — pair with `od_save_artifact` to checkpoint partial progress.
Full reference with file:line citations: [`docs/architecture/generate-design-flow.md`](./docs/architecture/generate-design-flow.md).
```mermaid
sequenceDiagram
autonumber
participant E as Editor
participant M as open-design-mcp
participant D as OD daemon
participant P as BYOK proxy
E->>M: tools/call od_generate_design
M->>M: composeSystemPrompt + lazy BYOK load
M->>D: POST /api/proxy/openai/stream
D->>P: POST /chat/completions stream true
loop SSE tokens
P-->>D: delta
D-->>M: event delta
end
M-->>E: tool result HTML
```
## Design-System-First Workflow
For multi-page projects, generate a design system **before** any individual pages to ensure visual consistency:
1. **Generate** a design system: `od_generate_design_system` with your brand brief.
2. **Save** it inside the project: `od_save_project_file` with `name: "design-system.html"`.
3. **Link** it: `od_update_project` with `designSystemId: "design-system.html"`.
4. **Generate pages**: Every subsequent `od_generate_design` call with that `projectId` auto-injects the design system contract (strict mode by default).
The `designSystemMode` argument on `od_generate_design` controls enforcement:
- `strict` (default when linked) — pages MUST use documented tokens and components; violations caught by `od_lint_artifact`.
- `advisory` — prefer documented tokens; deviations should be justified.
- `off` — no injection, equivalent to pre-v0.17 behavior.
Use `od_lint_artifact` with `designSystemHtml` to verify pages conform (findings DS001–DS005). Use `od_update_design_system` to evolve the system over time without regenerating all pages.
This workflow is fully opt-in — projects without a linked design system behave exactly as before.
> **Known limitation in v0.17.0:** Step 4 (auto-injection via `designSystemId` linkage) is currently a runtime no-op. The daemon's files-list endpoint returns metadata only — not file content — so `od_generate_design` cannot yet fetch and inject the linked design system. Until v0.18 adds a content endpoint, enforce design system adherence via `od_lint_artifact` + `designSystemHtml` (post-generation static checks DS001–DS005) and via `od_compose_brief` + `designSystemSummary` (Turn 3 prompt enrichment). Both paths are fully functional. See [CHANGELOG.md](./CHANGELOG.md) v0.17.0 "Known Limitations".
## Installation
Add the following entry to your MCP client config (OpenCode / Claude Code / Cursor / Zed):
```jsonc
{
"mcp": {
"open-design": {
"command": "npx",
"args": ["-y", "open-design-mcp"],
"env": {
// Pick the line that matches your deployment (see "Choosing OD_DAEMON_URL" below)
"OD_DAEMON_URL": "http://ai-open-design:7456",
"OD_API_TOKEN": "", // optional; bearer token if your daemon requires it
// BYOK vars — only needed if you call od_generate_design
"BYOK_BASE_URL": "https://your-ai-proxy.example.com/v1",
"BYOK_API_KEY": "<provider-api-key>",
"BYOK_MODEL": "open-design",
"BYOK_PROVIDER": "openai" // optional; one of openai/anthropic/azure/google/ollama
}
}
}
}
```
The server boots successfully with **only `OD_DAEMON_URL` set** — the BYOK vars are validated lazily when `od_generate_design` is invoked. This lets users explore via `od_list_projects` / `od_get_project` before wiring an AI provider.
## Choosing `OD_DAEMON_URL`
The right value depends on where the Open Design daemon is running **relative to the MCP server** (which itself runs wherever your coding agent spawns it):
| Your setup | `OD_DAEMON_URL` |
|---|---|
| MCP server + OD daemon both run as containers on a shared Docker network (e.g. via [`ai-sandbox-wrapper`](https://github.com/kokorolx/ai-sandbox-wrapper) with `--network ai-sandbox`) — **most common** | `http://ai-open-design:7456` |
| MCP server runs inside a Docker container, OD daemon runs natively on the host (exposed on host port `7456`) | `http://host.docker.internal:7456` (macOS / Windows Docker Desktop)<br>`http://172.17.0.1:7456` (Linux bridge gateway) |
| MCP server and OD daemon both run natively on the host (no Docker) | `http://localhost:7456` |
| OD container started via `ai-run open-design start --expose --port N` to host port `N` | `http://host.docker.internal:N` (from inside another container)<br>`http://localhost:N` (from host) |
**Quick diagnostic** (run from the environment where the MCP server will spawn):
```bash
curl -fsS http://ai-open-design:7456/ && echo " OK" # shared docker network
curl -fsS http://host.docker.internal:7456/ && echo " OK" # host gateway
curl -fsS http://localhost:7456/ && echo " OK" # native
```
Whichever one returns `200` is your correct `OD_DAEMON_URL`.
## Environment Variables
| Variable | Purpose | Required by | Default |
|---|---|---|---|
| `OD_DAEMON_URL` | Open Design daemon base URL — **see [Choosing `OD_DAEMON_URL`](#choosing-od_daemon_url) above** | **all tools** (validated eagerly at startup) | — |
| `OD_API_TOKEN` | Bearer token the OD daemon enforces when bound to non-loopback | optional | `""` (no auth header sent) |
| `OD_AUTH_MODE` | Auth mode: `none`, `bearer`, or `basic`. Auto-derived if unset (token set → `bearer`; basic creds set → `basic`; neither → `none`) | optional | inferred |
| `OD_BASIC_USER` | HTTP Basic Auth username | `OD_AUTH_MODE=basic` | — |
| `OD_BASIC_PASS` | HTTP Basic Auth password | `OD_AUTH_MODE=basic` | — |
| `OD_GENERATE_TIMEOUT_MS` | Server-side timeout for `od_generate_design`, in milliseconds. Raised from the previous 120s after [#33](https://github.com/nano-step/open-design-mcp/issues/33) confirmed full-page generations legitimately exceed it. | optional | `600000` (10 min) |
| `BYOK_BASE_URL` | OpenAI-compatible AI provider base URL | `od_generate_design` only (validated lazily) | — |
| `BYOK_API_KEY` | Provider API key forwarded via OD's `/api/proxy/*/stream` | `od_generate_design` only | — |
| `BYOK_MODEL` | Model id (e.g. `open-design`, `claude-sonnet-4-6`) | `od_generate_design` only | — |
| `BYOK_PROVIDER` | One of `openai` / `anthropic` / `azure` / `google` / `ollama` | optional | `openai` |
The server fails fast with a clear stderr message if `OD_DAEMON_URL` is missing or invalid. BYOK vars are checked only when `od_generate_design` is called — a missing var yields a friendly tool-level error (`isError: true`, text `"BYOK not configured: missing ..."`), never a crash.
### Hosted Open Design deployment (HTTP Basic Auth)
If the OD daemon is behind a reverse proxy with HTTP Basic Auth (e.g. the publicly-hosted instance at `https://od.thnkandgrow.com/`), set `OD_AUTH_MODE=basic` with the matching credentials:
```jsonc
{
"mcp": {
"open-design": {
"command": "npx",
"args": ["-y", "open-design-mcp"],
"env": {
"OD_DAEMON_URL": "https://od.thnkandgrow.com/",
"OD_AUTH_MODE": "basic",
"OD_BASIC_USER": "<your-username>",
"OD_BASIC_PASS": "<your-password>"
}
}
}
}
```
The server emits `Authorization: Basic <base64(user:pass)>` on every request to the OD daemon. Embedded credentials in the URL (`https://user:pass@host/`) are rejected at startup — use the env vars instead.
## Development
```bash
nvm use # picks Node 20 per .nvmrc
npm install
npm run lint
npm run typecheck
npm test
npm run build
npm run test:integration # spawns dist/src/server.js, mocks the OD daemon, exercises all 8 tools
```
The engineering harness ([`docs/HARNESS.md`](docs/HARNESS.md)) requires every feature, fix, or refactor to go through an OpenSpec proposal → deep-design → specs → implement → validate → review → PR → archive cycle. See [`docs/stories/`](docs/stories/) for in-flight stories.
## Vendored Dependencies
This project vendors a subset of source from [`nexu-io/open-design`](https://github.com/nexu-io/open-design) (Apache License 2.0) so we can compose the same `systemPrompt` the Open Design web UI builds for its BYOK chat turns — without depending on the upstream's private `@open-design/contracts` package.
| Path | Upstream | License | Notes |
|---|---|---|---|
| `vendor/od-contracts/` | [`nexu-io/open-design`](https://github.com/nexu-io/open-design) `packages/contracts/src/` | Apache-2.0 | 13 files (7 runtime + 6 type-only). Pinned commit + re-sync instructions in [`vendor/od-contracts/VENDORED_FROM.md`](vendor/od-contracts/VENDORED_FROM.md). Sync script at [`scripts/vendor-sync.sh`](scripts/vendor-sync.sh). |
**License compliance:**
- A copy of the upstream LICENSE travels in [`vendor/od-contracts/LICENSE`](vendor/od-contracts/LICENSE) (§4(a)).
- Modifications to vendored files (if any) carry a `MODIFICATION` header per Apache 2.0 §4(b); see [`vendor/od-contracts/VENDORED_FROM.md`](vendor/od-contracts/VENDORED_FROM.md) Modifications section for the running log.
- Original upstream copyright notices in each vendored file are retained verbatim (§4(c)).
- Attribution is in [`vendor/od-contracts/NOTICE`](vendor/od-contracts/NOTICE) and referenced from the top-level [`NOTICE`](NOTICE) (§4(d)).
All vendored code is redistributed under the same Apache License 2.0.
## License
Apache License 2.0. See [`LICENSE`](LICENSE) for the full text and [`NOTICE`](NOTICE) for attribution.
Copyright (c) 2026 kokorolx <kokoro.lehoang@gmail.com>.
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
No recommendations available