@thinkdata/mcp-server 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 ADDED
@@ -0,0 +1,110 @@
1
+ # @thinkdata/mcp-server
2
+
3
+ > ThinkERD MCP Server — AI agents can query your database schema
4
+
5
+ 외부 AI 에이전트(Cursor, Windsurf, Claude Desktop 등)가 ThinkERD의 ERD 스키마를 실시간으로 조회하고, 스키마 변경을 제안할 수 있는 [Model Context Protocol (MCP)](https://modelcontextprotocol.io) 서버입니다.
6
+
7
+ ## 주요 기능
8
+
9
+ - **Schema Read** — 다이어그램의 엔터티, 컬럼, 관계를 AI 에이전트의 컨텍스트로 제공
10
+ - **DDL 생성** — PostgreSQL, MySQL, Oracle, MSSQL, SQLite DDL 자동 생성
11
+ - **스키마 검증** — PK 누락, 고립 엔터티, 빈 테이블 등 자동 점검
12
+ - **스키마 변경 제안** — AI가 코드를 작성하다 필요한 스키마 변경을 ThinkERD에 Draft로 전송
13
+
14
+ ## 빠른 시작
15
+
16
+ ### 1. 토큰 발급
17
+
18
+ ThinkERD 캔버스 → **IDE 연동(MCP)** 버튼 → **토큰 발급**
19
+
20
+ 또는 API로 직접 발급:
21
+ ```bash
22
+ curl -X POST https://app.thinkdata.dev/api/tokens \
23
+ -H "Authorization: Bearer <YOUR_JWT>" \
24
+ -H "Content-Type: application/json" \
25
+ -d '{"name": "My MCP Token", "scopes": ["read", "write"]}'
26
+ ```
27
+
28
+ ### 2. IDE 설정
29
+
30
+ #### Cursor (`~/.cursor/mcp.json`)
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "thinkerd": {
35
+ "command": "npx",
36
+ "args": ["-y", "@thinkdata/mcp-server", "--api-url", "https://app.thinkdata.dev", "--token", "<YOUR_TOKEN>"]
37
+ }
38
+ }
39
+ }
40
+ ```
41
+
42
+ #### Windsurf (`~/.codeium/windsurf/mcp_config.json`)
43
+ ```json
44
+ {
45
+ "mcpServers": {
46
+ "thinkerd": {
47
+ "command": "npx",
48
+ "args": ["-y", "@thinkdata/mcp-server", "--api-url", "https://app.thinkdata.dev", "--token", "<YOUR_TOKEN>"]
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ #### Claude Desktop (`~/Library/Application Support/Claude/claude_desktop_config.json`)
55
+ ```json
56
+ {
57
+ "mcpServers": {
58
+ "thinkerd": {
59
+ "command": "npx",
60
+ "args": ["-y", "@thinkdata/mcp-server", "--api-url", "https://app.thinkdata.dev", "--token", "<YOUR_TOKEN>"]
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ ### 3. 사용 예시
67
+
68
+ AI 채팅에서 다음과 같이 요청하세요:
69
+
70
+ - *"ThinkERD에서 결제 관련 테이블 스키마를 가져와서 TypeORM 엔티티 코드를 만들어줘"*
71
+ - *"ThinkERD 다이어그램의 DDL을 PostgreSQL로 생성해줘"*
72
+ - *"ThinkERD 스키마를 검증해서 문제가 있는지 확인해줘"*
73
+
74
+ ## Resources (읽기 전용)
75
+
76
+ | Resource URI | 설명 |
77
+ |---|---|
78
+ | `thinkerd://diagrams` | 모든 다이어그램 목록 |
79
+ | `thinkerd://diagrams/{id}/schema` | 다이어그램 전체 스키마 (JSON) |
80
+ | `thinkerd://diagrams/{id}/entities/{id}` | 단일 엔터티 상세 |
81
+ | `thinkerd://diagrams/{id}/ddl` | DDL 생성 |
82
+ | `thinkerd://projects/{id}/dictionary` | 표준 사전 |
83
+
84
+ ## Tools (액션 실행)
85
+
86
+ | Tool | 설명 |
87
+ |---|---|
88
+ | `get_entity` | 엔터티 상세 조회 |
89
+ | `search_entities` | 엔터티 이름 검색 |
90
+ | `generate_ddl` | DDL 생성 (5개 DBMS 지원) |
91
+ | `validate_schema` | 스키마 검증 |
92
+ | `propose_schema_change` | 스키마 변경 제안 (Draft) |
93
+
94
+ ## 환경변수
95
+
96
+ | 변수 | 설명 | 기본값 |
97
+ |---|---|---|
98
+ | `THINKERD_API_URL` | ThinkERD API URL | `https://app.thinkdata.dev` |
99
+ | `THINKERD_TOKEN` | Personal Access Token | — |
100
+
101
+ ## 보안
102
+
103
+ - 토큰은 SHA-256 해시로만 서버에 저장됩니다
104
+ - 기본 발급 시 `read` 스코프만 부여됩니다
105
+ - 워크스페이스/프로젝트 단위로 접근 범위를 제한할 수 있습니다
106
+ - 토큰 만료일을 설정할 수 있습니다 (기본 90일)
107
+
108
+ ## 라이선스
109
+
110
+ MIT
@@ -0,0 +1,87 @@
1
+ /**
2
+ * ThinkERD API Client
3
+ *
4
+ * MCP Server가 ThinkERD 클라우드 API와 통신하기 위한 HTTP 클라이언트입니다.
5
+ * PAT(Personal Access Token) 기반 인증을 사용합니다.
6
+ *
7
+ * @module client
8
+ */
9
+ export interface DiagramSummary {
10
+ id: string;
11
+ name: string;
12
+ projectId: string;
13
+ projectName?: string;
14
+ entityCount: number;
15
+ relationshipCount: number;
16
+ updatedAt: string;
17
+ }
18
+ export interface EntityDetail {
19
+ id: string;
20
+ name: string;
21
+ tableName: string;
22
+ logicalName?: string;
23
+ entityRole?: string;
24
+ columns: ColumnDetail[];
25
+ }
26
+ export interface ColumnDetail {
27
+ id: string;
28
+ name: string;
29
+ logicalName?: string;
30
+ type: string;
31
+ isPrimaryKey?: boolean;
32
+ isForeignKey?: boolean;
33
+ isNullable?: boolean;
34
+ defaultValue?: string;
35
+ description?: string;
36
+ }
37
+ export interface RelationshipDetail {
38
+ id: string;
39
+ sourceEntityId: string;
40
+ sourceEntityName: string;
41
+ targetEntityId: string;
42
+ targetEntityName: string;
43
+ type: string;
44
+ sourceCardinality: string;
45
+ targetCardinality: string;
46
+ }
47
+ export interface DictionaryWord {
48
+ id: string;
49
+ logicalName: string;
50
+ physicalName: string;
51
+ classifier?: string;
52
+ isForbidden?: boolean;
53
+ }
54
+ export declare class ThinkERDClient {
55
+ private readonly baseUrl;
56
+ private readonly headers;
57
+ constructor(apiUrl: string, token: string);
58
+ private request;
59
+ /** 사용자의 모든 다이어그램 목록 조회 */
60
+ listDiagrams(): Promise<DiagramSummary[]>;
61
+ /** 다이어그램 상세 (엔터티 + 관계 포함) */
62
+ getDiagram(diagramId: string): Promise<{
63
+ diagram: DiagramSummary;
64
+ entities: EntityDetail[];
65
+ relationships: RelationshipDetail[];
66
+ }>;
67
+ /** 단일 엔터티 상세 조회 */
68
+ getEntity(diagramId: string, entityId: string): Promise<EntityDetail>;
69
+ /** 엔터티 이름으로 검색 */
70
+ searchEntities(diagramId: string, query: string): Promise<EntityDetail[]>;
71
+ /** 다이어그램의 DDL 생성 */
72
+ generateDDL(diagramId: string, dialect?: string): Promise<string>;
73
+ /** 표준 사전 단어 목록 */
74
+ getDictionary(projectId: string): Promise<DictionaryWord[]>;
75
+ /** 스키마 변경 제안 (Draft 생성) */
76
+ proposeSchemaChange(diagramId: string, data: {
77
+ entityName: string;
78
+ changeType: string;
79
+ changeData: Record<string, unknown>;
80
+ reason: string;
81
+ }): Promise<{
82
+ draft_id: string;
83
+ status: string;
84
+ }>;
85
+ private postRequest;
86
+ }
87
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/client.js ADDED
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ /**
3
+ * ThinkERD API Client
4
+ *
5
+ * MCP Server가 ThinkERD 클라우드 API와 통신하기 위한 HTTP 클라이언트입니다.
6
+ * PAT(Personal Access Token) 기반 인증을 사용합니다.
7
+ *
8
+ * @module client
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.ThinkERDClient = void 0;
12
+ class ThinkERDClient {
13
+ baseUrl;
14
+ headers;
15
+ constructor(apiUrl, token) {
16
+ this.baseUrl = apiUrl.replace(/\/+$/, '');
17
+ this.headers = {
18
+ 'Authorization': `Bearer ${token}`,
19
+ 'Content-Type': 'application/json',
20
+ };
21
+ }
22
+ async request(path) {
23
+ const url = `${this.baseUrl}${path}`;
24
+ const res = await fetch(url, { headers: this.headers });
25
+ if (!res.ok) {
26
+ const body = await res.text().catch(() => '');
27
+ throw new Error(`ThinkERD API error ${res.status}: ${body || res.statusText}`);
28
+ }
29
+ return res.json();
30
+ }
31
+ // ─── Diagrams ───
32
+ /** 사용자의 모든 다이어그램 목록 조회 */
33
+ async listDiagrams() {
34
+ return this.request('/api/mcp/diagrams');
35
+ }
36
+ /** 다이어그램 상세 (엔터티 + 관계 포함) */
37
+ async getDiagram(diagramId) {
38
+ return this.request(`/api/mcp/diagrams/${diagramId}`);
39
+ }
40
+ // ─── Entities ───
41
+ /** 단일 엔터티 상세 조회 */
42
+ async getEntity(diagramId, entityId) {
43
+ return this.request(`/api/mcp/diagrams/${diagramId}/entities/${entityId}`);
44
+ }
45
+ /** 엔터티 이름으로 검색 */
46
+ async searchEntities(diagramId, query) {
47
+ return this.request(`/api/mcp/diagrams/${diagramId}/entities?q=${encodeURIComponent(query)}`);
48
+ }
49
+ // ─── DDL ───
50
+ /** 다이어그램의 DDL 생성 */
51
+ async generateDDL(diagramId, dialect) {
52
+ const d = dialect ? `?dialect=${encodeURIComponent(dialect)}` : '';
53
+ return this.request(`/api/mcp/diagrams/${diagramId}/ddl${d}`);
54
+ }
55
+ // ─── Dictionary ───
56
+ /** 표준 사전 단어 목록 */
57
+ async getDictionary(projectId) {
58
+ return this.request(`/api/mcp/projects/${projectId}/dictionary`);
59
+ }
60
+ // ─── Drafts ───
61
+ /** 스키마 변경 제안 (Draft 생성) */
62
+ async proposeSchemaChange(diagramId, data) {
63
+ return this.postRequest(`/api/mcp/diagrams/${diagramId}/drafts`, data);
64
+ }
65
+ async postRequest(path, body) {
66
+ const url = `${this.baseUrl}${path}`;
67
+ const res = await fetch(url, {
68
+ method: 'POST',
69
+ headers: this.headers,
70
+ body: JSON.stringify(body),
71
+ });
72
+ if (!res.ok) {
73
+ const text = await res.text().catch(() => '');
74
+ throw new Error(`ThinkERD API error ${res.status}: ${text || res.statusText}`);
75
+ }
76
+ return res.json();
77
+ }
78
+ }
79
+ exports.ThinkERDClient = ThinkERDClient;
80
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ /**
5
+ * ThinkERD MCP Server
6
+ *
7
+ * 외부 AI 에이전트(Cursor, Windsurf, Claude Desktop 등)가
8
+ * ThinkERD의 ERD 스키마를 조회하고 활용할 수 있는 MCP Server입니다.
9
+ *
10
+ * 사용법:
11
+ * npx @thinkdata/mcp-server --api-url https://app.thinkdata.dev --token <PAT>
12
+ *
13
+ * @module @thinkdata/mcp-server
14
+ */
15
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
16
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
17
+ const resources_js_1 = require("./resources.js");
18
+ const tools_js_1 = require("./tools.js");
19
+ const client_js_1 = require("./client.js");
20
+ // ─── CLI 인수 파싱 ───
21
+ function parseArgs() {
22
+ const args = process.argv.slice(2);
23
+ let apiUrl = process.env.THINKERD_API_URL || 'https://app.thinkdata.dev';
24
+ let token = process.env.THINKERD_TOKEN || '';
25
+ for (let i = 0; i < args.length; i++) {
26
+ if (args[i] === '--api-url' && args[i + 1]) {
27
+ apiUrl = args[++i];
28
+ }
29
+ else if (args[i] === '--token' && args[i + 1]) {
30
+ token = args[++i];
31
+ }
32
+ }
33
+ if (!token) {
34
+ console.error('Error: ThinkERD API token is required.\n' +
35
+ 'Usage: thinkerd-mcp --token <YOUR_PAT>\n' +
36
+ 'Or set THINKERD_TOKEN environment variable.');
37
+ process.exit(1);
38
+ }
39
+ return { apiUrl, token };
40
+ }
41
+ // ─── 메인 ───
42
+ async function main() {
43
+ const { apiUrl, token } = parseArgs();
44
+ const client = new client_js_1.ThinkERDClient(apiUrl, token);
45
+ const server = new mcp_js_1.McpServer({
46
+ name: 'thinkerd',
47
+ version: '0.1.0',
48
+ });
49
+ // Resources & Tools 등록
50
+ (0, resources_js_1.registerResources)(server, client);
51
+ (0, tools_js_1.registerTools)(server, client);
52
+ // stdio 트랜스포트로 연결 (Claude Desktop, Cursor 등 로컬 호스트 지원)
53
+ const transport = new stdio_js_1.StdioServerTransport();
54
+ await server.connect(transport);
55
+ console.error('[ThinkERD MCP] Server started. Connected via stdio.');
56
+ }
57
+ main().catch((err) => {
58
+ console.error('[ThinkERD MCP] Fatal error:', err);
59
+ process.exit(1);
60
+ });
61
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AACA;;;;;;;;;;GAUG;AACH,oEAAoE;AACpE,wEAAiF;AACjF,iDAAmD;AACnD,yCAA2C;AAC3C,2CAA6C;AAE7C,oBAAoB;AACpB,SAAS,SAAS;IACd,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,IAAI,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,2BAA2B,CAAC;IACzE,IAAI,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,EAAE,CAAC;IAE7C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,WAAW,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACzC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QACvB,CAAC;aAAM,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAC9C,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QACtB,CAAC;IACL,CAAC;IAED,IAAI,CAAC,KAAK,EAAE,CAAC;QACT,OAAO,CAAC,KAAK,CACT,0CAA0C;YAC1C,0CAA0C;YAC1C,6CAA6C,CAChD,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AAC7B,CAAC;AAED,aAAa;AACb,KAAK,UAAU,IAAI;IACf,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,SAAS,EAAE,CAAC;IACtC,MAAM,MAAM,GAAG,IAAI,0BAAc,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAEjD,MAAM,MAAM,GAAG,IAAI,kBAAS,CAAC;QACzB,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,OAAO;KACnB,CAAC,CAAC;IAEH,uBAAuB;IACvB,IAAA,gCAAiB,EAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,IAAA,wBAAa,EAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE9B,uDAAuD;IACvD,MAAM,SAAS,GAAG,IAAI,+BAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,OAAO,CAAC,KAAK,CAAC,qDAAqD,CAAC,CAAC;AACzE,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACjB,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC;IAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * MCP Resources 등록
3
+ *
4
+ * Resources는 읽기 전용 데이터를 노출합니다.
5
+ * AI 에이전트가 ThinkERD의 스키마 정보를 컨텍스트로 가져갈 수 있습니다.
6
+ *
7
+ * @module resources
8
+ */
9
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
10
+ import type { ThinkERDClient } from './client.js';
11
+ export declare function registerResources(server: McpServer, client: ThinkERDClient): void;
12
+ //# sourceMappingURL=resources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CAwGjF"}
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerResources = registerResources;
4
+ function registerResources(server, client) {
5
+ // ── 다이어그램 목록 ──
6
+ server.resource('diagrams', 'thinkerd://diagrams', {
7
+ description: 'ThinkERD 프로젝트의 모든 다이어그램 목록을 반환합니다.',
8
+ }, async () => {
9
+ const diagrams = await client.listDiagrams();
10
+ return {
11
+ contents: [{
12
+ uri: 'thinkerd://diagrams',
13
+ mimeType: 'application/json',
14
+ text: JSON.stringify(diagrams, null, 2),
15
+ }],
16
+ };
17
+ });
18
+ // ── 다이어그램 스키마 (엔터티 + 관계) ──
19
+ server.resource('diagram-schema', 'thinkerd://diagrams/{diagramId}/schema', {
20
+ description: '특정 다이어그램의 전체 스키마(엔터티, 컬럼, 관계)를 JSON으로 반환합니다.',
21
+ }, async (uri) => {
22
+ const diagramId = extractParam(uri.href, 'diagrams');
23
+ const data = await client.getDiagram(diagramId);
24
+ return {
25
+ contents: [{
26
+ uri: uri.href,
27
+ mimeType: 'application/json',
28
+ text: JSON.stringify(data, null, 2),
29
+ }],
30
+ };
31
+ });
32
+ // ── 단일 엔터티 상세 ──
33
+ server.resource('entity-detail', 'thinkerd://diagrams/{diagramId}/entities/{entityId}', {
34
+ description: '특정 엔터티의 논리명, 물리명, 컬럼 상세(타입, PK/FK, Nullable 등)를 반환합니다.',
35
+ }, async (uri) => {
36
+ const parts = uri.href.split('/');
37
+ const entitiesIdx = parts.indexOf('entities');
38
+ const diagramsIdx = parts.indexOf('diagrams');
39
+ const diagramId = parts[diagramsIdx + 1];
40
+ const entityId = parts[entitiesIdx + 1];
41
+ const entity = await client.getEntity(diagramId, entityId);
42
+ return {
43
+ contents: [{
44
+ uri: uri.href,
45
+ mimeType: 'application/json',
46
+ text: JSON.stringify(entity, null, 2),
47
+ }],
48
+ };
49
+ });
50
+ // ── DDL 생성 ──
51
+ server.resource('diagram-ddl', 'thinkerd://diagrams/{diagramId}/ddl', {
52
+ description: '다이어그램의 DDL(CREATE TABLE 구문)을 생성하여 반환합니다.',
53
+ }, async (uri) => {
54
+ const diagramId = extractParam(uri.href, 'diagrams');
55
+ const ddl = await client.generateDDL(diagramId);
56
+ return {
57
+ contents: [{
58
+ uri: uri.href,
59
+ mimeType: 'text/plain',
60
+ text: typeof ddl === 'string' ? ddl : JSON.stringify(ddl),
61
+ }],
62
+ };
63
+ });
64
+ // ── 표준 사전 ──
65
+ server.resource('dictionary', 'thinkerd://projects/{projectId}/dictionary', {
66
+ description: '프로젝트의 표준 사전(단어, 도메인, 금칙어)을 반환합니다.',
67
+ }, async (uri) => {
68
+ const projectId = extractParam(uri.href, 'projects');
69
+ const words = await client.getDictionary(projectId);
70
+ return {
71
+ contents: [{
72
+ uri: uri.href,
73
+ mimeType: 'application/json',
74
+ text: JSON.stringify(words, null, 2),
75
+ }],
76
+ };
77
+ });
78
+ }
79
+ /** URI에서 특정 세그먼트 뒤의 ID를 추출하는 유틸리티 */
80
+ function extractParam(href, segment) {
81
+ const parts = href.split('/');
82
+ const idx = parts.indexOf(segment);
83
+ if (idx === -1 || idx + 1 >= parts.length) {
84
+ throw new Error(`Invalid URI: cannot extract '${segment}' parameter from ${href}`);
85
+ }
86
+ return parts[idx + 1];
87
+ }
88
+ //# sourceMappingURL=resources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resources.js","sourceRoot":"","sources":["../src/resources.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ThinkERDClient } from './client.js';
3
+ export declare function registerTools(server: McpServer, client: ThinkERDClient): void;
4
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CA8M7E"}
package/dist/tools.js ADDED
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerTools = registerTools;
4
+ /**
5
+ * MCP Tools 등록
6
+ *
7
+ * Tools는 부작용(Side-effect)이 있는 액션을 노출합니다.
8
+ * AI 에이전트가 ThinkERD 데이터를 조회하거나 스키마 변경을 제안할 수 있습니다.
9
+ *
10
+ * @module tools
11
+ */
12
+ const zod_1 = require("zod");
13
+ function registerTools(server, client) {
14
+ // ── get_entity: 엔터티 상세 조회 ──
15
+ server.tool('get_entity', 'ThinkERD 다이어그램에서 특정 엔터티의 컬럼, 타입, PK/FK 정보를 조회합니다.', {
16
+ diagramId: zod_1.z.string().describe('다이어그램 ID'),
17
+ entityId: zod_1.z.string().describe('엔터티 ID'),
18
+ }, async ({ diagramId, entityId }) => {
19
+ try {
20
+ const entity = await client.getEntity(diagramId, entityId);
21
+ return {
22
+ content: [{
23
+ type: 'text',
24
+ text: JSON.stringify(entity, null, 2),
25
+ }],
26
+ };
27
+ }
28
+ catch (err) {
29
+ return {
30
+ content: [{
31
+ type: 'text',
32
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`,
33
+ }],
34
+ isError: true,
35
+ };
36
+ }
37
+ });
38
+ // ── search_entities: 엔터티 검색 ──
39
+ server.tool('search_entities', 'ThinkERD 다이어그램에서 이름으로 엔터티를 검색합니다. 논리명/물리명 모두 검색 가능합니다.', {
40
+ diagramId: zod_1.z.string().describe('다이어그램 ID'),
41
+ query: zod_1.z.string().describe('검색 키워드 (엔터티 논리명 또는 물리명)'),
42
+ }, async ({ diagramId, query }) => {
43
+ try {
44
+ const entities = await client.searchEntities(diagramId, query);
45
+ const summary = entities.map(e => ({
46
+ id: e.id,
47
+ name: e.name,
48
+ tableName: e.tableName,
49
+ role: e.entityRole,
50
+ columnCount: e.columns.length,
51
+ columns: e.columns.map(c => `${c.name} (${c.type})${c.isPrimaryKey ? ' [PK]' : ''}${c.isForeignKey ? ' [FK]' : ''}`),
52
+ }));
53
+ return {
54
+ content: [{
55
+ type: 'text',
56
+ text: JSON.stringify(summary, null, 2),
57
+ }],
58
+ };
59
+ }
60
+ catch (err) {
61
+ return {
62
+ content: [{
63
+ type: 'text',
64
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`,
65
+ }],
66
+ isError: true,
67
+ };
68
+ }
69
+ });
70
+ // ── generate_ddl: DDL 생성 ──
71
+ server.tool('generate_ddl', 'ThinkERD 다이어그램의 DDL(CREATE TABLE 구문)을 생성합니다. PostgreSQL, MySQL, Oracle 등 다양한 방언을 지원합니다.', {
72
+ diagramId: zod_1.z.string().describe('다이어그램 ID'),
73
+ dialect: zod_1.z.enum(['postgresql', 'mysql', 'oracle', 'mssql', 'sqlite'])
74
+ .optional()
75
+ .default('postgresql')
76
+ .describe('SQL 방언 (기본값: postgresql)'),
77
+ }, async ({ diagramId, dialect }) => {
78
+ try {
79
+ const ddl = await client.generateDDL(diagramId, dialect);
80
+ return {
81
+ content: [{
82
+ type: 'text',
83
+ text: typeof ddl === 'string' ? ddl : JSON.stringify(ddl),
84
+ }],
85
+ };
86
+ }
87
+ catch (err) {
88
+ return {
89
+ content: [{
90
+ type: 'text',
91
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`,
92
+ }],
93
+ isError: true,
94
+ };
95
+ }
96
+ });
97
+ // ── validate_schema: 스키마 검증 ──
98
+ server.tool('validate_schema', 'ThinkERD 다이어그램의 스키마를 검증합니다. 누락된 PK, 미연결 FK, 비표준 네이밍 등을 점검합니다.', {
99
+ diagramId: zod_1.z.string().describe('다이어그램 ID'),
100
+ }, async ({ diagramId }) => {
101
+ try {
102
+ const data = await client.getDiagram(diagramId);
103
+ const issues = [];
104
+ // PK 누락 검사
105
+ for (const entity of data.entities) {
106
+ const hasPK = entity.columns.some(c => c.isPrimaryKey);
107
+ if (!hasPK) {
108
+ issues.push(`⚠️ [${entity.name}] PK(Primary Key)가 없습니다.`);
109
+ }
110
+ }
111
+ // 컬럼 없는 엔터티 검사
112
+ for (const entity of data.entities) {
113
+ if (entity.columns.length === 0) {
114
+ issues.push(`⚠️ [${entity.name}] 컬럼이 하나도 없습니다.`);
115
+ }
116
+ }
117
+ // 고립 엔터티 검사 (관계가 하나도 없는)
118
+ const connectedIds = new Set();
119
+ for (const rel of data.relationships) {
120
+ connectedIds.add(rel.sourceEntityId);
121
+ connectedIds.add(rel.targetEntityId);
122
+ }
123
+ for (const entity of data.entities) {
124
+ if (!connectedIds.has(entity.id) && data.entities.length > 1) {
125
+ issues.push(`ℹ️ [${entity.name}] 다른 엔터티와 관계가 없는 고립 엔터티입니다.`);
126
+ }
127
+ }
128
+ const summary = issues.length === 0
129
+ ? `✅ 스키마 검증 완료: ${data.entities.length}개 엔터티, 문제 없음.`
130
+ : `🔍 스키마 검증 완료: ${data.entities.length}개 엔터티, ${issues.length}개 이슈 발견.\n\n${issues.join('\n')}`;
131
+ return {
132
+ content: [{
133
+ type: 'text',
134
+ text: summary,
135
+ }],
136
+ };
137
+ }
138
+ catch (err) {
139
+ return {
140
+ content: [{
141
+ type: 'text',
142
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`,
143
+ }],
144
+ isError: true,
145
+ };
146
+ }
147
+ });
148
+ // ── propose_schema_change: 스키마 변경 제안 ──
149
+ server.tool('propose_schema_change', '외부 AI가 ThinkERD 다이어그램에 스키마 변경을 제안합니다. 사용자 승인이 필요한 Draft로 전송됩니다.', {
150
+ diagramId: zod_1.z.string().describe('대상 다이어그램 ID'),
151
+ entityName: zod_1.z.string().describe('변경 대상 엔터티명 (논리명 또는 물리명)'),
152
+ changeType: zod_1.z.enum(['add_column', 'modify_column', 'add_entity'])
153
+ .describe('변경 유형'),
154
+ changeData: zod_1.z.record(zod_1.z.unknown())
155
+ .describe('변경 데이터 (JSON). add_column: {name, type, logicalName}, add_entity: {name, tableName, columns}'),
156
+ reason: zod_1.z.string().describe('변경 사유'),
157
+ }, async ({ diagramId, entityName, changeType, changeData, reason }) => {
158
+ try {
159
+ const draft = await client.proposeSchemaChange(diagramId, {
160
+ entityName,
161
+ changeType,
162
+ changeData,
163
+ reason,
164
+ });
165
+ return {
166
+ content: [{
167
+ type: 'text',
168
+ text: `✅ 스키마 변경 제안이 ThinkERD에 전송되었습니다.\n\n` +
169
+ `Draft ID: ${draft.draft_id}\n` +
170
+ `상태: ${draft.status}\n` +
171
+ `대상: ${entityName}\n` +
172
+ `유형: ${changeType}\n` +
173
+ `사유: ${reason}\n\n` +
174
+ `⚠️ ThinkERD에서 사용자가 이 제안을 승인해야 적용됩니다.`,
175
+ }],
176
+ };
177
+ }
178
+ catch (err) {
179
+ return {
180
+ content: [{
181
+ type: 'text',
182
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`,
183
+ }],
184
+ isError: true,
185
+ };
186
+ }
187
+ });
188
+ }
189
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@thinkdata/mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "ThinkERD MCP Server — AI agents can query your database schema",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "bin": {
8
+ "thinkerd-mcp": "./dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md"
13
+ ],
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/Thinkdata-dev/thinkdata",
20
+ "directory": "packages/mcp-server"
21
+ },
22
+ "homepage": "https://thinkdata.dev",
23
+ "scripts": {
24
+ "build": "tsc",
25
+ "dev": "tsx src/index.ts",
26
+ "typecheck": "tsc --noEmit",
27
+ "prepublishOnly": "npm run build"
28
+ },
29
+ "keywords": ["mcp", "model-context-protocol", "erd", "database", "schema", "thinkerd"],
30
+ "license": "MIT",
31
+ "dependencies": {
32
+ "@modelcontextprotocol/sdk": "^1.12.1",
33
+ "zod": "^3.23.8"
34
+ },
35
+ "devDependencies": {
36
+ "typescript": "^5.4.5",
37
+ "tsx": "^4.19.0",
38
+ "@types/node": "^20.14.0"
39
+ }
40
+ }
41
+