db-diagram-tool-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +133 -0
- package/dist/client.js +71 -0
- package/dist/config.js +17 -0
- package/dist/diagramToDbml.js +98 -0
- package/dist/index.js +137 -0
- package/dist/types.js +7 -0
- package/package.json +28 -0
package/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# db-diagram-tool-mcp
|
|
2
|
+
|
|
3
|
+
A **read-only** [Model Context Protocol](https://modelcontextprotocol.io) server
|
|
4
|
+
(stdio) that lets MCP clients — **Claude Code** and **OpenAI Codex** — read your
|
|
5
|
+
[DB Diagram Tool](https://github.com/kai-exnodes) diagrams: list them, and fetch a
|
|
6
|
+
diagram's schema as JSON, DBML, or SQL DDL.
|
|
7
|
+
|
|
8
|
+
It talks to the DB Diagram Tool backend over its normal REST API, authenticated
|
|
9
|
+
with a **Personal Access Token (PAT)**. It performs **only GET requests** — it can
|
|
10
|
+
never modify your diagrams.
|
|
11
|
+
|
|
12
|
+
## Tools
|
|
13
|
+
|
|
14
|
+
| Tool | Args | Returns |
|
|
15
|
+
|------|------|---------|
|
|
16
|
+
| `list_diagrams` | — | The diagrams your token can access (id, name, role, visibility, updated time). |
|
|
17
|
+
| `get_diagram` | `id` | Metadata + the full schema snapshot (tables, columns, relationships) as JSON. |
|
|
18
|
+
| `get_diagram_dbml` | `id` | The diagram rendered as [DBML](https://dbml.dbdiagram.io) (dbdiagram.io text format). |
|
|
19
|
+
| `get_diagram_sql` | `id`, `dialect?` (`postgres` \| `mysql`, default `postgres`) | Server-generated `CREATE TABLE` SQL DDL. |
|
|
20
|
+
|
|
21
|
+
## Requirements
|
|
22
|
+
|
|
23
|
+
- **Node.js ≥ 18** (uses the built-in `fetch`).
|
|
24
|
+
- A **Personal Access Token** for your DB Diagram Tool account (create one in the
|
|
25
|
+
app's Settings → Personal Access Tokens). It looks like `ddt_pat_…`.
|
|
26
|
+
- The backend **origin** URL (e.g. `http://localhost:8090` for local dev, or your
|
|
27
|
+
hosted instance).
|
|
28
|
+
|
|
29
|
+
## Configuration
|
|
30
|
+
|
|
31
|
+
The server reads two environment variables (set them in your MCP client's server
|
|
32
|
+
config, below):
|
|
33
|
+
|
|
34
|
+
| Variable | Required | Example | Notes |
|
|
35
|
+
|----------|----------|---------|-------|
|
|
36
|
+
| `DDT_BASE_URL` | yes | `http://localhost:8090` | Backend **origin** only — scheme + host[:port], **no** `/api/v1` path. |
|
|
37
|
+
| `DDT_PAT` | yes | `ddt_pat_abc123…` | Your personal access token. Treat it like a password. |
|
|
38
|
+
|
|
39
|
+
## Build
|
|
40
|
+
|
|
41
|
+
Not published to npm yet — build it locally and point your client at the compiled
|
|
42
|
+
entry (`dist/index.js`):
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
cd db-diagram-tool-mcp
|
|
46
|
+
npm install
|
|
47
|
+
npm run build
|
|
48
|
+
# entry point: <this dir>/dist/index.js (use its ABSOLUTE path below)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Install in Claude Code
|
|
52
|
+
|
|
53
|
+
**CLI (adds it for you):**
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
claude mcp add --transport stdio db-diagram-tool \
|
|
57
|
+
--env DDT_BASE_URL=http://localhost:8090 \
|
|
58
|
+
--env DDT_PAT=ddt_pat_your_token_here \
|
|
59
|
+
-- node /ABSOLUTE/PATH/TO/db-diagram-tool-mcp/dist/index.js
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`--scope` defaults to `local` (this project, private to you); add `--scope user`
|
|
63
|
+
to enable it everywhere, or `--scope project` to write a shared, git-tracked
|
|
64
|
+
`.mcp.json`.
|
|
65
|
+
|
|
66
|
+
**Or a project `.mcp.json`** (git-tracked; shared with your team):
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"mcpServers": {
|
|
71
|
+
"db-diagram-tool": {
|
|
72
|
+
"command": "node",
|
|
73
|
+
"args": ["/ABSOLUTE/PATH/TO/db-diagram-tool-mcp/dist/index.js"],
|
|
74
|
+
"env": {
|
|
75
|
+
"DDT_BASE_URL": "http://localhost:8090",
|
|
76
|
+
"DDT_PAT": "${DDT_PAT}"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
> **Don't commit your token.** Claude Code expands `${VAR}` (and `${VAR:-default}`)
|
|
84
|
+
> in `.mcp.json`, so use `"DDT_PAT": "${DDT_PAT}"` and export `DDT_PAT` in your
|
|
85
|
+
> shell — the raw token stays out of the repo.
|
|
86
|
+
|
|
87
|
+
## Install in Codex
|
|
88
|
+
|
|
89
|
+
**CLI:**
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
codex mcp add db-diagram-tool \
|
|
93
|
+
--env DDT_BASE_URL=http://localhost:8090 \
|
|
94
|
+
--env DDT_PAT=ddt_pat_your_token_here \
|
|
95
|
+
-- node /ABSOLUTE/PATH/TO/db-diagram-tool-mcp/dist/index.js
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
(The `--` before the command is required.)
|
|
99
|
+
|
|
100
|
+
**Or `~/.codex/config.toml`** (global) — or `.codex/config.toml` in a project:
|
|
101
|
+
|
|
102
|
+
```toml
|
|
103
|
+
[mcp_servers.db-diagram-tool]
|
|
104
|
+
command = "node"
|
|
105
|
+
args = ["/ABSOLUTE/PATH/TO/db-diagram-tool-mcp/dist/index.js"]
|
|
106
|
+
|
|
107
|
+
[mcp_servers.db-diagram-tool.env]
|
|
108
|
+
DDT_BASE_URL = "http://localhost:8090"
|
|
109
|
+
DDT_PAT = "ddt_pat_your_token_here"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
> Codex TOML does **not** expand `${VAR}` — use literal values, and keep the file
|
|
113
|
+
> private (don't commit a token).
|
|
114
|
+
|
|
115
|
+
## Try it
|
|
116
|
+
|
|
117
|
+
Once configured, ask your agent things like:
|
|
118
|
+
|
|
119
|
+
- "List my DB Diagram Tool diagrams."
|
|
120
|
+
- "Show the DBML for the diagram named _Blog_."
|
|
121
|
+
- "Give me the Postgres DDL for diagram `<id>`."
|
|
122
|
+
|
|
123
|
+
## Security
|
|
124
|
+
|
|
125
|
+
- **Read-only.** Only GET endpoints are ever called.
|
|
126
|
+
- Your PAT grants read access to your diagrams — store it like a password, prefer
|
|
127
|
+
`${DDT_PAT}` env expansion (Claude Code) over inlining, and never commit it.
|
|
128
|
+
|
|
129
|
+
## Notes
|
|
130
|
+
|
|
131
|
+
- `diagramToDbml.ts` is ported verbatim from the web app
|
|
132
|
+
(`db-diagram-tool-fe/src/lib/dbml/diagramToDbml.ts`); keep the two in sync.
|
|
133
|
+
- Built against `@modelcontextprotocol/sdk` v1.30.
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only REST client for the DB Diagram Tool backend (frozen REST contract;
|
|
3
|
+
* same endpoints the web app uses). All requests are `Authorization: Bearer <pat>`
|
|
4
|
+
* and hit `${baseUrl}/api/v1<path>`. Only GETs — this package never writes.
|
|
5
|
+
*/
|
|
6
|
+
const API_PREFIX = "/api/v1";
|
|
7
|
+
/** A structured API error carrying the HTTP status + the backend's error message. */
|
|
8
|
+
export class ApiError extends Error {
|
|
9
|
+
status;
|
|
10
|
+
code;
|
|
11
|
+
constructor(status, message, code) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.status = status;
|
|
14
|
+
this.code = code;
|
|
15
|
+
this.name = "ApiError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export class DdtClient {
|
|
19
|
+
config;
|
|
20
|
+
constructor(config) {
|
|
21
|
+
this.config = config;
|
|
22
|
+
}
|
|
23
|
+
async get(path) {
|
|
24
|
+
const url = `${this.config.baseUrl}${API_PREFIX}${path}`;
|
|
25
|
+
let res;
|
|
26
|
+
try {
|
|
27
|
+
res = await fetch(url, {
|
|
28
|
+
headers: {
|
|
29
|
+
Authorization: `Bearer ${this.config.pat}`,
|
|
30
|
+
Accept: "application/json",
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
throw new ApiError(0, `could not reach the backend at ${this.config.baseUrl} (${e.message}). Check DDT_BASE_URL and that the server is running.`);
|
|
36
|
+
}
|
|
37
|
+
const isJson = res.headers
|
|
38
|
+
.get("content-type")
|
|
39
|
+
?.includes("application/json");
|
|
40
|
+
const payload = isJson ? await res.json().catch(() => undefined) : undefined;
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
const env = payload;
|
|
43
|
+
const message = env?.error?.message ?? env?.message ?? res.statusText ?? "request failed";
|
|
44
|
+
const hint = res.status === 401
|
|
45
|
+
? " (check DDT_PAT — the token may be invalid or expired)"
|
|
46
|
+
: res.status === 403
|
|
47
|
+
? " (the token lacks access to this diagram)"
|
|
48
|
+
: res.status === 404
|
|
49
|
+
? " (no such diagram, or it isn't shared with this token)"
|
|
50
|
+
: "";
|
|
51
|
+
throw new ApiError(res.status, `${res.status} ${message}${hint}`, env?.error?.code);
|
|
52
|
+
}
|
|
53
|
+
return payload;
|
|
54
|
+
}
|
|
55
|
+
/** GET /diagrams → the token's accessible diagrams. */
|
|
56
|
+
listDiagrams() {
|
|
57
|
+
return this.get("/diagrams");
|
|
58
|
+
}
|
|
59
|
+
/** GET /diagrams/{id} → metadata. */
|
|
60
|
+
getDiagram(id) {
|
|
61
|
+
return this.get(`/diagrams/${encodeURIComponent(id)}`);
|
|
62
|
+
}
|
|
63
|
+
/** GET /diagrams/{id}/snapshot → the canonical Diagram JSON. */
|
|
64
|
+
getSnapshot(id) {
|
|
65
|
+
return this.get(`/diagrams/${encodeURIComponent(id)}/snapshot`);
|
|
66
|
+
}
|
|
67
|
+
/** GET /diagrams/{id}/export/ddl?dialect=… → server-generated SQL DDL. */
|
|
68
|
+
getDdl(id, dialect) {
|
|
69
|
+
return this.get(`/diagrams/${encodeURIComponent(id)}/export/ddl?dialect=${encodeURIComponent(dialect)}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function loadConfig(env = process.env) {
|
|
2
|
+
const baseUrl = (env.DDT_BASE_URL ?? "").trim().replace(/\/+$/, "");
|
|
3
|
+
const pat = (env.DDT_PAT ?? "").trim();
|
|
4
|
+
const missing = [];
|
|
5
|
+
if (!baseUrl) {
|
|
6
|
+
missing.push("DDT_BASE_URL — the backend origin, e.g. http://localhost:8090");
|
|
7
|
+
}
|
|
8
|
+
if (!pat) {
|
|
9
|
+
missing.push("DDT_PAT — a personal access token (ddt_pat_…)");
|
|
10
|
+
}
|
|
11
|
+
if (missing.length > 0) {
|
|
12
|
+
throw new Error(`db-diagram-tool-mcp: missing required environment variable(s):\n` +
|
|
13
|
+
missing.map((m) => ` - ${m}`).join("\n") +
|
|
14
|
+
`\nSet them in your MCP client's server config (see the README).`);
|
|
15
|
+
}
|
|
16
|
+
return { baseUrl, pat };
|
|
17
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialize a Diagram to DBML text (dbdiagram.io format). Pure text emit — no
|
|
3
|
+
* dependency. Ported verbatim from db-diagram-tool-fe/src/lib/dbml/diagramToDbml.ts
|
|
4
|
+
* (only the type import path differs); keep in sync with that source of truth.
|
|
5
|
+
*
|
|
6
|
+
* Mapping:
|
|
7
|
+
* - Column: `name type [settings]`, settings from pk/unique/not-null/default/note.
|
|
8
|
+
* - Composite PK (>1 pk column, order = column order): an `indexes { (a,b) [pk] }`
|
|
9
|
+
* block instead of inline `[pk]` (DBML's composite-PK form).
|
|
10
|
+
* - Relationship: `Ref: from.col <op> to.col` where op = `<` (1:N, from=one),
|
|
11
|
+
* `-` (1:1), `<>` (M:N). Our `from` is the one/parent (referenced key) side.
|
|
12
|
+
* - Table note → `Note:`; column note → `[note: …]`; default → `[default: \`…\`]`.
|
|
13
|
+
*/
|
|
14
|
+
const SIMPLE_IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
15
|
+
/** DBML identifier: bare when simple, else double-quoted. */
|
|
16
|
+
function ident(name) {
|
|
17
|
+
return SIMPLE_IDENT.test(name)
|
|
18
|
+
? name
|
|
19
|
+
: `"${name.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
20
|
+
}
|
|
21
|
+
/** DBML string literal: single-quoted, or triple-quoted when multi-line. */
|
|
22
|
+
function str(s) {
|
|
23
|
+
if (s.includes("\n"))
|
|
24
|
+
return `'''${s}'''`;
|
|
25
|
+
return `'${s.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A column TYPE. DBML accepts `name` / `name(args)` bare; anything else (a space
|
|
29
|
+
* like "double precision", odd chars) is double-quoted so the parser keeps it
|
|
30
|
+
* whole. Empty type (mid-edit) falls back to a placeholder so the DBML stays
|
|
31
|
+
* parseable.
|
|
32
|
+
*/
|
|
33
|
+
function typeLit(type) {
|
|
34
|
+
const t = type.trim();
|
|
35
|
+
if (!t)
|
|
36
|
+
return "unknown";
|
|
37
|
+
if (/^[A-Za-z_][A-Za-z0-9_ ]*(\([^)]*\))?$/.test(t) && !t.includes(" ")) {
|
|
38
|
+
// A plain type or type(args); DBML tolerates a single internal space.
|
|
39
|
+
return /\s/.test(t) ? `"${t}"` : t;
|
|
40
|
+
}
|
|
41
|
+
return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
42
|
+
}
|
|
43
|
+
function columnSettings(col, inComposite) {
|
|
44
|
+
const parts = [];
|
|
45
|
+
// A single PK is inline [pk]; composite PK goes in the indexes block instead.
|
|
46
|
+
if (col.isPrimaryKey && !inComposite)
|
|
47
|
+
parts.push("pk");
|
|
48
|
+
if (col.isUnique)
|
|
49
|
+
parts.push("unique");
|
|
50
|
+
if (!col.isNullable)
|
|
51
|
+
parts.push("not null");
|
|
52
|
+
if (col.default != null && col.default !== "")
|
|
53
|
+
parts.push(`default: \`${col.default}\``);
|
|
54
|
+
if (col.note)
|
|
55
|
+
parts.push(`note: ${str(col.note)}`);
|
|
56
|
+
return parts.length ? ` [${parts.join(", ")}]` : "";
|
|
57
|
+
}
|
|
58
|
+
function tableToDbml(t) {
|
|
59
|
+
const pkCols = t.columns.filter((c) => c.isPrimaryKey);
|
|
60
|
+
const composite = pkCols.length > 1;
|
|
61
|
+
const lines = [`Table ${ident(t.name)} {`];
|
|
62
|
+
for (const c of t.columns) {
|
|
63
|
+
lines.push(` ${ident(c.name)} ${typeLit(c.type)}${columnSettings(c, composite)}`);
|
|
64
|
+
}
|
|
65
|
+
if (composite) {
|
|
66
|
+
lines.push(" indexes {");
|
|
67
|
+
lines.push(` (${pkCols.map((c) => ident(c.name)).join(", ")}) [pk]`);
|
|
68
|
+
lines.push(" }");
|
|
69
|
+
}
|
|
70
|
+
if (t.note)
|
|
71
|
+
lines.push(` Note: ${str(t.note)}`);
|
|
72
|
+
lines.push("}");
|
|
73
|
+
return lines.join("\n");
|
|
74
|
+
}
|
|
75
|
+
const OP = {
|
|
76
|
+
"1:1": "-",
|
|
77
|
+
"1:N": "<",
|
|
78
|
+
"M:N": "<>",
|
|
79
|
+
};
|
|
80
|
+
function refToDbml(r, diagram) {
|
|
81
|
+
const from = diagram.tables.find((t) => t.id === r.fromTableId);
|
|
82
|
+
const to = diagram.tables.find((t) => t.id === r.toTableId);
|
|
83
|
+
const fromCol = from?.columns.find((c) => c.id === r.fromColumnId);
|
|
84
|
+
const toCol = to?.columns.find((c) => c.id === r.toColumnId);
|
|
85
|
+
if (!from || !to || !fromCol || !toCol)
|
|
86
|
+
return null;
|
|
87
|
+
return `Ref: ${ident(from.name)}.${ident(fromCol.name)} ${OP[r.cardinality]} ${ident(to.name)}.${ident(toCol.name)}`;
|
|
88
|
+
}
|
|
89
|
+
export function diagramToDbml(diagram) {
|
|
90
|
+
const blocks = diagram.tables.map(tableToDbml);
|
|
91
|
+
const refs = diagram.relationships
|
|
92
|
+
.map((r) => refToDbml(r, diagram))
|
|
93
|
+
.filter((x) => x !== null);
|
|
94
|
+
const out = [...blocks];
|
|
95
|
+
if (refs.length)
|
|
96
|
+
out.push(refs.join("\n"));
|
|
97
|
+
return out.join("\n\n").trim() + "\n";
|
|
98
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { loadConfig } from "./config.js";
|
|
6
|
+
import { ApiError, DdtClient } from "./client.js";
|
|
7
|
+
import { diagramToDbml } from "./diagramToDbml.js";
|
|
8
|
+
/**
|
|
9
|
+
* db-diagram-tool-mcp — a read-only stdio MCP server exposing your DB Diagram Tool
|
|
10
|
+
* diagrams to MCP clients (Claude Code, Codex). Four tools: list_diagrams,
|
|
11
|
+
* get_diagram (schema JSON), get_diagram_dbml, get_diagram_sql. Auth is a PAT.
|
|
12
|
+
*
|
|
13
|
+
* stdio protocol note: stdout is the JSON-RPC channel — NEVER write to stdout.
|
|
14
|
+
* All diagnostics go to stderr.
|
|
15
|
+
*/
|
|
16
|
+
const SERVER_NAME = "db-diagram-tool";
|
|
17
|
+
const SERVER_VERSION = "0.1.0";
|
|
18
|
+
/** A successful text result. */
|
|
19
|
+
function text(body) {
|
|
20
|
+
return { content: [{ type: "text", text: body }] };
|
|
21
|
+
}
|
|
22
|
+
/** A tool-level error result (isError so the client shows it as a failure, but the
|
|
23
|
+
* server keeps running for the next call). */
|
|
24
|
+
function errorResult(e) {
|
|
25
|
+
const msg = e instanceof ApiError
|
|
26
|
+
? e.message
|
|
27
|
+
: e instanceof Error
|
|
28
|
+
? e.message
|
|
29
|
+
: String(e);
|
|
30
|
+
return {
|
|
31
|
+
content: [{ type: "text", text: `Error: ${msg}` }],
|
|
32
|
+
isError: true,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function buildServer(client) {
|
|
36
|
+
const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION });
|
|
37
|
+
const readOnly = { readOnlyHint: true, openWorldHint: true };
|
|
38
|
+
server.registerTool("list_diagrams", {
|
|
39
|
+
title: "List diagrams",
|
|
40
|
+
description: "List the DB Diagram Tool diagrams this token can access. Returns id, name, your role, visibility, and last-updated time for each. Use an id with the other tools.",
|
|
41
|
+
annotations: readOnly,
|
|
42
|
+
}, async () => {
|
|
43
|
+
try {
|
|
44
|
+
const { diagrams } = await client.listDiagrams();
|
|
45
|
+
const rows = (diagrams ?? []).map((d) => ({
|
|
46
|
+
id: d.id,
|
|
47
|
+
name: d.name,
|
|
48
|
+
role: d.role,
|
|
49
|
+
visibility: d.visibility,
|
|
50
|
+
updatedAt: d.updatedAt,
|
|
51
|
+
}));
|
|
52
|
+
return text(JSON.stringify(rows, null, 2));
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
return errorResult(e);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
server.registerTool("get_diagram", {
|
|
59
|
+
title: "Get diagram (schema JSON)",
|
|
60
|
+
description: "Get a diagram's metadata plus its schema (tables, columns with types/keys/nullability, and relationships) as JSON, by id. This is a DENOISED read-view: the editor's table layout coordinates are omitted (use get_diagram_dbml or get_diagram_sql for other views).",
|
|
61
|
+
inputSchema: {
|
|
62
|
+
id: z.string().describe("The diagram id (from list_diagrams)."),
|
|
63
|
+
},
|
|
64
|
+
annotations: readOnly,
|
|
65
|
+
}, async ({ id }) => {
|
|
66
|
+
try {
|
|
67
|
+
const [meta, diagram] = await Promise.all([
|
|
68
|
+
client.getDiagram(id),
|
|
69
|
+
client.getSnapshot(id),
|
|
70
|
+
]);
|
|
71
|
+
// Denoise for coding agents: drop each table's editor layout coords
|
|
72
|
+
// (position x,y) — pure UI cruft that just adds token noise. Everything
|
|
73
|
+
// schema-relevant stays. (Not the raw contract; see the DBML/SQL tools.)
|
|
74
|
+
const schema = {
|
|
75
|
+
...diagram,
|
|
76
|
+
tables: diagram.tables.map(({ position: _position, ...table }) => table),
|
|
77
|
+
};
|
|
78
|
+
return text(JSON.stringify({ meta, diagram: schema }, null, 2));
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
return errorResult(e);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
server.registerTool("get_diagram_dbml", {
|
|
85
|
+
title: "Get diagram as DBML",
|
|
86
|
+
description: "Get a diagram rendered as DBML (the dbdiagram.io text format: Table/Ref blocks), by id. Compact and human-readable.",
|
|
87
|
+
inputSchema: {
|
|
88
|
+
id: z.string().describe("The diagram id (from list_diagrams)."),
|
|
89
|
+
},
|
|
90
|
+
annotations: readOnly,
|
|
91
|
+
}, async ({ id }) => {
|
|
92
|
+
try {
|
|
93
|
+
const diagram = await client.getSnapshot(id);
|
|
94
|
+
return text(diagramToDbml(diagram));
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
return errorResult(e);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
server.registerTool("get_diagram_sql", {
|
|
101
|
+
title: "Get diagram as SQL DDL",
|
|
102
|
+
description: "Get a diagram's CREATE TABLE SQL DDL for a dialect (postgres or mysql), generated by the backend. Defaults to postgres.",
|
|
103
|
+
inputSchema: {
|
|
104
|
+
id: z.string().describe("The diagram id (from list_diagrams)."),
|
|
105
|
+
dialect: z
|
|
106
|
+
.enum(["postgres", "mysql"])
|
|
107
|
+
.default("postgres")
|
|
108
|
+
.describe("SQL dialect: postgres (default) or mysql."),
|
|
109
|
+
},
|
|
110
|
+
annotations: readOnly,
|
|
111
|
+
}, async ({ id, dialect }) => {
|
|
112
|
+
try {
|
|
113
|
+
const { sql, warnings } = await client.getDdl(id, dialect);
|
|
114
|
+
const header = warnings?.length
|
|
115
|
+
? warnings.map((w) => `-- warning: ${w}`).join("\n") + "\n"
|
|
116
|
+
: "";
|
|
117
|
+
return text(header + sql);
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
return errorResult(e);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
return server;
|
|
124
|
+
}
|
|
125
|
+
async function main() {
|
|
126
|
+
const config = loadConfig(); // throws with a clear message if unset
|
|
127
|
+
const client = new DdtClient(config);
|
|
128
|
+
const server = buildServer(client);
|
|
129
|
+
await server.connect(new StdioServerTransport());
|
|
130
|
+
// stderr only — stdout carries the MCP protocol.
|
|
131
|
+
process.stderr.write(`db-diagram-tool-mcp v${SERVER_VERSION} connected (backend: ${config.baseUrl})\n`);
|
|
132
|
+
}
|
|
133
|
+
main().catch((err) => {
|
|
134
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
135
|
+
process.stderr.write(`${msg}\n`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
});
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagram document types — a copy of the DB Diagram Tool contract-1 models
|
|
3
|
+
* (db-diagram-tool-fe/src/types/diagram.ts, frozen Diagram JSON schema v1.0.0).
|
|
4
|
+
* Kept in sync manually; this package only READS snapshots, so it needs the shape
|
|
5
|
+
* to (a) render DBML and (b) type the API responses.
|
|
6
|
+
*/
|
|
7
|
+
export const SCHEMA_VERSION = "1.0";
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "db-diagram-tool-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Read-only Model Context Protocol (stdio) server for DB Diagram Tool — lets Claude Code / Codex read your diagrams (list, schema JSON, DBML, SQL DDL).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"db-diagram-tool-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": ["dist", "README.md"],
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc -p tsconfig.json",
|
|
15
|
+
"dev": "tsx src/index.ts",
|
|
16
|
+
"start": "node dist/index.js",
|
|
17
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
21
|
+
"zod": "^3.25.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^22",
|
|
25
|
+
"tsx": "^4.19.0",
|
|
26
|
+
"typescript": "^5.6.0"
|
|
27
|
+
}
|
|
28
|
+
}
|