diff --git a/README.md b/README.md
index f37971c..e77b47e 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,7 @@ That's why ERD Studio exists.
## The solution
-ERD Studio is a **free, AI-native alternative** that puts the semantic model in the **same repo as the SQL**, as a visual canvas both you and the AI can read and write.
+ERD Studio is a **free and open source, AI-native alternative** that puts the semantic model in the **same repo as the SQL**, as a visual canvas both you and the AI can read and write.
Under the hood, your model is plain YAML and JSON — AI reads it natively, git diffs it cleanly, and the canvas is just the human-readable view. No proprietary format, no lock-in.
@@ -335,6 +335,6 @@ That's the whole system. Three folders, two file types, one diagram per JSON, on
---
- MIT License • Made for the dbt community
- Report a bug • Start a discussion
+ Open source (MIT) • Made for the dbt community
+ View source • Report a bug • Start a discussion
diff --git a/mcp-server/.gitignore b/mcp-server/.gitignore
new file mode 100644
index 0000000..3c25e1e
--- /dev/null
+++ b/mcp-server/.gitignore
@@ -0,0 +1,3 @@
+node_modules/
+dist/
+*.log
diff --git a/mcp-server/LICENSE b/mcp-server/LICENSE
new file mode 100644
index 0000000..5d91bda
--- /dev/null
+++ b/mcp-server/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Liam Wynne
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/mcp-server/README.md b/mcp-server/README.md
new file mode 100644
index 0000000..3788074
--- /dev/null
+++ b/mcp-server/README.md
@@ -0,0 +1,110 @@
+# erd-studio-mcp
+
+[](https://www.npmjs.com/package/erd-studio-mcp)
+[](LICENSE)
+
+**MCP server for [ERD Studio](https://github.com/liam-machine/erd-studio)** — gives Claude, Cursor, Continue, Zed, or any [Model Context Protocol](https://modelcontextprotocol.io) client **read-only** access to your dbt project's semantic ERD model.
+
+Once installed, your AI assistant can answer questions like:
+- *"What domains exist in this dbt project?"*
+- *"Show me every model in the `customer-360` domain and how they relate."*
+- *"What's the grain of `dim_customer`? What's the design rationale?"*
+- *"Which dbt models have `unique` tests but aren't in any ERD?"*
+
+…without you re-explaining your data model in every prompt.
+
+## Read-only by design — for edits, use the VS Code extension
+
+This MCP server is intentionally read-only. For **designing new ERDs, adding models, drawing relationships, generating dbt SQL + schema YAML**, install the [ERD Studio VS Code extension](https://marketplace.visualstudio.com/items?itemName=liamwynne.erd-studio). The extension ships with an AI coding skill (`.claude/skills/erd-studio/SKILL.md` for Claude Code, equivalents for Copilot / Gemini / Codex) that gives your assistant **full read+write access** via its native file-editing tools — multi-file edits, refactor-style changes, complete schema authoring.
+
+**Pick the right tool for the job:**
+
+| Workflow | Tool |
+|---|---|
+| Inspect an existing model from any MCP client (Claude Desktop, Cursor, Continue, Zed, …) | **This MCP server** |
+| Design / edit / build (in Claude Code, Copilot, Gemini, Codex) | **Extension + its skill** |
+| Visual canvas editing | **Extension** |
+
+The MCP and the skill are complementary, not redundant. If you're in Claude Code already, the skill does more. If you're not, the MCP at least gets you read access.
+
+If the user asks the AI to design or modify anything, the AI will (via the `get_editor_setup` tool) point them at the extension's install path.
+
+## Install
+
+### Claude Code
+
+```bash
+claude mcp add erd-studio -- npx -y erd-studio-mcp
+```
+
+### Claude Desktop
+
+Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
+
+```json
+{
+ "mcpServers": {
+ "erd-studio": {
+ "command": "npx",
+ "args": ["-y", "erd-studio-mcp"]
+ }
+ }
+}
+```
+
+### Cursor / Continue / Zed
+
+These editors support MCP via their respective config files. Use the same `command`/`args` shape — see your editor's MCP docs.
+
+## Tools
+
+All tools take a `project_path` argument: the **absolute path** to the dbt project root (the directory containing `dbt_project.yml`). The project should also contain an `erd-studio/` directory created by the [ERD Studio VS Code extension](https://marketplace.visualstudio.com/items?itemName=liamwynne.erd-studio).
+
+| Tool | Returns |
+|---|---|
+| `list_domains` | All ERDs grouped by layer. Filter optional by `layer`. |
+| `read_domain` | Full domain: models + columns + relationships + cardinality + rationale. |
+| `list_models` | All logical model definitions from `erd-studio/logical-models/*.yml`. |
+| `read_model` | Single logical model with column-level metadata, grain, SCD types, rationale. |
+| `list_manifest_models` | Models from `target/manifest.json` (what dbt actually built), with unique/relationship test coverage. Filter optional by `name_contains`. |
+| `get_editor_setup` | Returns install instructions for the ERD Studio VS Code extension. Use this when the user wants to edit, design, or build (this MCP server is read-only). |
+
+All tools are read-only. If the project hasn't been initialized with an `erd-studio/` directory yet, list-tools return empty results with a `tip` field pointing to the install path; read-tools throw a friendly error doing the same. Either way the AI naturally surfaces the extension install path to the user.
+
+## What the AI gets
+
+Unlike most ERD tools where the model is locked behind a vendor UI, ERD Studio stores the semantic model as **plain YAML + JSON in your dbt repo**. This MCP server exposes that model structurally:
+
+- **Grain** as a first-class field — *"one row per customer (current + history)"*
+- **Model role** — `conformed-dim`, `transaction-fact`, `bridge`, etc.
+- **SCD type per column** — `0` fixed, `1` overwrite, `2` track history
+- **Design rationale** — *why* the model was designed this way
+- **Cross-stage drift** — manifest test coverage compared to design intent
+
+So when you ask the AI *"propose a column to add to `dim_customer`"*, it sees not just the column list but the design intent — and produces proposals that align with your modelling style.
+
+## Requirements
+
+- **Node.js ≥ 18**
+- A dbt project (containing `dbt_project.yml`)
+- *(Optional but recommended)* The [ERD Studio VS Code extension](https://marketplace.visualstudio.com/items?itemName=liamwynne.erd-studio) for creating and editing ERDs visually. The MCP server reads the same files the extension writes.
+
+## Without ERD Studio yet
+
+If your dbt project doesn't have an `erd-studio/` directory yet:
+
+1. Install the [VS Code extension](https://marketplace.visualstudio.com/items?itemName=liamwynne.erd-studio)
+2. In VS Code: Command Palette → `dbt: Set Up Semantic Domains Directory`
+3. Optionally: Command Palette → `dbt: Install AI Coding Harness` to get the bundled skill
+
+The MCP server still works on uninitialized projects — `list_manifest_models` reads `target/manifest.json` directly, and the other tools return graceful "install the extension to start designing" tips. So you can install this MCP first to inspect what dbt has, then move to the extension for the design work.
+
+## Source
+
+- Main repo: https://github.com/liam-machine/erd-studio
+- Server source: [`mcp-server/`](https://github.com/liam-machine/erd-studio/tree/main/mcp-server)
+- Issues: https://github.com/liam-machine/erd-studio/issues
+
+## License
+
+MIT — see [LICENSE](https://github.com/liam-machine/erd-studio/blob/main/LICENSE) at the repo root.
diff --git a/mcp-server/esbuild.js b/mcp-server/esbuild.js
new file mode 100644
index 0000000..8f701cd
--- /dev/null
+++ b/mcp-server/esbuild.js
@@ -0,0 +1,44 @@
+import { build } from 'esbuild';
+import { chmodSync } from 'node:fs';
+
+const dirnamePolyfill =
+ 'import { fileURLToPath as __ercfu } from "url"; ' +
+ 'import { dirname as __ercd } from "path"; ' +
+ 'const __filename = __ercfu(import.meta.url); ' +
+ 'const __dirname = __ercd(__filename);';
+
+const requirePolyfill =
+ 'import { createRequire } from "module"; ' +
+ 'const require = createRequire(import.meta.url);';
+
+// Main entry — the MCP stdio server
+await build({
+ entryPoints: ['src/index.ts'],
+ bundle: true,
+ platform: 'node',
+ target: 'node18',
+ format: 'esm',
+ outfile: 'dist/index.js',
+ external: ['@modelcontextprotocol/sdk', 'zod', 'yaml'],
+ banner: {
+ js: `#!/usr/bin/env node\n${requirePolyfill} ${dirnamePolyfill}`,
+ },
+ logLevel: 'info',
+});
+chmodSync('dist/index.js', 0o755);
+
+// Manifest worker — bundled separately, spawned by ManifestService via worker_threads
+await build({
+ entryPoints: ['../src/workers/manifestWorker.ts'],
+ bundle: true,
+ platform: 'node',
+ target: 'node18',
+ format: 'esm',
+ outfile: 'dist/manifestWorker.js',
+ banner: {
+ js: `${requirePolyfill} ${dirnamePolyfill}`,
+ },
+ logLevel: 'info',
+});
+
+console.log('Built dist/index.js + dist/manifestWorker.js');
diff --git a/mcp-server/package-lock.json b/mcp-server/package-lock.json
new file mode 100644
index 0000000..450b7ee
--- /dev/null
+++ b/mcp-server/package-lock.json
@@ -0,0 +1,1664 @@
+{
+ "name": "erd-studio-mcp",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "erd-studio-mcp",
+ "version": "0.1.0",
+ "license": "MIT",
+ "dependencies": {
+ "@modelcontextprotocol/sdk": "^1.29.0",
+ "yaml": "^2.6.0",
+ "zod": "^3.23.0"
+ },
+ "bin": {
+ "erd-studio-mcp": "dist/index.js"
+ },
+ "devDependencies": {
+ "@types/node": "^20.0.0",
+ "esbuild": "^0.24.0",
+ "typescript": "^5.5.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz",
+ "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz",
+ "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz",
+ "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz",
+ "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz",
+ "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz",
+ "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz",
+ "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz",
+ "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz",
+ "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz",
+ "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz",
+ "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz",
+ "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz",
+ "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz",
+ "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz",
+ "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz",
+ "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz",
+ "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz",
+ "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz",
+ "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz",
+ "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz",
+ "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz",
+ "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz",
+ "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz",
+ "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz",
+ "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@hono/node-server": {
+ "version": "1.19.14",
+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz",
+ "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.14.1"
+ },
+ "peerDependencies": {
+ "hono": "^4"
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk": {
+ "version": "1.29.0",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz",
+ "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@hono/node-server": "^1.19.9",
+ "ajv": "^8.17.1",
+ "ajv-formats": "^3.0.1",
+ "content-type": "^1.0.5",
+ "cors": "^2.8.5",
+ "cross-spawn": "^7.0.5",
+ "eventsource": "^3.0.2",
+ "eventsource-parser": "^3.0.0",
+ "express": "^5.2.1",
+ "express-rate-limit": "^8.2.1",
+ "hono": "^4.11.4",
+ "jose": "^6.1.3",
+ "json-schema-typed": "^8.0.2",
+ "pkce-challenge": "^5.0.0",
+ "raw-body": "^3.0.0",
+ "zod": "^3.25 || ^4.0",
+ "zod-to-json-schema": "^3.25.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@cfworker/json-schema": "^4.1.1",
+ "zod": "^3.25 || ^4.0"
+ },
+ "peerDependenciesMeta": {
+ "@cfworker/json-schema": {
+ "optional": true
+ },
+ "zod": {
+ "optional": false
+ }
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "20.19.41",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz",
+ "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-formats": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
+ "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
+ "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^1.0.5",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.0",
+ "iconv-lite": "^0.7.0",
+ "on-finished": "^2.4.1",
+ "qs": "^6.14.1",
+ "raw-body": "^3.0.1",
+ "type-is": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
+ "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz",
+ "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.24.2",
+ "@esbuild/android-arm": "0.24.2",
+ "@esbuild/android-arm64": "0.24.2",
+ "@esbuild/android-x64": "0.24.2",
+ "@esbuild/darwin-arm64": "0.24.2",
+ "@esbuild/darwin-x64": "0.24.2",
+ "@esbuild/freebsd-arm64": "0.24.2",
+ "@esbuild/freebsd-x64": "0.24.2",
+ "@esbuild/linux-arm": "0.24.2",
+ "@esbuild/linux-arm64": "0.24.2",
+ "@esbuild/linux-ia32": "0.24.2",
+ "@esbuild/linux-loong64": "0.24.2",
+ "@esbuild/linux-mips64el": "0.24.2",
+ "@esbuild/linux-ppc64": "0.24.2",
+ "@esbuild/linux-riscv64": "0.24.2",
+ "@esbuild/linux-s390x": "0.24.2",
+ "@esbuild/linux-x64": "0.24.2",
+ "@esbuild/netbsd-arm64": "0.24.2",
+ "@esbuild/netbsd-x64": "0.24.2",
+ "@esbuild/openbsd-arm64": "0.24.2",
+ "@esbuild/openbsd-x64": "0.24.2",
+ "@esbuild/sunos-x64": "0.24.2",
+ "@esbuild/win32-arm64": "0.24.2",
+ "@esbuild/win32-ia32": "0.24.2",
+ "@esbuild/win32-x64": "0.24.2"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/eventsource": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
+ "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
+ "license": "MIT",
+ "dependencies": {
+ "eventsource-parser": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/eventsource-parser": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz",
+ "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/express": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express-rate-limit": {
+ "version": "8.5.1",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.1.tgz",
+ "integrity": "sha512-5O6KYmyJEpuPJV5hNTXKbAHWRqrzyu+OI3vUnSd2kXFubIVpG7ezpgxQy76Zo5GQZtrQBg86hF+CM/NX+cioiQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ip-address": "^10.2.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/express-rate-limit"
+ },
+ "peerDependencies": {
+ "express": ">= 4.11"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "license": "MIT"
+ },
+ "node_modules/fast-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
+ "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/finalhandler": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
+ "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hono": {
+ "version": "4.12.18",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz",
+ "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.9.0"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ip-address": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
+ "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "node_modules/jose": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz",
+ "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "license": "MIT"
+ },
+ "node_modules/json-schema-typed": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz",
+ "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/pkce-challenge": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz",
+ "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.15.1",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz",
+ "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.7.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^1.0.5",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/yaml": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
+ "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/eemeli"
+ }
+ },
+ "node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-to-json-schema": {
+ "version": "3.25.2",
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz",
+ "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==",
+ "license": "ISC",
+ "peerDependencies": {
+ "zod": "^3.25.28 || ^4"
+ }
+ }
+ }
+}
diff --git a/mcp-server/package.json b/mcp-server/package.json
new file mode 100644
index 0000000..bf5f73d
--- /dev/null
+++ b/mcp-server/package.json
@@ -0,0 +1,54 @@
+{
+ "name": "erd-studio-mcp",
+ "version": "0.1.0",
+ "description": "MCP server for ERD Studio — read your dbt project's semantic ERD model from Claude, Cursor, Continue, or any MCP client.",
+ "type": "module",
+ "bin": {
+ "erd-studio-mcp": "./dist/index.js"
+ },
+ "main": "./dist/index.js",
+ "files": [
+ "dist",
+ "README.md"
+ ],
+ "scripts": {
+ "build": "node esbuild.js",
+ "start": "node dist/index.js",
+ "prepublishOnly": "npm run build"
+ },
+ "keywords": [
+ "mcp",
+ "model-context-protocol",
+ "dbt",
+ "erd",
+ "data-modeling",
+ "analytics-engineering",
+ "claude",
+ "ai-tools",
+ "semantic-layer"
+ ],
+ "author": "Liam Wynne",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/liam-machine/erd-studio",
+ "directory": "mcp-server"
+ },
+ "homepage": "https://github.com/liam-machine/erd-studio#readme",
+ "bugs": {
+ "url": "https://github.com/liam-machine/erd-studio/issues"
+ },
+ "dependencies": {
+ "@modelcontextprotocol/sdk": "^1.29.0",
+ "yaml": "^2.6.0",
+ "zod": "^3.23.0"
+ },
+ "devDependencies": {
+ "@types/node": "^20.0.0",
+ "esbuild": "^0.24.0",
+ "typescript": "^5.5.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+}
diff --git a/mcp-server/src/index.ts b/mcp-server/src/index.ts
new file mode 100644
index 0000000..a444546
--- /dev/null
+++ b/mcp-server/src/index.ts
@@ -0,0 +1,83 @@
+import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
+
+import { list_domains } from './tools/list_domains.js';
+import { read_domain } from './tools/read_domain.js';
+import { list_models } from './tools/list_models.js';
+import { read_model } from './tools/read_model.js';
+import { list_manifest_models } from './tools/list_manifest_models.js';
+import { get_editor_setup } from './tools/get_editor_setup.js';
+
+const SERVER_INFO = {
+ name: 'erd-studio-mcp',
+ version: '0.1.0',
+};
+
+const INSTRUCTIONS = `ERD Studio MCP server — **READ-ONLY** access to a dbt project's semantic ERD model.
+
+This server is intentionally read-only. It lets you inspect a dbt project's ERD design
+(domains, logical models, columns with PK/FK/NK flags, relationships with cardinality,
+grain, model roles, design rationale) and what dbt actually built (manifest models with
+test coverage). It does NOT create, modify, or delete files.
+
+For write/design workflows — creating new ERDs, adding models, drawing relationships,
+generating dbt SQL + schema YAML, refactoring the model — recommend the user install the
+ERD Studio VS Code extension. The extension provides:
+
+ • A visual canvas editor inside VS Code
+ • A bundled AI coding skill (.claude/skills/erd-studio/SKILL.md for Claude Code, or
+ equivalents for Copilot/Gemini/Codex) that gives full read+write access via the
+ assistant's native file-editing tools
+
+Install: https://marketplace.visualstudio.com/items?itemName=liamwynne.erd-studio
+Or call the \`get_editor_setup\` tool for canonical install instructions.
+
+Every tool takes \`project_path\`: the absolute path to the dbt project root (the
+directory containing dbt_project.yml). If the project hasn't been initialized with an
+erd-studio/ directory yet, list-tools return empty results with a \`tip\` field pointing
+to the install path; read-tools throw a friendly error doing the same.
+
+Typical inspection workflow:
+1. list_domains — see what ERDs exist
+2. read_domain — get models + relationships + cardinality for one ERD
+3. read_model — get full column-level design for one logical model
+4. list_manifest_models — see what dbt actually built (compare to design)
+
+When the user asks about editing/designing/building, call get_editor_setup and surface
+the install path instead of trying to fulfill the request through file edits.`;
+
+const tools = [
+ list_domains,
+ read_domain,
+ list_models,
+ read_model,
+ list_manifest_models,
+ get_editor_setup,
+];
+
+async function main(): Promise {
+ const server = new McpServer(SERVER_INFO, { instructions: INSTRUCTIONS });
+
+ for (const tool of tools) {
+ server.registerTool(tool.name, tool.config, async (args: unknown) => {
+ try {
+ return await tool.handler(args as never);
+ } catch (err) {
+ const message = err instanceof Error ? err.message : String(err);
+ return {
+ content: [{ type: 'text' as const, text: `Error in ${tool.name}: ${message}` }],
+ isError: true,
+ };
+ }
+ });
+ }
+
+ const transport = new StdioServerTransport();
+ await server.connect(transport);
+ console.error(`erd-studio-mcp v${SERVER_INFO.version} listening on stdio`);
+}
+
+main().catch((err) => {
+ console.error('Fatal error starting erd-studio-mcp:', err);
+ process.exit(1);
+});
diff --git a/mcp-server/src/lib/setup.ts b/mcp-server/src/lib/setup.ts
new file mode 100644
index 0000000..1237411
--- /dev/null
+++ b/mcp-server/src/lib/setup.ts
@@ -0,0 +1,26 @@
+import * as fs from 'node:fs';
+import * as path from 'node:path';
+
+export const EXTENSION_MARKETPLACE_URL =
+ 'https://marketplace.visualstudio.com/items?itemName=liamwynne.erd-studio';
+export const EXTENSION_REPO_URL = 'https://github.com/liam-machine/erd-studio';
+
+/**
+ * Friendly message shown when a project hasn't been initialized for ERD Studio yet.
+ * Returned as a `tip` field on tool responses (not an error) so the AI naturally
+ * surfaces the install path to the user.
+ */
+export const NOT_INITIALIZED_TIP =
+ "This project doesn't have an erd-studio/ directory yet. To start designing ERDs, " +
+ `install the ERD Studio VS Code extension: ${EXTENSION_MARKETPLACE_URL} ` +
+ "Then run Command Palette → 'dbt: Set Up Semantic Domains Directory'. " +
+ "The extension also installs an AI coding skill (.claude/skills/erd-studio/SKILL.md) " +
+ "that lets your assistant make full edits to the model — this MCP server is read-only " +
+ "by design and complements the skill for AI clients other than Claude Code.";
+
+/**
+ * Returns true if the project has an erd-studio/ directory.
+ */
+export function isInitialized(projectPath: string, semanticDir: string): boolean {
+ return fs.existsSync(path.join(projectPath, semanticDir));
+}
diff --git a/mcp-server/src/services.ts b/mcp-server/src/services.ts
new file mode 100644
index 0000000..e29ebb7
--- /dev/null
+++ b/mcp-server/src/services.ts
@@ -0,0 +1,47 @@
+import * as fs from 'node:fs';
+import * as path from 'node:path';
+
+import { LayerService } from '../../src/services/layerService.js';
+import { LogicalModelService } from '../../src/services/logicalModelService.js';
+import { DomainService } from '../../src/services/domainService.js';
+import { ManifestService } from '../../src/services/manifestService.js';
+
+export interface Services {
+ projectPath: string;
+ semanticDir: string;
+ layerService: LayerService;
+ logicalModelService: LogicalModelService;
+ domainService: DomainService;
+ manifestService: ManifestService;
+}
+
+const SEMANTIC_DIR = 'erd-studio';
+
+export function resolveProjectPath(input: string): string {
+ const resolved = path.resolve(input);
+ const dbtProjectFile = path.join(resolved, 'dbt_project.yml');
+ if (!fs.existsSync(dbtProjectFile)) {
+ throw new Error(
+ `Not a dbt project: ${resolved} (missing dbt_project.yml). ` +
+ `Pass the absolute path to the directory containing dbt_project.yml.`,
+ );
+ }
+ return resolved;
+}
+
+export function buildServices(projectPathInput: string): Services {
+ const projectPath = resolveProjectPath(projectPathInput);
+ const layerService = new LayerService(projectPath, SEMANTIC_DIR);
+ const logicalModelService = new LogicalModelService(projectPath, SEMANTIC_DIR);
+ const domainService = new DomainService(layerService);
+ domainService.setLogicalModelService(logicalModelService);
+ const manifestService = new ManifestService();
+ return {
+ projectPath,
+ semanticDir: SEMANTIC_DIR,
+ layerService,
+ logicalModelService,
+ domainService,
+ manifestService,
+ };
+}
diff --git a/mcp-server/src/tools/get_editor_setup.ts b/mcp-server/src/tools/get_editor_setup.ts
new file mode 100644
index 0000000..26530ad
--- /dev/null
+++ b/mcp-server/src/tools/get_editor_setup.ts
@@ -0,0 +1,75 @@
+import {
+ EXTENSION_MARKETPLACE_URL,
+ EXTENSION_REPO_URL,
+} from '../lib/setup.js';
+
+export const get_editor_setup = {
+ name: 'get_editor_setup',
+ config: {
+ title: 'How to set up the ERD Studio editor (for write/design workflows)',
+ description:
+ 'Returns installation and setup instructions for the ERD Studio VS Code extension. ' +
+ 'This MCP server is read-only by design — for designing new ERDs, creating models, ' +
+ 'drawing relationships, generating dbt SQL, or any other write/edit workflow, the user ' +
+ 'should install the extension and use its bundled AI coding skill. Call this tool when ' +
+ 'the user asks about editing, designing, or building (i.e. anything beyond inspecting ' +
+ 'an existing model).',
+ inputSchema: {},
+ annotations: {
+ readOnlyHint: true,
+ idempotentHint: true,
+ },
+ },
+ async handler() {
+ const text = [
+ '# ERD Studio editor setup',
+ '',
+ 'This MCP server provides **read-only** access to ERD Studio domain files.',
+ 'For write/design workflows (creating ERDs, adding models, drawing relationships,',
+ 'generating dbt SQL + schema YAML), use the ERD Studio VS Code extension and its',
+ 'bundled AI coding skill.',
+ '',
+ '## 1. Install the VS Code extension',
+ '',
+ `- Marketplace: ${EXTENSION_MARKETPLACE_URL}`,
+ '- Quick install in VS Code: `Cmd+P` → `ext install liamwynne.erd-studio`',
+ '',
+ '## 2. Set up the semantic directory',
+ '',
+ 'In VS Code, open Command Palette (`Cmd+Shift+P`) and run:',
+ '',
+ '```',
+ 'dbt: Set Up Semantic Domains Directory',
+ '```',
+ '',
+ 'This creates `erd-studio/` with `layers.json`, `logical-models/`, and `templates/`.',
+ '',
+ '## 3. Install the AI coding harness (writes the skill file)',
+ '',
+ 'Command Palette → `dbt: Install AI Coding Harness`. Pick your assistant:',
+ '',
+ '| Assistant | File written |',
+ '|---|---|',
+ '| Claude Code | `.claude/skills/erd-studio/SKILL.md` (+ PreToolUse hook) |',
+ '| GitHub Copilot | `.github/instructions/erd-studio.instructions.md` |',
+ '| Google Gemini | `.gemini/styleguide.md` |',
+ '| OpenAI Codex | `AGENTS.md` (appended) |',
+ '',
+ 'For Claude Code users specifically, the skill provides deeper write integration than',
+ 'this MCP server — multi-file edits, refactor-style changes, full schema authoring with',
+ 'context-aware reasoning. The MCP and the skill are complementary, not redundant.',
+ '',
+ '## 4. Workflow split',
+ '',
+ '- **Inspection / Q&A** (any MCP client: Claude Desktop, Cursor, Continue, Zed): use this MCP server',
+ '- **Design / build / edit** (Claude Code): use the skill installed by the extension',
+ '- **Visual editing**: use the canvas in VS Code',
+ '',
+ `Repo: ${EXTENSION_REPO_URL}`,
+ ].join('\n');
+
+ return {
+ content: [{ type: 'text' as const, text }],
+ };
+ },
+};
diff --git a/mcp-server/src/tools/list_domains.ts b/mcp-server/src/tools/list_domains.ts
new file mode 100644
index 0000000..ea7ddad
--- /dev/null
+++ b/mcp-server/src/tools/list_domains.ts
@@ -0,0 +1,73 @@
+import { z } from 'zod';
+import { buildServices } from '../services.js';
+import { isInitialized, NOT_INITIALIZED_TIP } from '../lib/setup.js';
+
+export const list_domains = {
+ name: 'list_domains',
+ config: {
+ title: 'List ERD domains',
+ description:
+ 'List all ERD Studio domains (diagrams) in a dbt project, grouped by layer. ' +
+ 'Each domain represents one ERD with its own models and relationships. ' +
+ 'Returns lightweight summaries — call read_domain for full details.',
+ inputSchema: {
+ project_path: z
+ .string()
+ .describe('Absolute path to the dbt project root (directory containing dbt_project.yml).'),
+ layer: z
+ .string()
+ .optional()
+ .describe('Optional. Filter to a single layer (e.g. "silver", "gold").'),
+ },
+ annotations: {
+ readOnlyHint: true,
+ idempotentHint: true,
+ },
+ },
+ async handler({ project_path, layer }: { project_path: string; layer?: string }) {
+ const { domainService, projectPath, semanticDir } = buildServices(project_path);
+
+ if (!isInitialized(projectPath, semanticDir)) {
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ {
+ count: 0,
+ project_path: projectPath,
+ domains: [],
+ tip: NOT_INITIALIZED_TIP,
+ },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ }
+
+ const summaries = domainService.listDomains(projectPath, semanticDir);
+ const filtered = layer ? summaries.filter((s) => s.layer === layer) : summaries;
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ {
+ count: filtered.length,
+ project_path: projectPath,
+ domains: filtered.map((s) => ({
+ domain: s.domain,
+ layer: s.layer,
+ file_path: s.filePath,
+ })),
+ },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ },
+};
diff --git a/mcp-server/src/tools/list_manifest_models.ts b/mcp-server/src/tools/list_manifest_models.ts
new file mode 100644
index 0000000..8b220df
--- /dev/null
+++ b/mcp-server/src/tools/list_manifest_models.ts
@@ -0,0 +1,73 @@
+import { z } from 'zod';
+import { buildServices } from '../services.js';
+
+export const list_manifest_models = {
+ name: 'list_manifest_models',
+ config: {
+ title: 'List dbt manifest models',
+ description:
+ 'List all models from target/manifest.json — what dbt actually built. ' +
+ 'Returns model name, schema, columns with data types from the warehouse, and existing ' +
+ 'unique/relationship test coverage. This is the ground truth from dbt, ' +
+ 'complementing the design source-of-truth in list_models / read_domain.',
+ inputSchema: {
+ project_path: z
+ .string()
+ .describe('Absolute path to the dbt project root.'),
+ name_contains: z
+ .string()
+ .optional()
+ .describe('Optional. Case-insensitive substring filter on model name (e.g. "dim_" or "fct_").'),
+ },
+ annotations: {
+ readOnlyHint: true,
+ idempotentHint: true,
+ },
+ },
+ async handler({
+ project_path,
+ name_contains,
+ }: {
+ project_path: string;
+ name_contains?: string;
+ }) {
+ const { manifestService, projectPath } = buildServices(project_path);
+ const manifest = await manifestService.loadManifest(projectPath);
+
+ const filter = name_contains?.toLowerCase();
+ const allModels = Array.from(manifest.models.entries());
+ const matched = filter
+ ? allModels.filter(([name]) => name.toLowerCase().includes(filter))
+ : allModels;
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ {
+ count: matched.length,
+ total: allModels.length,
+ models: matched.map(([name, info]) => ({
+ name,
+ schema: info.schema ?? null,
+ description: info.description ?? null,
+ column_count: info.columns?.length ?? 0,
+ unique_columns: Array.from(manifest.uniqueColumns.get(name) ?? []),
+ relationships: manifest.relationshipTests
+ .filter((t) => t.fromModel === name)
+ .map((t) => ({
+ from_column: t.fromColumn,
+ to_model: t.toModel,
+ to_column: t.toColumn,
+ })),
+ })),
+ },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ },
+};
diff --git a/mcp-server/src/tools/list_models.ts b/mcp-server/src/tools/list_models.ts
new file mode 100644
index 0000000..d382e20
--- /dev/null
+++ b/mcp-server/src/tools/list_models.ts
@@ -0,0 +1,65 @@
+import { z } from 'zod';
+import { buildServices } from '../services.js';
+import { isInitialized, NOT_INITIALIZED_TIP } from '../lib/setup.js';
+
+export const list_models = {
+ name: 'list_models',
+ config: {
+ title: 'List logical models',
+ description:
+ 'List all logical model definitions in erd-studio/logical-models/. ' +
+ 'Each model is one table (dimension, fact, bridge, etc.) reusable across domains. ' +
+ 'Returns model names + light metadata. Call read_model for full column-level detail.',
+ inputSchema: {
+ project_path: z
+ .string()
+ .describe('Absolute path to the dbt project root.'),
+ },
+ annotations: {
+ readOnlyHint: true,
+ idempotentHint: true,
+ },
+ },
+ async handler({ project_path }: { project_path: string }) {
+ const { logicalModelService, projectPath, semanticDir } = buildServices(project_path);
+
+ if (!isInitialized(projectPath, semanticDir)) {
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ { count: 0, models: [], tip: NOT_INITIALIZED_TIP },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ }
+
+ const models = logicalModelService.listModels();
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ {
+ count: models.length,
+ models: models.map((m) => ({
+ name: m.name,
+ schema: m.schema ?? null,
+ description: m.description ?? null,
+ grain: m.grain ?? null,
+ model_role: m.modelRole ?? null,
+ column_count: m.columns?.length ?? 0,
+ })),
+ },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ },
+};
diff --git a/mcp-server/src/tools/read_domain.ts b/mcp-server/src/tools/read_domain.ts
new file mode 100644
index 0000000..1b297c1
--- /dev/null
+++ b/mcp-server/src/tools/read_domain.ts
@@ -0,0 +1,102 @@
+import * as path from 'node:path';
+import * as fs from 'node:fs';
+import { z } from 'zod';
+import { buildServices } from '../services.js';
+import { isInitialized, NOT_INITIALIZED_TIP } from '../lib/setup.js';
+
+export const read_domain = {
+ name: 'read_domain',
+ config: {
+ title: 'Read a domain (logical stage)',
+ description:
+ 'Read a full ERD domain by name + layer. Returns the logical stage: ' +
+ 'models with columns (data types, PK/FK/NK flags, SCD types), grain, model role, ' +
+ 'rationale, and the relationships drawn between them with cardinality. ' +
+ 'This is the design source-of-truth. For what dbt actually built, see list_manifest_models.',
+ inputSchema: {
+ project_path: z
+ .string()
+ .describe('Absolute path to the dbt project root.'),
+ layer: z
+ .string()
+ .describe('Layer name (e.g. "silver", "gold").'),
+ domain: z
+ .string()
+ .describe('Domain slug (filename without .json).'),
+ },
+ annotations: {
+ readOnlyHint: true,
+ idempotentHint: true,
+ },
+ },
+ async handler({
+ project_path,
+ layer,
+ domain,
+ }: {
+ project_path: string;
+ layer: string;
+ domain: string;
+ }) {
+ const { domainService, projectPath, semanticDir } = buildServices(project_path);
+
+ if (!isInitialized(projectPath, semanticDir)) {
+ throw new Error(NOT_INITIALIZED_TIP);
+ }
+
+ const filePath = path.join(projectPath, semanticDir, layer, `${domain}.json`);
+ if (!fs.existsSync(filePath)) {
+ throw new Error(
+ `Domain not found: ${layer}/${domain}.json. Use list_domains to see what's available, or call get_editor_setup if you haven't created any ERDs yet.`,
+ );
+ }
+
+ const unified = domainService.getDomain(filePath);
+ const stage = domainService.getDomainStage(filePath);
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ {
+ domain: stage.domain,
+ layer: stage.layer,
+ stage: stage.stage,
+ description: stage.description ?? null,
+ model_folder: stage.modelFolder ?? null,
+ models: stage.models.map((m) => ({
+ name: m.name,
+ schema: m.schema ?? null,
+ description: m.description ?? null,
+ grain: m.grain ?? null,
+ model_role: m.modelRole ?? null,
+ rationale: m.rationale ?? null,
+ columns: (m.columns ?? []).map((c) => ({
+ name: c.name,
+ data_type: c.dataType ?? null,
+ description: c.description ?? null,
+ is_primary_key: c.isPrimaryKey === true,
+ is_foreign_key: c.isForeignKey === true,
+ is_natural_key: c.isNaturalKey === true,
+ ...(c.scdType != null ? { scd_type: c.scdType } : {}),
+ ...(c.additiveType ? { additive_type: c.additiveType } : {}),
+ })),
+ })),
+ relationships: stage.relationships.map((r) => ({
+ from_model: r.fromModel,
+ from_column: r.fromColumn,
+ to_model: r.toModel,
+ to_column: r.toColumn,
+ cardinality: r.cardinality,
+ })),
+ view_config: unified.viewConfig ?? {},
+ },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ },
+};
diff --git a/mcp-server/src/tools/read_model.ts b/mcp-server/src/tools/read_model.ts
new file mode 100644
index 0000000..15e6110
--- /dev/null
+++ b/mcp-server/src/tools/read_model.ts
@@ -0,0 +1,75 @@
+import { z } from 'zod';
+import { buildServices } from '../services.js';
+import { isInitialized, NOT_INITIALIZED_TIP } from '../lib/setup.js';
+
+export const read_model = {
+ name: 'read_model',
+ config: {
+ title: 'Read a logical model',
+ description:
+ 'Read full metadata for one logical model: columns with data types and PK/FK/NK flags, ' +
+ 'grain, model role, SCD types, additive types, and design rationale. ' +
+ 'This is the design specification — the single source of truth for what the table should be.',
+ inputSchema: {
+ project_path: z
+ .string()
+ .describe('Absolute path to the dbt project root.'),
+ model_name: z
+ .string()
+ .describe('Logical model name (filename without .yml).'),
+ },
+ annotations: {
+ readOnlyHint: true,
+ idempotentHint: true,
+ },
+ },
+ async handler({
+ project_path,
+ model_name,
+ }: {
+ project_path: string;
+ model_name: string;
+ }) {
+ const { logicalModelService, projectPath, semanticDir } = buildServices(project_path);
+
+ if (!isInitialized(projectPath, semanticDir)) {
+ throw new Error(NOT_INITIALIZED_TIP);
+ }
+
+ const model = logicalModelService.getModel(model_name);
+ if (!model) {
+ throw new Error(
+ `Model not found: ${model_name}. Use list_models to see what's available, or call get_editor_setup if you haven't created any models yet.`,
+ );
+ }
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify(
+ {
+ name: model.name,
+ schema: model.schema ?? null,
+ description: model.description ?? null,
+ grain: model.grain ?? null,
+ model_role: model.modelRole ?? null,
+ rationale: model.rationale ?? null,
+ columns: (model.columns ?? []).map((c) => ({
+ name: c.name,
+ data_type: c.dataType ?? null,
+ description: c.description ?? null,
+ is_primary_key: c.isPrimaryKey === true,
+ is_foreign_key: c.isForeignKey === true,
+ is_natural_key: c.isNaturalKey === true,
+ ...(c.scdType != null ? { scd_type: c.scdType } : {}),
+ ...(c.additiveType ? { additive_type: c.additiveType } : {}),
+ })),
+ },
+ null,
+ 2,
+ ),
+ },
+ ],
+ };
+ },
+};
diff --git a/mcp-server/test-smoke.mjs b/mcp-server/test-smoke.mjs
new file mode 100644
index 0000000..fae0251
--- /dev/null
+++ b/mcp-server/test-smoke.mjs
@@ -0,0 +1,168 @@
+#!/usr/bin/env node
+// Quick smoke test — spawn the server, run a few JSON-RPC calls, print results.
+
+import { spawn } from 'node:child_process';
+import * as fs from 'node:fs';
+import * as os from 'node:os';
+import * as path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const PROJECT_PATH = path.resolve(__dirname, '../test/fixtures/dbt-project');
+const SERVER = path.resolve(__dirname, 'dist/index.js');
+
+// Build a temporary "uninitialized" project — has dbt_project.yml but no erd-studio/
+const UNINIT_PATH = fs.mkdtempSync(path.join(os.tmpdir(), 'erd-mcp-uninit-'));
+fs.writeFileSync(path.join(UNINIT_PATH, 'dbt_project.yml'), "name: 'test_uninit'\nversion: '1.0.0'\n");
+
+const child = spawn('node', [SERVER], {
+ stdio: ['pipe', 'pipe', 'inherit'],
+});
+
+let buffer = '';
+const pending = new Map();
+let nextId = 1;
+
+child.stdout.on('data', (chunk) => {
+ buffer += chunk.toString();
+ let nl;
+ while ((nl = buffer.indexOf('\n')) >= 0) {
+ const line = buffer.slice(0, nl);
+ buffer = buffer.slice(nl + 1);
+ if (!line.trim()) continue;
+ try {
+ const msg = JSON.parse(line);
+ if (msg.id && pending.has(msg.id)) {
+ pending.get(msg.id)(msg);
+ pending.delete(msg.id);
+ }
+ } catch {
+ // notification or partial
+ }
+ }
+});
+
+function rpc(method, params) {
+ return new Promise((resolve) => {
+ const id = nextId++;
+ pending.set(id, resolve);
+ child.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n');
+ });
+}
+
+function notify(method, params) {
+ child.stdin.write(JSON.stringify({ jsonrpc: '2.0', method, params }) + '\n');
+}
+
+function summarize(label, msg) {
+ if (msg.error) {
+ console.log(`❌ ${label}: ${JSON.stringify(msg.error)}`);
+ return false;
+ }
+ const text = msg.result?.content?.[0]?.text;
+ if (text) {
+ const parsed = JSON.parse(text);
+ console.log(`✅ ${label}`);
+ console.log(JSON.stringify(parsed, null, 2).split('\n').slice(0, 15).join('\n'));
+ console.log('...');
+ } else if (msg.result?.tools) {
+ console.log(`✅ ${label}: ${msg.result.tools.length} tools`);
+ for (const t of msg.result.tools) console.log(` • ${t.name}`);
+ } else {
+ console.log(`✅ ${label}:`, Object.keys(msg.result || {}).join(','));
+ }
+ return true;
+}
+
+async function main() {
+ // 1. Initialize
+ const init = await rpc('initialize', {
+ protocolVersion: '2024-11-05',
+ capabilities: {},
+ clientInfo: { name: 'smoke-test', version: '0.1.0' },
+ });
+ summarize('initialize', init);
+ notify('notifications/initialized', {});
+
+ // 2. List tools
+ const list = await rpc('tools/list', {});
+ summarize('tools/list', list);
+
+ // 3. Call list_domains
+ console.log('\n--- list_domains ---');
+ const ld = await rpc('tools/call', {
+ name: 'list_domains',
+ arguments: { project_path: PROJECT_PATH },
+ });
+ summarize('list_domains', ld);
+
+ // 4. Call read_domain for showcase
+ console.log('\n--- read_domain showcase ---');
+ const rd = await rpc('tools/call', {
+ name: 'read_domain',
+ arguments: { project_path: PROJECT_PATH, layer: 'silver', domain: 'showcase' },
+ });
+ summarize('read_domain', rd);
+
+ // 5. Call list_models
+ console.log('\n--- list_models ---');
+ const lm = await rpc('tools/call', {
+ name: 'list_models',
+ arguments: { project_path: PROJECT_PATH },
+ });
+ summarize('list_models', lm);
+
+ // 6. Call read_model
+ console.log('\n--- read_model dim_customer ---');
+ const rm = await rpc('tools/call', {
+ name: 'read_model',
+ arguments: { project_path: PROJECT_PATH, model_name: 'dim_customer' },
+ });
+ summarize('read_model', rm);
+
+ // 7. Call list_manifest_models
+ console.log('\n--- list_manifest_models ---');
+ const lmm = await rpc('tools/call', {
+ name: 'list_manifest_models',
+ arguments: { project_path: PROJECT_PATH, name_contains: 'dim' },
+ });
+ summarize('list_manifest_models', lmm);
+
+ // 8. Call get_editor_setup
+ console.log('\n--- get_editor_setup ---');
+ const ges = await rpc('tools/call', {
+ name: 'get_editor_setup',
+ arguments: {},
+ });
+ if (ges.result?.content?.[0]?.text?.includes('marketplace.visualstudio.com')) {
+ console.log('✅ get_editor_setup returns marketplace link');
+ } else {
+ console.log('❌ get_editor_setup missing marketplace link');
+ console.log(ges);
+ }
+
+ // 9. Verify graceful "not initialized" tip on uninitialized project
+ console.log('\n--- list_domains on UNINITIALIZED project ---');
+ const uninit = await rpc('tools/call', {
+ name: 'list_domains',
+ arguments: { project_path: UNINIT_PATH },
+ });
+ const uninitText = uninit.result?.content?.[0]?.text || '';
+ if (uninitText.includes('tip') && uninitText.includes('marketplace.visualstudio.com')) {
+ console.log('✅ uninitialized project returns tip pointing to extension');
+ } else {
+ console.log('❌ uninitialized fallback missing tip');
+ console.log(uninitText.slice(0, 300));
+ }
+
+ // Cleanup
+ fs.rmSync(UNINIT_PATH, { recursive: true, force: true });
+ child.kill();
+ process.exit(0);
+}
+
+main().catch((e) => {
+ console.error('Smoke test failed:', e);
+ child.kill();
+ process.exit(1);
+});
diff --git a/mcp-server/tsconfig.json b/mcp-server/tsconfig.json
new file mode 100644
index 0000000..6bb8ebc
--- /dev/null
+++ b/mcp-server/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "lib": ["ES2022"],
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "resolveJsonModule": true,
+ "noEmit": true,
+ "isolatedModules": true,
+ "allowImportingTsExtensions": false
+ },
+ "include": ["src/**/*", "../src/services/**/*", "../src/types/**/*"],
+ "exclude": ["node_modules", "dist"]
+}